diff --git a/.rustfmt.toml b/.rustfmt.toml index bf772232..9e319b84 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1,2 @@ imports_granularity = "Crate" -edition = "2021" +edition = "2024" diff --git a/Cargo.lock b/Cargo.lock index 4498789a..c20bba7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,79 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 1.0.0", + "k256", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777d58b30eb9a4db0e5f59bc30e8c2caef877fee7dc8734cf242a51a60f22e05" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 2.0.1", + "foldhash", + "hashbrown 0.15.3", + "indexmap", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.8.5", + "ruint", + "rustc-hash 2.1.1", + "serde", + "sha3", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "annotate-snippets" version = "0.12.10" @@ -77,18 +150,261 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + [[package]] name = "bimap" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.9.1" @@ -104,6 +420,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -113,6 +441,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "borsh" version = "1.5.7" @@ -122,12 +462,49 @@ dependencies = [ "cfg_aliases", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "cc" +version = "1.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -207,6 +584,53 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -262,6 +686,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -287,13 +723,56 @@ dependencies = [ "rayon", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl", + "derive_more-impl 2.0.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.106", + "unicode-xid", ] [[package]] @@ -308,6 +787,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -315,7 +803,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -365,6 +855,32 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "egglog" version = "0.5.0" @@ -410,12 +926,62 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -451,6 +1017,38 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "filecheck" version = "0.5.0" @@ -461,21 +1059,42 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "fixed-hash" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", "static_assertions", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -484,6 +1103,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -527,10 +1147,21 @@ dependencies = [ "into-attr-derive", "pest", "pest_derive", - "rand", + "rand 0.8.5", "tempfile", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -572,6 +1203,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "humantime" version = "2.3.0" @@ -585,7 +1225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" dependencies = [ "bitmaps", - "rand_core", + "rand_core 0.6.4", "rand_xoshiro", "sized-chunks", "typenum", @@ -599,13 +1239,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" dependencies = [ "bitmaps", - "rand_core", + "rand_core 0.6.4", "rand_xoshiro", "sized-chunks", "typenum", "version_check", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "indenter" version = "0.3.4" @@ -683,17 +1343,70 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -701,6 +1414,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -800,6 +1519,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] @@ -821,10 +1551,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", "serde", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "parking_lot_core" version = "0.9.10" @@ -838,6 +1596,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pest" version = "2.8.3" @@ -881,6 +1645,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -890,6 +1664,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint 0.9.5", +] + [[package]] name = "primitive-types" version = "0.14.0" @@ -897,7 +1682,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794" dependencies = [ "fixed-hash", - "uint", + "uint 0.10.0", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", ] [[package]] @@ -909,6 +1703,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.41" @@ -924,6 +1743,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -931,29 +1756,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "serde", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", + "serde", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "ppv-lite86", - "rand_core", + "getrandom 0.3.4", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "getrandom 0.2.16", - "serde", + "rand_core 0.9.3", ] [[package]] @@ -962,7 +1825,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1023,6 +1886,131 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "revm" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15689a3c6a8d14b647b4666f2e236ef47b5a5133cdfd423f545947986fff7013" +dependencies = [ + "auto_impl", + "cfg-if", + "dyn-clone", + "revm-interpreter", + "revm-precompile", + "serde", + "serde_json", +] + +[[package]] +name = "revm-interpreter" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74e3f11d0fed049a4a10f79820c59113a79b38aed4ebec786a79d5c667bfeb51" +dependencies = [ + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381060af24b750069a2b2d2c54bba273d84e8f5f9e8026fc9262298e26cc336" +dependencies = [ + "aurora-engine-modexp", + "c-kzg", + "cfg-if", + "k256", + "once_cell", + "revm-primitives", + "ripemd", + "secp256k1", + "sha2", + "substrate-bn", +] + +[[package]] +name = "revm-primitives" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3702f132bb484f4f0d0ca4f6fbde3c82cfd745041abbedd6eda67730e1868ef0" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "auto_impl", + "bitflags", + "bitvec", + "cfg-if", + "dyn-clone", + "enumn", + "hex", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1035,6 +2023,30 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + [[package]] name = "rustix" version = "1.1.2" @@ -1048,6 +2060,24 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.20" @@ -1069,6 +2099,63 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -1121,7 +2208,43 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", ] [[package]] @@ -1160,12 +2283,16 @@ dependencies = [ name = "sonatina-codegen" version = "0.0.3-alpha" dependencies = [ + "bit-set", "cranelift-entity", "dashmap", + "derive_more 1.0.0", "dir-test", "egglog", + "hex", "indexmap", "insta", + "revm", "rustc-hash 2.1.1", "smallvec", "sonatina-ir", @@ -1211,10 +2338,11 @@ dependencies = [ "dyn-clone", "indexmap", "insta", - "primitive-types", + "primitive-types 0.14.0", "rayon", "rustc-hash 2.1.1", "smallvec", + "smol_str", "sonatina-macros", "sonatina-parser", "sonatina-triple", @@ -1229,10 +2357,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "sonatina-object" -version = "0.0.3-alpha" - [[package]] name = "sonatina-parser" version = "0.0.3-alpha" @@ -1240,7 +2364,7 @@ dependencies = [ "annotate-snippets", "bimap", "cranelift-entity", - "derive_more", + "derive_more 2.0.1", "dir-test", "either", "hex", @@ -1272,6 +2396,22 @@ dependencies = [ "sonatina-ir", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1284,6 +2424,25 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "symbol_table" version = "0.4.0" @@ -1317,6 +2476,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.23.0" @@ -1379,6 +2544,54 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + [[package]] name = "typenum" version = "1.18.0" @@ -1391,6 +2604,18 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "uint" version = "0.10.0" @@ -1403,12 +2628,24 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.2" @@ -1427,12 +2664,27 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -1555,12 +2807,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.8.31" @@ -1580,3 +2850,23 @@ dependencies = [ "quote", "syn 2.0.106", ] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] diff --git a/Cargo.toml b/Cargo.toml index 7d88e6b6..dabdd196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "2" members = [ "crates/ir", "crates/codegen", - "crates/object", "crates/parser", "crates/filecheck", "crates/triple", diff --git a/README.md b/README.md index 67396ecc..224c70cb 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Sonatina is really early stage in development, so do NOT use it for production! * `filecheck`: Provides test runner for `filecheck` and test fixtures. * `interpreter`: Interpreter for `sonatina` IR, this is mainly for testing transformation passes. * `ir`: `sonatina` intermediate representation. -* `object`: Provides abstract object file format for linker. * `parser`: Parser for `sonatina` IR, this is mainly for `filecheck` test. * `triple`: Provides target triple for smart contracts. * `verifier`: Verifier for `sonatina` IR, this is mainly for testing transformation passes. @@ -25,4 +24,4 @@ Sonatina is really early stage in development, so do NOT use it for production! * [ ] Linker ## Test -Run `test_all.sh`. \ No newline at end of file +Run `test_all.sh`. diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index e1cbca9d..c5148e09 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -23,9 +23,13 @@ sonatina-triple = { path = "../triple", version = "0.0.3-alpha" } sonatina-macros = { path = "../macros", version = "0.0.3-alpha" } dashmap = { version = "6.1", features = ["rayon"] } egglog = "0.5" +bit-set = "0.8.0" indexmap = { version = "2.12" } [dev-dependencies] -sonatina-parser = { path = "../parser", version = "0.0.3-alpha" } +hex = "0.4.3" insta = "1.43" dir-test = "0.4" +derive_more = { version = "1", default-features = false, features = ["full"] } +revm = { version = "18.0.0", default-features = false, features = ["std"] } +sonatina-parser = { path = "../parser", version = "0.0.3-alpha" } diff --git a/crates/codegen/src/bitset.rs b/crates/codegen/src/bitset.rs new file mode 100644 index 00000000..36c6d875 --- /dev/null +++ b/crates/codegen/src/bitset.rs @@ -0,0 +1,130 @@ +use bit_set::BitSet as Bs; +use cranelift_entity::EntityRef; +use std::{fmt, marker::PhantomData}; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct BitSet { + bs: Bs, + marker: PhantomData, +} + +impl BitSet { + pub fn new() -> Self { + Self { + bs: Bs::new(), + marker: PhantomData, + } + } + + pub fn is_empty(&self) -> bool { + self.bs.is_empty() + } + + pub fn len(&self) -> usize { + self.bs.len() + } + + pub fn union_with(&mut self, other: &Self) { + self.bs.union_with(&other.bs) + } + + pub fn intersect_with(&mut self, other: &Self) { + self.bs.intersect_with(&other.bs) + } + + pub fn difference_with(&mut self, other: &Self) { + self.bs.difference_with(&other.bs) + } + + pub fn symmetric_difference_with(&mut self, other: &Self) { + self.bs.symmetric_difference_with(&other.bs) + } + + pub fn is_subset(&self, other: &Self) -> bool { + self.bs.is_subset(&other.bs) + } + + pub fn is_superset(&self, other: &Self) -> bool { + self.bs.is_superset(&other.bs) + } + + pub fn is_disjoint(&self, other: &Self) -> bool { + self.bs.is_disjoint(&other.bs) + } + + pub fn clear(&mut self) { + self.bs.clear() + } +} + +impl BitSet +where + T: EntityRef, +{ + pub fn difference(a: &Self, b: &Self) -> Self { + let mut d = a.clone(); + d.difference_with(b); + d + } + + pub fn insert(&mut self, elem: T) -> bool { + self.bs.insert(elem.index()) + } + pub fn remove(&mut self, elem: T) -> bool { + self.bs.remove(elem.index()) + } + pub fn contains(&self, elem: T) -> bool { + self.bs.contains(elem.index()) + } + pub fn iter(&self) -> impl Iterator + '_ { + self.bs.iter().map(|v| T::new(v)) + } +} + +impl Default for BitSet { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for BitSet +where + T: EntityRef + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.bs.iter()).finish() + } +} + +impl From<&[T]> for BitSet { + fn from(elems: &[T]) -> Self { + let mut bs = BitSet::new(); + for e in elems { + bs.insert(*e); + } + bs + } +} + +impl From<[T; N]> for BitSet { + fn from(elems: [T; N]) -> Self { + let mut bs = BitSet::new(); + for e in elems { + bs.insert(e); + } + bs + } +} + +impl FromIterator for BitSet { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + let mut bs = BitSet::new(); + for e in iter { + bs.insert(e); + } + bs + } +} diff --git a/crates/codegen/src/cfg_scc.rs b/crates/codegen/src/cfg_scc.rs new file mode 100644 index 00000000..53b2748c --- /dev/null +++ b/crates/codegen/src/cfg_scc.rs @@ -0,0 +1,579 @@ +use std::collections::BTreeSet; + +use cranelift_entity::{ + EntityRef, PrimaryMap, SecondaryMap, entity_impl, packed_option::PackedOption, +}; +use sonatina_ir::{BlockId, ControlFlowGraph}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct SccId(u32); +entity_impl!(SccId); + +#[derive(Debug, Default)] +pub struct CfgSccAnalysis { + block_to_scc: SecondaryMap>, + sccs: PrimaryMap, + + topo_order: Vec, + topo_index: SecondaryMap, + + cfg_rpo: Vec, +} + +#[derive(Debug, Clone)] +pub struct SccData { + pub blocks_rpo: Vec, + pub entry_blocks: Vec, + pub exit_blocks: Vec, + pub succ_sccs: Vec, + pub pred_sccs: Vec, + pub is_cycle: bool, +} + +impl Default for SccData { + fn default() -> Self { + Self::new() + } +} + +impl SccData { + fn new() -> Self { + Self { + blocks_rpo: Vec::new(), + entry_blocks: Vec::new(), + exit_blocks: Vec::new(), + succ_sccs: Vec::new(), + pred_sccs: Vec::new(), + is_cycle: false, + } + } + + pub fn is_multi_entry(&self) -> bool { + self.entry_blocks.len() > 1 + } + + pub fn header(&self) -> Option { + (self.entry_blocks.len() == 1).then(|| self.entry_blocks[0]) + } + + fn topo_tiebreak_key(&self) -> BlockId { + debug_assert!(!self.blocks_rpo.is_empty()); + self.blocks_rpo[0] + } +} + +impl CfgSccAnalysis { + pub fn new() -> Self { + Self::default() + } + + pub fn clear(&mut self) { + self.block_to_scc.clear(); + self.sccs.clear(); + self.topo_order.clear(); + self.topo_index.clear(); + self.cfg_rpo.clear(); + } + + /// Compute SCCs for the subgraph reachable from `cfg.entry()`. + pub fn compute(&mut self, cfg: &ControlFlowGraph) { + self.clear(); + + let Some(entry) = cfg.entry() else { + return; + }; + + self.cfg_rpo.extend(cfg.post_order()); + self.cfg_rpo.reverse(); + + let mut reachable = SecondaryMap::::default(); + for &block in &self.cfg_rpo { + reachable[block] = true; + } + + let mut visited = SecondaryMap::::default(); + + for &root in &self.cfg_rpo { + if visited[root] { + continue; + } + + let scc = self.sccs.push(SccData::new()); + let mut stack = vec![root]; + visited[root] = true; + + while let Some(node) = stack.pop() { + self.block_to_scc[node] = scc.into(); + + for &pred in cfg.preds_of(node) { + if !reachable[pred] { + continue; + } + + if !visited[pred] { + visited[pred] = true; + stack.push(pred); + } + } + } + } + + for &block in &self.cfg_rpo { + let scc = self.scc_of(block).unwrap(); + self.sccs[scc].blocks_rpo.push(block); + } + + let scc_count = self.sccs.len(); + let mut succ_sets = vec![BTreeSet::::new(); scc_count]; + let mut pred_sets = vec![BTreeSet::::new(); scc_count]; + let mut entry_sets = vec![BTreeSet::::new(); scc_count]; + let mut exit_sets = vec![BTreeSet::::new(); scc_count]; + let mut has_self_loop = vec![false; scc_count]; + + for &block in self.cfg_rpo.iter().rev() { + let from_scc = self.scc_of(block).unwrap(); + let from_idx = from_scc.index(); + + if block == entry + || cfg + .preds_of(block) + .filter(|&&pred| reachable[pred]) + .any(|&pred| self.scc_of(pred).unwrap() != from_scc) + { + entry_sets[from_idx].insert(block); + } + + let mut is_exit = false; + for &succ in cfg.succs_of(block) { + if !reachable[succ] { + continue; + } + + let to_scc = self.scc_of(succ).unwrap(); + if to_scc != from_scc { + is_exit = true; + succ_sets[from_idx].insert(to_scc); + pred_sets[to_scc.index()].insert(from_scc); + } else if succ == block { + has_self_loop[from_idx] = true; + } + } + + if is_exit { + exit_sets[from_idx].insert(block); + } + } + + for scc in self.sccs.keys() { + let idx = scc.index(); + let data = &mut self.sccs[scc]; + + data.entry_blocks = entry_sets[idx].iter().copied().collect(); + data.exit_blocks = exit_sets[idx].iter().copied().collect(); + data.succ_sccs = succ_sets[idx].iter().copied().collect(); + data.pred_sccs = pred_sets[idx].iter().copied().collect(); + + data.is_cycle = data.blocks_rpo.len() > 1 || has_self_loop[idx]; + } + + self.compute_topo_order(); + } + + pub fn scc_of(&self, block: BlockId) -> Option { + self.block_to_scc[block].expand() + } + + pub fn scc_data(&self, scc: SccId) -> &SccData { + &self.sccs[scc] + } + + pub fn scc_count(&self) -> usize { + self.sccs.len() + } + + pub fn topo_order(&self) -> &[SccId] { + &self.topo_order + } + + pub fn topo_index(&self, scc: SccId) -> u32 { + self.topo_index[scc] + } + + pub fn topo_before(&self, a: SccId, b: SccId) -> bool { + self.topo_index(a) < self.topo_index(b) + } + + pub fn entry_scc(&self, cfg: &ControlFlowGraph) -> Option { + cfg.entry().and_then(|entry| self.scc_of(entry)) + } + + pub fn is_reachable(&self, block: BlockId) -> bool { + self.scc_of(block).is_some() + } + + fn compute_topo_order(&mut self) { + self.topo_order.clear(); + self.topo_index.clear(); + + let scc_count = self.sccs.len(); + if scc_count == 0 { + return; + } + + let mut indegree = vec![0u32; scc_count]; + let mut ready = BTreeSet::<(BlockId, SccId)>::new(); + + for scc in self.sccs.keys() { + let idx = scc.index(); + indegree[idx] = self.sccs[scc].pred_sccs.len() as u32; + if indegree[idx] == 0 { + ready.insert((self.sccs[scc].topo_tiebreak_key(), scc)); + } + } + + while let Some((_rep, scc)) = ready.pop_first() { + self.topo_order.push(scc); + + for &succ in &self.sccs[scc].succ_sccs { + let succ_idx = succ.index(); + indegree[succ_idx] -= 1; + if indegree[succ_idx] == 0 { + ready.insert((self.sccs[succ].topo_tiebreak_key(), succ)); + } + } + } + + debug_assert_eq!(self.topo_order.len(), scc_count); + + for (index, &scc) in self.topo_order.iter().enumerate() { + self.topo_index[scc] = index as u32; + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use sonatina_ir::{ + Function, Type, + builder::test_util::*, + inst::control_flow::{Br, Jump, Return}, + prelude::*, + }; + + use super::*; + + fn analyze_cfg(func: &Function) -> (ControlFlowGraph, CfgSccAnalysis) { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(func); + let mut analysis = CfgSccAnalysis::new(); + analysis.compute(&cfg); + (cfg, analysis) + } + + fn assert_invariants(cfg: &ControlFlowGraph, analysis: &CfgSccAnalysis) { + for from in cfg.post_order() { + let from_scc = analysis.scc_of(from).unwrap(); + for succ in cfg.succs_of(from) { + let Some(to_scc) = analysis.scc_of(*succ) else { + continue; + }; + if from_scc != to_scc { + assert!(analysis.topo_before(from_scc, to_scc)); + } + } + } + + assert_eq!(analysis.topo_order().len(), analysis.scc_count()); + + for &scc in analysis.topo_order() { + let data = analysis.scc_data(scc); + assert!(!data.blocks_rpo.is_empty()); + + let block_set: BTreeSet = data.blocks_rpo.iter().copied().collect(); + assert_eq!(block_set.len(), data.blocks_rpo.len()); + + assert!(data.blocks_rpo.iter().all(|b| block_set.contains(b))); + + assert!(data.entry_blocks.iter().all(|b| block_set.contains(b))); + assert!(data.exit_blocks.iter().all(|b| block_set.contains(b))); + + assert!(data.succ_sccs.windows(2).all(|w| w[0] < w[1])); + assert!(data.pred_sccs.windows(2).all(|w| w[0] < w[1])); + assert!(!data.succ_sccs.contains(&scc)); + assert!(!data.pred_sccs.contains(&scc)); + + assert_eq!(data.is_multi_entry(), data.entry_blocks.len() > 1); + assert_eq!( + data.header(), + (data.entry_blocks.len() == 1).then(|| data.entry_blocks[0]) + ); + } + } + + #[test] + fn empty_cfg() { + let cfg = ControlFlowGraph::new(); + let mut analysis = CfgSccAnalysis::new(); + analysis.compute(&cfg); + assert_eq!(analysis.scc_count(), 0); + assert!(analysis.topo_order().is_empty()); + } + + #[test] + fn linear_chain() { + let mb = test_module_builder(); + let (evm, mut builder) = test_func_builder(&mb, &[], Type::Unit); + let is = evm.inst_set(); + + let b0 = builder.append_block(); + let b1 = builder.append_block(); + let b2 = builder.append_block(); + + builder.switch_to_block(b0); + builder.insert_inst_no_result_with(|| Jump::new(is, b1)); + + builder.switch_to_block(b1); + builder.insert_inst_no_result_with(|| Jump::new(is, b2)); + + builder.switch_to_block(b2); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.seal_all(); + builder.finish(); + + let module = mb.build(); + let func_ref = module.funcs()[0]; + let (cfg, analysis) = module.func_store.view(func_ref, analyze_cfg); + + assert_eq!(analysis.scc_count(), 3); + assert_eq!( + analysis + .topo_order() + .iter() + .map(|&scc| analysis.scc_data(scc).topo_tiebreak_key()) + .collect::>(), + vec![b0, b1, b2] + ); + + assert_invariants(&cfg, &analysis); + } + + #[test] + fn diamond() { + let mb = test_module_builder(); + let (evm, mut builder) = test_func_builder(&mb, &[], Type::Unit); + let is = evm.inst_set(); + + let entry = builder.append_block(); + let then_block = builder.append_block(); + let else_block = builder.append_block(); + let merge = builder.append_block(); + + builder.switch_to_block(entry); + let cond = builder.make_imm_value(true); + builder.insert_inst_no_result_with(|| Br::new(is, cond, else_block, then_block)); + + builder.switch_to_block(then_block); + builder.insert_inst_no_result_with(|| Jump::new(is, merge)); + + builder.switch_to_block(else_block); + builder.insert_inst_no_result_with(|| Jump::new(is, merge)); + + builder.switch_to_block(merge); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.seal_all(); + builder.finish(); + + let module = mb.build(); + let func_ref = module.funcs()[0]; + let (cfg, analysis) = module.func_store.view(func_ref, analyze_cfg); + + assert_eq!(analysis.scc_count(), 4); + assert_eq!( + analysis + .topo_order() + .iter() + .map(|&scc| analysis.scc_data(scc).topo_tiebreak_key()) + .collect::>(), + vec![entry, then_block, else_block, merge] + ); + + assert_invariants(&cfg, &analysis); + } + + #[test] + fn simple_loop() { + let mb = test_module_builder(); + let (evm, mut builder) = test_func_builder(&mb, &[], Type::Unit); + let is = evm.inst_set(); + + let entry = builder.append_block(); + let header = builder.append_block(); + let body = builder.append_block(); + let exit = builder.append_block(); + + builder.switch_to_block(entry); + builder.insert_inst_no_result_with(|| Jump::new(is, header)); + + builder.switch_to_block(header); + let cond = builder.make_imm_value(true); + builder.insert_inst_no_result_with(|| Br::new(is, cond, exit, body)); + + builder.switch_to_block(body); + builder.insert_inst_no_result_with(|| Jump::new(is, header)); + + builder.switch_to_block(exit); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.seal_all(); + builder.finish(); + + let module = mb.build(); + let func_ref = module.funcs()[0]; + let (cfg, analysis) = module.func_store.view(func_ref, analyze_cfg); + + assert_eq!(analysis.scc_count(), 3); + + let loop_scc = analysis.scc_of(header).unwrap(); + assert_eq!(analysis.scc_of(body), Some(loop_scc)); + + let loop_data = analysis.scc_data(loop_scc); + assert!(loop_data.is_cycle); + assert_eq!(loop_data.blocks_rpo, vec![header, body]); + assert_eq!(loop_data.entry_blocks, vec![header]); + assert_eq!(loop_data.header(), Some(header)); + assert_eq!(loop_data.exit_blocks, vec![header]); + + assert_invariants(&cfg, &analysis); + } + + #[test] + fn self_loop() { + let mb = test_module_builder(); + let (evm, mut builder) = test_func_builder(&mb, &[Type::I1], Type::Unit); + let is = evm.inst_set(); + + let entry = builder.append_block(); + let loop_block = builder.append_block(); + let exit = builder.append_block(); + + let arg = builder.args()[0]; + + builder.switch_to_block(entry); + builder.insert_inst_no_result_with(|| Jump::new(is, loop_block)); + + builder.switch_to_block(loop_block); + builder.insert_inst_no_result_with(|| Br::new(is, arg, loop_block, exit)); + + builder.switch_to_block(exit); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.seal_all(); + builder.finish(); + + let module = mb.build(); + let func_ref = module.funcs()[0]; + let (cfg, analysis) = module.func_store.view(func_ref, analyze_cfg); + + let scc = analysis.scc_of(loop_block).unwrap(); + let data = analysis.scc_data(scc); + assert_eq!(data.blocks_rpo, vec![loop_block]); + assert!(data.is_cycle); + assert_eq!(data.entry_blocks, vec![loop_block]); + assert_eq!(data.exit_blocks, vec![loop_block]); + + assert_invariants(&cfg, &analysis); + } + + #[test] + fn irreducible_multi_entry_scc() { + let mb = test_module_builder(); + let (evm, mut builder) = test_func_builder(&mb, &[], Type::Unit); + let is = evm.inst_set(); + + let entry = builder.append_block(); + let a = builder.append_block(); + let b = builder.append_block(); + let c = builder.append_block(); + let d = builder.append_block(); + let exit = builder.append_block(); + + builder.switch_to_block(entry); + let cond = builder.make_imm_value(true); + builder.insert_inst_no_result_with(|| Br::new(is, cond, a, b)); + + builder.switch_to_block(a); + builder.insert_inst_no_result_with(|| Jump::new(is, c)); + + builder.switch_to_block(b); + builder.insert_inst_no_result_with(|| Jump::new(is, d)); + + builder.switch_to_block(c); + builder.insert_inst_no_result_with(|| Br::new(is, cond, exit, d)); + + builder.switch_to_block(d); + builder.insert_inst_no_result_with(|| Jump::new(is, c)); + + builder.switch_to_block(exit); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.seal_all(); + builder.finish(); + + let module = mb.build(); + let func_ref = module.funcs()[0]; + let (cfg, analysis) = module.func_store.view(func_ref, analyze_cfg); + + let cycle_scc = analysis.scc_of(c).unwrap(); + assert_eq!(analysis.scc_of(d), Some(cycle_scc)); + + let data = analysis.scc_data(cycle_scc); + assert!(data.is_cycle); + assert!(data.is_multi_entry()); + assert_eq!(data.header(), None); + assert_eq!(data.entry_blocks, vec![c, d]); + + assert_invariants(&cfg, &analysis); + } + + #[test] + fn unreachable_subgraph_is_ignored() { + let mb = test_module_builder(); + let (evm, mut builder) = test_func_builder(&mb, &[], Type::Unit); + let is = evm.inst_set(); + + let entry = builder.append_block(); + let exit = builder.append_block(); + + let u0 = builder.append_block(); + let u1 = builder.append_block(); + + builder.switch_to_block(entry); + builder.insert_inst_no_result_with(|| Jump::new(is, exit)); + + builder.switch_to_block(exit); + builder.insert_inst_no_result_with(|| Return::new(is, None)); + + builder.switch_to_block(u0); + builder.insert_inst_no_result_with(|| Jump::new(is, u1)); + + builder.switch_to_block(u1); + builder.insert_inst_no_result_with(|| Jump::new(is, u0)); + + builder.seal_all(); + builder.finish(); + + let module = mb.build(); + let func_ref = module.funcs()[0]; + let (cfg, analysis) = module.func_store.view(func_ref, analyze_cfg); + + assert_eq!(analysis.scc_count(), 2); + assert_eq!(analysis.scc_of(u0), None); + assert_eq!(analysis.scc_of(u1), None); + + assert_invariants(&cfg, &analysis); + } +} diff --git a/crates/codegen/src/critical_edge.rs b/crates/codegen/src/critical_edge.rs index ccf5a8d8..b4c9d94e 100644 --- a/crates/codegen/src/critical_edge.rs +++ b/crates/codegen/src/critical_edge.rs @@ -75,12 +75,13 @@ impl CriticalEdgeSplitter { func.dfg .rewrite_branch_dest(inst, original_dest, inserted_dest); self.modify_cfg(cfg, source_block, original_dest, inserted_dest); - self.modify_phi_blocks(func, original_dest, inserted_dest); + self.modify_phi_blocks(func, source_block, original_dest, inserted_dest); } fn modify_phi_blocks( &self, func: &mut Function, + source_block: BlockId, original_dest: BlockId, inserted_dest: BlockId, ) { @@ -90,7 +91,7 @@ impl CriticalEdgeSplitter { }; for (_, block) in phi.args_mut() { - if *block == original_dest { + if *block == source_block { *block = inserted_dest; } } diff --git a/crates/codegen/src/isa/evm/alloca_plan.rs b/crates/codegen/src/isa/evm/alloca_plan.rs new file mode 100644 index 00000000..8869fc97 --- /dev/null +++ b/crates/codegen/src/isa/evm/alloca_plan.rs @@ -0,0 +1,642 @@ +use crate::{bitset::BitSet, liveness::InstLiveness}; +use cranelift_entity::SecondaryMap; +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + BlockId, Function, InstId, InstSetExt, ValueId, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, +}; +use std::{cmp::Reverse, collections::BinaryHeap}; + +use super::{ + memory_plan::{AllocaClass, StackObjectPlan, WORD_BYTES}, + provenance::{Provenance, compute_value_provenance}, + ptr_escape::PtrEscapeSummary, +}; + +#[derive(Clone, Copy, Debug)] +struct AllocaObject { + inst: InstId, + start_pos: u32, + end_pos: u32, + size_words: u32, +} + +#[derive(Clone, Copy, Debug)] +struct FreeBlock { + offset_words: u32, + size_words: u32, +} + +#[derive(Default)] +pub(crate) struct StackAllocaLayout { + pub(crate) plan: FxHashMap, + pub(crate) persistent_words: u32, + pub(crate) transient_words: u32, +} + +#[derive(Clone, Copy)] +pub(crate) struct AllocaLayoutLiveness<'a> { + pub(crate) values_persistent_across_calls: &'a BitSet, + pub(crate) inst_liveness: &'a InstLiveness, +} + +pub(crate) fn compute_stack_alloca_layout( + func_ref: FuncRef, + function: &Function, + module: &ModuleCtx, + isa: &Evm, + ptr_escape: &FxHashMap, + live: AllocaLayoutLiveness<'_>, + block_order: &[BlockId], +) -> StackAllocaLayout { + let (inst_order, inst_pos) = compute_inst_order(function, block_order); + let block_end_pos = compute_block_end_pos(function, &inst_pos); + + let mut allocas: FxHashMap = FxHashMap::default(); + for &inst in &inst_order { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + let EvmInstKind::Alloca(alloca) = data else { + continue; + }; + + let size_bytes: u32 = isa + .type_layout() + .size_of(*alloca.ty(), module) + .expect("alloca has invalid type") as u32; + let size_words = size_bytes.div_ceil(WORD_BYTES); + allocas.insert(inst, size_words); + } + + if allocas.is_empty() { + return StackAllocaLayout::default(); + } + + let prov = compute_value_provenance(function, module, isa, |callee| { + ptr_escape + .get(&callee) + .cloned() + .unwrap_or_else(|| conservative_unknown_ptr_summary(module, callee)) + .arg_may_be_returned + }); + + let escaping_sites = compute_escaping_allocas(function, module, isa, ptr_escape, &prov); + if !escaping_sites.is_empty() { + let name = module.func_sig(func_ref, |sig| sig.name().to_string()); + let mut allocas: Vec<(InstId, Vec)> = + escaping_sites.into_iter().collect(); + allocas.sort_unstable_by_key(|(inst, _)| inst.as_u32()); + + let mut msg = String::new(); + msg.push_str(&format!("alloca escapes in {name}:\n")); + for (alloca_inst, mut sites) in allocas { + sites.sort_unstable_by_key(|s| (s.escape_inst().as_u32(), s.derived_value().as_u32())); + msg.push_str(&format!(" alloca inst {}:\n", alloca_inst.as_u32())); + for site in sites { + msg.push_str(" - "); + msg.push_str(&site.render(module)); + msg.push('\n'); + } + } + panic!("{msg}"); + } + + let mut persistent: FxHashSet = FxHashSet::default(); + for val in live.values_persistent_across_calls.iter() { + for inst in prov[val].alloca_insts() { + persistent.insert(inst); + } + } + + let mut last_live: FxHashMap = FxHashMap::default(); + for &inst in allocas.keys() { + let pos = inst_pos.get(&inst).copied().unwrap_or_default(); + last_live.insert(inst, pos); + } + + for &inst in &inst_order { + let pos = inst_pos.get(&inst).copied().unwrap_or_default(); + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + if let EvmInstKind::Phi(phi) = data { + for (val, pred) in phi.args().iter() { + let use_pos = block_end_pos.get(pred).copied().unwrap_or_default(); + for base in prov[*val].alloca_insts() { + let entry = last_live.get_mut(&base).expect("missing alloca last-live"); + *entry = (*entry).max(use_pos); + } + } + } else { + function.dfg.inst(inst).for_each_value(&mut |v| { + for base in prov[v].alloca_insts() { + let entry = last_live.get_mut(&base).expect("missing alloca last-live"); + *entry = (*entry).max(pos); + } + }); + } + + for val in live.inst_liveness.live_out(inst).iter() { + if prov[val].is_empty() { + continue; + } + for base in prov[val].alloca_insts() { + let entry = last_live.get_mut(&base).expect("missing alloca last-live"); + *entry = (*entry).max(pos); + } + } + } + + let mut persistent_objects: Vec = Vec::new(); + let mut transient_objects: Vec = Vec::new(); + for (&inst, &size_words) in &allocas { + let start_pos = inst_pos.get(&inst).copied().unwrap_or_default(); + let end_pos = last_live.get(&inst).copied().unwrap_or(start_pos); + let obj = AllocaObject { + inst, + start_pos, + end_pos, + size_words, + }; + + if persistent.contains(&inst) { + persistent_objects.push(obj); + } else { + transient_objects.push(obj); + } + } + + let (persistent_offsets, persistent_words) = color_allocas(&mut persistent_objects); + let (transient_offsets, transient_words) = color_allocas(&mut transient_objects); + + let mut plan: FxHashMap = FxHashMap::default(); + for obj in persistent_objects { + let offset_words = persistent_offsets.get(&obj.inst).copied().unwrap_or(0); + plan.insert( + obj.inst, + StackObjectPlan { + class: AllocaClass::Persistent, + offset_words, + }, + ); + } + for obj in transient_objects { + let offset_words = transient_offsets.get(&obj.inst).copied().unwrap_or(0); + plan.insert( + obj.inst, + StackObjectPlan { + class: AllocaClass::Transient, + offset_words, + }, + ); + } + + StackAllocaLayout { + plan, + persistent_words, + transient_words, + } +} + +fn compute_inst_order( + function: &Function, + block_order: &[BlockId], +) -> (Vec, FxHashMap) { + let mut blocks: Vec = Vec::new(); + let mut seen: FxHashSet = FxHashSet::default(); + + for &b in block_order { + if seen.insert(b) { + blocks.push(b); + } + } + + for b in function.layout.iter_block() { + if seen.insert(b) { + blocks.push(b); + } + } + + let mut order: Vec = Vec::new(); + let mut pos: FxHashMap = FxHashMap::default(); + let mut next: u32 = 0; + for b in blocks { + for inst in function.layout.iter_inst(b) { + pos.insert(inst, next); + order.push(inst); + next = next.checked_add(1).expect("instruction position overflow"); + } + } + + (order, pos) +} + +fn compute_block_end_pos( + function: &Function, + inst_pos: &FxHashMap, +) -> FxHashMap { + let mut out: FxHashMap = FxHashMap::default(); + for block in function.layout.iter_block() { + let mut end: Option = None; + for inst in function.layout.iter_inst(block) { + end = Some(inst_pos.get(&inst).copied().unwrap_or_default()); + } + out.insert(block, end.unwrap_or(0)); + } + out +} + +fn conservative_unknown_ptr_summary(module: &ModuleCtx, func_ref: FuncRef) -> PtrEscapeSummary { + let arg_count = module.func_sig(func_ref, |sig| sig.args().len()); + PtrEscapeSummary { + arg_may_escape: vec![true; arg_count], + arg_may_be_returned: vec![true; arg_count], + returns_any_ptr: module.func_sig(func_ref, |sig| sig.ret_ty().is_pointer(module)), + } +} + +fn compute_escaping_allocas( + function: &Function, + module: &ModuleCtx, + isa: &Evm, + ptr_escape: &FxHashMap, + prov: &SecondaryMap, +) -> FxHashMap> { + let mut escaping: FxHashMap> = FxHashMap::default(); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + match data { + EvmInstKind::Return(ret) => { + let Some(ret_val) = *ret.arg() else { + continue; + }; + for base in prov[ret_val].alloca_insts() { + escaping + .entry(base) + .or_default() + .push(AllocaEscapeSite::Return { + inst, + value: ret_val, + }); + } + } + EvmInstKind::Mstore(mstore) => { + let addr = *mstore.addr(); + if prov[addr].is_local_addr() { + continue; + } + + let val = *mstore.value(); + for base in prov[val].alloca_insts() { + escaping + .entry(base) + .or_default() + .push(AllocaEscapeSite::NonLocalStore { + inst, + addr, + value: val, + }); + } + } + EvmInstKind::Call(call) => { + let callee = *call.callee(); + let callee_sum = ptr_escape + .get(&callee) + .cloned() + .unwrap_or_else(|| conservative_unknown_ptr_summary(module, callee)); + for (idx, &arg) in call.args().iter().enumerate() { + if idx < callee_sum.arg_may_escape.len() && callee_sum.arg_may_escape[idx] { + for base in prov[arg].alloca_insts() { + escaping + .entry(base) + .or_default() + .push(AllocaEscapeSite::CallArg { + inst, + callee, + arg_index: idx, + value: arg, + }); + } + } + } + } + _ => {} + } + } + } + + escaping +} + +#[derive(Clone, Debug)] +enum AllocaEscapeSite { + Return { + inst: InstId, + value: ValueId, + }, + NonLocalStore { + inst: InstId, + addr: ValueId, + value: ValueId, + }, + CallArg { + inst: InstId, + callee: FuncRef, + arg_index: usize, + value: ValueId, + }, +} + +impl AllocaEscapeSite { + fn escape_inst(&self) -> InstId { + match self { + AllocaEscapeSite::Return { inst, .. } + | AllocaEscapeSite::NonLocalStore { inst, .. } + | AllocaEscapeSite::CallArg { inst, .. } => *inst, + } + } + + fn derived_value(&self) -> ValueId { + match self { + AllocaEscapeSite::Return { value, .. } + | AllocaEscapeSite::NonLocalStore { value, .. } + | AllocaEscapeSite::CallArg { value, .. } => *value, + } + } + + fn render(&self, module: &ModuleCtx) -> String { + match self { + AllocaEscapeSite::Return { inst, value } => { + format!("return of v{} at inst {}", value.as_u32(), inst.as_u32()) + } + AllocaEscapeSite::NonLocalStore { inst, addr, value } => format!( + "non-local store of v{} to addr v{} at inst {}", + value.as_u32(), + addr.as_u32(), + inst.as_u32() + ), + AllocaEscapeSite::CallArg { + inst, + callee, + arg_index, + value, + } => { + let callee_name = module.func_sig(*callee, |sig| sig.name().to_string()); + format!( + "call arg {arg_index} v{} to %{callee_name} at inst {}", + value.as_u32(), + inst.as_u32() + ) + } + } + } +} + +fn color_allocas(objects: &mut [AllocaObject]) -> (FxHashMap, u32) { + objects.sort_unstable_by_key(|obj| (obj.start_pos, obj.end_pos, obj.inst.as_u32())); + + let mut offsets: FxHashMap = FxHashMap::default(); + let mut heap: BinaryHeap> = BinaryHeap::new(); // (end, offset, size) + let mut free: Vec = Vec::new(); + let mut pool_words: u32 = 0; + + for obj in objects.iter().copied() { + while let Some(Reverse((end, offset, size))) = heap.peek().copied() + && end < obj.start_pos + { + heap.pop(); + free.push(FreeBlock { + offset_words: offset, + size_words: size, + }); + } + + if obj.size_words == 0 { + offsets.insert(obj.inst, 0); + continue; + } + + let mut best_idx: Option = None; + for (idx, block) in free.iter().enumerate() { + if block.size_words < obj.size_words { + continue; + } + match best_idx { + None => best_idx = Some(idx), + Some(best) if free[best].size_words > block.size_words => best_idx = Some(idx), + _ => {} + } + } + + let offset_words = if let Some(idx) = best_idx { + let block = &mut free[idx]; + let offset = block.offset_words; + if block.size_words == obj.size_words { + free.swap_remove(idx); + } else { + block.offset_words = block + .offset_words + .checked_add(obj.size_words) + .expect("alloca offset overflow"); + block.size_words = block + .size_words + .checked_sub(obj.size_words) + .expect("alloca free block underflow"); + } + offset + } else { + let offset = pool_words; + pool_words = pool_words + .checked_add(obj.size_words) + .expect("alloca pool overflow"); + offset + }; + + offsets.insert(obj.inst, offset_words); + heap.push(Reverse((obj.end_pos, offset_words, obj.size_words))); + } + + (offsets, pool_words) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + domtree::DomTree, + liveness::{InstLiveness, Liveness}, + }; + use sonatina_ir::cfg::ControlFlowGraph; + use sonatina_parser::parse_module; + use sonatina_triple::{Architecture, EvmVersion, OperatingSystem, TargetTriple, Vendor}; + + #[test] + fn phi_uses_are_accounted_on_predecessor_edges() { + let parsed = parse_module( + r#" +target = "evm-ethereum-osaka" + +func public %f() -> i256 { +block0: + v0.*i256 = alloca i256; + jump block1; + +block1: + v2.*i256 = phi (v0 block0) (v2 block3); + v3.i32 = phi (0.i32 block0) (v6 block3); + v4.i1 = lt v3 1.i32; + br v4 block2 block4; + +block2: + v5.*i256 = alloca i256; + mstore v5 1.i256 i256; + v6.i32 = add v3 1.i32; + jump block3; + +block3: + jump block1; + +block4: + return 0.i256; +} +"#, + ) + .unwrap(); + + let f = parsed + .module + .funcs() + .into_iter() + .find(|&f| parsed.module.ctx.func_sig(f, |sig| sig.name() == "f")) + .expect("missing function f"); + + let isa = Evm::new(TargetTriple { + architecture: Architecture::Evm, + vendor: Vendor::Ethereum, + operating_system: OperatingSystem::Evm(EvmVersion::Osaka), + }); + + parsed.module.func_store.view(f, |function| { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + + let mut liveness = Liveness::new(); + liveness.compute(function, &cfg); + + let mut inst_liveness = InstLiveness::new(); + inst_liveness.compute(function, &cfg, &liveness); + + let mut dom = DomTree::new(); + dom.compute(&cfg); + + let block_order = dom.rpo().to_owned(); + + let values_persistent_across_calls = BitSet::default(); + let layout = compute_stack_alloca_layout( + f, + function, + &parsed.module.ctx, + &isa, + &FxHashMap::default(), + AllocaLayoutLiveness { + values_persistent_across_calls: &values_persistent_across_calls, + inst_liveness: &inst_liveness, + }, + &block_order, + ); + + assert_eq!(layout.persistent_words, 0); + assert_eq!(layout.transient_words, 2); + + let mut allocas: Vec = Vec::new(); + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + if matches!( + isa.inst_set().resolve_inst(function.dfg.inst(inst)), + EvmInstKind::Alloca(_) + ) { + allocas.push(inst); + } + } + } + allocas.sort_unstable_by_key(|inst| inst.as_u32()); + assert_eq!(allocas.len(), 2); + + let off0 = layout + .plan + .get(&allocas[0]) + .expect("missing alloca plan") + .offset_words; + let off1 = layout + .plan + .get(&allocas[1]) + .expect("missing alloca plan") + .offset_words; + assert_ne!(off0, off1, "allocas should not overlap in memory"); + }); + } + + #[test] + #[should_panic(expected = "alloca escapes in f")] + fn alloca_escape_through_local_store_load_is_rejected() { + let parsed = parse_module( + r#" +target = "evm-ethereum-osaka" + +func public %f() -> *i256 { +block0: + v0.*i256 = alloca i256; + v1.**i256 = alloca *i256; + mstore v1 v0 *i256; + v2.*i256 = mload v1 *i256; + return v2; +} +"#, + ) + .unwrap(); + + let f = parsed + .module + .funcs() + .into_iter() + .find(|&f| parsed.module.ctx.func_sig(f, |sig| sig.name() == "f")) + .expect("missing function f"); + + let isa = Evm::new(TargetTriple { + architecture: Architecture::Evm, + vendor: Vendor::Ethereum, + operating_system: OperatingSystem::Evm(EvmVersion::Osaka), + }); + + parsed.module.func_store.view(f, |function| { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + + let mut liveness = Liveness::new(); + liveness.compute(function, &cfg); + + let mut inst_liveness = InstLiveness::new(); + inst_liveness.compute(function, &cfg, &liveness); + + let mut dom = DomTree::new(); + dom.compute(&cfg); + + let block_order = dom.rpo().to_owned(); + + let values_persistent_across_calls = BitSet::default(); + let _ = compute_stack_alloca_layout( + f, + function, + &parsed.module.ctx, + &isa, + &FxHashMap::default(), + AllocaLayoutLiveness { + values_persistent_across_calls: &values_persistent_across_calls, + inst_liveness: &inst_liveness, + }, + &block_order, + ); + }); + } +} diff --git a/crates/codegen/src/isa/evm/heap_plan.rs b/crates/codegen/src/isa/evm/heap_plan.rs new file mode 100644 index 00000000..0326734a --- /dev/null +++ b/crates/codegen/src/isa/evm/heap_plan.rs @@ -0,0 +1,484 @@ +use cranelift_entity::SecondaryMap; +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + BlockId, Function, InstId, InstSetExt, Module, ValueId, + cfg::ControlFlowGraph, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, +}; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::{ + module_analysis::{CallGraph, CallGraphSccs, SccBuilder, SccRef}, + stackalloc::{Action, Allocator, StackifyAlloc}, +}; + +use super::{ + memory_plan::{ + AllocaClass, FuncLocalMemInfo, FuncMemPlan, MemScheme, ProgramMemoryPlan, STATIC_BASE, + WORD_BYTES, + }, + provenance::compute_value_provenance, +}; + +pub(crate) fn compute_malloc_future_static_words( + module: &Module, + funcs: &[FuncRef], + plan: &ProgramMemoryPlan, + local_mem: &FxHashMap, + allocs: &FxHashMap, + isa: &Evm, +) -> FxHashMap> { + let funcs_set: FxHashSet = funcs.iter().copied().collect(); + let call_graph = CallGraph::build_graph_subset(module, &funcs_set); + let scc = SccBuilder::new().compute_scc(&call_graph); + + let static_max_words = plan + .dyn_base + .checked_sub(STATIC_BASE) + .expect("dyn base below static base") + / WORD_BYTES; + + let mut self_bound_words: FxHashMap = FxHashMap::default(); + for &f in funcs { + let scheme = plan.funcs.get(&f).map(|p| &p.scheme); + let bound = match scheme { + Some(MemScheme::StaticTree(st)) => local_mem.get(&f).map_or(0, |mem| { + st.base_words + .checked_add(mem.persistent_words) + .and_then(|w| w.checked_add(mem.transient_words)) + .expect("self static bound overflow") + }), + _ => 0, + }; + self_bound_words.insert(f, bound); + } + + let entry_bound_words = compute_entry_bounds( + &funcs_set, + &call_graph, + &scc, + &self_bound_words, + static_max_words, + ); + + let mut out: FxHashMap> = FxHashMap::default(); + for &f in funcs { + let func_plan = plan.funcs.get(&f); + let func_alloc = allocs.get(&f); + let ctx = FutureBoundsCtx { + module: &module.ctx, + isa, + entry_bounds: &entry_bound_words, + unknown_callee_bound: static_max_words, + func_plan, + alloc: func_alloc, + }; + let map = module.func_store.view(f, |function| { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + compute_future_bounds_for_func(function, &cfg, &ctx) + }); + out.insert(f, map); + } + + out +} + +fn compute_entry_bounds( + funcs: &FxHashSet, + call_graph: &CallGraph, + scc: &CallGraphSccs, + self_bounds: &FxHashMap, + unknown_callee_bound: u32, +) -> FxHashMap { + let topo = topo_sort_sccs(funcs, call_graph, scc); + + let mut entry: FxHashMap = FxHashMap::default(); + for &f in funcs { + entry.insert(f, self_bounds.get(&f).copied().unwrap_or(0)); + } + + for scc_ref in topo.into_iter().rev() { + let mut component: Vec = scc + .scc_info(scc_ref) + .components + .iter() + .copied() + .filter(|f| funcs.contains(f)) + .collect(); + component.sort_unstable_by_key(|f| f.as_u32()); + + loop { + let mut changed = false; + + for &f in &component { + let mut bound = self_bounds.get(&f).copied().unwrap_or(0); + for &callee in call_graph.callee_of(f) { + bound = bound.max(entry.get(&callee).copied().unwrap_or(unknown_callee_bound)); + } + + let cur = entry.get_mut(&f).expect("missing entry bound"); + if bound > *cur { + *cur = bound; + changed = true; + } + } + + if !changed { + break; + } + } + } + + entry +} + +fn topo_sort_sccs( + funcs: &FxHashSet, + call_graph: &CallGraph, + scc: &CallGraphSccs, +) -> Vec { + let mut sccs: BTreeSet = BTreeSet::new(); + for &f in funcs { + sccs.insert(scc.scc_ref(f)); + } + + let mut edges: BTreeMap> = BTreeMap::new(); + let mut indegree: BTreeMap = BTreeMap::new(); + for &s in &sccs { + edges.insert(s, BTreeSet::new()); + indegree.insert(s, 0); + } + + for &f in funcs { + let from = scc.scc_ref(f); + for &callee in call_graph.callee_of(f) { + let to = scc.scc_ref(callee); + if from == to { + continue; + } + + let tos = edges.get_mut(&from).expect("missing scc"); + if tos.insert(to) { + *indegree.get_mut(&to).expect("missing scc") += 1; + } + } + } + + let mut ready: BTreeSet = BTreeSet::new(); + for (&s, °) in &indegree { + if deg == 0 { + ready.insert(s); + } + } + + let mut topo: Vec = Vec::with_capacity(sccs.len()); + while let Some(&s) = ready.first() { + ready.remove(&s); + topo.push(s); + + let tos: Vec = edges + .get(&s) + .expect("missing scc") + .iter() + .copied() + .collect(); + for to in tos { + let deg = indegree.get_mut(&to).expect("missing scc"); + *deg = deg.checked_sub(1).expect("indegree underflow"); + if *deg == 0 { + ready.insert(to); + } + } + } + + debug_assert_eq!(topo.len(), sccs.len(), "SCC topo sort incomplete"); + topo +} + +fn compute_future_bounds_for_func( + function: &Function, + cfg: &ControlFlowGraph, + ctx: &FutureBoundsCtx<'_>, +) -> FxHashMap { + #[derive(Clone, Copy)] + struct StaticCtx { + base_words: u32, + persistent_words: u32, + alloca_words: u32, + persistent_alloca_words: u32, + } + + impl StaticCtx { + fn persistent_spills(self) -> u32 { + self.persistent_words + .checked_sub(self.persistent_alloca_words) + .expect("persistent spill words underflow") + } + + fn spill_slot_end_words(self, spill_slot: u32) -> u32 { + let persistent_spills = self.persistent_spills(); + let spill_words = if spill_slot >= persistent_spills { + spill_slot + .checked_add(self.alloca_words) + .expect("spill slot words overflow") + } else { + spill_slot + }; + + let addr_words = self + .base_words + .checked_add(spill_words) + .expect("spill addr words overflow"); + addr_words + .checked_add(1) + .expect("spill addr words overflow") + } + } + + let static_ctx = match ctx.func_plan.map(|p| &p.scheme) { + Some(MemScheme::StaticTree(st)) => ctx.func_plan.map(|p| StaticCtx { + base_words: st.base_words, + persistent_words: st.persistent_words, + alloca_words: p.alloca_words, + persistent_alloca_words: p.persistent_alloca_words, + }), + _ => None, + }; + + let mut value_alloca_bound: SecondaryMap = SecondaryMap::new(); + for value in function.dfg.values.keys() { + let _ = &mut value_alloca_bound[value]; + } + + if let (Some(static_ctx), Some(func_plan)) = (static_ctx, ctx.func_plan) { + let prov = compute_value_provenance(function, ctx.module, ctx.isa, |callee| { + let arg_count = ctx.module.func_sig(callee, |sig| sig.args().len()); + vec![true; arg_count] + }); + + let persistent_spills = static_ctx.persistent_spills(); + let mut alloca_end_words: FxHashMap = FxHashMap::default(); + for (&inst, plan) in &func_plan.alloca { + let data = ctx.isa.inst_set().resolve_inst(function.dfg.inst(inst)); + let EvmInstKind::Alloca(alloca) = data else { + continue; + }; + + let size_bytes: u32 = ctx + .isa + .type_layout() + .size_of(*alloca.ty(), ctx.module) + .expect("alloca has invalid type") as u32; + let size_words = size_bytes.div_ceil(WORD_BYTES); + + let start_words = match plan.class { + AllocaClass::Persistent => static_ctx + .base_words + .checked_add(persistent_spills) + .and_then(|w| w.checked_add(plan.offset_words)) + .expect("alloca addr words overflow"), + AllocaClass::Transient => static_ctx + .base_words + .checked_add(static_ctx.persistent_words) + .and_then(|w| w.checked_add(plan.offset_words)) + .expect("alloca addr words overflow"), + }; + + let end_words = start_words + .checked_add(size_words) + .expect("alloca end words overflow"); + alloca_end_words.insert(inst, end_words); + } + + for value in function.dfg.values.keys() { + let mut max_end = 0; + for base in prov[value].alloca_insts() { + if let Some(end) = alloca_end_words.get(&base) { + max_end = max_end.max(*end); + } + } + value_alloca_bound[value] = max_end; + } + } + + fn action_static_bound(action: &Action, static_ctx: Option) -> u32 { + match action { + Action::MemLoadAbs(offset) | Action::MemStoreAbs(offset) => { + if *offset < STATIC_BASE { + return 0; + } + let delta = offset + .checked_sub(STATIC_BASE) + .expect("abs addr below static base"); + delta / WORD_BYTES + 1 + } + Action::MemLoadFrameSlot(slot) | Action::MemStoreFrameSlot(slot) => { + static_ctx.map_or(0, |ctx| ctx.spill_slot_end_words(*slot)) + } + _ => 0, + } + } + + fn actions_static_bound(actions: &[Action], static_ctx: Option) -> u32 { + actions + .iter() + .map(|a| action_static_bound(a, static_ctx)) + .max() + .unwrap_or(0) + } + + fn inst_alloca_bound( + data: &EvmInstKind, + value_alloca_bound: &SecondaryMap, + ) -> u32 { + match data { + EvmInstKind::Mload(mload) => value_alloca_bound[*mload.addr()], + EvmInstKind::Mstore(mstore) => value_alloca_bound[*mstore.addr()], + EvmInstKind::EvmMstore8(mstore8) => value_alloca_bound[*mstore8.addr()], + EvmInstKind::EvmMcopy(mcopy) => { + value_alloca_bound[*mcopy.dest()].max(value_alloca_bound[*mcopy.addr()]) + } + EvmInstKind::EvmKeccak256(keccak) => value_alloca_bound[*keccak.addr()], + EvmInstKind::EvmCalldataCopy(copy) => value_alloca_bound[*copy.dst_addr()], + EvmInstKind::EvmCodeCopy(copy) => value_alloca_bound[*copy.dst_addr()], + EvmInstKind::EvmExtCodeCopy(copy) => value_alloca_bound[*copy.dst_addr()], + EvmInstKind::EvmReturnDataCopy(copy) => value_alloca_bound[*copy.dst_addr()], + EvmInstKind::EvmReturn(ret) => value_alloca_bound[*ret.addr()], + EvmInstKind::EvmRevert(rev) => value_alloca_bound[*rev.addr()], + EvmInstKind::EvmLog0(log) => value_alloca_bound[*log.addr()], + EvmInstKind::EvmLog1(log) => value_alloca_bound[*log.addr()], + EvmInstKind::EvmLog2(log) => value_alloca_bound[*log.addr()], + EvmInstKind::EvmLog3(log) => value_alloca_bound[*log.addr()], + EvmInstKind::EvmLog4(log) => value_alloca_bound[*log.addr()], + EvmInstKind::EvmCreate(create) => value_alloca_bound[*create.addr()], + EvmInstKind::EvmCreate2(create) => value_alloca_bound[*create.addr()], + EvmInstKind::EvmCall(call) => { + value_alloca_bound[*call.arg_addr()].max(value_alloca_bound[*call.ret_addr()]) + } + EvmInstKind::EvmCallCode(call) => { + value_alloca_bound[*call.arg_addr()].max(value_alloca_bound[*call.ret_addr()]) + } + EvmInstKind::EvmDelegateCall(call) => { + value_alloca_bound[*call.arg_addr()].max(value_alloca_bound[*call.ret_addr()]) + } + EvmInstKind::EvmStaticCall(call) => { + value_alloca_bound[*call.arg_addr()].max(value_alloca_bound[*call.ret_addr()]) + } + EvmInstKind::Call(call) => call + .args() + .iter() + .map(|v| value_alloca_bound[*v]) + .max() + .unwrap_or(0), + _ => 0, + } + } + + let mut block_in: SecondaryMap = SecondaryMap::new(); + for block in function.layout.iter_block() { + let _ = &mut block_in[block]; + } + + let blocks: Vec = cfg.post_order().collect(); + + let mut block_local: SecondaryMap = SecondaryMap::new(); + for block in function.layout.iter_block() { + let _ = &mut block_local[block]; + } + + for block in function.layout.iter_block() { + let mut bound = 0; + + for inst in function.layout.iter_inst(block) { + let data = ctx.isa.inst_set().resolve_inst(function.dfg.inst(inst)); + + if let Some(call) = function.dfg.call_info(inst) { + let callee = call.callee(); + bound = bound.max( + ctx.entry_bounds + .get(&callee) + .copied() + .unwrap_or(ctx.unknown_callee_bound), + ); + } + + bound = bound.max(inst_alloca_bound(&data, &value_alloca_bound)); + + if let Some(alloc) = ctx.alloc { + let args = function.dfg.inst(inst).collect_values(); + let pre = alloc.read(inst, &args); + let post = alloc.write(inst, function.dfg.inst_result(inst)); + bound = bound.max(actions_static_bound(&pre, static_ctx)); + bound = bound.max(actions_static_bound(&post, static_ctx)); + } + } + + block_local[block] = bound; + } + + let mut changed = true; + while changed { + changed = false; + + for &block in &blocks { + let out = cfg.succs_of(block).map(|b| block_in[*b]).max().unwrap_or(0); + let bound = out.max(block_local[block]); + + if bound > block_in[block] { + block_in[block] = bound; + changed = true; + } + } + } + + let mut malloc_bounds: FxHashMap = FxHashMap::default(); + for block in function.layout.iter_block() { + let out = cfg.succs_of(block).map(|b| block_in[*b]).max().unwrap_or(0); + let mut bound = out; + + let insts: Vec = function.layout.iter_inst(block).collect(); + for inst in insts.into_iter().rev() { + if let Some(alloc) = ctx.alloc { + let post = alloc.write(inst, function.dfg.inst_result(inst)); + bound = bound.max(actions_static_bound(&post, static_ctx)); + } + + let data = ctx.isa.inst_set().resolve_inst(function.dfg.inst(inst)); + bound = bound.max(inst_alloca_bound(&data, &value_alloca_bound)); + + if matches!(data, EvmInstKind::EvmMalloc(_)) { + malloc_bounds.insert(inst, bound); + } + + if let Some(call) = function.dfg.call_info(inst) { + let callee = call.callee(); + bound = bound.max( + ctx.entry_bounds + .get(&callee) + .copied() + .unwrap_or(ctx.unknown_callee_bound), + ); + } + + if let Some(alloc) = ctx.alloc { + let args = function.dfg.inst(inst).collect_values(); + let pre = alloc.read(inst, &args); + bound = bound.max(actions_static_bound(&pre, static_ctx)); + } + } + } + + malloc_bounds +} + +struct FutureBoundsCtx<'a> { + module: &'a ModuleCtx, + isa: &'a Evm, + entry_bounds: &'a FxHashMap, + unknown_callee_bound: u32, + func_plan: Option<&'a FuncMemPlan>, + alloc: Option<&'a StackifyAlloc>, +} diff --git a/crates/codegen/src/isa/evm/malloc_plan.rs b/crates/codegen/src/isa/evm/malloc_plan.rs new file mode 100644 index 00000000..e3b684a9 --- /dev/null +++ b/crates/codegen/src/isa/evm/malloc_plan.rs @@ -0,0 +1,301 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + Function, InstId, InstSetExt, Type, ValueId, + func_cursor::{CursorLocation, FuncCursor, InstInserter}, + inst::{ + data::{Mload, Mstore}, + evm::inst_set::EvmInstKind, + }, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, +}; + +use crate::liveness::InstLiveness; + +use super::{ + mem_effects::FuncMemEffects, + provenance::{Provenance, compute_value_provenance}, + ptr_escape::PtrEscapeSummary, +}; + +pub(crate) fn should_restore_free_ptr_on_internal_returns( + function: &Function, + module: &ModuleCtx, + isa: &Evm, + ptr_escape: &FxHashMap, + transient_mallocs: &FxHashSet, +) -> bool { + let mut has_internal_return = false; + let mut has_persistent_malloc = false; + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + if matches!(data, EvmInstKind::Return(_)) { + has_internal_return = true; + } + if matches!(data, EvmInstKind::EvmMalloc(_)) && !transient_mallocs.contains(&inst) { + has_persistent_malloc = true; + } + } + } + + if !has_internal_return || !has_persistent_malloc { + return false; + } + + let prov = compute_value_provenance(function, module, isa, |callee| { + ptr_escape + .get(&callee) + .cloned() + .unwrap_or_else(|| conservative_unknown_ptr_summary(module, callee)) + .arg_may_be_returned + }); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + match data { + EvmInstKind::Return(ret) => { + let Some(ret_val) = *ret.arg() else { + continue; + }; + if value_may_be_heap_derived(function, module, ret_val, &prov) { + return false; + } + } + EvmInstKind::Mstore(mstore) => { + let addr = *mstore.addr(); + if prov[addr].is_local_addr() { + continue; + } + + let val = *mstore.value(); + if value_may_be_heap_derived(function, module, val, &prov) { + return false; + } + } + EvmInstKind::Call(call) => { + let callee = *call.callee(); + let callee_sum = ptr_escape + .get(&callee) + .cloned() + .unwrap_or_else(|| conservative_unknown_ptr_summary(module, callee)); + for (idx, &arg) in call.args().iter().enumerate() { + if idx < callee_sum.arg_may_escape.len() + && callee_sum.arg_may_escape[idx] + && value_may_be_heap_derived(function, module, arg, &prov) + { + return false; + } + } + } + _ => {} + } + } + } + + true +} + +pub(crate) fn insert_free_ptr_restore_on_internal_returns(function: &mut Function, isa: &Evm) { + let Some(entry) = function.layout.entry_block() else { + return; + }; + + let addr = function.dfg.make_imm_value(64i32); + + let mut insert_loc = CursorLocation::BlockTop(entry); + for inst in function.layout.iter_inst(entry) { + if function.dfg.is_phi(inst) { + insert_loc = CursorLocation::At(inst); + } else { + break; + } + } + + let mut cursor = InstInserter::at_location(insert_loc); + let load_inst = cursor.insert_inst_data(function, Mload::new(isa.inst_set(), addr, Type::I256)); + let saved = cursor.make_result(function, load_inst, Type::I256); + cursor.attach_result(function, load_inst, saved); + + let mut return_insts: Vec<(sonatina_ir::BlockId, InstId)> = Vec::new(); + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + if matches!( + isa.inst_set().resolve_inst(function.dfg.inst(inst)), + EvmInstKind::Return(_) + ) { + return_insts.push((block, inst)); + } + } + } + + for (block, ret_inst) in return_insts { + let prev = function.layout.prev_inst_of(ret_inst); + let loc = prev.map_or(CursorLocation::BlockTop(block), CursorLocation::At); + let mut cursor = InstInserter::at_location(loc); + let _ = cursor.insert_inst_data( + function, + Mstore::new(isa.inst_set(), addr, saved, Type::I256), + ); + } +} + +pub(crate) fn compute_transient_mallocs( + function: &Function, + module: &ModuleCtx, + isa: &Evm, + ptr_escape: &FxHashMap, + mem_effects: Option<&FxHashMap>, + inst_liveness: &InstLiveness, +) -> FxHashSet { + let mut mallocs: FxHashSet = FxHashSet::default(); + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + if matches!( + isa.inst_set().resolve_inst(function.dfg.inst(inst)), + EvmInstKind::EvmMalloc(_) + ) { + mallocs.insert(inst); + } + } + } + + if mallocs.is_empty() { + return FxHashSet::default(); + } + + let prov = compute_value_provenance(function, module, isa, |callee| { + ptr_escape + .get(&callee) + .cloned() + .unwrap_or_else(|| conservative_unknown_ptr_summary(module, callee)) + .arg_may_be_returned + }); + + let escaping = compute_escaping_mallocs(function, module, isa, ptr_escape, &prov); + + for base in escaping { + mallocs.remove(&base); + } + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + + if matches!(data, EvmInstKind::EvmMalloc(_)) { + let mut live = inst_liveness.live_out(inst).clone(); + if let Some(def) = function.dfg.inst_result(inst) { + live.remove(def); + } + + remove_live_mallocs(&mut mallocs, &live, &prov); + continue; + } + + let Some(call) = function.dfg.call_info(inst) else { + continue; + }; + + let callee = call.callee(); + let is_barrier = mem_effects.is_none_or(|effects| { + let eff = effects.get(&callee).copied().unwrap_or_default(); + eff.touches_heap_meta || eff.touches_dyn_frame + }); + if !is_barrier { + continue; + } + + let live = inst_liveness.live_out(inst); + remove_live_mallocs(&mut mallocs, live, &prov); + } + } + + mallocs +} + +fn remove_live_mallocs( + mallocs: &mut FxHashSet, + live: &crate::bitset::BitSet, + prov: &cranelift_entity::SecondaryMap, +) { + for v in live.iter() { + for base in prov[v].malloc_insts() { + mallocs.remove(&base); + } + } +} + +fn compute_escaping_mallocs( + function: &Function, + module: &ModuleCtx, + isa: &Evm, + ptr_escape: &FxHashMap, + prov: &cranelift_entity::SecondaryMap, +) -> FxHashSet { + let mut escaping: FxHashSet = FxHashSet::default(); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + match data { + EvmInstKind::Return(ret) => { + let Some(ret_val) = *ret.arg() else { + continue; + }; + for base in prov[ret_val].malloc_insts() { + escaping.insert(base); + } + } + EvmInstKind::Mstore(mstore) => { + let addr = *mstore.addr(); + if prov[addr].is_local_addr() { + continue; + } + + let val = *mstore.value(); + for base in prov[val].malloc_insts() { + escaping.insert(base); + } + } + EvmInstKind::Call(call) => { + let callee = *call.callee(); + let callee_sum = ptr_escape + .get(&callee) + .cloned() + .unwrap_or_else(|| conservative_unknown_ptr_summary(module, callee)); + for (idx, &arg) in call.args().iter().enumerate() { + if idx < callee_sum.arg_may_escape.len() && callee_sum.arg_may_escape[idx] { + for base in prov[arg].malloc_insts() { + escaping.insert(base); + } + } + } + } + _ => {} + } + } + } + + escaping +} + +fn conservative_unknown_ptr_summary(module: &ModuleCtx, func_ref: FuncRef) -> PtrEscapeSummary { + let arg_count = module.func_sig(func_ref, |sig| sig.args().len()); + PtrEscapeSummary { + arg_may_escape: vec![true; arg_count], + arg_may_be_returned: vec![true; arg_count], + returns_any_ptr: module.func_sig(func_ref, |sig| sig.ret_ty().is_pointer(module)), + } +} + +fn value_may_be_heap_derived( + function: &Function, + module: &ModuleCtx, + value: ValueId, + prov: &cranelift_entity::SecondaryMap, +) -> bool { + prov[value].malloc_insts().next().is_some() + || (function.dfg.value_ty(value).is_pointer(module) && prov[value].is_empty()) +} diff --git a/crates/codegen/src/isa/evm/mem_effects.rs b/crates/codegen/src/isa/evm/mem_effects.rs new file mode 100644 index 00000000..690a87fa --- /dev/null +++ b/crates/codegen/src/isa/evm/mem_effects.rs @@ -0,0 +1,238 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + Function, InstSetExt, Module, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, +}; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::module_analysis::{CallGraph, CallGraphSccs, SccBuilder, SccRef}; + +use super::{ + memory_plan::{FuncLocalMemInfo, MemScheme, ProgramMemoryPlan}, + provenance::compute_value_provenance, + scratch_plan::inst_is_scratch_clobber, +}; + +#[derive(Clone, Copy, Default)] +pub(crate) struct FuncMemEffects { + pub(crate) touches_static_arena: bool, + pub(crate) touches_scratch: bool, + pub(crate) touches_dyn_frame: bool, + pub(crate) touches_heap_meta: bool, +} + +impl FuncMemEffects { + fn union_with(&mut self, other: FuncMemEffects) { + self.touches_static_arena |= other.touches_static_arena; + self.touches_scratch |= other.touches_scratch; + self.touches_dyn_frame |= other.touches_dyn_frame; + self.touches_heap_meta |= other.touches_heap_meta; + } +} + +pub(crate) fn compute_func_mem_effects( + module: &Module, + funcs: &[FuncRef], + plan: &ProgramMemoryPlan, + local_mem: &FxHashMap, + scratch_spill_funcs: &FxHashSet, + isa: &Evm, +) -> FxHashMap { + let funcs_set: FxHashSet = funcs.iter().copied().collect(); + let call_graph = CallGraph::build_graph_subset(module, &funcs_set); + let scc = SccBuilder::new().compute_scc(&call_graph); + + let topo = topo_sort_sccs(&funcs_set, &call_graph, &scc); + let edges = build_scc_edges(&funcs_set, &call_graph, &scc, &topo); + + let mut local_effects: FxHashMap = FxHashMap::default(); + for &func in funcs { + let scheme = plan.funcs.get(&func).map(|p| &p.scheme); + let mem = local_mem.get(&func).copied().unwrap_or(FuncLocalMemInfo { + persistent_words: 0, + transient_words: 0, + alloca_words: 0, + persistent_alloca_words: 0, + }); + + let touches_static_arena = matches!(scheme, Some(MemScheme::StaticTree(_))) + && (mem.persistent_words != 0 || mem.transient_words != 0); + let touches_dyn_frame = matches!(scheme, Some(MemScheme::DynamicFrame)) + && (mem.persistent_words != 0 || mem.transient_words != 0); + + let (touches_heap_meta, touches_scratch_clobber) = module.func_store.view(func, |f| { + ( + func_uses_malloc(f, isa), + func_clobbers_scratch(f, &module.ctx, isa), + ) + }); + let touches_scratch = scratch_spill_funcs.contains(&func) || touches_scratch_clobber; + + local_effects.insert( + func, + FuncMemEffects { + touches_static_arena, + touches_scratch, + touches_dyn_frame, + touches_heap_meta, + }, + ); + } + + let mut scc_effects: FxHashMap = FxHashMap::default(); + for scc_ref in &topo { + scc_effects.insert(*scc_ref, FuncMemEffects::default()); + } + + for scc_ref in &topo { + let mut eff = FuncMemEffects::default(); + for &f in scc + .scc_info(*scc_ref) + .components + .iter() + .filter(|f| funcs_set.contains(f)) + { + eff.union_with(local_effects.get(&f).copied().unwrap_or_default()); + } + scc_effects.insert(*scc_ref, eff); + } + + for &scc_ref in topo.iter().rev() { + let mut eff = scc_effects.get(&scc_ref).copied().unwrap_or_default(); + for &callee in edges.get(&scc_ref).into_iter().flatten() { + eff.union_with(scc_effects.get(&callee).copied().unwrap_or_default()); + } + scc_effects.insert(scc_ref, eff); + } + + let mut out: FxHashMap = FxHashMap::default(); + for &f in funcs { + out.insert(f, scc_effects[&scc.scc_ref(f)]); + } + out +} + +fn func_uses_malloc(function: &Function, isa: &Evm) -> bool { + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + if matches!( + isa.inst_set().resolve_inst(function.dfg.inst(inst)), + EvmInstKind::EvmMalloc(_) + ) { + return true; + } + } + } + false +} + +fn func_clobbers_scratch(function: &Function, module: &ModuleCtx, isa: &Evm) -> bool { + let prov = compute_value_provenance(function, module, isa, |callee| { + let arg_count = module.func_sig(callee, |sig| sig.args().len()); + vec![true; arg_count] + }); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + if inst_is_scratch_clobber(function, isa, inst, &prov) { + return true; + } + } + } + + false +} + +fn topo_sort_sccs( + funcs: &FxHashSet, + call_graph: &CallGraph, + scc: &CallGraphSccs, +) -> Vec { + let mut sccs: BTreeSet = BTreeSet::new(); + for &f in funcs { + sccs.insert(scc.scc_ref(f)); + } + + let mut edges: BTreeMap> = BTreeMap::new(); + let mut indegree: BTreeMap = BTreeMap::new(); + for &s in &sccs { + edges.insert(s, BTreeSet::new()); + indegree.insert(s, 0); + } + + for &f in funcs { + let from = scc.scc_ref(f); + for &callee in call_graph.callee_of(f) { + let to = scc.scc_ref(callee); + if from == to { + continue; + } + + let tos = edges.get_mut(&from).expect("missing scc"); + if tos.insert(to) { + *indegree.get_mut(&to).expect("missing scc") += 1; + } + } + } + + let mut ready: BTreeSet = BTreeSet::new(); + for (&s, °) in &indegree { + if deg == 0 { + ready.insert(s); + } + } + + let mut topo: Vec = Vec::with_capacity(sccs.len()); + while let Some(&s) = ready.first() { + ready.remove(&s); + topo.push(s); + + let tos: Vec = edges + .get(&s) + .expect("missing scc") + .iter() + .copied() + .collect(); + for to in tos { + let deg = indegree.get_mut(&to).expect("missing scc"); + *deg = deg.checked_sub(1).expect("indegree underflow"); + if *deg == 0 { + ready.insert(to); + } + } + } + + debug_assert_eq!(topo.len(), sccs.len(), "SCC topo sort incomplete"); + topo +} + +fn build_scc_edges( + funcs: &FxHashSet, + call_graph: &CallGraph, + scc: &CallGraphSccs, + topo: &[SccRef], +) -> BTreeMap> { + let mut edges: BTreeMap> = BTreeMap::new(); + for &s in topo { + edges.insert(s, BTreeSet::new()); + } + + for &f in funcs { + let from = scc.scc_ref(f); + for &callee in call_graph.callee_of(f) { + let to = scc.scc_ref(callee); + if from == to { + continue; + } + edges.get_mut(&from).expect("missing scc").insert(to); + } + } + + let mut out: BTreeMap> = BTreeMap::new(); + for (s, tos) in edges { + out.insert(s, tos.into_iter().collect()); + } + out +} diff --git a/crates/codegen/src/isa/evm/memory_plan.rs b/crates/codegen/src/isa/evm/memory_plan.rs new file mode 100644 index 00000000..bd3348a6 --- /dev/null +++ b/crates/codegen/src/isa/evm/memory_plan.rs @@ -0,0 +1,540 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{InstId, Module, module::FuncRef}; +use std::collections::BTreeSet; + +use crate::module_analysis::{CallGraph, CallGraphSccs, SccBuilder, SccRef}; + +pub const WORD_BYTES: u32 = 32; + +pub const FREE_PTR_SLOT: u8 = 0x40; + +pub const DYN_SP_SLOT: u8 = 0x80; +pub const DYN_FP_SLOT: u8 = 0xa0; + +pub const STATIC_BASE: u32 = 0xc0; + +#[derive(Clone, Debug)] +pub struct ProgramMemoryPlan { + pub dyn_base: u32, + pub funcs: FxHashMap, +} + +#[derive(Clone, Debug)] +pub struct FuncMemPlan { + pub scheme: MemScheme, + pub alloca: FxHashMap, + pub alloca_words: u32, + pub persistent_alloca_words: u32, + pub malloc_future_static_words: FxHashMap, + pub transient_mallocs: FxHashSet, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AllocaClass { + Persistent, + Transient, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StackObjectPlan { + pub class: AllocaClass, + pub offset_words: u32, +} + +#[derive(Clone, Debug)] +pub enum MemScheme { + StaticTree(StaticTreeFuncPlan), + DynamicFrame, +} + +#[derive(Clone, Debug)] +pub struct StaticTreeFuncPlan { + pub base_words: u32, + pub persistent_words: u32, +} + +#[derive(Clone, Copy, Debug)] +pub struct FuncLocalMemInfo { + pub persistent_words: u32, + pub transient_words: u32, + pub alloca_words: u32, + pub persistent_alloca_words: u32, +} + +pub fn compute_program_memory_plan( + module: &Module, + funcs: &[FuncRef], + local_mem: &FxHashMap, + alloca_offsets: &FxHashMap>, +) -> ProgramMemoryPlan { + let funcs_set: FxHashSet = funcs.iter().copied().collect(); + let call_graph = CallGraph::build_graph_subset(module, &funcs_set); + let scc = SccBuilder::new().compute_scc(&call_graph); + + let callers = compute_callers(&funcs_set, &call_graph); + + let mut eligible: FxHashSet = FxHashSet::default(); + for &f in funcs_set.iter() { + let caller_count = callers.get(&f).map_or(0, |cs| cs.len()); + if !scc.scc_of(f).is_cycle && caller_count <= 1 { + eligible.insert(f); + } + } + + let (children, roots) = build_forest(&eligible, &callers); + + let static_enter_words = + compute_static_enter_words(&funcs_set, &call_graph, &scc, &eligible, local_mem); + + let mut need_words: FxHashMap = FxHashMap::default(); + let mut static_max_words: u32 = 0; + for &root in &roots { + let need = compute_need_words(root, local_mem, &children, &mut need_words); + let base_words = static_enter_words.get(&root).copied().unwrap_or(0); + let total = base_words + .checked_add(need) + .expect("static max words overflow"); + static_max_words = static_max_words.max(total); + } + + let static_max_bytes = static_max_words + .checked_mul(WORD_BYTES) + .expect("static max bytes overflow"); + let dyn_base = STATIC_BASE + .checked_add(static_max_bytes) + .expect("dyn base overflow"); + + let mut plans: FxHashMap = FxHashMap::default(); + for &f in funcs { + let Some(mem) = local_mem.get(&f) else { + continue; + }; + + let alloca = alloca_offsets.get(&f).cloned().unwrap_or_default(); + + let scheme = if eligible.contains(&f) { + let base_words = static_enter_words.get(&f).copied().unwrap_or(0); + MemScheme::StaticTree(StaticTreeFuncPlan { + base_words, + persistent_words: mem.persistent_words, + }) + } else { + MemScheme::DynamicFrame + }; + + plans.insert( + f, + FuncMemPlan { + scheme, + alloca, + alloca_words: mem.alloca_words, + persistent_alloca_words: mem.persistent_alloca_words, + malloc_future_static_words: FxHashMap::default(), + transient_mallocs: FxHashSet::default(), + }, + ); + } + + ProgramMemoryPlan { + dyn_base, + funcs: plans, + } +} + +fn compute_callers( + funcs: &FxHashSet, + call_graph: &CallGraph, +) -> FxHashMap> { + let mut callers: FxHashMap> = FxHashMap::default(); + for &f in funcs { + callers.entry(f).or_default(); + } + + for &f in funcs { + for &callee in call_graph.callee_of(f) { + debug_assert!(funcs.contains(&callee), "subset call graph invariant"); + callers.entry(callee).or_default().push(f); + } + } + + for cs in callers.values_mut() { + cs.sort_unstable_by_key(|f| f.as_u32()); + } + + callers +} + +fn build_forest( + eligible: &FxHashSet, + callers: &FxHashMap>, +) -> (FxHashMap>, Vec) { + let mut children: FxHashMap> = FxHashMap::default(); + let mut roots: Vec = Vec::new(); + + for &f in eligible { + let p = match callers.get(&f).map(Vec::as_slice).unwrap_or_default() { + [only] if eligible.contains(only) => Some(*only), + _ => None, + }; + children.entry(f).or_default(); + if let Some(p) = p { + children.entry(p).or_default().push(f); + } else { + roots.push(f); + } + } + + for cs in children.values_mut() { + cs.sort_unstable_by_key(|f| f.as_u32()); + } + + roots.sort_unstable_by_key(|f| f.as_u32()); + + (children, roots) +} + +fn compute_static_enter_words( + funcs: &FxHashSet, + call_graph: &CallGraph, + scc: &CallGraphSccs, + eligible: &FxHashSet, + local_mem: &FxHashMap, +) -> FxHashMap { + let mut sccs: BTreeSet = BTreeSet::new(); + for &f in funcs { + sccs.insert(scc.scc_ref(f)); + } + + let mut edges: FxHashMap> = FxHashMap::default(); + let mut indegree: FxHashMap = FxHashMap::default(); + for &s in &sccs { + edges.insert(s, BTreeSet::new()); + indegree.insert(s, 0); + } + + for &f in funcs { + let from = scc.scc_ref(f); + for &callee in call_graph.callee_of(f) { + debug_assert!(funcs.contains(&callee), "subset call graph invariant"); + let to = scc.scc_ref(callee); + if from == to { + continue; + } + + let tos = edges.get_mut(&from).expect("missing scc"); + if tos.insert(to) { + *indegree.get_mut(&to).expect("missing scc") += 1; + } + } + } + + let mut ready: BTreeSet = BTreeSet::new(); + for (&s, °) in &indegree { + if deg == 0 { + ready.insert(s); + } + } + + let mut topo: Vec = Vec::with_capacity(sccs.len()); + while let Some(&s) = ready.first() { + ready.remove(&s); + topo.push(s); + + let tos: Vec = edges + .get(&s) + .expect("missing scc") + .iter() + .copied() + .collect(); + for to in tos { + let deg = indegree.get_mut(&to).expect("missing scc"); + *deg = deg.checked_sub(1).expect("indegree underflow"); + if *deg == 0 { + ready.insert(to); + } + } + } + + debug_assert_eq!(topo.len(), sccs.len(), "SCC topo sort incomplete"); + + let mut enter_words: FxHashMap = FxHashMap::default(); + for &s in &sccs { + enter_words.insert(s, 0); + } + + for &s in &topo { + let base = enter_words.get(&s).copied().unwrap_or(0); + let push_words = scc_push_words(scc, s, eligible, local_mem); + + for &to in edges.get(&s).expect("missing scc") { + let next = base + .checked_add(push_words) + .expect("static enter words overflow"); + let dest = enter_words.get_mut(&to).expect("missing scc"); + *dest = (*dest).max(next); + } + } + + let mut out: FxHashMap = FxHashMap::default(); + for &f in funcs { + let s = scc.scc_ref(f); + out.insert(f, enter_words.get(&s).copied().unwrap_or(0)); + } + + out +} + +fn scc_push_words( + scc: &CallGraphSccs, + scc_ref: SccRef, + eligible: &FxHashSet, + local_mem: &FxHashMap, +) -> u32 { + let info = scc.scc_info(scc_ref); + if info.is_cycle { + return 0; + } + + let Some(&func) = info.components.iter().min_by_key(|f| f.as_u32()) else { + return 0; + }; + + if eligible.contains(&func) { + local_mem.get(&func).map_or(0, |m| m.persistent_words) + } else { + 0 + } +} + +fn compute_need_words( + f: FuncRef, + local_mem: &FxHashMap, + children: &FxHashMap>, + memo: &mut FxHashMap, +) -> u32 { + if let Some(&cached) = memo.get(&f) { + return cached; + } + + let Some(mem) = local_mem.get(&f) else { + memo.insert(f, 0); + return 0; + }; + + let child_need = children + .get(&f) + .map(Vec::as_slice) + .unwrap_or_default() + .iter() + .map(|&c| compute_need_words(c, local_mem, children, memo)) + .max() + .unwrap_or(0); + + let need = mem + .persistent_words + .checked_add(mem.transient_words.max(child_need)) + .expect("need words overflow"); + memo.insert(f, need); + need +} + +#[cfg(test)] +mod tests { + use super::*; + use sonatina_parser::parse_module; + + fn plan_from_src(src: &str) -> (ProgramMemoryPlan, FxHashMap) { + let parsed = parse_module(src).unwrap(); + let funcs: Vec = parsed.module.funcs(); + + let mut local_mem: FxHashMap = FxHashMap::default(); + let mut alloca_offsets: FxHashMap> = + FxHashMap::default(); + for f in &funcs { + local_mem.insert( + *f, + FuncLocalMemInfo { + persistent_words: 1, + transient_words: 0, + alloca_words: 0, + persistent_alloca_words: 0, + }, + ); + alloca_offsets.insert(*f, FxHashMap::default()); + } + + let plan = compute_program_memory_plan(&parsed.module, &funcs, &local_mem, &alloca_offsets); + + let mut names: FxHashMap = FxHashMap::default(); + for f in funcs { + let name = parsed.module.ctx.func_sig(f, |sig| sig.name().to_string()); + names.insert(name, f); + } + + (plan, names) + } + + #[test] + fn static_tree_chain() { + let (plan, names) = plan_from_src( + r#" +target = "evm-ethereum-osaka" + +func public %c() -> i256 { +block0: + return 0.i256; +} + +func public %b() -> i256 { +block0: + v0.i256 = call %c; + return v0; +} + +func public %a() -> i256 { +block0: + v0.i256 = call %b; + return v0; +} +"#, + ); + + assert_eq!(plan.dyn_base, STATIC_BASE + 3 * WORD_BYTES); + + let a = names["a"]; + let b = names["b"]; + let c = names["c"]; + + let MemScheme::StaticTree(a_plan) = &plan.funcs[&a].scheme else { + panic!("expected a to be StaticTree"); + }; + let MemScheme::StaticTree(b_plan) = &plan.funcs[&b].scheme else { + panic!("expected b to be StaticTree"); + }; + let MemScheme::StaticTree(c_plan) = &plan.funcs[&c].scheme else { + panic!("expected c to be StaticTree"); + }; + + assert_eq!(a_plan.base_words, 0); + assert_eq!(b_plan.base_words, 1); + assert_eq!(c_plan.base_words, 2); + } + + #[test] + fn static_tree_diamond_marks_multi_parent_ineligible() { + let (plan, names) = plan_from_src( + r#" +target = "evm-ethereum-osaka" + +func public %d() -> i256 { +block0: + return 0.i256; +} + +func public %b() -> i256 { +block0: + v0.i256 = call %d; + return v0; +} + +func public %c() -> i256 { +block0: + v0.i256 = call %d; + return v0; +} + +func public %a() -> i256 { +block0: + v0.i256 = call %b; + v1.i256 = call %c; + return v1; +} +"#, + ); + + let a = names["a"]; + let b = names["b"]; + let c = names["c"]; + let d = names["d"]; + + assert!(matches!(plan.funcs[&d].scheme, MemScheme::DynamicFrame)); + + let MemScheme::StaticTree(a_plan) = &plan.funcs[&a].scheme else { + panic!("expected a to be StaticTree"); + }; + let MemScheme::StaticTree(b_plan) = &plan.funcs[&b].scheme else { + panic!("expected b to be StaticTree"); + }; + let MemScheme::StaticTree(c_plan) = &plan.funcs[&c].scheme else { + panic!("expected c to be StaticTree"); + }; + + assert_eq!(a_plan.base_words, 0); + assert_eq!(b_plan.base_words, 1); + assert_eq!(c_plan.base_words, 1); + assert_eq!(plan.dyn_base, STATIC_BASE + 2 * WORD_BYTES); + } + + #[test] + fn static_tree_self_recursion_ineligible() { + let (plan, names) = plan_from_src( + r#" +target = "evm-ethereum-osaka" + +func public %a() -> i256 { +block0: + v0.i256 = call %a; + return v0; +} +"#, + ); + + let a = names["a"]; + assert!(matches!(plan.funcs[&a].scheme, MemScheme::DynamicFrame)); + assert_eq!(plan.dyn_base, STATIC_BASE); + } + + #[test] + fn static_tree_base_propagates_through_dynamic_parent() { + let (plan, names) = plan_from_src( + r#" +target = "evm-ethereum-osaka" + +func public %b() -> i256 { +block0: + return 0.i256; +} + +func public %d() -> i256 { +block0: + v0.i256 = call %d; + v1.i256 = call %b; + return v1; +} + +func public %a() -> i256 { +block0: + v0.i256 = call %d; + return v0; +} +"#, + ); + + let a = names["a"]; + let b = names["b"]; + let d = names["d"]; + + assert!(matches!(plan.funcs[&d].scheme, MemScheme::DynamicFrame)); + + let MemScheme::StaticTree(a_plan) = &plan.funcs[&a].scheme else { + panic!("expected a to be StaticTree"); + }; + let MemScheme::StaticTree(b_plan) = &plan.funcs[&b].scheme else { + panic!("expected b to be StaticTree"); + }; + + assert_eq!(a_plan.base_words, 0); + assert_eq!(b_plan.base_words, 1); + assert_eq!(plan.dyn_base, STATIC_BASE + 2 * WORD_BYTES); + } +} diff --git a/crates/codegen/src/isa/evm/mod.rs b/crates/codegen/src/isa/evm/mod.rs new file mode 100644 index 00000000..2669adca --- /dev/null +++ b/crates/codegen/src/isa/evm/mod.rs @@ -0,0 +1,1938 @@ +mod alloca_plan; +mod heap_plan; +mod malloc_plan; +mod mem_effects; +mod memory_plan; +pub mod opcode; +mod provenance; +mod ptr_escape; +mod scratch_plan; + +use opcode::OpCode; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; + +use crate::{ + bitset::BitSet, + critical_edge::CriticalEdgeSplitter, + domtree::DomTree, + liveness::{InstLiveness, Liveness}, + machinst::{ + lower::{FixupUpdate, Lower, LowerBackend, LoweredFunction, SectionLoweringCtx}, + vcode::{Label, SymFixup, SymFixupKind, VCode, VCodeInst}, + }, + stackalloc::{Action, Actions, Allocator, StackifyAlloc, StackifyLiveValues}, +}; +use smallvec::{SmallVec, smallvec}; +use sonatina_ir::{ + BlockId, Function, Immediate, InstId, InstSetExt, Module, Type, U256, ValueId, + cfg::ControlFlowGraph, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, + types::CompoundType, +}; +use sonatina_triple::{EvmVersion, OperatingSystem}; + +use mem_effects::{FuncMemEffects, compute_func_mem_effects}; +use memory_plan::{ + AllocaClass, DYN_FP_SLOT, DYN_SP_SLOT, FREE_PTR_SLOT, FuncLocalMemInfo, FuncMemPlan, MemScheme, + ProgramMemoryPlan, STATIC_BASE, StackObjectPlan, WORD_BYTES, compute_program_memory_plan, +}; +use ptr_escape::{PtrEscapeSummary, compute_ptr_escape_summaries}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum PushWidthPolicy { + #[default] + Push4, + MinimalRelax, +} + +struct PreparedSection { + plan: ProgramMemoryPlan, + allocs: FxHashMap, + block_orders: FxHashMap>, +} + +struct PreparedFunction { + alloc: StackifyAlloc, + block_order: Vec, + local_mem: FuncLocalMemInfo, + alloca_plan: FxHashMap, + transient_mallocs: FxHashSet, +} + +struct PreparedLowering { + alloc: StackifyAlloc, + block_order: Vec, + mem_plan: FuncMemPlan, +} + +pub struct EvmBackend { + isa: Evm, + stackify_reach_depth: u8, + section_state: RefCell>, + current_mem_plan: RefCell>, +} +impl EvmBackend { + pub fn new(isa: Evm) -> Self { + let triple = isa.triple(); + assert!( + matches!( + triple.operating_system, + OperatingSystem::Evm(EvmVersion::Osaka) + ), + "EvmBackend requires evm-ethereum-osaka (got {triple})" + ); + Self { + isa, + stackify_reach_depth: 16, + section_state: RefCell::new(None), + current_mem_plan: RefCell::new(None), + } + } + + pub fn with_stackify_reach_depth(mut self, reach_depth: u8) -> Self { + assert!( + (1..=16).contains(&reach_depth), + "stackify reach_depth must be in 1..=16" + ); + self.stackify_reach_depth = reach_depth; + self + } + + pub fn stackify_reach_depth(&self) -> u8 { + self.stackify_reach_depth + } + + /// Render a deterministic memory-plan summary for snapshot tests. + /// + /// This intentionally includes alloca layout (class/offset/size + computed address), + /// so snapshots can assert that alloca reuse is correct across loops/branches. + pub fn snapshot_mem_plan(&self, module: &Module, funcs: &[FuncRef]) -> String { + use std::fmt::Write as _; + + let state = self.section_state.borrow(); + let Some(section) = state.as_ref() else { + return String::new(); + }; + + let mut out = String::new(); + writeln!( + &mut out, + "evm mem plan: dyn_base=0x{:x} static_base=0x{:x}", + section.plan.dyn_base, STATIC_BASE + ) + .expect("mem plan write failed"); + + for &func in funcs { + let Some(func_plan) = section.plan.funcs.get(&func) else { + continue; + }; + + let name = module.ctx.func_sig(func, |sig| sig.name().to_string()); + + let mut spill_slots: Option = None; + match &func_plan.scheme { + MemScheme::StaticTree(st) => { + writeln!( + &mut out, + "evm mem plan: {name} scheme=StaticTree base_words={} persistent_words={} alloca_words={} persistent_alloca_words={}", + st.base_words, + st.persistent_words, + func_plan.alloca_words, + func_plan.persistent_alloca_words + ) + .expect("mem plan write failed"); + } + MemScheme::DynamicFrame => { + let spill_slots_inner = section + .allocs + .get(&func) + .map(|alloc| alloc.frame_size_slots()); + spill_slots = spill_slots_inner; + let frame_slots = spill_slots_inner + .and_then(|slots| slots.checked_add(func_plan.alloca_words)); + + let frame_slots = + frame_slots.map_or_else(|| "-".to_string(), |w| w.to_string()); + let spill_slots = + spill_slots_inner.map_or_else(|| "-".to_string(), |w| w.to_string()); + + writeln!( + &mut out, + "evm mem plan: {name} scheme=DynamicFrame frame_slots={frame_slots} spill_slots={spill_slots} alloca_words={} persistent_alloca_words={}", + func_plan.alloca_words, func_plan.persistent_alloca_words + ) + .expect("mem plan write failed"); + } + } + + let mut allocas: Vec<(ValueId, AllocaClass, u32, u32, String)> = Vec::new(); + module.func_store.view(func, |function| { + for block in function.layout.iter_block() { + for insn in function.layout.iter_inst(block) { + let data = self.isa.inst_set().resolve_inst(function.dfg.inst(insn)); + let EvmInstKind::Alloca(alloca) = data else { + continue; + }; + let Some(value) = function.dfg.inst_result(insn) else { + continue; + }; + let StackObjectPlan { + class, + offset_words, + } = *func_plan.alloca.get(&insn).expect("missing alloca plan"); + + let size_bytes = self + .isa + .type_layout() + .size_of(*alloca.ty(), &module.ctx) + .expect("alloca has invalid type") + as u32; + let size_words = size_bytes.div_ceil(WORD_BYTES); + + let addr = match &func_plan.scheme { + MemScheme::StaticTree(st) => { + let persistent_spills = st + .persistent_words + .checked_sub(func_plan.persistent_alloca_words) + .expect("persistent spill words underflow"); + + let addr_words = match class { + AllocaClass::Persistent => st + .base_words + .checked_add(persistent_spills) + .and_then(|w| w.checked_add(offset_words)) + .expect("alloca address words overflow"), + AllocaClass::Transient => st + .base_words + .checked_add(st.persistent_words) + .and_then(|w| w.checked_add(offset_words)) + .expect("alloca address words overflow"), + }; + + let addr_bytes = STATIC_BASE + .checked_add( + WORD_BYTES + .checked_mul(addr_words) + .expect("alloca address bytes overflow"), + ) + .expect("alloca address bytes overflow"); + + format!("0x{addr_bytes:x}") + } + MemScheme::DynamicFrame => match spill_slots { + Some(spill_slots) => { + let offset_words = match class { + AllocaClass::Persistent => offset_words, + AllocaClass::Transient => func_plan + .persistent_alloca_words + .checked_add(offset_words) + .expect("dynamic alloca offset overflow"), + }; + let addr_words = spill_slots + .checked_add(offset_words) + .expect("alloca address words overflow"); + let addr_bytes = addr_words + .checked_mul(WORD_BYTES) + .expect("alloca address bytes overflow"); + if addr_bytes == 0 { + "fp".to_string() + } else { + format!("fp+0x{addr_bytes:x}") + } + } + None => "".to_string(), + }, + }; + + allocas.push((value, class, offset_words, size_words, addr)); + } + } + }); + + if allocas.is_empty() { + continue; + } + + allocas.sort_unstable_by_key(|(v, _, _, _, _)| v.as_u32()); + for (value, class, offset_words, size_words, addr) in allocas { + writeln!( + &mut out, + " alloca v{} class={class:?} offset_words={offset_words} size_words={size_words} addr={addr}", + value.as_u32() + ) + .expect("mem plan write failed"); + } + } + + out + } + + fn take_prepared_function(&self, func: FuncRef) -> Option { + let mut state = self.section_state.borrow_mut(); + let section = state.as_mut()?; + + let alloc = section.allocs.remove(&func)?; + let block_order = section.block_orders.remove(&func)?; + let mem_plan = section.plan.funcs.get(&func).cloned()?; + + Some(PreparedLowering { + alloc, + block_order, + mem_plan, + }) + } + + fn dyn_base(&self) -> u32 { + self.section_state + .borrow() + .as_ref() + .map(|s| s.plan.dyn_base) + .unwrap_or(STATIC_BASE) + } + + fn lower_prepared_function( + &self, + module: &Module, + func: FuncRef, + prepared: PreparedLowering, + ) -> Result, String> { + let mem_plan = prepared.mem_plan; + + let vcode = module.func_store.view(func, |function| { + let _plan_guard = CurrentMemPlanGuard::new(&self.current_mem_plan, mem_plan.clone()); + let mut alloc = PlannedAlloc::new(prepared.alloc, mem_plan); + let lower = Lower::new(&module.ctx, function); + lower.lower(self, &mut alloc).map_err(|e| format!("{e:?}")) + })?; + + Ok(LoweredFunction { + vcode, + block_order: prepared.block_order, + }) + } +} + +struct CurrentMemPlanGuard<'a> { + slot: &'a RefCell>, +} + +impl<'a> CurrentMemPlanGuard<'a> { + fn new(slot: &'a RefCell>, plan: FuncMemPlan) -> Self { + *slot.borrow_mut() = Some(plan); + Self { slot } + } +} + +impl Drop for CurrentMemPlanGuard<'_> { + fn drop(&mut self) { + *self.slot.borrow_mut() = None; + } +} + +impl LowerBackend for EvmBackend { + type MInst = OpCode; + type Error = String; + type FixupPolicy = PushWidthPolicy; + + fn prepare_section( + &self, + module: &Module, + funcs: &[FuncRef], + _section_ctx: &SectionLoweringCtx<'_>, + ) { + let ptr_escape = compute_ptr_escape_summaries(module, funcs, &self.isa); + + // Pass 1: conservative (assume internal calls may clobber scratch/static memory). + let mut local_mem_pass1: FxHashMap = FxHashMap::default(); + let mut alloca_plan_pass1: FxHashMap> = + FxHashMap::default(); + + for &func in funcs { + let prepared = module.func_store.modify(func, |function| { + prepare_function(func, function, &module.ctx, self, &ptr_escape, None) + }); + local_mem_pass1.insert(func, prepared.local_mem); + alloca_plan_pass1.insert(func, prepared.alloca_plan); + } + + let plan_pass1 = + compute_program_memory_plan(module, funcs, &local_mem_pass1, &alloca_plan_pass1); + let mut scratch_spill_funcs: FxHashSet = FxHashSet::default(); + let mut mem_effects = compute_func_mem_effects( + module, + funcs, + &plan_pass1, + &local_mem_pass1, + &scratch_spill_funcs, + &self.isa, + ); + + // Pass 2: effect-aware (refine which callees touch scratch/static memory). + let mut allocs: FxHashMap = FxHashMap::default(); + let mut block_orders: FxHashMap> = FxHashMap::default(); + let mut local_mem: FxHashMap = FxHashMap::default(); + let mut alloca_plan: FxHashMap> = + FxHashMap::default(); + let mut transient_mallocs: FxHashMap> = FxHashMap::default(); + + loop { + allocs.clear(); + block_orders.clear(); + local_mem.clear(); + alloca_plan.clear(); + transient_mallocs.clear(); + + let mut added_scratch_spill_func = false; + + for &func in funcs { + let prepared = module.func_store.modify(func, |function| { + prepare_function( + func, + function, + &module.ctx, + self, + &ptr_escape, + Some(&mem_effects), + ) + }); + + if prepared.alloc.uses_scratch_spills() && scratch_spill_funcs.insert(func) { + added_scratch_spill_func = true; + } + + local_mem.insert(func, prepared.local_mem); + alloca_plan.insert(func, prepared.alloca_plan); + allocs.insert(func, prepared.alloc); + block_orders.insert(func, prepared.block_order); + transient_mallocs.insert(func, prepared.transient_mallocs); + } + + if !added_scratch_spill_func { + break; + } + + mem_effects = compute_func_mem_effects( + module, + funcs, + &plan_pass1, + &local_mem_pass1, + &scratch_spill_funcs, + &self.isa, + ); + } + + let mut plan = compute_program_memory_plan(module, funcs, &local_mem, &alloca_plan); + for &func in funcs { + if let Some(mem_plan) = plan.funcs.get_mut(&func) { + mem_plan.transient_mallocs = transient_mallocs.remove(&func).unwrap_or_default(); + } + } + let malloc_bounds = heap_plan::compute_malloc_future_static_words( + module, funcs, &plan, &local_mem, &allocs, &self.isa, + ); + for (func, bounds) in malloc_bounds { + if let Some(mem_plan) = plan.funcs.get_mut(&func) { + mem_plan.malloc_future_static_words = bounds; + } + } + + if std::env::var_os("SONATINA_EVM_MEM_DEBUG").is_some() { + debug_print_mem_plan(module, funcs, &plan, &local_mem); + } + + *self.section_state.borrow_mut() = Some(PreparedSection { + plan, + allocs, + block_orders, + }); + } + + fn lower_function( + &self, + module: &Module, + func: FuncRef, + section_ctx: &SectionLoweringCtx<'_>, + ) -> Result, Self::Error> { + let _ = section_ctx; + + if let Some(prepared) = self.take_prepared_function(func) { + return self.lower_prepared_function(module, func, prepared); + } + + let ptr_escape = + compute_ptr_escape_summaries(module, std::slice::from_ref(&func), &self.isa); + let prepared = module.func_store.modify(func, |function| { + prepare_function(func, function, &module.ctx, self, &ptr_escape, None) + }); + let lowering = PreparedLowering { + alloc: prepared.alloc, + block_order: prepared.block_order, + mem_plan: FuncMemPlan { + scheme: MemScheme::DynamicFrame, + alloca: prepared.alloca_plan, + alloca_words: prepared.local_mem.alloca_words, + persistent_alloca_words: prepared.local_mem.persistent_alloca_words, + malloc_future_static_words: FxHashMap::default(), + transient_mallocs: FxHashSet::default(), + }, + }; + self.lower_prepared_function(module, func, lowering) + } + + fn apply_sym_fixup( + &self, + vcode: &mut VCode, + inst: VCodeInst, + fixup: &SymFixup, + value: u32, + policy: &Self::FixupPolicy, + ) -> Result { + let _ = fixup; + let (_, bytes) = vcode + .inst_imm_bytes + .get_mut(inst) + .ok_or_else(|| "missing fixup immediate bytes".to_string())?; + + let new_bytes = u32_to_evm_push_bytes(value, *policy); + + if bytes.as_slice() == new_bytes.as_slice() { + return Ok(FixupUpdate::Unchanged); + } + + let layout_changed = bytes.len() != new_bytes.len(); + bytes.clear(); + bytes.extend_from_slice(&new_bytes); + + self.update_opcode_with_immediate_bytes(&mut vcode.insts[inst], bytes); + + Ok(if layout_changed { + FixupUpdate::LayoutChanged + } else { + FixupUpdate::ContentChanged + }) + } + + fn enter_function( + &self, + ctx: &mut Lower, + alloc: &mut dyn Allocator, + function: &Function, + ) { + enter_frame(ctx, alloc.frame_size_slots(), self.dyn_base()); + perform_actions(ctx, &alloc.enter_function(function)); + } + + fn enter_block( + &self, + ctx: &mut Lower, + _alloc: &mut dyn Allocator, + _block: BlockId, + ) { + // Every block start is a jumpdest unless + // - all incoming edges are fallthroughs (TODO) + // - it's the entry block of the main fn (TODO) + ctx.push(OpCode::JUMPDEST); + } + + fn lower(&self, ctx: &mut Lower, alloc: &mut dyn Allocator, insn: InstId) { + let result = ctx.insn_result(insn); + let args = ctx.insn_data(insn).collect_values(); + let data = self.isa.inst_set().resolve_inst(ctx.insn_data(insn)); + + let basic_op = |ctx: &mut Lower, ops: &[OpCode]| { + perform_actions(ctx, &alloc.read(insn, &args)); + for op in ops { + ctx.push(*op); + } + perform_actions(ctx, &alloc.write(insn, result)); + }; + + match &data { + EvmInstKind::Neg(_) => basic_op(ctx, &[OpCode::PUSH0, OpCode::SUB]), + EvmInstKind::Add(_) => basic_op(ctx, &[OpCode::ADD]), + EvmInstKind::Mul(_) => basic_op(ctx, &[OpCode::MUL]), + EvmInstKind::Sub(_) => basic_op(ctx, &[OpCode::SUB]), + EvmInstKind::Shl(_) => basic_op(ctx, &[OpCode::SHL]), + EvmInstKind::Shr(_) => basic_op(ctx, &[OpCode::SHR]), + EvmInstKind::Sar(_) => basic_op(ctx, &[OpCode::SAR]), + EvmInstKind::Sext(_sext) => { + let from = args[0]; + let src_ty = ctx.value_ty(from); + let src_bits = scalar_bit_width(src_ty, ctx.module).unwrap_or(256); + + perform_actions(ctx, &alloc.read(insn, &args)); + + // `i1` is treated as a boolean; sext is equivalent to zext. + if src_bits == 1 { + push_bytes(ctx, &[1]); + ctx.push(OpCode::AND); + } else if (8..256).contains(&src_bits) { + let src_bytes = (src_bits / 8) as u8; + debug_assert!(src_bytes > 0 && src_bytes <= 32); + // `SIGNEXTEND` takes (byte_index, value) with `byte_index` at top of stack. + push_bytes(ctx, &[src_bytes - 1]); + ctx.push(OpCode::SIGNEXTEND); + } + + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::Zext(_) => { + let from = args[0]; + let src_ty = ctx.value_ty(from); + let src_bits = scalar_bit_width(src_ty, ctx.module).unwrap_or(256); + + perform_actions(ctx, &alloc.read(insn, &args)); + if let Some(mask) = low_bits_mask(src_bits) { + let bytes = u256_to_be(&mask); + push_bytes(ctx, &bytes); + ctx.push(OpCode::AND); + } + perform_actions(ctx, &alloc.write(insn, result)); + } + + EvmInstKind::Trunc(trunc) => { + let dst_ty = *trunc.ty(); + let dst_bits = scalar_bit_width(dst_ty, ctx.module).unwrap_or(256); + + perform_actions(ctx, &alloc.read(insn, &args)); + if let Some(mask) = low_bits_mask(dst_bits) { + let bytes = u256_to_be(&mask); + push_bytes(ctx, &bytes); + ctx.push(OpCode::AND); + } + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::Bitcast(_) => { + // No-op. + perform_actions(ctx, &alloc.read(insn, &args)); + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::IntToPtr(_) => { + // Pointers are represented as 256-bit integers on the EVM. + let from = args[0]; + let src_ty = ctx.value_ty(from); + let src_bits = scalar_bit_width(src_ty, ctx.module).unwrap_or(256); + + perform_actions(ctx, &alloc.read(insn, &args)); + if let Some(mask) = low_bits_mask(src_bits) { + let bytes = u256_to_be(&mask); + push_bytes(ctx, &bytes); + ctx.push(OpCode::AND); + } + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::PtrToInt(ptr_to_int) => { + let dst_ty = *ptr_to_int.ty(); + let dst_bits = scalar_bit_width(dst_ty, ctx.module).unwrap_or(256); + + perform_actions(ctx, &alloc.read(insn, &args)); + if let Some(mask) = low_bits_mask(dst_bits) { + let bytes = u256_to_be(&mask); + push_bytes(ctx, &bytes); + ctx.push(OpCode::AND); + } + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::Lt(_) => basic_op(ctx, &[OpCode::LT]), + EvmInstKind::Gt(_) => basic_op(ctx, &[OpCode::GT]), + EvmInstKind::Slt(_) => basic_op(ctx, &[OpCode::SLT]), + EvmInstKind::Sgt(_) => basic_op(ctx, &[OpCode::SGT]), + EvmInstKind::Le(_) => basic_op(ctx, &[OpCode::GT, OpCode::ISZERO]), + EvmInstKind::Ge(_) => basic_op(ctx, &[OpCode::LT, OpCode::ISZERO]), + EvmInstKind::Sge(_) => basic_op(ctx, &[OpCode::SLT, OpCode::ISZERO]), + EvmInstKind::Eq(_) => basic_op(ctx, &[OpCode::EQ]), + EvmInstKind::Ne(_) => basic_op(ctx, &[OpCode::EQ, OpCode::ISZERO]), + EvmInstKind::IsZero(_) => basic_op(ctx, &[OpCode::ISZERO]), + + EvmInstKind::Not(_) => basic_op(ctx, &[OpCode::NOT]), + EvmInstKind::And(_) => basic_op(ctx, &[OpCode::AND]), + EvmInstKind::Or(_) => basic_op(ctx, &[OpCode::OR]), + EvmInstKind::Xor(_) => basic_op(ctx, &[OpCode::XOR]), + + EvmInstKind::Jump(jump) => { + let dest = *jump.dest(); + perform_actions(ctx, &alloc.read(insn, &[])); + + if !ctx.is_next_block(dest) { + let push_op = ctx.push(OpCode::PUSH1); + ctx.add_label_reference(push_op, Label::Block(dest)); + ctx.push(OpCode::JUMP); + } + } + EvmInstKind::Br(br) => { + let nz_dest = *br.nz_dest(); + let z_dest = *br.z_dest(); + + // JUMPI: dest is top of stack, bool val is next + perform_actions(ctx, &alloc.read(insn, &args)); + + if ctx.is_next_block(nz_dest) { + // Prefer fallthrough to the next block. + ctx.push(OpCode::ISZERO); + ctx.push_jump_target(OpCode::PUSH1, Label::Block(z_dest)); + ctx.push(OpCode::JUMPI); + } else { + ctx.push_jump_target(OpCode::PUSH1, Label::Block(nz_dest)); + ctx.push(OpCode::JUMPI); + + if !ctx.is_next_block(z_dest) { + ctx.push_jump_target(OpCode::PUSH1, Label::Block(z_dest)); + ctx.push(OpCode::JUMP); + } + } + } + EvmInstKind::Phi(_) => {} + + EvmInstKind::BrTable(br) => { + let table = br.table().clone(); + let scrutinee = *br.scrutinee(); + let default = *br.default(); + + // TODO: sanitize br_table ops + assert!(!table.is_empty(), "empty br_table"); + assert_eq!( + table.len(), + table.iter().map(|(v, _)| v).collect::>().len(), + "br_table has duplicate scrutinee values" + ); + + for (case_val, dest) in table.iter() { + perform_actions(ctx, &alloc.read(insn, &[scrutinee, *case_val])); + ctx.push(OpCode::EQ); + + ctx.push_jump_target(OpCode::PUSH1, Label::Block(*dest)); + ctx.push(OpCode::JUMPI); + } + + if let Some(dest) = default + && !ctx.is_next_block(dest) + { + ctx.push_jump_target(OpCode::PUSH1, Label::Block(dest)); + ctx.push(OpCode::JUMP); + } + } + + EvmInstKind::Call(call) => { + // xxx if func uses memory, store new fp + + let callee = *call.callee(); + let mut actions = alloc.read(insn, &args); + + let Some(cont_pos) = actions + .iter() + .position(|a| matches!(a, Action::PushContinuationOffset)) + else { + panic!("call lowering expected Action::PushContinuationOffset"); + }; + + // Some allocators need to run block-entry prologues before pushing the + // continuation address for the call. We therefore allow the marker to + // appear anywhere in the action list and split around it. + let suffix: SmallVec<[Action; 2]> = actions.drain(cont_pos + 1..).collect(); + debug_assert_eq!( + actions.remove(cont_pos), + Action::PushContinuationOffset, + "expected continuation marker at split point" + ); + + // Prefix actions run before the continuation address is pushed. + perform_actions(ctx, &actions); + + // Push the return pc / continuation address. + let push_callback = ctx.push(OpCode::PUSH1); + + // Move fn args onto stack + perform_actions(ctx, &suffix); + + // Push fn address onto stack and jump + let p = ctx.push(OpCode::PUSH1); + ctx.add_label_reference(p, Label::Function(callee)); + ctx.push(OpCode::JUMP); + + // Mark return pc as jumpdest + let jumpdest_op = ctx.push(OpCode::JUMPDEST); + ctx.add_label_reference(push_callback, Label::Insn(jumpdest_op)); + + // Post-call: spill the call result if needed. + perform_actions(ctx, &alloc.write(insn, result)); + } + + EvmInstKind::Return(_) => { + perform_actions(ctx, &alloc.read(insn, &args)); + leave_frame(ctx, alloc.frame_size_slots()); + + // Caller pushes return location onto stack prior to call. + if !args.is_empty() { + // Swap the return loc to the top. + ctx.push(OpCode::SWAP1); + } + ctx.push(OpCode::JUMP); + } + EvmInstKind::Mload(_) => basic_op(ctx, &[OpCode::MLOAD]), + EvmInstKind::Mstore(mstore) => { + let ty_size = self + .isa + .type_layout() + .size_of(*mstore.ty(), ctx.module) + .unwrap(); + + perform_actions(ctx, &alloc.read(insn, &args)); + if ty_size == 0 { + // TODO: optimize away mstores of size 0 + // Pop the args, and don't do an mstore. + ctx.push(OpCode::POP); + ctx.push(OpCode::POP); + } else { + debug_assert_eq!(ty_size, 32, "word-slot model: expected 32-byte store"); + ctx.push(OpCode::MSTORE); + } + } + EvmInstKind::EvmMcopy(_) => basic_op(ctx, &[OpCode::MCOPY]), + EvmInstKind::Gep(_) => { + perform_actions(ctx, &alloc.read(insn, &args)); + if args.is_empty() { + panic!("gep without operands"); + } + + let mut current_ty = ctx.value_ty(args[0]); + if !current_ty.is_pointer(ctx.module) { + panic!("gep base must be a pointer (got {current_ty:?})"); + } + + let mut steps: Vec = Vec::new(); + for &idx_val in args.iter().skip(1) { + let Some(cmpd) = current_ty.resolve_compound(ctx.module) else { + panic!("invalid gep: indexing into scalar {current_ty:?}"); + }; + + let idx_imm_u32 = ctx.value_imm(idx_val).and_then(immediate_u32); + + match cmpd { + CompoundType::Ptr(elem_ty) => { + let elem_size = u32::try_from(ctx.module.size_of_unchecked(elem_ty)) + .expect("gep element too large"); + steps.push(gep_step(elem_size, idx_imm_u32)); + current_ty = elem_ty; + } + CompoundType::Array { elem, .. } => { + let elem_size = u32::try_from(ctx.module.size_of_unchecked(elem)) + .expect("gep element too large"); + steps.push(gep_step(elem_size, idx_imm_u32)); + current_ty = elem; + } + CompoundType::Struct(s) => { + let Some(idx) = idx_imm_u32.map(|idx| idx as usize) else { + panic!("struct gep indices must be immediate constants"); + }; + let (field_offset, field_ty) = + struct_field_offset_bytes(&s.fields, s.packed, idx, ctx.module); + steps.push(GepStep::AddConst(field_offset)); + current_ty = field_ty; + } + CompoundType::Func { .. } => { + panic!("invalid gep: indexing into function type"); + } + } + } + + for step in steps { + match step { + GepStep::AddConst(offset_bytes) => { + ctx.push(OpCode::SWAP1); + ctx.push(OpCode::POP); + if offset_bytes != 0 { + push_bytes(ctx, &u32_to_be(offset_bytes)); + ctx.push(OpCode::ADD); + } + } + GepStep::AddScaled(scale_bytes) => { + ctx.push(OpCode::SWAP1); + push_bytes(ctx, &u32_to_be(scale_bytes)); + ctx.push(OpCode::MUL); + ctx.push(OpCode::ADD); + } + } + } + + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::Alloca(_) => { + let mem_plan = self.current_mem_plan.borrow(); + let mem_plan = mem_plan + .as_ref() + .expect("missing memory plan during lowering"); + + let StackObjectPlan { + class, + offset_words: alloca_offset, + } = *mem_plan.alloca.get(&insn).expect("missing alloca plan"); + + perform_actions(ctx, &alloc.read(insn, &args)); + + match &mem_plan.scheme { + MemScheme::StaticTree(plan) => { + let persistent_spills = plan + .persistent_words + .checked_sub(mem_plan.persistent_alloca_words) + .expect("persistent spill words underflow"); + + let addr_words = match class { + AllocaClass::Persistent => plan + .base_words + .checked_add(persistent_spills) + .and_then(|w| w.checked_add(alloca_offset)) + .expect("alloca address words overflow"), + AllocaClass::Transient => plan + .base_words + .checked_add(plan.persistent_words) + .and_then(|w| w.checked_add(alloca_offset)) + .expect("alloca address words overflow"), + }; + + let addr_bytes = STATIC_BASE + .checked_add( + WORD_BYTES + .checked_mul(addr_words) + .expect("alloca address bytes overflow"), + ) + .expect("alloca address bytes overflow"); + + push_bytes(ctx, &u32_to_be(addr_bytes)); + } + MemScheme::DynamicFrame => { + let frame_slots = alloc.frame_size_slots(); + let spill_slots = frame_slots + .checked_sub(mem_plan.alloca_words) + .expect("alloca words exceed dynamic frame size"); + let offset_words = match class { + AllocaClass::Persistent => alloca_offset, + AllocaClass::Transient => mem_plan + .persistent_alloca_words + .checked_add(alloca_offset) + .expect("dynamic alloca offset overflow"), + }; + + let addr_words = spill_slots + .checked_add(offset_words) + .expect("alloca address words overflow"); + let addr_bytes = addr_words + .checked_mul(WORD_BYTES) + .expect("alloca address bytes overflow"); + + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MLOAD); + if addr_bytes != 0 { + push_bytes(ctx, &u32_to_be(addr_bytes)); + ctx.push(OpCode::ADD); + } + } + } + + perform_actions(ctx, &alloc.write(insn, result)); + } + + EvmInstKind::EvmStop(_) => basic_op(ctx, &[OpCode::STOP]), + + EvmInstKind::EvmSdiv(_) => basic_op(ctx, &[OpCode::SDIV]), + EvmInstKind::EvmUdiv(_) => basic_op(ctx, &[OpCode::DIV]), + EvmInstKind::EvmUmod(_) => basic_op(ctx, &[OpCode::MOD]), + EvmInstKind::EvmSmod(_) => basic_op(ctx, &[OpCode::SMOD]), + EvmInstKind::EvmAddMod(_) => basic_op(ctx, &[OpCode::ADDMOD]), + EvmInstKind::EvmMulMod(_) => basic_op(ctx, &[OpCode::MULMOD]), + EvmInstKind::EvmExp(_) => basic_op(ctx, &[OpCode::EXP]), + EvmInstKind::EvmByte(_) => basic_op(ctx, &[OpCode::BYTE]), + EvmInstKind::EvmClz(_) => basic_op(ctx, &[OpCode::CLZ]), + EvmInstKind::EvmKeccak256(_) => basic_op(ctx, &[OpCode::KECCAK256]), + EvmInstKind::EvmAddress(_) => basic_op(ctx, &[OpCode::ADDRESS]), + EvmInstKind::EvmBalance(_) => basic_op(ctx, &[OpCode::BALANCE]), + EvmInstKind::EvmOrigin(_) => basic_op(ctx, &[OpCode::ORIGIN]), + EvmInstKind::EvmCaller(_) => basic_op(ctx, &[OpCode::CALLER]), + EvmInstKind::EvmCallValue(_) => basic_op(ctx, &[OpCode::CALLVALUE]), + EvmInstKind::EvmCalldataLoad(_) => basic_op(ctx, &[OpCode::CALLDATALOAD]), + EvmInstKind::EvmCalldataCopy(_) => basic_op(ctx, &[OpCode::CALLDATACOPY]), + EvmInstKind::EvmCalldataSize(_) => basic_op(ctx, &[OpCode::CALLDATASIZE]), + EvmInstKind::EvmCodeSize(_) => basic_op(ctx, &[OpCode::CODESIZE]), + EvmInstKind::EvmCodeCopy(_) => basic_op(ctx, &[OpCode::CODECOPY]), + EvmInstKind::EvmExtCodeCopy(_) => basic_op(ctx, &[OpCode::EXTCODECOPY]), + EvmInstKind::EvmReturnDataSize(_) => basic_op(ctx, &[OpCode::RETURNDATASIZE]), + EvmInstKind::EvmReturnDataCopy(_) => basic_op(ctx, &[OpCode::RETURNDATACOPY]), + EvmInstKind::EvmExtCodeHash(_) => basic_op(ctx, &[OpCode::EXTCODEHASH]), + EvmInstKind::EvmBlockHash(_) => basic_op(ctx, &[OpCode::BLOCKHASH]), + EvmInstKind::EvmCoinBase(_) => basic_op(ctx, &[OpCode::COINBASE]), + EvmInstKind::EvmTimestamp(_) => basic_op(ctx, &[OpCode::TIMESTAMP]), + EvmInstKind::EvmNumber(_) => basic_op(ctx, &[OpCode::NUMBER]), + EvmInstKind::EvmPrevRandao(_) => basic_op(ctx, &[OpCode::PREVRANDAO]), + EvmInstKind::EvmGasLimit(_) => basic_op(ctx, &[OpCode::GASLIMIT]), + EvmInstKind::EvmChainId(_) => basic_op(ctx, &[OpCode::CHAINID]), + EvmInstKind::EvmSelfBalance(_) => basic_op(ctx, &[OpCode::SELFBALANCE]), + EvmInstKind::EvmBaseFee(_) => basic_op(ctx, &[OpCode::BASEFEE]), + EvmInstKind::EvmBlobHash(_) => basic_op(ctx, &[OpCode::BLOBHASH]), + EvmInstKind::EvmBlobBaseFee(_) => basic_op(ctx, &[OpCode::BLOBBASEFEE]), + EvmInstKind::EvmMstore8(_) => basic_op(ctx, &[OpCode::MSTORE8]), + EvmInstKind::EvmSload(_) => basic_op(ctx, &[OpCode::SLOAD]), + EvmInstKind::EvmSstore(_) => basic_op(ctx, &[OpCode::SSTORE]), + EvmInstKind::EvmMsize(_) => basic_op(ctx, &[OpCode::MSIZE]), + EvmInstKind::EvmGas(_) => basic_op(ctx, &[OpCode::GAS]), + EvmInstKind::EvmTload(_) => basic_op(ctx, &[OpCode::TLOAD]), + EvmInstKind::EvmTstore(_) => basic_op(ctx, &[OpCode::TSTORE]), + EvmInstKind::EvmLog0(_) => basic_op(ctx, &[OpCode::LOG0]), + EvmInstKind::EvmLog1(_) => basic_op(ctx, &[OpCode::LOG1]), + EvmInstKind::EvmLog2(_) => basic_op(ctx, &[OpCode::LOG2]), + EvmInstKind::EvmLog3(_) => basic_op(ctx, &[OpCode::LOG3]), + EvmInstKind::EvmLog4(_) => basic_op(ctx, &[OpCode::LOG4]), + EvmInstKind::EvmCreate(_) => basic_op(ctx, &[OpCode::CREATE]), + EvmInstKind::EvmCall(_) => basic_op(ctx, &[OpCode::CALL]), + EvmInstKind::EvmCallCode(_) => basic_op(ctx, &[OpCode::CALLCODE]), + EvmInstKind::EvmReturn(_) => basic_op(ctx, &[OpCode::RETURN]), + EvmInstKind::EvmDelegateCall(_) => basic_op(ctx, &[OpCode::DELEGATECALL]), + EvmInstKind::EvmCreate2(_) => basic_op(ctx, &[OpCode::CREATE2]), + EvmInstKind::EvmStaticCall(_) => basic_op(ctx, &[OpCode::STATICCALL]), + EvmInstKind::EvmRevert(_) => basic_op(ctx, &[OpCode::REVERT]), + EvmInstKind::EvmSelfDestruct(_) => basic_op(ctx, &[OpCode::SELFDESTRUCT]), + + EvmInstKind::EvmMalloc(_) => { + let mem_plan = self.current_mem_plan.borrow(); + let mem_plan = mem_plan + .as_ref() + .expect("missing memory plan during lowering"); + let is_transient = mem_plan.transient_mallocs.contains(&insn); + + let dyn_base_words = self + .dyn_base() + .checked_sub(STATIC_BASE) + .expect("dyn base below static base") + / WORD_BYTES; + let future_words = mem_plan + .malloc_future_static_words + .get(&insn) + .copied() + .unwrap_or(dyn_base_words); + + let min_base_bytes = STATIC_BASE + .checked_add( + WORD_BYTES + .checked_mul(future_words) + .expect("malloc static bound bytes overflow"), + ) + .expect("malloc static bound bytes overflow"); + + perform_actions(ctx, &alloc.read(insn, &args)); + + if is_transient { + // Drop the requested size; this is a transient bump allocation that does not + // update `FREE_PTR_SLOT` and is allowed to overlap with later allocations. + ctx.push(OpCode::POP); + + // heap_end = mload(0x40) + push_bytes(ctx, &[FREE_PTR_SLOT]); + ctx.push(OpCode::MLOAD); + + // sp = mload(DYN_SP_SLOT) + push_bytes(ctx, &[DYN_SP_SLOT]); + ctx.push(OpCode::MLOAD); + + // max(heap_end, sp) + emit_max_top_two(ctx); + + // max(max(heap_end, sp), min_base) + push_bytes(ctx, &u32_to_be(min_base_bytes)); + emit_max_top_two(ctx); + + perform_actions(ctx, &alloc.write(insn, result)); + return; + } + + // Align to 32 bytes: + // aligned = ((size + 31) / 32) * 32 + push_bytes(ctx, &[0x1f]); + ctx.push(OpCode::ADD); + push_bytes(ctx, &[0x20]); + ctx.push(OpCode::DIV); + push_bytes(ctx, &[0x20]); + ctx.push(OpCode::MUL); + + // heap_end = mload(0x40) + push_bytes(ctx, &[FREE_PTR_SLOT]); + ctx.push(OpCode::MLOAD); + + // sp = mload(DYN_SP_SLOT) + push_bytes(ctx, &[DYN_SP_SLOT]); + ctx.push(OpCode::MLOAD); + + // max(heap_end, sp) + emit_max_top_two(ctx); + + // max(max(heap_end, sp), min_base) + push_bytes(ctx, &u32_to_be(min_base_bytes)); + emit_max_top_two(ctx); + + // new_end = base + aligned_size; mstore(0x40, new_end); return base + ctx.push(OpCode::DUP1); + ctx.push(OpCode::SWAP2); + ctx.push(OpCode::ADD); + push_bytes(ctx, &[FREE_PTR_SLOT]); + ctx.push(OpCode::MSTORE); + + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::InsertValue(_) => todo!(), + EvmInstKind::ExtractValue(_) => todo!(), + EvmInstKind::GetFunctionPtr(get_fn) => { + let func = *get_fn.func(); + perform_actions(ctx, &alloc.read(insn, &args)); + ctx.push_jump_target(OpCode::PUSH1, Label::Function(func)); + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::EvmInvalid(_) => basic_op(ctx, &[OpCode::INVALID]), + + EvmInstKind::SymAddr(sym_addr) => { + let sym = sym_addr.sym().clone(); + perform_actions(ctx, &alloc.read(insn, &args)); + ctx.push_sym_fixup( + OpCode::PUSH0, + SymFixup { + kind: SymFixupKind::Addr, + sym, + }, + ); + perform_actions(ctx, &alloc.write(insn, result)); + } + EvmInstKind::SymSize(sym_size) => { + let sym = sym_size.sym().clone(); + perform_actions(ctx, &alloc.read(insn, &args)); + ctx.push_sym_fixup( + OpCode::PUSH0, + SymFixup { + kind: SymFixupKind::Size, + sym, + }, + ); + perform_actions(ctx, &alloc.write(insn, result)); + } + } + } + + fn update_opcode_with_immediate_bytes( + &self, + opcode: &mut OpCode, + bytes: &mut SmallVec<[u8; 8]>, + ) { + debug_assert!( + bytes.len() <= 32, + "PUSH immediate too wide: {} bytes", + bytes.len() + ); + *opcode = push_op(bytes.len()); + } + + fn update_opcode_with_label( + &self, + opcode: &mut OpCode, + label_offset: u32, + ) -> SmallVec<[u8; 4]> { + let bytes = label_offset + .to_be_bytes() + .into_iter() + .skip_while(|b| *b == 0) + .collect::>(); + + *opcode = push_op(bytes.len()); + bytes + } + + fn emit_opcode(&self, opcode: &OpCode, buf: &mut Vec) { + buf.push(*opcode as u8); + } + + fn emit_immediate_bytes(&self, bytes: &[u8], buf: &mut Vec) { + buf.extend_from_slice(bytes); + } + fn emit_label(&self, address: u32, buf: &mut Vec) { + buf.extend(address.to_be_bytes().into_iter().skip_while(|b| *b == 0)); + } +} + +struct PlannedAlloc { + inner: StackifyAlloc, + mem_plan: FuncMemPlan, +} + +impl PlannedAlloc { + fn new(inner: StackifyAlloc, mem_plan: FuncMemPlan) -> Self { + Self { inner, mem_plan } + } + + fn rewrite_actions(&self, mut actions: Actions) -> Actions { + let MemScheme::StaticTree(plan) = &self.mem_plan.scheme else { + return actions; + }; + + let base_words = plan.base_words; + let alloca_words = self.mem_plan.alloca_words; + let persistent_spills = plan + .persistent_words + .checked_sub(self.mem_plan.persistent_alloca_words) + .expect("persistent spill words underflow"); + + for action in actions.iter_mut() { + match action { + Action::MemLoadFrameSlot(slot) => { + *action = Action::MemLoadAbs(self.static_spill_addr( + base_words, + *slot, + persistent_spills, + alloca_words, + )); + } + Action::MemStoreFrameSlot(slot) => { + *action = Action::MemStoreAbs(self.static_spill_addr( + base_words, + *slot, + persistent_spills, + alloca_words, + )); + } + _ => {} + } + } + + actions + } + + fn static_spill_addr( + &self, + base_words: u32, + spill_slot: u32, + persistent_spills: u32, + alloca_words: u32, + ) -> u32 { + let spill_words = if spill_slot >= persistent_spills { + spill_slot + .checked_add(alloca_words) + .expect("spill slot words overflow") + } else { + spill_slot + }; + + let addr_words = base_words + .checked_add(spill_words) + .expect("spill addr words overflow"); + + STATIC_BASE + .checked_add( + WORD_BYTES + .checked_mul(addr_words) + .expect("spill addr bytes overflow"), + ) + .expect("spill addr bytes overflow") + } +} + +impl Allocator for PlannedAlloc { + fn enter_function(&self, function: &Function) -> Actions { + self.rewrite_actions(self.inner.enter_function(function)) + } + + fn frame_size_slots(&self) -> u32 { + match self.mem_plan.scheme { + MemScheme::StaticTree(_) => 0, + MemScheme::DynamicFrame => self + .inner + .frame_size_slots() + .checked_add(self.mem_plan.alloca_words) + .expect("frame size overflow"), + } + } + + fn read(&self, inst: InstId, vals: &[sonatina_ir::ValueId]) -> Actions { + self.rewrite_actions(self.inner.read(inst, vals)) + } + + fn write(&self, inst: InstId, val: Option) -> Actions { + self.rewrite_actions(self.inner.write(inst, val)) + } + + fn traverse_edge(&self, from: BlockId, to: BlockId) -> Actions { + self.rewrite_actions(self.inner.traverse_edge(from, to)) + } +} + +enum GepStep { + AddConst(u32), + AddScaled(u32), +} + +fn gep_step(elem_size_bytes: u32, idx: Option) -> GepStep { + let Some(idx) = idx else { + return if elem_size_bytes == 0 { + GepStep::AddConst(0) + } else { + GepStep::AddScaled(elem_size_bytes) + }; + }; + + GepStep::AddConst( + elem_size_bytes + .checked_mul(idx) + .expect("gep offset overflow"), + ) +} + +fn immediate_u32(imm: Immediate) -> Option { + if imm.is_negative() { + return None; + } + + let u256 = imm.as_i256().to_u256(); + if u256 > U256::from(u32::MAX) { + return None; + } + + Some(u256.low_u32()) +} + +fn struct_field_offset_bytes( + fields: &[Type], + packed: bool, + idx: usize, + module: &ModuleCtx, +) -> (u32, Type) { + let &field_ty = fields.get(idx).expect("struct gep index out of bounds"); + + let mut offset: u32 = 0; + for &ty in fields.iter().take(idx) { + if !packed { + let align = + u32::try_from(module.align_of_unchecked(ty)).expect("struct field align too large"); + offset = align_to(offset, align); + } + + let size = + u32::try_from(module.size_of_unchecked(ty)).expect("struct field size too large"); + offset = offset + .checked_add(size) + .expect("struct field offset overflow"); + } + + if !packed { + let align = u32::try_from(module.align_of_unchecked(field_ty)) + .expect("struct field align too large"); + offset = align_to(offset, align); + } + + (offset, field_ty) +} + +fn align_to(offset: u32, align: u32) -> u32 { + if align <= 1 { + return offset; + } + debug_assert!(align.is_power_of_two(), "alignment must be power of two"); + + offset.checked_add(align - 1).expect("align overflow") & !(align - 1) +} + +fn perform_actions(ctx: &mut Lower, actions: &[Action]) { + for action in actions { + match action { + Action::StackDup(slot) => { + debug_assert!(*slot < 16, "DUP out of range: {slot}"); + ctx.push(dup_op(*slot)); + } + Action::StackSwap(n) => { + debug_assert!((1..=16).contains(n), "SWAP out of range: {n}"); + ctx.push(swap_op(*n)); + } + Action::Push(imm) => { + if imm.is_zero() { + ctx.push(OpCode::PUSH0); + } else { + let bytes = match imm { + Immediate::I1(v) => smallvec![*v as u8], + Immediate::I8(v) => SmallVec::from_slice(&v.to_be_bytes()), + Immediate::I16(v) => shrink_bytes(&v.to_be_bytes()), + Immediate::I32(v) => shrink_bytes(&v.to_be_bytes()), + Immediate::I64(v) => shrink_bytes(&v.to_be_bytes()), + Immediate::I128(v) => shrink_bytes(&v.to_be_bytes()), + Immediate::I256(v) => shrink_bytes(&v.to_u256().to_big_endian()), + }; + push_bytes(ctx, &bytes); + + // Sign-extend negative numbers to 32 bytes + // TODO: signextend isn't always needed (eg push then mstore8) + if imm.is_negative() && bytes.len() < 32 { + push_bytes(ctx, &u32_to_be((bytes.len() - 1) as u32)); + ctx.push(OpCode::SIGNEXTEND); + } + } + } + Action::Pop => { + ctx.push(OpCode::POP); + } + Action::MemLoadAbs(offset) => { + let bytes = u32_to_be(*offset); + push_bytes(ctx, &bytes); + ctx.push(OpCode::MLOAD); + } + Action::MemLoadFrameSlot(offset) => { + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MLOAD); + let byte_offset = offset + .checked_mul(WORD_BYTES) + .expect("frame slot offset overflow"); + if byte_offset != 0 { + let bytes = u32_to_be(byte_offset); + push_bytes(ctx, &bytes); + ctx.push(OpCode::ADD); + } + ctx.push(OpCode::MLOAD); + } + Action::MemStoreAbs(offset) => { + let bytes = u32_to_be(*offset); + push_bytes(ctx, &bytes); + ctx.push(OpCode::MSTORE); + } + Action::MemStoreFrameSlot(offset) => { + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MLOAD); + let byte_offset = offset + .checked_mul(WORD_BYTES) + .expect("frame slot offset overflow"); + if byte_offset != 0 { + let bytes = u32_to_be(byte_offset); + push_bytes(ctx, &bytes); + ctx.push(OpCode::ADD); + } + ctx.push(OpCode::MSTORE); + } + Action::PushContinuationOffset => { + panic!("handle PushContinuationOffset elsewhere"); + } + } + } +} + +fn push_bytes(ctx: &mut Lower, bytes: &[u8]) { + assert!(!bytes.is_empty()); + if bytes == [0] { + ctx.push(OpCode::PUSH0); + } else { + ctx.push_with_imm(push_op(bytes.len()), bytes); + } +} + +/// Remove unnecessary leading bytes of the big-endian two's complement +/// representation of a number. +fn shrink_bytes(bytes: &[u8]) -> SmallVec<[u8; 8]> { + assert!(!bytes.is_empty()); + + let is_neg = bytes[0].leading_ones() > 0; + let skip = if is_neg { 0xff } else { 0x00 }; + let mut bytes = bytes + .iter() + .copied() + .skip_while(|b| *b == skip) + .collect::>(); + + // Negative numbers need a leading 1 bit for sign-extension + if is_neg && bytes.first().map(|&b| b < 0x80).unwrap_or(true) { + bytes.insert(0, 0xff); + } + bytes +} + +fn u32_to_be(num: u32) -> SmallVec<[u8; 4]> { + if num == 0 { + smallvec![0] + } else { + num.to_be_bytes() + .into_iter() + .skip_while(|b| *b == 0) + .collect() + } +} + +fn u256_to_be(num: &U256) -> SmallVec<[u8; 8]> { + if num.is_zero() { + smallvec![0] + } else { + let b = num.to_big_endian(); + b.into_iter().skip_while(|b| *b == 0).collect() + } +} + +fn scalar_bit_width(ty: Type, module: &sonatina_ir::module::ModuleCtx) -> Option { + let bits = match ty { + Type::I1 => 1, + Type::I8 => 8, + Type::I16 => 16, + Type::I32 => 32, + Type::I64 => 64, + Type::I128 => 128, + Type::I256 => 256, + Type::Unit => 0, + Type::Compound(_) if ty.is_pointer(module) => 256, + Type::Compound(_) => return None, + }; + Some(bits) +} + +fn low_bits_mask(bits: u16) -> Option { + if bits >= 256 { + None + } else if bits == 0 { + Some(U256::zero()) + } else { + Some((U256::one() << (bits as usize)) - U256::one()) + } +} + +fn emit_max_top_two(ctx: &mut Lower) { + // Stack: [..., b, a] (a is top). + // Result: [..., max(a, b)] + ctx.push(OpCode::DUP2); + ctx.push(OpCode::DUP2); + ctx.push(OpCode::GT); + + let keep_a_push = ctx.push(OpCode::PUSH1); + ctx.push(OpCode::JUMPI); + + // keep b (a <= b): drop a. + ctx.push(OpCode::POP); + let end_push = ctx.push(OpCode::PUSH1); + ctx.push(OpCode::JUMP); + + // keep a (a > b): drop b. + let keep_a = ctx.push(OpCode::JUMPDEST); + ctx.add_label_reference(keep_a_push, Label::Insn(keep_a)); + ctx.push(OpCode::SWAP1); + ctx.push(OpCode::POP); + + let end = ctx.push(OpCode::JUMPDEST); + ctx.add_label_reference(end_push, Label::Insn(end)); +} + +fn enter_frame(ctx: &mut Lower, frame_slots: u32, dyn_base: u32) { + if frame_slots == 0 { + return; + } + + let frame_bytes = frame_slots + .checked_mul(WORD_BYTES) + .expect("frame size overflow"); + + // sp = mload(DYN_SP_SLOT); if sp == 0, initialize it. + push_bytes(ctx, &[DYN_SP_SLOT]); + ctx.push(OpCode::MLOAD); + + // if sp != 0, skip init. + ctx.push(OpCode::DUP1); + ctx.push(OpCode::ISZERO); + ctx.push(OpCode::ISZERO); + let skip_init_push = ctx.push(OpCode::PUSH1); + ctx.push(OpCode::JUMPI); + + // init: pop 0 sp; sp = dyn_base; mstore(DYN_SP_SLOT, sp) + ctx.push(OpCode::POP); + push_bytes(ctx, &u32_to_be(dyn_base)); + ctx.push(OpCode::DUP1); + push_bytes(ctx, &[DYN_SP_SLOT]); + ctx.push(OpCode::MSTORE); + + let skip_init = ctx.push(OpCode::JUMPDEST); + ctx.add_label_reference(skip_init_push, Label::Insn(skip_init)); + + // Clamp dynamic stack pointer above any heap allocations. + push_bytes(ctx, &[FREE_PTR_SLOT]); + ctx.push(OpCode::MLOAD); + emit_max_top_two(ctx); + + // Save old fp at frame_base (sp): mstore(sp, mload(DYN_FP_SLOT)) + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MLOAD); + ctx.push(OpCode::DUP2); + ctx.push(OpCode::MSTORE); + + // new_fp = sp + WORD_BYTES; mstore(DYN_FP_SLOT, new_fp) + ctx.push(OpCode::DUP1); + push_bytes(ctx, &u32_to_be(WORD_BYTES)); + ctx.push(OpCode::ADD); + ctx.push(OpCode::DUP1); + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MSTORE); + + // new_sp = new_fp + frame_bytes; mstore(DYN_SP_SLOT, new_sp) + if frame_bytes != 0 { + push_bytes(ctx, &u32_to_be(frame_bytes)); + ctx.push(OpCode::ADD); + } + push_bytes(ctx, &[DYN_SP_SLOT]); + ctx.push(OpCode::MSTORE); + + // Discard frame_base (sp). + ctx.push(OpCode::POP); +} + +fn leave_frame(ctx: &mut Lower, frame_slots: u32) { + if frame_slots == 0 { + return; + } + + // frame_base = fp - WORD_BYTES + // + // NOTE: `SUB` computes `a - b` with `a` on top of stack. + push_bytes(ctx, &u32_to_be(WORD_BYTES)); + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MLOAD); + ctx.push(OpCode::SUB); + + // old_fp = mload(frame_base) + ctx.push(OpCode::DUP1); + ctx.push(OpCode::MLOAD); + + // mstore(DYN_FP_SLOT, old_fp) + push_bytes(ctx, &[DYN_FP_SLOT]); + ctx.push(OpCode::MSTORE); + + // mstore(DYN_SP_SLOT, frame_base) + push_bytes(ctx, &[DYN_SP_SLOT]); + ctx.push(OpCode::MSTORE); +} + +fn dup_op(n: u8) -> OpCode { + match n + 1 { + 1 => OpCode::DUP1, + 2 => OpCode::DUP2, + 3 => OpCode::DUP3, + 4 => OpCode::DUP4, + 5 => OpCode::DUP5, + 6 => OpCode::DUP6, + 7 => OpCode::DUP7, + 8 => OpCode::DUP8, + 9 => OpCode::DUP9, + 10 => OpCode::DUP10, + 11 => OpCode::DUP11, + 12 => OpCode::DUP12, + 13 => OpCode::DUP13, + 14 => OpCode::DUP14, + 15 => OpCode::DUP15, + 16 => OpCode::DUP16, + _ => unreachable!(), + } +} + +fn swap_op(n: u8) -> OpCode { + match n { + 1 => OpCode::SWAP1, + 2 => OpCode::SWAP2, + 3 => OpCode::SWAP3, + 4 => OpCode::SWAP4, + 5 => OpCode::SWAP5, + 6 => OpCode::SWAP6, + 7 => OpCode::SWAP7, + 8 => OpCode::SWAP8, + 9 => OpCode::SWAP9, + 10 => OpCode::SWAP10, + 11 => OpCode::SWAP11, + 12 => OpCode::SWAP12, + 13 => OpCode::SWAP13, + 14 => OpCode::SWAP14, + 15 => OpCode::SWAP15, + 16 => OpCode::SWAP16, + _ => unreachable!(), + } +} + +fn push_op(bytes: usize) -> OpCode { + match bytes { + 0 => OpCode::PUSH0, + 1 => OpCode::PUSH1, + 2 => OpCode::PUSH2, + 3 => OpCode::PUSH3, + 4 => OpCode::PUSH4, + 5 => OpCode::PUSH5, + 6 => OpCode::PUSH6, + 7 => OpCode::PUSH7, + 8 => OpCode::PUSH8, + 9 => OpCode::PUSH9, + 10 => OpCode::PUSH10, + 11 => OpCode::PUSH11, + 12 => OpCode::PUSH12, + 13 => OpCode::PUSH13, + 14 => OpCode::PUSH14, + 15 => OpCode::PUSH15, + 16 => OpCode::PUSH16, + 17 => OpCode::PUSH17, + 18 => OpCode::PUSH18, + 19 => OpCode::PUSH19, + 20 => OpCode::PUSH20, + 21 => OpCode::PUSH21, + 22 => OpCode::PUSH22, + 23 => OpCode::PUSH23, + 24 => OpCode::PUSH24, + 25 => OpCode::PUSH25, + 26 => OpCode::PUSH26, + 27 => OpCode::PUSH27, + 28 => OpCode::PUSH28, + 29 => OpCode::PUSH29, + 30 => OpCode::PUSH30, + 31 => OpCode::PUSH31, + 32 => OpCode::PUSH32, + _ => unreachable!(), + } +} + +fn prepare_function( + func_ref: FuncRef, + function: &mut Function, + module: &ModuleCtx, + backend: &EvmBackend, + ptr_escape: &FxHashMap, + mem_effects: Option<&FxHashMap>, +) -> PreparedFunction { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + + let mut splitter = CriticalEdgeSplitter::new(); + splitter.run(function, &mut cfg); + + let mut dom = DomTree::new(); + dom.compute(&cfg); + + let block_order = dom.rpo().to_owned(); + + let mut did_insert_free_ptr_restore = false; + loop { + let mut liveness = Liveness::new(); + liveness.compute(function, &cfg); + + let mut inst_liveness = InstLiveness::new(); + inst_liveness.compute(function, &cfg, &liveness); + + let transient_mallocs = malloc_plan::compute_transient_mallocs( + function, + module, + &backend.isa, + ptr_escape, + mem_effects, + &inst_liveness, + ); + + if mem_effects.is_none() + && !did_insert_free_ptr_restore + && malloc_plan::should_restore_free_ptr_on_internal_returns( + function, + module, + &backend.isa, + ptr_escape, + &transient_mallocs, + ) + { + malloc_plan::insert_free_ptr_restore_on_internal_returns(function, &backend.isa); + did_insert_free_ptr_restore = true; + continue; + } + + // Values live across internal calls are pre-seeded into the spill set to keep + // call preparation within EVM `DUP16`/`SWAP16` reach. This is a planning convenience: + // internal calls do not wipe the EVM operand stack, but the temporary call return address + // plus argument shuffling can make deep transfer-region values unreachable. + let values_live_across_calls = inst_liveness.call_live_values(function); + let values_persistent_across_calls = mem_effects.map_or_else( + || values_live_across_calls.clone(), + |effects| compute_values_persistent_across_calls(function, &inst_liveness, effects), + ); + + let scratch_spill_slots = scratch_plan::SCRATCH_SPILL_SLOTS; + let scratch_live_values = scratch_plan::compute_scratch_live_values( + function, + module, + &backend.isa, + ptr_escape, + mem_effects, + &inst_liveness, + ); + let alloc = StackifyAlloc::for_function_with_values_live_across_calls_and_scratch_spills( + function, + &cfg, + &dom, + &liveness, + backend.stackify_reach_depth, + StackifyLiveValues { + values_live_across_calls: values_live_across_calls.clone(), + values_persistent_across_calls: values_persistent_across_calls.clone(), + scratch_live_values, + }, + scratch_spill_slots, + ); + let persistent_frame_slots = alloc.persistent_frame_slots; + let transient_frame_slots = alloc.transient_frame_slots; + + let alloca_layout = alloca_plan::compute_stack_alloca_layout( + func_ref, + function, + module, + &backend.isa, + ptr_escape, + alloca_plan::AllocaLayoutLiveness { + values_persistent_across_calls: &values_persistent_across_calls, + inst_liveness: &inst_liveness, + }, + &block_order, + ); + + let persistent_alloca_words = alloca_layout.persistent_words; + let transient_alloca_words = alloca_layout.transient_words; + let alloca_words = persistent_alloca_words + .checked_add(transient_alloca_words) + .expect("alloca words overflow"); + + let local_mem = FuncLocalMemInfo { + persistent_words: persistent_frame_slots + .checked_add(persistent_alloca_words) + .expect("persistent words overflow"), + transient_words: transient_frame_slots + .checked_add(transient_alloca_words) + .expect("transient words overflow"), + alloca_words, + persistent_alloca_words, + }; + + return PreparedFunction { + alloc, + block_order, + local_mem, + alloca_plan: alloca_layout.plan, + transient_mallocs, + }; + } +} + +fn compute_values_persistent_across_calls( + function: &Function, + inst_liveness: &InstLiveness, + effects: &FxHashMap, +) -> BitSet { + // In the StaticTree memory scheme, transient frame slots may be reused (and therefore + // clobbered) by callees that touch the static arena. Any value that is live across such a + // call needs a persistent memory home. + let mut persistent: BitSet = BitSet::default(); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let Some(call) = function.dfg.call_info(inst) else { + continue; + }; + let callee = call.callee(); + let may_clobber_transient = effects + .get(&callee) + .copied() + .unwrap_or_default() + .touches_static_arena; + if !may_clobber_transient { + continue; + } + + persistent.union_with(inst_liveness.live_out(inst)); + if let Some(def) = function.dfg.inst_result(inst) { + persistent.remove(def); + } + } + } + + persistent +} + +fn debug_print_mem_plan( + module: &Module, + funcs: &[FuncRef], + plan: &ProgramMemoryPlan, + local_mem: &FxHashMap, +) { + let mut funcs_by_name: Vec<(String, FuncRef)> = funcs + .iter() + .copied() + .map(|f| (module.ctx.func_sig(f, |sig| sig.name().to_string()), f)) + .collect(); + funcs_by_name.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + + eprintln!( + "evm mem debug: dyn_base=0x{:x} static_base=0x{:x}", + plan.dyn_base, STATIC_BASE + ); + eprintln!("evm mem debug: entry_mem_init_stores=0"); + + for (name, func) in funcs_by_name { + let Some(func_plan) = plan.funcs.get(&func) else { + continue; + }; + let mem = local_mem.get(&func).copied().unwrap_or(FuncLocalMemInfo { + persistent_words: 0, + transient_words: 0, + alloca_words: 0, + persistent_alloca_words: 0, + }); + + let (scheme, base_words) = match &func_plan.scheme { + MemScheme::StaticTree(st) => ("StaticTree", Some(st.base_words)), + MemScheme::DynamicFrame => ("DynamicFrame", None), + }; + + let (malloc_min, malloc_max, malloc_count) = + if func_plan.malloc_future_static_words.is_empty() { + (None, None, 0) + } else { + let mut min: u32 = u32::MAX; + let mut max: u32 = 0; + for &w in func_plan.malloc_future_static_words.values() { + min = min.min(w); + max = max.max(w); + } + ( + Some(min), + Some(max), + func_plan.malloc_future_static_words.len(), + ) + }; + + let static_end = match &func_plan.scheme { + MemScheme::StaticTree(st) => st + .base_words + .checked_add(mem.persistent_words) + .and_then(|w| w.checked_add(mem.transient_words)) + .and_then(|w| w.checked_mul(WORD_BYTES)) + .and_then(|b| STATIC_BASE.checked_add(b)), + MemScheme::DynamicFrame => None, + }; + + eprintln!( + "evm mem debug: {name} scheme={scheme} base_words={:?} persistent_words={} transient_words={} malloc_bounds(min,max,count)=({:?},{:?},{malloc_count}) static_end={:?}", + base_words, + mem.persistent_words, + mem.transient_words, + malloc_min, + malloc_max, + static_end + ); + } +} + +fn u32_to_evm_push_bytes(value: u32, policy: PushWidthPolicy) -> SmallVec<[u8; 4]> { + match policy { + PushWidthPolicy::Push4 => SmallVec::from_slice(&value.to_be_bytes()), + PushWidthPolicy::MinimalRelax => { + if value == 0 { + SmallVec::new() + } else { + value + .to_be_bytes() + .into_iter() + .skip_while(|b| *b == 0) + .collect() + } + } + } +} diff --git a/crates/codegen/src/isa/evm/opcode.rs b/crates/codegen/src/isa/evm/opcode.rs new file mode 100644 index 00000000..9f8e6367 --- /dev/null +++ b/crates/codegen/src/isa/evm/opcode.rs @@ -0,0 +1,162 @@ +#[derive(Debug, Default, Clone, Copy)] +#[repr(u8)] +pub enum OpCode { + STOP = 0x00, + ADD = 0x01, + MUL = 0x02, + SUB = 0x03, + DIV = 0x04, + SDIV = 0x05, + MOD = 0x06, + SMOD = 0x07, + ADDMOD = 0x08, + MULMOD = 0x09, + EXP = 0x0A, + SIGNEXTEND = 0x0B, + // 0C-0F unused + LT = 0x10, + GT = 0x11, + SLT = 0x12, + SGT = 0x13, + EQ = 0x14, + ISZERO = 0x15, + AND = 0x16, + OR = 0x17, + XOR = 0x18, + NOT = 0x19, + BYTE = 0x1A, + SHL = 0x1B, + SHR = 0x1C, + SAR = 0x1D, + CLZ = 0x1E, + // 1F unused + KECCAK256 = 0x20, + // 21-2F unused + ADDRESS = 0x30, + BALANCE = 0x31, + ORIGIN = 0x32, + CALLER = 0x33, + CALLVALUE = 0x34, + CALLDATALOAD = 0x35, + CALLDATASIZE = 0x36, + CALLDATACOPY = 0x37, + CODESIZE = 0x38, + CODECOPY = 0x39, + GASPRICE = 0x3A, + EXTCODESIZE = 0x3B, + EXTCODECOPY = 0x3C, + RETURNDATASIZE = 0x3D, + RETURNDATACOPY = 0x3E, + EXTCODEHASH = 0x3F, + BLOCKHASH = 0x40, + COINBASE = 0x41, + TIMESTAMP = 0x42, + NUMBER = 0x43, + PREVRANDAO = 0x44, + GASLIMIT = 0x45, + CHAINID = 0x46, + SELFBALANCE = 0x47, + BASEFEE = 0x48, + BLOBHASH = 0x49, + BLOBBASEFEE = 0x4A, + // 4B-4F unused + POP = 0x50, + MLOAD = 0x51, + MSTORE = 0x52, + MSTORE8 = 0x53, + SLOAD = 0x54, + SSTORE = 0x55, + JUMP = 0x56, + JUMPI = 0x57, + PC = 0x58, + MSIZE = 0x59, + GAS = 0x5A, + JUMPDEST = 0x5B, + TLOAD = 0x5C, + TSTORE = 0x5D, + MCOPY = 0x5E, + PUSH0 = 0x5F, + PUSH1 = 0x60, + PUSH2 = 0x61, + PUSH3 = 0x62, + PUSH4 = 0x63, + PUSH5 = 0x64, + PUSH6 = 0x65, + PUSH7 = 0x66, + PUSH8 = 0x67, + PUSH9 = 0x68, + PUSH10 = 0x69, + PUSH11 = 0x6A, + PUSH12 = 0x6B, + PUSH13 = 0x6C, + PUSH14 = 0x6D, + PUSH15 = 0x6E, + PUSH16 = 0x6F, + PUSH17 = 0x70, + PUSH18 = 0x71, + PUSH19 = 0x72, + PUSH20 = 0x73, + PUSH21 = 0x74, + PUSH22 = 0x75, + PUSH23 = 0x76, + PUSH24 = 0x77, + PUSH25 = 0x78, + PUSH26 = 0x79, + PUSH27 = 0x7A, + PUSH28 = 0x7B, + PUSH29 = 0x7C, + PUSH30 = 0x7D, + PUSH31 = 0x7E, + PUSH32 = 0x7F, + DUP1 = 0x80, + DUP2 = 0x81, + DUP3 = 0x82, + DUP4 = 0x83, + DUP5 = 0x84, + DUP6 = 0x85, + DUP7 = 0x86, + DUP8 = 0x87, + DUP9 = 0x88, + DUP10 = 0x89, + DUP11 = 0x8A, + DUP12 = 0x8B, + DUP13 = 0x8C, + DUP14 = 0x8D, + DUP15 = 0x8E, + DUP16 = 0x8F, + SWAP1 = 0x90, + SWAP2 = 0x91, + SWAP3 = 0x92, + SWAP4 = 0x93, + SWAP5 = 0x94, + SWAP6 = 0x95, + SWAP7 = 0x96, + SWAP8 = 0x97, + SWAP9 = 0x98, + SWAP10 = 0x99, + SWAP11 = 0x9A, + SWAP12 = 0x9B, + SWAP13 = 0x9C, + SWAP14 = 0x9D, + SWAP15 = 0x9E, + SWAP16 = 0x9F, + LOG0 = 0xA0, + LOG1 = 0xA1, + LOG2 = 0xA2, + LOG3 = 0xA3, + LOG4 = 0xA4, + // A5-EF unused + CREATE = 0xF0, + CALL = 0xF1, + CALLCODE = 0xF2, + RETURN = 0xF3, + DELEGATECALL = 0xF4, + CREATE2 = 0xF5, + // F6-F9 unused + STATICCALL = 0xFA, + // FB-FC unused + REVERT = 0xFD, + #[default] + INVALID = 0xFE, + SELFDESTRUCT = 0xFF, +} diff --git a/crates/codegen/src/isa/evm/provenance.rs b/crates/codegen/src/isa/evm/provenance.rs new file mode 100644 index 00000000..858a150f --- /dev/null +++ b/crates/codegen/src/isa/evm/provenance.rs @@ -0,0 +1,233 @@ +use cranelift_entity::SecondaryMap; +use rustc_hash::FxHashMap; +use smallvec::SmallVec; +use sonatina_ir::{ + Function, InstId, InstSetExt, ValueId, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum PtrBase { + Arg(u32), + Alloca(InstId), + Malloc(InstId), +} + +impl PtrBase { + fn key(self) -> (u8, u32) { + match self { + PtrBase::Arg(i) => (0, i), + PtrBase::Alloca(inst) => (1, inst.as_u32()), + PtrBase::Malloc(inst) => (2, inst.as_u32()), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub(crate) struct Provenance { + bases: SmallVec<[PtrBase; 4]>, +} + +impl Provenance { + pub(crate) fn is_empty(&self) -> bool { + self.bases.is_empty() + } + + pub(crate) fn union_with(&mut self, other: &Self) -> bool { + if other.bases.is_empty() { + return false; + } + + let mut changed = false; + for base in other.bases.iter().copied() { + if !self.bases.contains(&base) { + self.bases.push(base); + changed = true; + } + } + + if changed { + self.bases.sort_unstable_by_key(|b| b.key()); + self.bases.dedup(); + } + changed + } + + pub(crate) fn has_any_arg(&self) -> bool { + self.bases.iter().any(|b| matches!(b, PtrBase::Arg(_))) + } + + pub(crate) fn arg_indices(&self) -> impl Iterator + '_ { + self.bases.iter().filter_map(|b| match b { + PtrBase::Arg(i) => Some(*i), + PtrBase::Alloca(_) | PtrBase::Malloc(_) => None, + }) + } + + pub(crate) fn is_local_addr(&self) -> bool { + !self.bases.is_empty() && self.bases.iter().all(|b| matches!(b, PtrBase::Alloca(_))) + } + + pub(crate) fn alloca_insts(&self) -> impl Iterator + '_ { + self.bases.iter().filter_map(|b| match b { + PtrBase::Alloca(inst) => Some(*inst), + PtrBase::Arg(_) => None, + PtrBase::Malloc(_) => None, + }) + } + + pub(crate) fn malloc_insts(&self) -> impl Iterator + '_ { + self.bases.iter().filter_map(|b| match b { + PtrBase::Malloc(inst) => Some(*inst), + PtrBase::Arg(_) | PtrBase::Alloca(_) => None, + }) + } +} + +pub(crate) fn compute_value_provenance( + function: &Function, + module: &ModuleCtx, + isa: &Evm, + callee_arg_may_be_returned: impl Fn(FuncRef) -> Vec, +) -> SecondaryMap { + let mut prov: SecondaryMap = SecondaryMap::new(); + for value in function.dfg.values.keys() { + let _ = &mut prov[value]; + } + + for (idx, &arg) in function.arg_values.iter().enumerate() { + if function.dfg.value_ty(arg).is_pointer(module) { + prov[arg].bases.push(PtrBase::Arg(idx as u32)); + } + } + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + if let EvmInstKind::Alloca(_) = data + && let Some(def) = function.dfg.inst_result(inst) + { + prov[def].bases.push(PtrBase::Alloca(inst)); + } + + if let EvmInstKind::EvmMalloc(_) = data + && let Some(def) = function.dfg.inst_result(inst) + { + prov[def].bases.push(PtrBase::Malloc(inst)); + } + } + } + + let mut mem: FxHashMap = FxHashMap::default(); + + let mut changed = true; + while changed { + changed = false; + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + + if let EvmInstKind::Mstore(mstore) = data { + let addr = *mstore.addr(); + let addr_prov = &prov[addr]; + if addr_prov.is_local_addr() { + let val = *mstore.value(); + let val_prov = &prov[val]; + for base in addr_prov.alloca_insts() { + let entry = mem.entry(base).or_default(); + if entry.union_with(val_prov) { + changed = true; + } + } + } + } + + let Some(def) = function.dfg.inst_result(inst) else { + continue; + }; + + let mut next = Provenance::default(); + + match data { + EvmInstKind::Alloca(_) => next.bases.push(PtrBase::Alloca(inst)), + EvmInstKind::EvmMalloc(_) => next.bases.push(PtrBase::Malloc(inst)), + EvmInstKind::Mload(mload) => { + let addr = *mload.addr(); + let addr_prov = &prov[addr]; + if addr_prov.is_local_addr() { + for base in addr_prov.alloca_insts() { + if let Some(stored) = mem.get(&base) { + let _ = next.union_with(stored); + } + } + } + } + EvmInstKind::Phi(phi) => { + for (val, _) in phi.args().iter() { + let _ = next.union_with(&prov[*val]); + } + } + EvmInstKind::Gep(gep) => { + let Some(&base) = gep.values().first() else { + continue; + }; + let _ = next.union_with(&prov[base]); + } + EvmInstKind::Bitcast(bc) => { + let _ = next.union_with(&prov[*bc.from()]); + } + EvmInstKind::IntToPtr(i2p) => { + let _ = next.union_with(&prov[*i2p.from()]); + } + EvmInstKind::PtrToInt(p2i) => { + let _ = next.union_with(&prov[*p2i.from()]); + } + EvmInstKind::InsertValue(iv) => { + let _ = next.union_with(&prov[*iv.dest()]); + let _ = next.union_with(&prov[*iv.value()]); + } + EvmInstKind::ExtractValue(ev) => { + let _ = next.union_with(&prov[*ev.dest()]); + } + EvmInstKind::Call(call) => { + let arg_may_be_returned = callee_arg_may_be_returned(*call.callee()); + for (idx, &arg) in call.args().iter().enumerate() { + if arg_may_be_returned.get(idx).copied().unwrap_or(false) { + let _ = next.union_with(&prov[arg]); + } + } + } + EvmInstKind::Add(_) + | EvmInstKind::Sub(_) + | EvmInstKind::Mul(_) + | EvmInstKind::And(_) + | EvmInstKind::Or(_) + | EvmInstKind::Xor(_) + | EvmInstKind::Shl(_) + | EvmInstKind::Shr(_) + | EvmInstKind::Sar(_) + | EvmInstKind::Not(_) + | EvmInstKind::Sext(_) + | EvmInstKind::Zext(_) + | EvmInstKind::Trunc(_) => { + function.dfg.inst(inst).for_each_value(&mut |v| { + let _ = next.union_with(&prov[v]); + }); + } + _ => {} + } + + let cur = &mut prov[def]; + if *cur != next { + *cur = next; + changed = true; + } + } + } + } + + prov +} diff --git a/crates/codegen/src/isa/evm/ptr_escape.rs b/crates/codegen/src/isa/evm/ptr_escape.rs new file mode 100644 index 00000000..8eeb59aa --- /dev/null +++ b/crates/codegen/src/isa/evm/ptr_escape.rs @@ -0,0 +1,358 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + InstSetExt, Module, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::FuncRef, +}; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::module_analysis::{CallGraph, CallGraphSccs, SccBuilder, SccRef}; + +use super::provenance::compute_value_provenance; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct PtrEscapeSummary { + pub(crate) arg_may_escape: Vec, + pub(crate) arg_may_be_returned: Vec, + pub(crate) returns_any_ptr: bool, +} + +impl PtrEscapeSummary { + fn new(arg_count: usize) -> Self { + Self { + arg_may_escape: vec![false; arg_count], + arg_may_be_returned: vec![false; arg_count], + returns_any_ptr: false, + } + } + + fn conservative_unknown(module: &Module, func: FuncRef) -> Self { + let arg_count = module.func_store.view(func, |f| f.arg_values.len()); + Self { + arg_may_escape: vec![true; arg_count], + arg_may_be_returned: vec![true; arg_count], + returns_any_ptr: module + .ctx + .func_sig(func, |sig| sig.ret_ty().is_pointer(&module.ctx)), + } + } +} + +pub(crate) fn compute_ptr_escape_summaries( + module: &Module, + funcs: &[FuncRef], + isa: &Evm, +) -> FxHashMap { + let funcs_set: FxHashSet = funcs.iter().copied().collect(); + let call_graph = CallGraph::build_graph_subset(module, &funcs_set); + let scc = SccBuilder::new().compute_scc(&call_graph); + + let topo = topo_sort_sccs(&funcs_set, &call_graph, &scc); + + let mut summaries: FxHashMap = FxHashMap::default(); + for &f in funcs { + let arg_count = module.func_store.view(f, |func| func.arg_values.len()); + summaries.insert(f, PtrEscapeSummary::new(arg_count)); + } + + for scc_ref in topo.into_iter().rev() { + let mut component: Vec = scc + .scc_info(scc_ref) + .components + .iter() + .copied() + .filter(|f| funcs_set.contains(f)) + .collect(); + component.sort_unstable_by_key(|f| f.as_u32()); + + loop { + let mut changed = false; + for &f in &component { + let new_summary = compute_summary_for_func(module, f, isa, &summaries); + let cur = summaries.get(&f).expect("missing ptr escape summary"); + if *cur != new_summary { + summaries.insert(f, new_summary); + changed = true; + } + } + + if !changed { + break; + } + } + } + + summaries +} + +fn topo_sort_sccs( + funcs: &FxHashSet, + call_graph: &CallGraph, + scc: &CallGraphSccs, +) -> Vec { + let mut sccs: BTreeSet = BTreeSet::new(); + for &f in funcs { + sccs.insert(scc.scc_ref(f)); + } + + let mut edges: BTreeMap> = BTreeMap::new(); + let mut indegree: BTreeMap = BTreeMap::new(); + for &s in &sccs { + edges.insert(s, BTreeSet::new()); + indegree.insert(s, 0); + } + + for &f in funcs { + let from = scc.scc_ref(f); + for &callee in call_graph.callee_of(f) { + let to = scc.scc_ref(callee); + if from == to { + continue; + } + + let tos = edges.get_mut(&from).expect("missing scc"); + if tos.insert(to) { + *indegree.get_mut(&to).expect("missing scc") += 1; + } + } + } + + let mut ready: BTreeSet = BTreeSet::new(); + for (&s, °) in &indegree { + if deg == 0 { + ready.insert(s); + } + } + + let mut topo: Vec = Vec::with_capacity(sccs.len()); + while let Some(&s) = ready.first() { + ready.remove(&s); + topo.push(s); + + let tos: Vec = edges + .get(&s) + .expect("missing scc") + .iter() + .copied() + .collect(); + for to in tos { + let deg = indegree.get_mut(&to).expect("missing scc"); + *deg = deg.checked_sub(1).expect("indegree underflow"); + if *deg == 0 { + ready.insert(to); + } + } + } + + debug_assert_eq!(topo.len(), sccs.len(), "SCC topo sort incomplete"); + topo +} + +fn compute_summary_for_func( + module: &Module, + func: FuncRef, + isa: &Evm, + summaries: &FxHashMap, +) -> PtrEscapeSummary { + module.func_store.view(func, |function| { + let arg_count = function.arg_values.len(); + let mut summary = PtrEscapeSummary::new(arg_count); + + let mut arg_is_ptr: Vec = Vec::with_capacity(arg_count); + for &arg in &function.arg_values { + arg_is_ptr.push(function.dfg.value_ty(arg).is_pointer(&module.ctx)); + } + + let prov = compute_value_provenance(function, &module.ctx, isa, |callee| { + summaries + .get(&callee) + .cloned() + .unwrap_or_else(|| PtrEscapeSummary::conservative_unknown(module, callee)) + .arg_may_be_returned + }); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let data = isa.inst_set().resolve_inst(function.dfg.inst(inst)); + + match data { + EvmInstKind::Return(ret) => { + let Some(ret_val) = *ret.arg() else { + continue; + }; + + let ret_ty = function.dfg.value_ty(ret_val); + if ret_ty.is_pointer(&module.ctx) { + summary.returns_any_ptr = true; + } + + let ret_prov = &prov[ret_val]; + for idx in ret_prov.arg_indices() { + let idx = idx as usize; + if idx < summary.arg_may_be_returned.len() { + summary.arg_may_be_returned[idx] = true; + } + } + + if ret_ty.is_pointer(&module.ctx) && !ret_prov.has_any_arg() { + for (idx, is_ptr) in arg_is_ptr.iter().copied().enumerate() { + if is_ptr { + summary.arg_may_be_returned[idx] = true; + } + } + } + } + EvmInstKind::Mstore(mstore) => { + let addr_prov = &prov[*mstore.addr()]; + if addr_prov.is_local_addr() { + continue; + } + + let val_prov = &prov[*mstore.value()]; + for idx in val_prov.arg_indices() { + let idx = idx as usize; + if idx < summary.arg_may_escape.len() { + summary.arg_may_escape[idx] = true; + } + } + } + EvmInstKind::Call(call) => { + let callee = *call.callee(); + let callee_sum = summaries.get(&callee).cloned().unwrap_or_else(|| { + PtrEscapeSummary::conservative_unknown(module, callee) + }); + + let args = call.args(); + for (idx, &arg) in args.iter().enumerate() { + if idx < callee_sum.arg_may_escape.len() + && callee_sum.arg_may_escape[idx] + { + let p = &prov[arg]; + for arg_idx in p.arg_indices() { + let arg_idx = arg_idx as usize; + if arg_idx < summary.arg_may_escape.len() { + summary.arg_may_escape[arg_idx] = true; + } + } + } + } + } + _ => {} + } + } + } + + summary + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use sonatina_parser::parse_module; + use sonatina_triple::{Architecture, EvmVersion, OperatingSystem, TargetTriple, Vendor}; + + fn compute( + src: &str, + ) -> ( + FxHashMap, + FxHashMap, + ) { + let parsed = parse_module(src).unwrap(); + let funcs: Vec = parsed.module.funcs(); + let mut names: FxHashMap = FxHashMap::default(); + for f in funcs.iter().copied() { + let name = parsed.module.ctx.func_sig(f, |sig| sig.name().to_string()); + names.insert(name, f); + } + + let isa = Evm::new(TargetTriple { + architecture: Architecture::Evm, + vendor: Vendor::Ethereum, + operating_system: OperatingSystem::Evm(EvmVersion::Osaka), + }); + + let summaries = compute_ptr_escape_summaries(&parsed.module, &funcs, &isa); + + let mut out: FxHashMap = FxHashMap::default(); + for (name, f) in &names { + if let Some(sum) = summaries.get(f) { + out.insert(name.clone(), sum.clone()); + } + } + + (out, names) + } + + #[test] + fn ptr_escape_direct_return() { + let (summaries, _) = compute( + r#" +target = "evm-ethereum-osaka" + +func public %g(v0.*i8) -> *i8 { +block0: + return v0; +} +"#, + ); + + let g = &summaries["g"]; + assert!(g.returns_any_ptr); + assert_eq!(g.arg_may_be_returned, vec![true]); + assert_eq!(g.arg_may_escape, vec![false]); + } + + #[test] + fn ptr_escape_propagates_through_calls() { + let (summaries, _) = compute( + r#" +target = "evm-ethereum-osaka" + +func public %sink(v0.*i8) { +block0: + mstore 0.i32 v0 *i8; + return; +} + +func public %f(v0.*i8) { +block0: + call %sink v0; + return; +} +"#, + ); + + let sink = &summaries["sink"]; + assert_eq!(sink.arg_may_escape, vec![true]); + + let f = &summaries["f"]; + assert_eq!(f.arg_may_escape, vec![true]); + } + + #[test] + fn ptr_escape_call_return_flow() { + let (summaries, _) = compute( + r#" +target = "evm-ethereum-osaka" + +func public %id(v0.*i8) -> *i8 { +block0: + return v0; +} + +func public %f(v0.*i8) -> *i8 { +block0: + v1.*i8 = call %id v0; + return v1; +} +"#, + ); + + let f = &summaries["f"]; + assert!(f.returns_any_ptr); + assert_eq!(f.arg_may_be_returned, vec![true]); + assert_eq!(f.arg_may_escape, vec![false]); + } +} diff --git a/crates/codegen/src/isa/evm/scratch_plan.rs b/crates/codegen/src/isa/evm/scratch_plan.rs new file mode 100644 index 00000000..66e87105 --- /dev/null +++ b/crates/codegen/src/isa/evm/scratch_plan.rs @@ -0,0 +1,135 @@ +use cranelift_entity::SecondaryMap; +use rustc_hash::FxHashMap; +use sonatina_ir::{ + Function, Immediate, InstId, InstSetExt, U256, ValueId, + inst::evm::inst_set::EvmInstKind, + isa::{Isa, evm::Evm}, + module::{FuncRef, ModuleCtx}, +}; + +use crate::{bitset::BitSet, liveness::InstLiveness}; + +use super::{ + mem_effects::FuncMemEffects, + memory_plan::WORD_BYTES, + provenance::{Provenance, compute_value_provenance}, + ptr_escape::PtrEscapeSummary, +}; + +pub(crate) const SCRATCH_SPILL_SLOTS: u32 = 2; + +const SCRATCH_END_BYTES: u32 = SCRATCH_SPILL_SLOTS * WORD_BYTES; + +fn imm_lt_u32(imm: Immediate, bound: u32) -> bool { + match imm { + Immediate::I1(val) => (val as u32) < bound, + Immediate::I8(val) => val >= 0 && (val as u32) < bound, + Immediate::I16(val) => val >= 0 && (val as u32) < bound, + Immediate::I32(val) => val >= 0 && (val as u32) < bound, + Immediate::I64(val) => val >= 0 && (val as u64) < bound as u64, + Immediate::I128(val) => val >= 0 && (val as u128) < bound as u128, + Immediate::I256(val) => !val.is_negative() && val.to_u256() < U256::from(bound), + } +} + +fn addr_may_overlap_scratch( + function: &Function, + addr: ValueId, + prov: &SecondaryMap, +) -> bool { + if function.dfg.value_is_imm(addr) { + let imm = function + .dfg + .value_imm(addr) + .expect("imm value missing payload"); + return imm_lt_u32(imm, SCRATCH_END_BYTES); + } + + let prov = &prov[addr]; + prov.is_empty() || prov.has_any_arg() +} + +pub(crate) fn inst_is_scratch_clobber( + function: &Function, + isa: &Evm, + inst: InstId, + prov: &SecondaryMap, +) -> bool { + match isa.inst_set().resolve_inst(function.dfg.inst(inst)) { + EvmInstKind::Mstore(mstore) => addr_may_overlap_scratch(function, *mstore.addr(), prov), + EvmInstKind::EvmMstore8(mstore8) => { + addr_may_overlap_scratch(function, *mstore8.addr(), prov) + } + EvmInstKind::EvmCalldataCopy(copy) => { + addr_may_overlap_scratch(function, *copy.dst_addr(), prov) + } + EvmInstKind::EvmCodeCopy(copy) => { + addr_may_overlap_scratch(function, *copy.dst_addr(), prov) + } + EvmInstKind::EvmExtCodeCopy(copy) => { + addr_may_overlap_scratch(function, *copy.dst_addr(), prov) + } + EvmInstKind::EvmReturnDataCopy(copy) => { + addr_may_overlap_scratch(function, *copy.dst_addr(), prov) + } + EvmInstKind::EvmMcopy(copy) => addr_may_overlap_scratch(function, *copy.dest(), prov), + EvmInstKind::EvmCall(call) => addr_may_overlap_scratch(function, *call.ret_addr(), prov), + EvmInstKind::EvmCallCode(call) => { + addr_may_overlap_scratch(function, *call.ret_addr(), prov) + } + EvmInstKind::EvmDelegateCall(call) => { + addr_may_overlap_scratch(function, *call.ret_addr(), prov) + } + EvmInstKind::EvmStaticCall(call) => { + addr_may_overlap_scratch(function, *call.ret_addr(), prov) + } + _ => false, + } +} + +pub(crate) fn compute_scratch_live_values( + function: &Function, + module: &ModuleCtx, + isa: &Evm, + ptr_escape: &FxHashMap, + mem_effects: Option<&FxHashMap>, + inst_liveness: &InstLiveness, +) -> BitSet { + let prov = compute_value_provenance(function, module, isa, |callee| { + ptr_escape.get(&callee).map_or_else( + || { + let arg_count = module.func_sig(callee, |sig| sig.args().len()); + vec![true; arg_count] + }, + |summary| summary.arg_may_be_returned.clone(), + ) + }); + + let mut scratch_live_values: BitSet = BitSet::default(); + + for block in function.layout.iter_block() { + for inst in function.layout.iter_inst(block) { + let is_barrier = if let Some(call) = function.dfg.call_info(inst) { + let callee = call.callee(); + mem_effects.is_none_or(|effects| { + effects + .get(&callee) + .copied() + .unwrap_or_default() + .touches_scratch + }) + } else { + inst_is_scratch_clobber(function, isa, inst, &prov) + }; + + if is_barrier { + scratch_live_values.union_with(inst_liveness.live_out(inst)); + if let Some(def) = function.dfg.inst_result(inst) { + scratch_live_values.remove(def); + } + } + } + } + + scratch_live_values +} diff --git a/crates/codegen/src/isa/mod.rs b/crates/codegen/src/isa/mod.rs new file mode 100644 index 00000000..c469d0c8 --- /dev/null +++ b/crates/codegen/src/isa/mod.rs @@ -0,0 +1 @@ +pub mod evm; diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index 01a42b73..0cb9e7f7 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -1,6 +1,13 @@ +mod bitset; +pub mod cfg_scc; pub mod critical_edge; pub mod domtree; +pub mod isa; +pub mod liveness; pub mod loop_analysis; +pub mod machinst; pub mod module_analysis; +pub mod object; pub mod optim; pub mod post_domtree; +pub mod stackalloc; diff --git a/crates/codegen/src/liveness.rs b/crates/codegen/src/liveness.rs new file mode 100644 index 00000000..a58b129f --- /dev/null +++ b/crates/codegen/src/liveness.rs @@ -0,0 +1,386 @@ +//! Compute the "liveness" of values in a control flow graph. +//! +//! This is an implementation of "Liveness Sets using Path Exploration", +//! as described in +//! Section 2.5.1: +//! +//! > a variable is live at a program point p, if p belongs to a path of the CFG +//! > leading from a definition of that variable to one of its uses without +//! > passing through the definition. Therefore, the live-range of a variable can +//! > be computed using a backward traversal starting at its uses and stopping +//! > when reaching its (unique) definition. This idea was first proposed by +//! > Appel in his “Tiger” book... +//! > +//! > Starting from a use of a variable, all paths where that variable is live +//! > are followed by traversing the CFG backwards until the variable’s +//! > definition is reached. Along the encountered paths, the variable is added +//! > to the live-in and live-out sets of the respective basic blocks. +//! +//! +//! Note that the arguments to a phi instruction are considered to be used by +//! their associated predecessor blocks, *not* by the block containing the phi. +//! The result of a phi instruction is live-in for the block containing the phi, +//! but *not* live-out for the predecessor blocks (so no block defines the result). + +use std::collections::BTreeMap; + +use crate::bitset::BitSet; +use cranelift_entity::SecondaryMap; +use sonatina_ir::{BlockId, Function, InstId, ValueId, cfg::ControlFlowGraph}; + +#[derive(Default)] +pub struct Liveness { + /// block => (live_ins, live_outs) + live_ins: SecondaryMap>, + live_outs: SecondaryMap>, + + /// value => (defining block, is_defined_by_phi) + defs: SecondaryMap>, + pub val_live_blocks: SecondaryMap>, + pub val_use_blocks: SecondaryMap>, + pub val_use_count: SecondaryMap, +} + +impl Liveness { + pub fn new() -> Self { + Self::default() + } + + pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) { + self.clear(); + + for arg in &func.arg_values { + self.defs[*arg] = Some(ValDef::FnArg); + } + for block in cfg.post_order() { + for_each_def(func, block, |val, is_phi_def| { + self.defs[val] = if is_phi_def { + Some(ValDef::Phi(block)) + } else { + Some(ValDef::Normal(block)) + } + }); + } + + for block in cfg.post_order() { + for_each_use(func, block, |val, phi_source_block| { + if func.dfg.value_is_imm(val) { + self.mark_use(val, block); + } else if let Some(pred_block) = phi_source_block { + // A phi input is considered to be a use by the associated + // predecessor block + self.mark_use(val, pred_block); + self.up_and_mark(cfg, pred_block, val); + } else { + self.mark_use(val, block); + self.up_and_mark(cfg, block, val); + } + }); + } + } + + // XXX better api + pub fn block_live_ins(&self, block: BlockId) -> &BitSet { + &self.live_ins[block] + } + pub fn block_live_outs(&self, block: BlockId) -> &BitSet { + &self.live_outs[block] + } + + /// Propagate liveness of `val` "upward" from its use in `block` + fn up_and_mark(&mut self, cfg: &ControlFlowGraph, block: BlockId, val: ValueId) { + let def = self.defs[val].unwrap(); + + self.val_live_blocks[val].insert(block); + + // If `val` is defined in this block, there's nothing to do. + if def == ValDef::Normal(block) { + return; + } + + if self.live_ins[block].contains(val) { + // Already marked, so propagation to preds already done + return; + } + self.live_ins[block].insert(val); + + // If `val` is the result of a phi, then it's live-in for the block + // containing the phi, but not live-out for any predecessor block. + if def == ValDef::Phi(block) { + return; + } + + for pred in cfg.preds_of(block) { + self.live_outs[*pred].insert(val); + self.up_and_mark(cfg, *pred, val); + } + } + + fn mark_use(&mut self, val: ValueId, block: BlockId) { + self.val_use_blocks[val].insert(block); + self.val_use_count[val] += 1; + } + + /// Reset the `Liveness` struct so that it can be reused. + pub fn clear(&mut self) { + self.live_ins.clear(); + self.live_outs.clear(); + self.defs.clear(); + self.val_live_blocks.clear(); + } +} + +#[derive(Default)] +pub struct InstLiveness { + /// `inst` => values live after `inst` executes. + live_out: SecondaryMap>, +} + +impl InstLiveness { + pub fn new() -> Self { + Self::default() + } + + pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, liveness: &Liveness) { + self.live_out.clear(); + + for block in cfg.post_order() { + let mut live = liveness.block_live_outs(block).clone(); + + let insts: Vec<_> = func.layout.iter_inst(block).collect(); + for inst in insts.into_iter().rev() { + self.live_out[inst] = live.clone(); + + if let Some(def) = func.dfg.inst_result(inst) { + live.remove(def); + } + + if func.dfg.is_phi(inst) { + continue; + } + + func.dfg.inst(inst).for_each_value(&mut |v| { + if !func.dfg.value_is_imm(v) { + live.insert(v); + } + }); + } + } + } + + pub fn live_out(&self, inst: InstId) -> &BitSet { + &self.live_out[inst] + } + + pub fn call_live_values(&self, func: &Function) -> BitSet { + let mut call_live_values = BitSet::default(); + for block in func.layout.iter_block() { + for inst in func.layout.iter_inst(block) { + if func.dfg.call_info(inst).is_some() { + call_live_values.union_with(self.live_out(inst)); + if let Some(def) = func.dfg.inst_result(inst) { + call_live_values.remove(def); + } + } + } + } + call_live_values + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +enum ValDef { + FnArg, + Normal(BlockId), + Phi(BlockId), +} + +pub fn value_uses_in_block_matching_predicate( + func: &Function, + block: BlockId, + pred: impl Fn(ValueId) -> bool, +) -> BTreeMap { + let mut counts = BTreeMap::new(); + for_each_use(func, block, |val, _| { + if pred(val) { + counts.entry(val).and_modify(|n| *n += 1).or_insert(1); + } + }); + counts +} + +fn for_each_use(func: &Function, block: BlockId, mut f: impl FnMut(ValueId, Option)) { + for inst in func.layout.iter_inst(block) { + if let Some(phi) = func.dfg.cast_phi(inst) { + for (val, block) in phi.args().iter() { + f(*val, Some(*block)) + } + } else { + func.dfg.inst(inst).for_each_value(&mut |v| f(v, None)); + } + } +} + +fn for_each_def(func: &Function, block: BlockId, mut f: impl FnMut(ValueId, bool)) { + for inst in func.layout.iter_inst(block) { + if let Some(val) = func.dfg.inst_result(inst) { + f(val, func.dfg.is_phi(inst)) + } + } +} + +#[cfg(test)] +mod tests { + use super::{InstLiveness, Liveness}; + use sonatina_ir::{BlockId, cfg::ControlFlowGraph}; + use sonatina_parser::parse_module; + + const SRC: &str = r#" +target = "evm-ethereum-london" +func public %complex_loop(v0.i8, v20.i8) -> i8 { + block1: + v1.i8 = sub v0 1.i8; + jump block2; + + block2: + v2.i8 = phi (v8 block7) (v0 block1); + v3.i8 = phi (v9 block7) (v1 block1); + v4.i1 = lt v3 100.i8; + br v4 block3 block4; + + block3: + v5.i1 = lt v2 20.i8; + br v5 block5 block6; + + block4: + return v2; + + block5: + v6.i8 = add 1.i8 v3; + jump block7; + + block6: + v7.i8 = add v3 2.i8; + jump block7; + + block7: + v8.i8 = phi (v0 block5) (v3 block6); + v9.i8 = phi (v6 block5) (v7 block6); + jump block2; +} +"#; + + #[test] + fn test() { + let parsed = parse_module(SRC).unwrap(); + let funcref = *parsed.module.funcs().first().unwrap(); + + let live = parsed.module.func_store.view(funcref, |function| { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + let mut live = Liveness::default(); + live.compute(function, &cfg); + live + }); + + let v = |n: usize| parsed.debug.value(funcref, &format!("v{n}")).unwrap(); + + assert_eq!(live.block_live_ins(BlockId(1)), &[v(0)].as_slice().into()); + assert_eq!(live.block_live_outs(BlockId(1)), &[v(0)].as_slice().into()); + + assert_eq!( + live.block_live_ins(BlockId(2)), + &[v(0), v(2), v(3)].as_slice().into() + ); + assert_eq!( + live.block_live_outs(BlockId(2)), + &[v(0), v(2), v(3)].as_slice().into() + ); + + assert_eq!( + live.block_live_ins(BlockId(3)), + &[v(0), v(2), v(3)].as_slice().into() + ); + assert_eq!( + live.block_live_outs(BlockId(3)), + &[v(0), v(3)].as_slice().into() + ); + + assert_eq!(live.block_live_ins(BlockId(4)), &[v(2)].as_slice().into()); + assert_eq!(live.block_live_outs(BlockId(4)), &[].as_slice().into()); + + assert_eq!( + live.block_live_ins(BlockId(5)), + &[v(0), v(3)].as_slice().into() + ); + assert_eq!(live.block_live_outs(BlockId(5)), &[v(0)].as_slice().into()); + + assert_eq!( + live.block_live_ins(BlockId(6)), + &[v(0), v(3)].as_slice().into() + ); + assert_eq!(live.block_live_outs(BlockId(6)), &[v(0)].as_slice().into()); + } + + #[test] + fn call_live_values_excludes_call_results() { + const SRC: &str = r#" +target = "evm-ethereum-osaka" + +func private %callee(v0.i256) -> i256 { +block0: + return v0; +} + +func public %caller(v0.i256) -> i256 { +block0: + v1.i256 = add v0 1.i256; + v2.i256 = call %callee v1; + v3.i256 = add v2 v1; + return v3; +} +"#; + + let parsed = parse_module(SRC).unwrap(); + let caller = parsed + .debug + .func_order + .iter() + .copied() + .find(|&f| parsed.module.ctx.func_sig(f, |sig| sig.name() == "caller")) + .expect("missing caller"); + + let (inst_live, call_inst) = parsed.module.func_store.view(caller, |function| { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + + let mut live = Liveness::default(); + live.compute(function, &cfg); + + let mut inst_live = InstLiveness::default(); + inst_live.compute(function, &cfg, &live); + + let call_inst = function + .layout + .iter_block() + .flat_map(|b| function.layout.iter_inst(b)) + .find(|&inst| function.dfg.call_info(inst).is_some()) + .expect("missing call inst"); + + (inst_live, call_inst) + }); + + let v1 = parsed.debug.value(caller, "v1").unwrap(); + let v2 = parsed.debug.value(caller, "v2").unwrap(); + + assert!(inst_live.live_out(call_inst).contains(v1)); + assert!(inst_live.live_out(call_inst).contains(v2)); + + let call_live = parsed + .module + .func_store + .view(caller, |function| inst_live.call_live_values(function)); + assert!(call_live.contains(v1)); + assert!(!call_live.contains(v2)); + } +} diff --git a/crates/codegen/src/machinst/assemble.rs b/crates/codegen/src/machinst/assemble.rs new file mode 100644 index 00000000..b4ee212c --- /dev/null +++ b/crates/codegen/src/machinst/assemble.rs @@ -0,0 +1,261 @@ +use std::{io, ops::IndexMut}; + +use cranelift_entity::SecondaryMap; +use indexmap::IndexMap; +use sonatina_ir::{ + BlockId, Module, U256, + ir_writer::{DebugProvider, FuncWriteCtx, FunctionSignature, InstStatement, IrWrite}, + module::FuncRef, +}; + +use super::{ + lower::LowerBackend, + vcode::{Label, LabelId, VCode, VCodeFixup, VCodeInst}, +}; + +pub struct ObjectLayout { + // TODO: data, suffix (solc metadata) + _offset: u32, + _size: u32, + functions: IndexMap>, + func_offsets: SecondaryMap, +} + +impl ObjectLayout { + pub fn new(funcs: Vec<(FuncRef, VCode, Vec)>, mut offset: u32) -> Self { + let start = offset; + + let mut func_offsets = SecondaryMap::with_capacity(funcs.len()); + let functions = funcs + .into_iter() + .map(|(f, vcode, block_order)| { + func_offsets[f] = offset; + let layout = FuncLayout::new(vcode, block_order, offset); + offset += layout.size; + (f, layout) + }) + .collect(); + + Self { + _offset: start, + _size: offset - start, + functions, + func_offsets, + } + } + + pub fn resize(&mut self, backend: &impl LowerBackend, mut offset: u32) -> bool { + let mut did_change = false; + for (funcref, layout) in self.functions.iter_mut() { + did_change |= layout.resize(backend, offset, &self.func_offsets); + self.func_offsets[*funcref] = offset; + offset += layout.size; + } + did_change + } + + pub fn emit(&self, backend: &impl LowerBackend, buf: &mut Vec) { + for layout in self.functions.values() { + layout.emit(backend, buf); + } + } + + pub(crate) fn func_offset(&self, func: FuncRef) -> u32 { + self.func_offsets[func] + } + + pub(crate) fn func_size(&self, func: FuncRef) -> Option { + self.functions.get(&func).map(|layout| layout.size) + } + + pub(crate) fn func_vcode_mut(&mut self, func: FuncRef) -> Option<&mut VCode> { + self.functions + .get_mut(&func) + .map(|layout| &mut layout.vcode) + } +} + +pub struct FuncLayout { + offset: u32, + size: u32, + vcode: VCode, + block_order: Vec, + block_offsets: SecondaryMap, + insn_offsets: SecondaryMap, + label_targets: SecondaryMap, +} + +impl FuncLayout { + fn new(vcode: VCode, block_order: Vec, mut offset: u32) -> Self { + let start = offset; + + // Rough block offsets, ignoring immediates and labels + let mut block_offsets = SecondaryMap::default(); + for b in &block_order { + block_offsets[*b] = offset; + let block_size = vcode.blocks.get(*b).unwrap().len(&vcode.insts_pool) as u32; + offset += block_size; + } + + let imm_bytes: u32 = vcode + .inst_imm_bytes + .values() + .map(|(_, bytes)| bytes.len() as u32) + .sum(); + + // Guess that each label is 2 bytes + let label_bytes = 2 * vcode.labels.len() as u32; + + let size = offset - start + imm_bytes + label_bytes; + + Self { + offset, + size, + vcode, + block_order, + block_offsets, + insn_offsets: SecondaryMap::default(), + label_targets: SecondaryMap::default(), + } + } + + fn resize( + &mut self, + backend: &impl LowerBackend, + mut offset: u32, + fn_offsets: &SecondaryMap, + ) -> bool { + let mut did_change = update(&mut self.offset, offset); + + for block in self.block_order.iter().copied() { + did_change |= update(self.block_offsets.index_mut(block), offset); + + let block_insts = self.vcode.blocks[block].as_slice(&self.vcode.insts_pool); + for insn in block_insts { + did_change |= update(self.insn_offsets.index_mut(*insn), offset); + offset += std::mem::size_of::() as u32; + + if let Some((_, fixup)) = self.vcode.fixups.get(*insn) + && let VCodeFixup::Label(label) = fixup + { + let address = match self.vcode.labels[*label] { + Label::Block(b) => self.block_offsets[b], + Label::Function(f) => fn_offsets[f], + Label::Insn(i) => self.insn_offsets[i], + }; + did_change |= update(self.label_targets.index_mut(*label), address); + + let label_bytes = + backend.update_opcode_with_label(&mut self.vcode.insts[*insn], address); + offset += label_bytes.len() as u32; + } + + if let Some((_, bytes)) = self.vcode.inst_imm_bytes.get_mut(*insn) { + backend.update_opcode_with_immediate_bytes(&mut self.vcode.insts[*insn], bytes); + offset += bytes.len() as u32; + } + } + } + did_change |= update(&mut self.size, offset - self.offset); + did_change + } + + fn emit(&self, backend: &impl LowerBackend, buf: &mut Vec) { + for block in self.block_order.iter().copied() { + for insn in self.vcode.block_insns(block) { + backend.emit_opcode(&self.vcode.insts[insn], buf); + if let Some((_, fixup)) = self.vcode.fixups.get(insn) + && let VCodeFixup::Label(label) = fixup + { + let address = self.label_targets[*label]; + backend.emit_label(address, buf); // xxx emit_address_bytes + } + + if let Some((_, bytes)) = self.vcode.inst_imm_bytes.get(insn) { + backend.emit_immediate_bytes(bytes, buf); + } + } + } + } +} + +impl ObjectLayout +where + Op: std::fmt::Debug, +{ + pub fn print( + &self, + w: &mut impl std::io::Write, + module: &Module, + dbg: &dyn DebugProvider, + ) -> std::io::Result<()> { + for (funcref, layout) in self.functions.iter() { + module.func_store.view(*funcref, |function| { + let ctx = FuncWriteCtx::with_debug_provider(function, *funcref, dbg); + layout.write(w, &ctx) + })?; + } + Ok(()) + } +} + +impl IrWrite> for FuncLayout +where + Op: std::fmt::Debug, +{ + fn write(&self, w: &mut W, ctx: &FuncWriteCtx) -> io::Result<()> + where + W: io::Write, + { + // Mostly copied from VCode::print + + write!(w, "// ")?; + FunctionSignature.write(w, ctx)?; + writeln!(w)?; + ctx.module_ctx() + .func_sig(ctx.func_ref, |sig| writeln!(w, "{}:", sig.name()))?; + + let mut cur_ir = None; + for block in self.block_order.iter().copied() { + writeln!(w, " block{}:", block.0)?; + for insn in self.vcode.block_insns(block) { + write!( + w, + "{: >5} {:?}", + self.insn_offsets[insn], self.vcode.insts[insn], + )?; + if let Some((_, bytes)) = self.vcode.inst_imm_bytes.get(insn) { + let mut be = [0; 32]; + be[32 - bytes.len()..].copy_from_slice(bytes); + let imm = U256::from_big_endian(&be); + write!(w, " 0x{imm:x} ({imm})")?; + } else if let Some((_, fixup)) = self.vcode.fixups.get(insn) + && let VCodeFixup::Label(label) = fixup + { + write!(w, " {}", self.label_targets[*label])?; + match self.vcode.labels[*label] { + Label::Block(BlockId(n)) => write!(w, " (block{n})")?, + Label::Insn(_) => {} + Label::Function(func) => write!(w, " ({func:?})")?, + }; + } + + if let Some(ir) = self.vcode.inst_ir[insn].expand() + && cur_ir != Some(ir) + { + cur_ir = Some(ir); + write!(w, " // ")?; + InstStatement(ir).write(w, ctx)?; + } + writeln!(w)?; + } + } + Ok(()) + } +} + +fn update(val: &mut u32, to: u32) -> bool { + let did_change = *val != to; + *val = to; + did_change +} diff --git a/crates/codegen/src/machinst/lower.rs b/crates/codegen/src/machinst/lower.rs new file mode 100644 index 00000000..b00fc53c --- /dev/null +++ b/crates/codegen/src/machinst/lower.rs @@ -0,0 +1,204 @@ +use super::vcode::{Label, SymFixup, VCode, VCodeFixup, VCodeInst}; +use crate::stackalloc::Allocator; +use smallvec::SmallVec; +use sonatina_ir::{ + BlockId, Function, Immediate, Inst, InstId, Module, Type, ValueId, + module::{FuncRef, ModuleCtx}, + object::{EmbedSymbol, ObjectName, SectionName}, +}; + +pub struct LoweredFunction { + pub vcode: VCode, + pub block_order: Vec, +} + +pub struct SectionLoweringCtx<'a> { + pub object: &'a ObjectName, + pub section: &'a SectionName, + pub embed_symbols: &'a [EmbedSymbol], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FixupUpdate { + Unchanged, + ContentChanged, + LayoutChanged, +} + +pub trait LowerBackend { + type MInst; + type Error: std::fmt::Display; + type FixupPolicy: Clone; + + fn prepare_section( + &self, + _module: &Module, + _funcs: &[FuncRef], + _section_ctx: &SectionLoweringCtx<'_>, + ) { + } + + fn lower_function( + &self, + module: &Module, + func: FuncRef, + section_ctx: &SectionLoweringCtx<'_>, + ) -> Result, Self::Error>; + + fn apply_sym_fixup( + &self, + vcode: &mut VCode, + inst: VCodeInst, + fixup: &SymFixup, + value: u32, + policy: &Self::FixupPolicy, + ) -> Result; + + fn lower(&self, ctx: &mut Lower, alloc: &mut dyn Allocator, inst: InstId); + // -> Option == SmallVec<[ValueRegs; 2]> + + fn enter_function( + &self, + ctx: &mut Lower, + alloc: &mut dyn Allocator, + function: &Function, + ); + fn enter_block(&self, ctx: &mut Lower, alloc: &mut dyn Allocator, block: BlockId); + + fn update_opcode_with_immediate_bytes( + &self, + opcode: &mut Self::MInst, + bytes: &mut SmallVec<[u8; 8]>, + ); + + fn update_opcode_with_label( + &self, + opcode: &mut Self::MInst, + label_offset: u32, + ) -> SmallVec<[u8; 4]>; + + fn emit_opcode(&self, opcode: &Self::MInst, buf: &mut Vec); + fn emit_immediate_bytes(&self, bytes: &[u8], buf: &mut Vec); + fn emit_label(&self, address: u32, buf: &mut Vec); +} + +#[derive(Debug)] +pub struct CodegenError {} +pub type CodegenResult = Result; + +pub struct Lower<'a, Op> { + pub module: &'a ModuleCtx, + function: &'a Function, + vcode: VCode, + + cur_insn: Option, + cur_block: Option, +} + +impl<'a, Op: Default> Lower<'a, Op> { + pub fn new(module: &'a ModuleCtx, function: &'a Function) -> Self { + Lower { + module, + function, + vcode: VCode::default(), + cur_insn: None, + cur_block: None, + } + } + + pub fn lower>( + mut self, + backend: &B, + alloc: &mut dyn Allocator, + ) -> CodegenResult> { + let function = self.function; + let entry = function.layout.entry_block(); + for block in function.layout.iter_block() { + self.cur_block = Some(block); + self.cur_insn = None; + backend.enter_block(&mut self, alloc, block); + if entry == Some(block) { + backend.enter_function(&mut self, alloc, function); + } + + for insn in function.layout.iter_inst(block) { + self.cur_insn = Some(insn); + backend.lower(&mut self, alloc, insn); + } + } + + Ok(self.vcode) + } + + pub fn push(&mut self, op: Op) -> VCodeInst { + self.vcode + .add_inst_to_block(op, self.cur_insn, self.cur_block.unwrap()) + } + + pub fn push_with_imm(&mut self, op: Op, bytes: &[u8]) { + let i = self.push(op); + self.add_immediate(i, bytes); + } + + pub fn push_jump_target(&mut self, op: Op, dest: Label) { + let op = self.push(op); + self.add_label_reference(op, dest); + } + + pub fn next_insn(&self) -> VCodeInst { + self.vcode.insts.next_key() + } + + pub fn add_immediate(&mut self, inst: VCodeInst, bytes: &[u8]) { + self.vcode.inst_imm_bytes.insert((inst, bytes.into())); + } + + pub fn add_label_reference(&mut self, inst: VCodeInst, dest: Label) { + let label = self.vcode.labels.push(dest); + self.vcode.fixups.insert((inst, VCodeFixup::Label(label))); + } + + pub fn add_sym_fixup(&mut self, inst: VCodeInst, fixup: SymFixup) { + self.vcode.fixups.insert((inst, VCodeFixup::Sym(fixup))); + } + + pub fn push_sym_fixup(&mut self, op: Op, fixup: SymFixup) -> VCodeInst { + let inst = self.push(op); + self.add_immediate(inst, &[]); + self.add_sym_fixup(inst, fixup); + inst + } + + pub fn insn_data(&self, inst: InstId) -> &dyn Inst { + self.function.dfg.inst(inst) + } + + pub fn value_imm(&self, value: ValueId) -> Option { + self.function.dfg.value_imm(value) + } + + pub fn value_ty(&self, value: ValueId) -> Type { + self.function.dfg.value_ty(value) + } + + pub fn insn_result(&self, inst: InstId) -> Option { + self.function.dfg.inst_result(inst) + } + + pub fn insn_block(&self, inst: InstId) -> BlockId { + self.function.layout.inst_block(inst) + } + + pub fn is_entry(&self, block: BlockId) -> bool { + self.function.layout.entry_block() == Some(block) + } + + /// Check if the given `BlockId` is next in the layout. + /// Used for avoiding unnecessary `jump` operations. + pub fn is_next_block(&self, block: BlockId) -> bool { + let Some(cur) = self.cur_block else { + return false; + }; + self.function.layout.next_block_of(cur) == Some(block) + } +} diff --git a/crates/codegen/src/machinst/mod.rs b/crates/codegen/src/machinst/mod.rs new file mode 100644 index 00000000..758fad00 --- /dev/null +++ b/crates/codegen/src/machinst/mod.rs @@ -0,0 +1,3 @@ +pub mod assemble; +pub mod lower; +pub mod vcode; diff --git a/crates/codegen/src/machinst/vcode.rs b/crates/codegen/src/machinst/vcode.rs new file mode 100644 index 00000000..545d524e --- /dev/null +++ b/crates/codegen/src/machinst/vcode.rs @@ -0,0 +1,188 @@ +use cranelift_entity::{ + EntityList, EntityRef, ListPool, PrimaryMap, SecondaryMap, SparseMap, SparseMapValue, + entity_impl, + packed_option::{PackedOption, ReservedValue}, +}; +use smallvec::SmallVec; +use sonatina_ir::{ + BlockId, InstId, U256, + inst::data::SymbolRef, + ir_writer::{FuncWriteCtx, FunctionSignature, InstStatement, IrWrite}, + module::FuncRef, +}; +use std::{fmt, io}; + +// xxx offset reference graph? +// fn call graph + +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)] +pub struct LabelId(pub u32); +entity_impl!(LabelId); + +#[derive(Debug, Copy, Clone)] +pub enum Label { + Insn(VCodeInst), + Block(BlockId), + Function(FuncRef), +} + +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)] +pub struct VCodeInst(pub u32); +entity_impl!(VCodeInst); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymFixupKind { + Addr, + Size, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SymFixup { + pub kind: SymFixupKind, + pub sym: SymbolRef, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VCodeFixup { + Label(LabelId), + Sym(SymFixup), +} + +pub struct VCode { + pub insts: PrimaryMap, + pub inst_ir: SecondaryMap>, + + /// Immediate bytes for PUSH* ops + pub inst_imm_bytes: SparseMap)>, + + pub fixups: SparseMap, + + pub labels: PrimaryMap, + + pub blocks: SecondaryMap>, + + pub insts_pool: ListPool, +} + +impl VCode { + pub fn add_inst_to_block( + &mut self, + op: Op, + source_insn: Option, + block: BlockId, + ) -> VCodeInst { + let inst = self.insts.push(op); + self.inst_ir[inst] = source_insn.into(); + self.blocks[block].push(inst, &mut self.insts_pool); + inst + } + + pub fn block_insns(&self, block: BlockId) -> impl Iterator + '_ { + EntityIter { + list: &self.blocks[block], + pool: &self.insts_pool, + next: 0, + } + } + + // pub fn emit(self, alloc: &sonatina_stackalloc::Output) +} + +impl IrWrite> for VCode +where + Op: fmt::Debug, +{ + fn write(&self, w: &mut W, ctx: &FuncWriteCtx) -> std::io::Result<()> + where + W: io::Write, + { + write!(w, "// ")?; + FunctionSignature.write(w, ctx)?; + writeln!(w)?; + ctx.module_ctx() + .func_sig(ctx.func_ref, |sig| writeln!(w, "{}:", sig.name()))?; + + let mut cur_ir = None; + for block in self.blocks.keys() { + if self.block_insns(block).count() > 0 { + writeln!(w, " block{}:", block.0)?; + } + for (idx, insn) in self.block_insns(block).enumerate() { + write!(w, " {:?}", self.insts[insn])?; + if let Some((_, bytes)) = self.inst_imm_bytes.get(insn) { + let mut be = [0; 32]; + be[32 - bytes.len()..].copy_from_slice(bytes); + let imm = U256::from_big_endian(&be); + write!(w, " 0x{imm:x} ({imm})")?; + } else if let Some((_, fixup)) = self.fixups.get(insn) + && let VCodeFixup::Label(label) = fixup + { + match self.labels[*label] { + Label::Block(BlockId(n)) => write!(w, " block{n}"), + Label::Insn(insn) => { + let pos = self + .block_insns(block) + .position(|i| i == insn) + .expect("Label::Insn must be in same block"); + let offset: i32 = pos as i32 - idx as i32; + write!(w, " `pc + ({offset})`") + } + Label::Function(func) => write!(w, " {func:?}"), + }?; + } + + if let Some(ir) = self.inst_ir[insn].expand() + && cur_ir != Some(ir) + { + cur_ir = Some(ir); + write!(w, " // ")?; + InstStatement(ir).write(w, ctx)?; + } + writeln!(w)?; + } + } + Ok(()) + } +} + +impl Default for VCode { + fn default() -> Self { + Self { + insts: Default::default(), + inst_ir: Default::default(), + inst_imm_bytes: SparseMap::new(), // no `Default` impl + fixups: SparseMap::new(), + labels: PrimaryMap::default(), + blocks: Default::default(), + insts_pool: Default::default(), + } + } +} + +impl SparseMapValue for (VCodeInst, T) { + fn key(&self) -> VCodeInst { + self.0 + } +} + +struct EntityIter<'a, T> +where + T: EntityRef + ReservedValue, +{ + list: &'a EntityList, + pool: &'a ListPool, + next: usize, +} + +impl<'a, T> Iterator for EntityIter<'a, T> +where + T: EntityRef + ReservedValue, +{ + type Item = T; + + fn next(&mut self) -> Option { + let next = self.list.get(self.next, self.pool)?; + self.next += 1; + Some(next) + } +} diff --git a/crates/codegen/src/module_analysis.rs b/crates/codegen/src/module_analysis.rs index 5f335de6..f6a48521 100644 --- a/crates/codegen/src/module_analysis.rs +++ b/crates/codegen/src/module_analysis.rs @@ -133,6 +133,40 @@ impl CallGraph { CallGraph { nodes } } + /// Builds a call graph restricted to `funcs`. + /// + /// Any call edges to functions outside `funcs` are ignored. + pub fn build_graph_subset(module: &Module, funcs: &FxHashSet) -> Self { + let mut nodes = SecondaryMap::new(); + + let mut ordered: Vec = funcs.iter().copied().collect(); + ordered.sort_unstable_by_key(|f| f.as_u32()); + + for func_ref in ordered { + let callees = module.func_store.view(func_ref, |func| { + let mut callees = FxHashSet::default(); + for block in func.layout.iter_block() { + for inst_id in func.layout.iter_inst(block) { + if let Some(call) = func.dfg.call_info(inst_id) { + let callee = call.callee(); + if funcs.contains(&callee) { + callees.insert(callee); + } + } + } + } + + let mut callees: Vec<_> = callees.into_iter().collect(); + callees.sort_unstable(); + callees + }); + + nodes[func_ref] = Node { callees }; + } + + CallGraph { nodes } + } + pub fn funcs(&self) -> impl Iterator { self.nodes.keys() } diff --git a/crates/codegen/src/object/artifact.rs b/crates/codegen/src/object/artifact.rs new file mode 100644 index 00000000..a5a1d4c9 --- /dev/null +++ b/crates/codegen/src/object/artifact.rs @@ -0,0 +1,32 @@ +use indexmap::IndexMap; +use rustc_hash::FxHashMap; +use sonatina_ir::{ + GlobalVariableRef, + module::FuncRef, + object::{EmbedSymbol, ObjectName, SectionName}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SymbolId { + Func(FuncRef), + Global(GlobalVariableRef), + Embed(EmbedSymbol), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SymbolDef { + pub offset: u32, + pub size: u32, +} + +#[derive(Debug, Clone)] +pub struct SectionArtifact { + pub bytes: Vec, + pub symtab: FxHashMap, +} + +#[derive(Debug, Clone)] +pub struct ObjectArtifact { + pub object: ObjectName, + pub sections: IndexMap, +} diff --git a/crates/codegen/src/object/compile.rs b/crates/codegen/src/object/compile.rs new file mode 100644 index 00000000..3b64203f --- /dev/null +++ b/crates/codegen/src/object/compile.rs @@ -0,0 +1,848 @@ +use indexmap::IndexMap; +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + GlobalVariableRef, InstDowncast, Module, + inst::data::{GetFunctionPtr, SymAddr, SymSize, SymbolRef}, + module::FuncRef, + object::EmbedSymbol, +}; + +use super::{ + CompileOptions, + artifact::ObjectArtifact, + error::ObjectCompileError, + link::{LinkSectionError, link_section}, + resolve::{ObjectId, ResolvedProgram, SectionId}, +}; +use crate::machinst::lower::{LowerBackend, SectionLoweringCtx}; + +fn compile_required_sections_into_cache( + program: &ResolvedProgram<'_>, + backend: &B, + required: Option<&FxHashSet>, + opts: &CompileOptions, +) -> Result, Vec> { + let order = topo_sort_sections(program); + + let mut cache: FxHashMap = FxHashMap::default(); + for section_id in order { + if required.is_none_or(|required| required.contains(§ion_id)) { + compile_section(program, backend, section_id, &mut cache, opts)?; + } + } + + Ok(cache) +} + +pub fn compile_all_objects( + module: &Module, + backend: &B, + opts: &CompileOptions, +) -> Result, Vec> { + let program = ResolvedProgram::resolve(module)?; + let mut cache = compile_required_sections_into_cache(&program, backend, None, opts)?; + + let mut artifacts = Vec::new(); + for (idx, obj) in program.objects.iter().enumerate() { + let object_id = ObjectId(idx as u32); + let mut sections = IndexMap::new(); + for (section_idx, section) in obj.sections.iter().enumerate() { + let id = SectionId { + object: object_id, + section: section_idx as u32, + }; + let artifact = cache.remove(&id).ok_or_else(|| { + vec![ObjectCompileError::LinkError { + object: obj.name.clone(), + section: section.name.clone(), + message: "missing compiled section".to_string(), + }] + })?; + sections.insert(section.name.clone(), artifact); + } + artifacts.push(ObjectArtifact { + object: obj.name.clone(), + sections, + }); + } + + Ok(artifacts) +} + +pub fn compile_object( + module: &Module, + backend: &B, + object: &str, + opts: &CompileOptions, +) -> Result> { + let program = ResolvedProgram::resolve(module)?; + + let Some((object_idx, obj)) = program + .objects + .iter() + .enumerate() + .find(|(_, obj)| obj.name.0.as_str() == object) + else { + return Err(vec![ObjectCompileError::ObjectNotFound { + object: object.to_string(), + }]); + }; + + let object_id = ObjectId(object_idx as u32); + let mut required: FxHashSet = FxHashSet::default(); + for (section_idx, _) in obj.sections.iter().enumerate() { + let id = SectionId { + object: object_id, + section: section_idx as u32, + }; + collect_embed_deps(&program, id, &mut required); + } + + let mut cache = compile_required_sections_into_cache(&program, backend, Some(&required), opts)?; + + let mut sections = IndexMap::new(); + for (section_idx, section) in obj.sections.iter().enumerate() { + let id = SectionId { + object: object_id, + section: section_idx as u32, + }; + let artifact = cache.remove(&id).ok_or_else(|| { + vec![ObjectCompileError::LinkError { + object: obj.name.clone(), + section: section.name.clone(), + message: "missing compiled section".to_string(), + }] + })?; + sections.insert(section.name.clone(), artifact); + } + + Ok(ObjectArtifact { + object: obj.name.clone(), + sections, + }) +} + +fn compile_section( + program: &ResolvedProgram<'_>, + backend: &B, + section_id: SectionId, + cache: &mut FxHashMap, + opts: &CompileOptions, +) -> Result<(), Vec> { + if cache.contains_key(§ion_id) { + return Ok(()); + } + + let section = program.section(section_id); + let (object_name, section_name) = program.section_name(section_id); + + let defined_embed_symbols: Vec = + section.embeds.iter().map(|e| e.as_symbol.clone()).collect(); + let defined_embed_symbol_set: FxHashSet = + defined_embed_symbols.iter().cloned().collect(); + + let membership = build_membership(program, section_id); + for used in &membership.used_embed_symbols { + if !defined_embed_symbol_set.contains(used) { + return Err(vec![ObjectCompileError::UndefinedEmbedSymbol { + object: object_name.clone(), + section: section_name.clone(), + symbol: used.clone(), + }]); + } + } + + let section_ctx = SectionLoweringCtx { + object: object_name, + section: section_name, + embed_symbols: &defined_embed_symbols, + }; + + let funcs = compute_function_emission_order(program, section, &membership); + + let mut data: Vec<(GlobalVariableRef, Vec)> = Vec::new(); + let mut gvs: Vec = membership.globals.iter().copied().collect(); + gvs.sort_unstable(); + for gv in gvs { + let bytes = + super::data::encode_gv_initializer_to_bytes(&program.module.ctx, gv).map_err(|e| { + vec![ObjectCompileError::InvalidGlobalForData { + object: object_name.clone(), + section: section_name.clone(), + gv, + reason: format!("{e:?}"), + }] + })?; + data.push((gv, bytes)); + } + + let mut embeds: Vec<(EmbedSymbol, Vec)> = Vec::new(); + for embed in §ion.embeds { + let source = cache.get(&embed.source).ok_or_else(|| { + vec![ObjectCompileError::LinkError { + object: object_name.clone(), + section: section_name.clone(), + message: "missing embedded section artifact".to_string(), + }] + })?; + embeds.push((embed.as_symbol.clone(), source.bytes.clone())); + } + + let artifact = link_section( + program.module, + backend, + &funcs, + &data, + &embeds, + §ion_ctx, + opts, + ) + .map_err(|e| match e { + LinkSectionError::Backend { func, error } => vec![ObjectCompileError::BackendError { + object: object_name.clone(), + section: section_name.clone(), + func, + message: error.to_string(), + }], + LinkSectionError::Link(message) => vec![ObjectCompileError::LinkError { + object: object_name.clone(), + section: section_name.clone(), + message, + }], + })?; + + cache.insert(section_id, artifact); + Ok(()) +} + +#[derive(Default)] +struct SectionMembership { + funcs: FxHashSet, + globals: FxHashSet, + used_embed_symbols: FxHashSet, +} + +fn build_membership(program: &ResolvedProgram<'_>, section_id: SectionId) -> SectionMembership { + let module = program.module; + let section = program.section(section_id); + + let mut membership = SectionMembership::default(); + membership.globals.extend(section.data.iter().copied()); + + let mut worklist = Vec::new(); + let roots = std::iter::once(section.entry).chain(section.includes.iter().copied()); + for func in roots { + if membership.funcs.insert(func) { + worklist.push(func); + } + } + + while let Some(func_ref) = worklist.pop() { + module.func_store.view(func_ref, |func| { + let is = func.inst_set(); + for block in func.layout.iter_block() { + for inst_id in func.layout.iter_inst(block) { + if let Some(call) = func.dfg.call_info(inst_id) { + let callee = call.callee(); + if membership.funcs.insert(callee) { + worklist.push(callee); + } + } + + let inst = func.dfg.inst(inst_id); + + if let Some(ptr) = <&GetFunctionPtr as InstDowncast>::downcast(is, inst) { + let func = *ptr.func(); + if membership.funcs.insert(func) { + worklist.push(func); + } + } + + if let Some(sym) = <&SymAddr as InstDowncast>::downcast(is, inst) { + record_symbol(sym.sym(), &mut membership, &mut worklist); + } + if let Some(sym) = <&SymSize as InstDowncast>::downcast(is, inst) { + record_symbol(sym.sym(), &mut membership, &mut worklist); + } + } + } + }); + } + + membership +} + +fn record_symbol(sym: &SymbolRef, membership: &mut SectionMembership, worklist: &mut Vec) { + match sym { + SymbolRef::Func(func) => { + if membership.funcs.insert(*func) { + worklist.push(*func); + } + } + SymbolRef::Global(gv) => { + membership.globals.insert(*gv); + } + SymbolRef::Embed(sym) => { + membership.used_embed_symbols.insert(sym.clone()); + } + } +} + +fn compute_function_emission_order( + program: &ResolvedProgram<'_>, + section: &super::resolve::ResolvedSection, + membership: &SectionMembership, +) -> Vec { + let module = program.module; + + let mut include_priority: FxHashMap = FxHashMap::default(); + for (idx, func) in section.includes.iter().copied().enumerate() { + include_priority.entry(func).or_insert(idx); + } + + let mut func_names: FxHashMap = FxHashMap::default(); + for func in membership.funcs.iter().copied() { + let name = module.ctx.func_sig(func, |sig| sig.name().to_string()); + func_names.insert(func, name); + } + + let mut adjacency: FxHashMap> = FxHashMap::default(); + for &func_ref in &membership.funcs { + let mut callees: FxHashSet = FxHashSet::default(); + module.func_store.view(func_ref, |func| { + for block in func.layout.iter_block() { + for inst_id in func.layout.iter_inst(block) { + if let Some(call) = func.dfg.call_info(inst_id) { + let callee = call.callee(); + if membership.funcs.contains(&callee) { + callees.insert(callee); + } + } + } + } + }); + + let mut callees: Vec<_> = callees.into_iter().collect(); + callees.sort_by(|a, b| compare_func(*a, *b, &include_priority, &func_names)); + adjacency.insert(func_ref, callees); + } + + let mut visited: FxHashSet = FxHashSet::default(); + let mut order = Vec::new(); + + fn dfs( + node: FuncRef, + adjacency: &FxHashMap>, + visited: &mut FxHashSet, + out: &mut Vec, + ) { + if !visited.insert(node) { + return; + } + out.push(node); + if let Some(callees) = adjacency.get(&node) { + for &callee in callees { + dfs(callee, adjacency, visited, out); + } + } + } + + dfs(section.entry, &adjacency, &mut visited, &mut order); + + let mut extra_roots: Vec = section.includes.to_vec(); + extra_roots.sort_by(|a, b| compare_func(*a, *b, &include_priority, &func_names)); + for root in extra_roots { + dfs(root, &adjacency, &mut visited, &mut order); + } + + let mut remaining: Vec = membership + .funcs + .iter() + .copied() + .filter(|f| !visited.contains(f)) + .collect(); + remaining.sort_by(|a, b| compare_func(*a, *b, &include_priority, &func_names)); + for root in remaining { + dfs(root, &adjacency, &mut visited, &mut order); + } + + debug_assert_eq!(visited.len(), membership.funcs.len()); + order +} + +fn compare_func( + a: FuncRef, + b: FuncRef, + include_priority: &FxHashMap, + func_names: &FxHashMap, +) -> std::cmp::Ordering { + let a_pri = include_priority.get(&a).copied().unwrap_or(usize::MAX); + let b_pri = include_priority.get(&b).copied().unwrap_or(usize::MAX); + let a_name = func_names.get(&a).unwrap(); + let b_name = func_names.get(&b).unwrap(); + + (a_pri, a_name, a.as_u32()).cmp(&(b_pri, b_name, b.as_u32())) +} + +fn collect_embed_deps( + program: &ResolvedProgram<'_>, + id: SectionId, + out: &mut FxHashSet, +) { + if !out.insert(id) { + return; + } + for embed in &program.section(id).embeds { + collect_embed_deps(program, embed.source, out); + } +} + +fn topo_sort_sections(program: &ResolvedProgram<'_>) -> Vec { + let roots = program.all_sections(); + + let mut visited: FxHashSet = FxHashSet::default(); + let mut order = Vec::new(); + + fn dfs( + program: &ResolvedProgram<'_>, + node: SectionId, + visited: &mut FxHashSet, + order: &mut Vec, + ) { + if !visited.insert(node) { + return; + } + + for embed in &program.section(node).embeds { + dfs(program, embed.source, visited, order); + } + order.push(node); + } + + for root in roots { + dfs(program, root, &mut visited, &mut order); + } + + order +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + isa::evm::PushWidthPolicy, + machinst::{ + lower::{FixupUpdate, LowerBackend, LoweredFunction, SectionLoweringCtx}, + vcode::{SymFixup, SymFixupKind, VCode, VCodeFixup, VCodeInst}, + }, + object::CompileOptions, + }; + use smallvec::SmallVec; + use sonatina_ir::{ + InstDowncast, Module, + inst::data::{SymAddr, SymSize}, + module::FuncRef, + }; + use sonatina_parser::parse_module; + use std::sync::{Arc, Mutex}; + + struct FakeBackend; + + impl LowerBackend for FakeBackend { + type MInst = u8; + type Error = String; + type FixupPolicy = PushWidthPolicy; + + fn lower_function( + &self, + module: &Module, + func: FuncRef, + section_ctx: &SectionLoweringCtx<'_>, + ) -> Result, Self::Error> { + let _ = section_ctx; + + let mut vcode: VCode = VCode::default(); + let mut block_order = Vec::new(); + + module.func_store.view(func, |function| { + let is = function.inst_set(); + for block in function.layout.iter_block() { + let _ = &mut vcode.blocks[block]; + block_order.push(block); + + for inst_id in function.layout.iter_inst(block) { + let inst = function.dfg.inst(inst_id); + + if let Some(sym) = <&SymAddr as InstDowncast>::downcast(is, inst) { + let insn = vcode.add_inst_to_block(0, Some(inst_id), block); + vcode.inst_imm_bytes.insert((insn, SmallVec::new())); + vcode.fixups.insert(( + insn, + VCodeFixup::Sym(SymFixup { + kind: SymFixupKind::Addr, + sym: sym.sym().clone(), + }), + )); + } + + if let Some(sym) = <&SymSize as InstDowncast>::downcast(is, inst) { + let insn = vcode.add_inst_to_block(0, Some(inst_id), block); + vcode.inst_imm_bytes.insert((insn, SmallVec::new())); + vcode.fixups.insert(( + insn, + VCodeFixup::Sym(SymFixup { + kind: SymFixupKind::Size, + sym: sym.sym().clone(), + }), + )); + } + } + } + }); + + Ok(LoweredFunction { vcode, block_order }) + } + + fn apply_sym_fixup( + &self, + vcode: &mut VCode, + inst: VCodeInst, + fixup: &SymFixup, + value: u32, + policy: &Self::FixupPolicy, + ) -> Result { + let _ = fixup; + let (_, bytes) = vcode + .inst_imm_bytes + .get_mut(inst) + .ok_or_else(|| "missing fixup immediate bytes".to_string())?; + + let new_bytes: SmallVec<[u8; 4]> = match policy { + PushWidthPolicy::Push4 => SmallVec::from_slice(&value.to_be_bytes()), + PushWidthPolicy::MinimalRelax => { + if value == 0 { + SmallVec::new() + } else { + value + .to_be_bytes() + .into_iter() + .skip_while(|b| *b == 0) + .collect() + } + } + }; + + if bytes.as_slice() == new_bytes.as_slice() { + return Ok(FixupUpdate::Unchanged); + } + + let layout_changed = bytes.len() != new_bytes.len(); + bytes.clear(); + bytes.extend_from_slice(&new_bytes); + self.update_opcode_with_immediate_bytes(&mut vcode.insts[inst], bytes); + + Ok(if layout_changed { + FixupUpdate::LayoutChanged + } else { + FixupUpdate::ContentChanged + }) + } + + fn lower( + &self, + ctx: &mut crate::machinst::lower::Lower, + alloc: &mut dyn crate::stackalloc::Allocator, + inst: sonatina_ir::InstId, + ) { + let _ = (ctx, alloc, inst); + unreachable!("FakeBackend does not use machinst lowering") + } + + fn enter_function( + &self, + ctx: &mut crate::machinst::lower::Lower, + alloc: &mut dyn crate::stackalloc::Allocator, + function: &sonatina_ir::Function, + ) { + let _ = (ctx, alloc, function); + } + + fn enter_block( + &self, + ctx: &mut crate::machinst::lower::Lower, + alloc: &mut dyn crate::stackalloc::Allocator, + block: sonatina_ir::BlockId, + ) { + let _ = (ctx, alloc, block); + } + + fn update_opcode_with_immediate_bytes( + &self, + opcode: &mut Self::MInst, + bytes: &mut SmallVec<[u8; 8]>, + ) { + debug_assert!(bytes.len() <= 32); + *opcode = 0x5f_u8.saturating_add(bytes.len() as u8); + } + + fn update_opcode_with_label( + &self, + opcode: &mut Self::MInst, + label_offset: u32, + ) -> SmallVec<[u8; 4]> { + let bytes = label_offset + .to_be_bytes() + .into_iter() + .skip_while(|b| *b == 0) + .collect::>(); + debug_assert!(bytes.len() <= 32); + *opcode = 0x5f_u8.saturating_add(bytes.len() as u8); + bytes + } + + fn emit_opcode(&self, opcode: &Self::MInst, buf: &mut Vec) { + buf.push(*opcode); + } + + fn emit_immediate_bytes(&self, bytes: &[u8], buf: &mut Vec) { + buf.extend_from_slice(bytes); + } + + fn emit_label(&self, address: u32, buf: &mut Vec) { + buf.extend(address.to_be_bytes().into_iter().skip_while(|b| *b == 0)); + } + } + + #[test] + fn compile_object_embeds_sections_and_patches_fixups() { + let s = r#" +target = "evm-ethereum-london" + +global public const [i8; 3] $blob = [1, 2, 3]; + +func public %runtime() { + block0: + v0.i256 = sym_addr $blob; + v1.i256 = sym_size $blob; + return; +} + +func public %init() { + block0: + v0.i256 = sym_addr &runtime; + v1.i256 = sym_size &runtime; + return; +} + +object @Contract { + section init { + entry %init; + embed .runtime as &runtime; + } + section runtime { + entry %runtime; + data $blob; + } +} +"#; + + let parsed = parse_module(s).unwrap(); + let backend = FakeBackend; + let opts = CompileOptions { + fixup_policy: PushWidthPolicy::Push4, + emit_symtab: true, + }; + + let artifact = compile_object(&parsed.module, &backend, "Contract", &opts).unwrap(); + + let runtime = artifact + .sections + .iter() + .find(|(name, _)| name.0.as_str() == "runtime") + .unwrap() + .1 + .bytes + .clone(); + assert_eq!(runtime.len(), 13); + assert_eq!(runtime[0], 0x63); + assert_eq!(&runtime[1..5], &10u32.to_be_bytes()); + assert_eq!(runtime[5], 0x63); + assert_eq!(&runtime[6..10], &3u32.to_be_bytes()); + assert_eq!(&runtime[10..], &[1, 2, 3]); + + let init = artifact + .sections + .iter() + .find(|(name, _)| name.0.as_str() == "init") + .unwrap() + .1 + .bytes + .clone(); + assert_eq!(init.len(), 23); + assert_eq!(init[0], 0x63); + assert_eq!(&init[1..5], &10u32.to_be_bytes()); + assert_eq!(init[5], 0x63); + assert_eq!(&init[6..10], &13u32.to_be_bytes()); + assert_eq!(&init[10..], runtime.as_slice()); + } + + #[test] + fn emits_functions_in_call_graph_dfs_order() { + let s = r#" +target = "evm-ethereum-london" + +func public %main() { + block0: + call %b; + call %c; + return; +} + +func public %b() { + block0: + return; +} + +func public %c() { + block0: + return; +} + +func public %d() { + block0: + call %e; + return; +} + +func public %e() { + block0: + return; +} + +object @O { + section runtime { + entry %main; + include %c; + include %d; + } +} +"#; + + let parsed = parse_module(s).unwrap(); + let order: Arc>> = Arc::new(Mutex::new(Vec::new())); + + struct RecordingBackend { + order: Arc>>, + } + + impl LowerBackend for RecordingBackend { + type MInst = u8; + type Error = String; + type FixupPolicy = PushWidthPolicy; + + fn lower_function( + &self, + module: &Module, + func: FuncRef, + section_ctx: &SectionLoweringCtx<'_>, + ) -> Result, Self::Error> { + let _ = section_ctx; + let name = module.ctx.func_sig(func, |sig| sig.name().to_string()); + self.order.lock().unwrap().push(name); + Ok(LoweredFunction { + vcode: VCode::default(), + block_order: Vec::new(), + }) + } + + fn apply_sym_fixup( + &self, + vcode: &mut VCode, + inst: VCodeInst, + fixup: &SymFixup, + value: u32, + policy: &Self::FixupPolicy, + ) -> Result { + let _ = (vcode, inst, fixup, value, policy); + Err("unexpected sym fixup in RecordingBackend".to_string()) + } + + fn lower( + &self, + ctx: &mut crate::machinst::lower::Lower, + alloc: &mut dyn crate::stackalloc::Allocator, + inst: sonatina_ir::InstId, + ) { + let _ = (ctx, alloc, inst); + unreachable!("RecordingBackend does not use machinst lowering") + } + + fn enter_function( + &self, + ctx: &mut crate::machinst::lower::Lower, + alloc: &mut dyn crate::stackalloc::Allocator, + function: &sonatina_ir::Function, + ) { + let _ = (ctx, alloc, function); + } + + fn enter_block( + &self, + ctx: &mut crate::machinst::lower::Lower, + alloc: &mut dyn crate::stackalloc::Allocator, + block: sonatina_ir::BlockId, + ) { + let _ = (ctx, alloc, block); + } + + fn update_opcode_with_immediate_bytes( + &self, + opcode: &mut Self::MInst, + bytes: &mut SmallVec<[u8; 8]>, + ) { + debug_assert!(bytes.len() <= 32); + *opcode = 0x5f_u8.saturating_add(bytes.len() as u8); + } + + fn update_opcode_with_label( + &self, + opcode: &mut Self::MInst, + label_offset: u32, + ) -> SmallVec<[u8; 4]> { + let bytes = label_offset + .to_be_bytes() + .into_iter() + .skip_while(|b| *b == 0) + .collect::>(); + debug_assert!(bytes.len() <= 32); + *opcode = 0x5f_u8.saturating_add(bytes.len() as u8); + bytes + } + + fn emit_opcode(&self, opcode: &Self::MInst, buf: &mut Vec) { + buf.push(*opcode); + } + + fn emit_immediate_bytes(&self, bytes: &[u8], buf: &mut Vec) { + buf.extend_from_slice(bytes); + } + + fn emit_label(&self, address: u32, buf: &mut Vec) { + buf.extend(address.to_be_bytes().into_iter().skip_while(|b| *b == 0)); + } + } + + let backend = RecordingBackend { + order: order.clone(), + }; + let opts = CompileOptions { + fixup_policy: PushWidthPolicy::Push4, + emit_symtab: false, + }; + + compile_object(&parsed.module, &backend, "O", &opts).unwrap(); + + let got = order.lock().unwrap().clone(); + assert_eq!(got, vec!["main", "c", "b", "d", "e"]); + } +} diff --git a/crates/codegen/src/object/data.rs b/crates/codegen/src/object/data.rs new file mode 100644 index 00000000..e6d84e17 --- /dev/null +++ b/crates/codegen/src/object/data.rs @@ -0,0 +1,233 @@ +use sonatina_ir::{ + GlobalVariableRef, I256, Immediate, Type, global_variable::GvInitializer, module::ModuleCtx, + types::CompoundType, +}; + +#[derive(Debug, Clone)] +pub enum DataEncodingError { + NotConst, + MissingInitializer, + UnsupportedType(Type), + ShapeMismatch, +} + +pub fn encode_gv_initializer_to_bytes( + ctx: &ModuleCtx, + gv: GlobalVariableRef, +) -> Result, DataEncodingError> { + let gv_data = ctx.with_gv_store(|s| s.gv_data(gv).clone()); + + if !gv_data.is_const { + return Err(DataEncodingError::NotConst); + } + let Some(init) = gv_data.initializer.clone() else { + return Err(DataEncodingError::MissingInitializer); + }; + + let bytes = encode_initializer(ctx, gv_data.ty, &init)?; + let expected_size = encoded_size(ctx, gv_data.ty)?; + if bytes.len() != expected_size { + return Err(DataEncodingError::ShapeMismatch); + } + + Ok(bytes) +} + +fn encode_initializer( + ctx: &ModuleCtx, + ty: Type, + init: &GvInitializer, +) -> Result, DataEncodingError> { + if ty.is_pointer(ctx) { + return Err(DataEncodingError::UnsupportedType(ty)); + } + + if ty.is_integral() { + let GvInitializer::Immediate(imm) = init else { + return Err(DataEncodingError::ShapeMismatch); + }; + + return encode_int(ty, *imm); + } + + match ty.resolve_compound(ctx) { + Some(CompoundType::Array { elem, len }) => { + let GvInitializer::Array(elems) = init else { + return Err(DataEncodingError::ShapeMismatch); + }; + if elems.len() != len { + return Err(DataEncodingError::ShapeMismatch); + } + + let mut bytes = Vec::new(); + for elem_init in elems { + bytes.extend(encode_initializer(ctx, elem, elem_init)?); + } + Ok(bytes) + } + + Some(CompoundType::Struct(s)) => { + if s.packed { + return Err(DataEncodingError::UnsupportedType(ty)); + } + let GvInitializer::Struct(fields) = init else { + return Err(DataEncodingError::ShapeMismatch); + }; + if fields.len() != s.fields.len() { + return Err(DataEncodingError::ShapeMismatch); + } + + let mut bytes = Vec::new(); + for (field_ty, field_init) in s.fields.into_iter().zip(fields) { + bytes.extend(encode_initializer(ctx, field_ty, field_init)?); + } + Ok(bytes) + } + + Some(CompoundType::Ptr(_)) => Err(DataEncodingError::UnsupportedType(ty)), + Some(CompoundType::Func { .. }) => Err(DataEncodingError::UnsupportedType(ty)), + None => Err(DataEncodingError::UnsupportedType(ty)), + } +} + +fn encode_int(ty: Type, imm: Immediate) -> Result, DataEncodingError> { + let size = scalar_byte_width(ty).ok_or(DataEncodingError::UnsupportedType(ty))?; + let bytes = encode_i256_be(imm.as_i256(), size); + Ok(bytes) +} + +fn encode_i256_be(val: I256, width: usize) -> Vec { + debug_assert!(width <= 32); + let tmp = val.to_u256().to_big_endian(); + tmp[32 - width..].to_vec() +} + +fn encoded_size(ctx: &ModuleCtx, ty: Type) -> Result { + if ty.is_pointer(ctx) { + return Err(DataEncodingError::UnsupportedType(ty)); + } + + if let Some(width) = scalar_byte_width(ty) { + return Ok(width); + } + + match ty.resolve_compound(ctx) { + Some(CompoundType::Array { elem, len }) => Ok(encoded_size(ctx, elem)? * len), + + Some(CompoundType::Struct(s)) => { + if s.packed { + return Err(DataEncodingError::UnsupportedType(ty)); + } + let mut size = 0usize; + for &field in &s.fields { + size = size + .checked_add(encoded_size(ctx, field)?) + .ok_or(DataEncodingError::ShapeMismatch)?; + } + Ok(size) + } + + Some(CompoundType::Ptr(_)) => Err(DataEncodingError::UnsupportedType(ty)), + Some(CompoundType::Func { .. }) => Err(DataEncodingError::UnsupportedType(ty)), + None => Err(DataEncodingError::UnsupportedType(ty)), + } +} + +fn scalar_byte_width(ty: Type) -> Option { + Some(match ty { + Type::I1 | Type::I8 => 1, + Type::I16 => 2, + Type::I32 => 4, + Type::I64 => 8, + Type::I128 => 16, + Type::I256 => 32, + _ => return None, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use sonatina_ir::{ + Linkage, + builder::test_util::test_isa, + global_variable::{GlobalVariableData, GvInitializer}, + module::ModuleCtx, + }; + + #[test] + fn encode_i32_big_endian() { + let ctx = ModuleCtx::new(&test_isa()); + let gv = ctx.with_gv_store_mut(|s| { + s.make_gv(GlobalVariableData::constant( + "x".to_string(), + Type::I32, + Linkage::Public, + GvInitializer::make_imm(0x01020304_i32), + )) + }); + + let bytes = encode_gv_initializer_to_bytes(&ctx, gv).unwrap(); + assert_eq!(bytes, vec![0x01, 0x02, 0x03, 0x04]); + } + + #[test] + fn encode_array_concatenation() { + let ctx = ModuleCtx::new(&test_isa()); + let array_ty = ctx.with_ty_store_mut(|s| s.make_array(Type::I8, 4)); + let gv = ctx.with_gv_store_mut(|s| { + s.make_gv(GlobalVariableData::constant( + "arr".to_string(), + array_ty, + Linkage::Public, + GvInitializer::make_array(vec![ + GvInitializer::make_imm(1i8), + GvInitializer::make_imm(2i8), + GvInitializer::make_imm(3i8), + GvInitializer::make_imm(4i8), + ]), + )) + }); + + let bytes = encode_gv_initializer_to_bytes(&ctx, gv).unwrap(); + assert_eq!(bytes, vec![1, 2, 3, 4]); + } + + #[test] + fn encode_struct_concatenation() { + let ctx = ModuleCtx::new(&test_isa()); + let struct_ty = + ctx.with_ty_store_mut(|s| s.make_struct("S", &[Type::I8, Type::I16], false)); + let gv = ctx.with_gv_store_mut(|s| { + s.make_gv(GlobalVariableData::constant( + "s".to_string(), + struct_ty, + Linkage::Public, + GvInitializer::make_struct(vec![ + GvInitializer::make_imm(0x11i8), + GvInitializer::make_imm(0x2233i16), + ]), + )) + }); + + let bytes = encode_gv_initializer_to_bytes(&ctx, gv).unwrap(); + assert_eq!(bytes, vec![0x11, 0x22, 0x33]); + } + + #[test] + fn reject_pointer_types() { + let ctx = ModuleCtx::new(&test_isa()); + let ptr_ty = ctx.with_ty_store_mut(|s| s.make_ptr(Type::I8)); + let gv = ctx.with_gv_store_mut(|s| { + s.make_gv(GlobalVariableData::constant( + "p".to_string(), + ptr_ty, + Linkage::Public, + GvInitializer::make_imm(I256::zero()), + )) + }); + + let err = encode_gv_initializer_to_bytes(&ctx, gv).unwrap_err(); + assert!(matches!(err, DataEncodingError::UnsupportedType(_))); + } +} diff --git a/crates/codegen/src/object/error.rs b/crates/codegen/src/object/error.rs new file mode 100644 index 00000000..3625aae1 --- /dev/null +++ b/crates/codegen/src/object/error.rs @@ -0,0 +1,75 @@ +use sonatina_ir::{ + GlobalVariableRef, + module::FuncRef, + object::{EmbedSymbol, ObjectName, SectionName, SectionRef}, +}; + +#[derive(Debug, Clone)] +pub enum ObjectCompileError { + ObjectNotFound { + object: String, + }, + + DuplicateSectionName { + object: ObjectName, + section: SectionName, + }, + MissingEntry { + object: ObjectName, + section: SectionName, + }, + MultipleEntries { + object: ObjectName, + section: SectionName, + }, + DuplicateEmbedSymbol { + object: ObjectName, + section: SectionName, + symbol: EmbedSymbol, + }, + + InvalidFunctionRef { + object: ObjectName, + section: SectionName, + func: FuncRef, + }, + InvalidGlobalRef { + object: ObjectName, + section: SectionName, + gv: GlobalVariableRef, + }, + UndefinedSectionRef { + object: ObjectName, + section: SectionName, + ref_: SectionRef, + }, + + EmbedCycle { + cycle: Vec<(ObjectName, SectionName)>, + }, + UndefinedEmbedSymbol { + object: ObjectName, + section: SectionName, + symbol: EmbedSymbol, + }, + + InvalidGlobalForData { + object: ObjectName, + section: SectionName, + gv: GlobalVariableRef, + reason: String, + }, + + BackendError { + object: ObjectName, + section: SectionName, + func: FuncRef, + message: String, + }, + + LinkError { + object: ObjectName, + section: SectionName, + message: String, + }, +} diff --git a/crates/codegen/src/object/link.rs b/crates/codegen/src/object/link.rs new file mode 100644 index 00000000..b80e6401 --- /dev/null +++ b/crates/codegen/src/object/link.rs @@ -0,0 +1,172 @@ +use crate::machinst::{ + assemble::ObjectLayout, + lower::{FixupUpdate, LowerBackend, SectionLoweringCtx}, + vcode::{SymFixup, SymFixupKind, VCodeFixup, VCodeInst}, +}; +use rustc_hash::FxHashMap; +use sonatina_ir::{ + GlobalVariableRef, Module, inst::data::SymbolRef, module::FuncRef, object::EmbedSymbol, +}; + +use super::{ + CompileOptions, + artifact::{SectionArtifact, SymbolDef, SymbolId}, +}; + +#[derive(Debug)] +pub enum LinkSectionError { + Backend { func: FuncRef, error: E }, + Link(String), +} + +pub fn link_section( + module: &Module, + backend: &B, + funcs: &[FuncRef], + data: &[(GlobalVariableRef, Vec)], + embeds: &[(EmbedSymbol, Vec)], + section_ctx: &SectionLoweringCtx<'_>, + opts: &CompileOptions, +) -> Result> { + const MAX_ITERS: usize = 64; + + backend.prepare_section(module, funcs, section_ctx); + + let mut sym_fixups: Vec<(FuncRef, VCodeInst, SymFixup)> = Vec::new(); + let mut layout_funcs = Vec::with_capacity(funcs.len()); + + for &func in funcs { + let lowered = backend + .lower_function(module, func, section_ctx) + .map_err(|error| LinkSectionError::Backend { func, error })?; + + for (insn, fixup) in lowered.vcode.fixups.values() { + let VCodeFixup::Sym(fixup) = fixup else { + continue; + }; + sym_fixups.push((func, *insn, fixup.clone())); + } + + layout_funcs.push((func, lowered.vcode, lowered.block_order)); + } + + let mut layout = ObjectLayout::new(layout_funcs, 0); + + for _ in 0..MAX_ITERS { + while layout.resize(backend, 0) {} + + let symtab = + build_section_symtab(&layout, funcs, data, embeds).map_err(LinkSectionError::Link)?; + + let mut layout_changed = false; + for (func, insn, fixup) in &sym_fixups { + let value = fixup_value(&symtab, fixup).map_err(LinkSectionError::Link)?; + + let vcode = layout + .func_vcode_mut(*func) + .ok_or_else(|| LinkSectionError::Link("missing function vcode".to_string()))?; + + let update = backend + .apply_sym_fixup(vcode, *insn, fixup, value, &opts.fixup_policy) + .map_err(|error| LinkSectionError::Backend { func: *func, error })?; + + layout_changed |= update == FixupUpdate::LayoutChanged; + } + + if !layout_changed { + while layout.resize(backend, 0) {} + + let section_size: usize = symtab.values().map(|def| def.size as usize).sum(); + let mut bytes = Vec::with_capacity(section_size); + layout.emit(backend, &mut bytes); + for (_, blob) in data { + bytes.extend_from_slice(blob); + } + for (_, blob) in embeds { + bytes.extend_from_slice(blob); + } + + return Ok(SectionArtifact { + bytes, + symtab: if opts.emit_symtab { + symtab + } else { + FxHashMap::default() + }, + }); + } + } + + Err(LinkSectionError::Link( + "fixup relaxation did not converge".to_string(), + )) +} + +fn build_section_symtab( + layout: &ObjectLayout, + funcs: &[FuncRef], + data: &[(GlobalVariableRef, Vec)], + embeds: &[(EmbedSymbol, Vec)], +) -> Result, String> { + let mut symtab: FxHashMap = FxHashMap::default(); + + let mut code_end: u32 = 0; + for &func in funcs { + let offset = layout.func_offset(func); + let size = layout + .func_size(func) + .ok_or_else(|| "missing function size".to_string())?; + + let end = offset + .checked_add(size) + .ok_or_else(|| "function offset overflow".to_string())?; + code_end = code_end.max(end); + symtab.insert(SymbolId::Func(func), SymbolDef { offset, size }); + } + + let mut cursor = code_end; + for (gv, bytes) in data { + let size: u32 = bytes + .len() + .try_into() + .map_err(|_| "data size overflow".to_string())?; + let offset = cursor; + cursor = cursor + .checked_add(size) + .ok_or_else(|| "data offset overflow".to_string())?; + symtab.insert(SymbolId::Global(*gv), SymbolDef { offset, size }); + } + + for (symbol, bytes) in embeds { + let size: u32 = bytes + .len() + .try_into() + .map_err(|_| "embed size overflow".to_string())?; + let offset = cursor; + cursor = cursor + .checked_add(size) + .ok_or_else(|| "embed offset overflow".to_string())?; + symtab.insert(SymbolId::Embed(symbol.clone()), SymbolDef { offset, size }); + } + + Ok(symtab) +} + +fn fixup_value(symtab: &FxHashMap, fixup: &SymFixup) -> Result { + let sym = symbol_id(&fixup.sym); + let def = symtab + .get(&sym) + .ok_or_else(|| "unknown symbol".to_string())?; + match fixup.kind { + SymFixupKind::Addr => Ok(def.offset), + SymFixupKind::Size => Ok(def.size), + } +} + +fn symbol_id(sym: &SymbolRef) -> SymbolId { + match sym { + SymbolRef::Func(func) => SymbolId::Func(*func), + SymbolRef::Global(gv) => SymbolId::Global(*gv), + SymbolRef::Embed(sym) => SymbolId::Embed(sym.clone()), + } +} diff --git a/crates/codegen/src/object/mod.rs b/crates/codegen/src/object/mod.rs new file mode 100644 index 00000000..6a6daea3 --- /dev/null +++ b/crates/codegen/src/object/mod.rs @@ -0,0 +1,29 @@ +pub mod artifact; +pub mod compile; +pub mod data; +pub mod error; +pub mod link; +pub mod resolve; + +pub use artifact::{ObjectArtifact, SectionArtifact, SymbolDef, SymbolId}; +pub use compile::{compile_all_objects, compile_object}; +pub use data::encode_gv_initializer_to_bytes; +pub use error::ObjectCompileError; +pub use resolve::{ + ObjectId, ResolvedEmbed, ResolvedObject, ResolvedProgram, ResolvedSection, SectionId, +}; + +#[derive(Debug, Clone)] +pub struct CompileOptions

{ + pub fixup_policy: P, + pub emit_symtab: bool, +} + +impl Default for CompileOptions

{ + fn default() -> Self { + Self { + fixup_policy: P::default(), + emit_symtab: true, + } + } +} diff --git a/crates/codegen/src/object/resolve.rs b/crates/codegen/src/object/resolve.rs new file mode 100644 index 00000000..7290b04d --- /dev/null +++ b/crates/codegen/src/object/resolve.rs @@ -0,0 +1,418 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use sonatina_ir::{ + GlobalVariableRef, Module, + module::FuncRef, + object::{Directive, EmbedSymbol, Object, ObjectName, SectionName, SectionRef}, +}; + +use super::error::ObjectCompileError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ObjectId(pub u32); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SectionId { + pub object: ObjectId, + pub section: u32, +} + +pub struct ResolvedProgram<'m> { + pub module: &'m Module, + pub object_index: FxHashMap, + pub objects: Vec, +} + +#[derive(Debug)] +pub struct ResolvedObject { + pub name: ObjectName, + pub sections: Vec, +} + +#[derive(Debug)] +pub struct ResolvedSection { + pub name: SectionName, + pub entry: FuncRef, + pub includes: Vec, + pub data: FxHashSet, + pub embeds: Vec, +} + +#[derive(Debug)] +pub struct ResolvedEmbed { + pub source: SectionId, + pub as_symbol: EmbedSymbol, +} + +#[derive(Debug)] +struct UnvalidatedObject { + name: ObjectName, + sections: Vec, +} + +#[derive(Debug)] +struct UnvalidatedSection { + name: SectionName, + entry: Option, + includes: Vec, + data: FxHashSet, + embeds: Vec, +} + +impl<'m> ResolvedProgram<'m> { + pub fn resolve(module: &'m Module) -> Result> { + let mut errors = Vec::new(); + + let mut raw_objects: Vec<&Object> = module.objects.values().collect(); + raw_objects.sort_by(|a, b| a.name.0.as_str().cmp(b.name.0.as_str())); + + let mut object_index = FxHashMap::default(); + for (idx, obj) in raw_objects.iter().enumerate() { + object_index.insert(obj.name.clone(), ObjectId(idx as u32)); + } + + let mut section_index: Vec> = Vec::new(); + for obj in &raw_objects { + let mut map = FxHashMap::default(); + for (idx, section) in obj.sections.iter().enumerate() { + let is_dup = map.insert(section.name.clone(), idx as u32).is_some(); + if is_dup { + errors.push(ObjectCompileError::DuplicateSectionName { + object: obj.name.clone(), + section: section.name.clone(), + }); + } + } + section_index.push(map); + } + + let mut objects: Vec = Vec::new(); + for obj in &raw_objects { + let object_id = object_index[&obj.name]; + let mut resolved_sections: Vec = Vec::new(); + + for (section_idx, section) in obj.sections.iter().enumerate() { + let section_name = section.name.clone(); + let mut entry: Option = None; + let mut includes = Vec::new(); + let mut include_set = FxHashSet::default(); + let mut data = FxHashSet::default(); + let mut embeds = Vec::new(); + let mut embed_syms = FxHashSet::default(); + + let mut entry_count = 0usize; + + for directive in §ion.directives { + match directive { + Directive::Entry(func) => { + entry_count += 1; + if entry.is_some() { + continue; + } + if module.ctx.declared_funcs.contains_key(func) { + entry = Some(*func); + } else { + errors.push(ObjectCompileError::InvalidFunctionRef { + object: obj.name.clone(), + section: section_name.clone(), + func: *func, + }); + } + } + Directive::Include(func) => { + if !module.ctx.declared_funcs.contains_key(func) { + errors.push(ObjectCompileError::InvalidFunctionRef { + object: obj.name.clone(), + section: section_name.clone(), + func: *func, + }); + } else if include_set.insert(*func) { + includes.push(*func); + } + } + Directive::Data(gv) => { + let is_valid = module.ctx.with_gv_store(|s| (gv.0 as usize) < s.len()); + if !is_valid { + errors.push(ObjectCompileError::InvalidGlobalRef { + object: obj.name.clone(), + section: section_name.clone(), + gv: *gv, + }); + } else { + data.insert(*gv); + } + } + Directive::Embed(embed) => { + if !embed_syms.insert(embed.as_symbol.clone()) { + errors.push(ObjectCompileError::DuplicateEmbedSymbol { + object: obj.name.clone(), + section: section_name.clone(), + symbol: embed.as_symbol.clone(), + }); + } + if let Some(source) = resolve_section_ref( + &object_index, + §ion_index, + object_id, + &embed.source, + obj.name.clone(), + section_name.clone(), + &mut errors, + ) { + embeds.push(ResolvedEmbed { + source, + as_symbol: embed.as_symbol.clone(), + }); + } + } + } + } + + if entry_count == 0 { + errors.push(ObjectCompileError::MissingEntry { + object: obj.name.clone(), + section: section_name.clone(), + }); + } else if entry_count > 1 { + errors.push(ObjectCompileError::MultipleEntries { + object: obj.name.clone(), + section: section_name.clone(), + }); + } + + resolved_sections.push(UnvalidatedSection { + name: section_name, + entry, + includes, + data, + embeds, + }); + debug_assert_eq!(section_idx, resolved_sections.len() - 1); + } + + objects.push(UnvalidatedObject { + name: obj.name.clone(), + sections: resolved_sections, + }); + } + + if !errors.is_empty() { + return Err(errors); + } + + let objects: Vec = objects + .into_iter() + .map(|obj| ResolvedObject { + name: obj.name, + sections: obj + .sections + .into_iter() + .map(|section| ResolvedSection { + name: section.name, + entry: section + .entry + .expect("entry validated in ResolvedProgram::resolve"), + includes: section.includes, + data: section.data, + embeds: section.embeds, + }) + .collect(), + }) + .collect(); + + let program = Self { + module, + object_index, + objects, + }; + + if let Some(cycle) = detect_embed_cycle(&program) { + let mut pretty = Vec::new(); + for id in cycle { + let (obj, section) = program.section_name(id); + pretty.push((obj.clone(), section.clone())); + } + return Err(vec![ObjectCompileError::EmbedCycle { cycle: pretty }]); + } + + Ok(program) + } + + pub fn object(&self, id: ObjectId) -> &ResolvedObject { + &self.objects[id.0 as usize] + } + + pub fn section(&self, id: SectionId) -> &ResolvedSection { + &self.objects[id.object.0 as usize].sections[id.section as usize] + } + + pub fn section_name(&self, id: SectionId) -> (&ObjectName, &SectionName) { + let obj = &self.objects[id.object.0 as usize]; + let section = &obj.sections[id.section as usize]; + (&obj.name, §ion.name) + } + + pub fn all_sections(&self) -> Vec { + let mut ids = Vec::new(); + for obj in &self.objects { + let object = self.object_index[&obj.name]; + for (idx, _) in obj.sections.iter().enumerate() { + ids.push(SectionId { + object, + section: idx as u32, + }); + } + } + ids + } +} + +fn resolve_section_ref( + object_index: &FxHashMap, + section_index: &[FxHashMap], + this_object: ObjectId, + ref_: &SectionRef, + object: ObjectName, + section: SectionName, + errors: &mut Vec, +) -> Option { + let (target_object, target_section_name) = match ref_ { + SectionRef::Local(name) => (this_object, name.clone()), + SectionRef::External { + object: obj, + section: name, + } => { + let Some(object_id) = object_index.get(obj).copied() else { + errors.push(ObjectCompileError::UndefinedSectionRef { + object, + section, + ref_: ref_.clone(), + }); + return None; + }; + (object_id, name.clone()) + } + }; + + let section_map = §ion_index[target_object.0 as usize]; + let Some(section_idx) = section_map.get(&target_section_name).copied() else { + errors.push(ObjectCompileError::UndefinedSectionRef { + object, + section, + ref_: ref_.clone(), + }); + return None; + }; + + Some(SectionId { + object: target_object, + section: section_idx, + }) +} + +fn detect_embed_cycle(program: &ResolvedProgram<'_>) -> Option> { + let roots = program.all_sections(); + + #[derive(Clone, Copy, PartialEq, Eq)] + enum State { + Visiting, + Done, + } + + let mut state: FxHashMap = FxHashMap::default(); + let mut stack: Vec = Vec::new(); + + fn dfs( + program: &ResolvedProgram<'_>, + node: SectionId, + state: &mut FxHashMap, + stack: &mut Vec, + ) -> Option> { + match state.get(&node) { + Some(State::Done) => return None, + Some(State::Visiting) => { + let start = stack.iter().position(|id| *id == node).unwrap_or(0); + return Some(stack[start..].to_vec()); + } + None => {} + } + + state.insert(node, State::Visiting); + stack.push(node); + + let deps = program.section(node).embeds.iter().map(|e| e.source); + for dep in deps { + if let Some(cycle) = dfs(program, dep, state, stack) { + return Some(cycle); + } + } + + stack.pop(); + state.insert(node, State::Done); + None + } + + for root in roots { + if let Some(cycle) = dfs(program, root, &mut state, &mut stack) { + return Some(cycle); + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + use sonatina_parser::parse_module; + + #[test] + fn reject_embed_cycles() { + let s = r#" +target = "evm-ethereum-london" + +func public %main() { + block0: + return; +} + +object @A { + section init { entry %main; embed .runtime as &rt; } + section runtime { entry %main; embed .init as &init; } +} +"#; + + let parsed = parse_module(s).unwrap(); + let err = ResolvedProgram::resolve(&parsed.module).err().unwrap(); + assert!( + err.iter() + .any(|e| matches!(e, ObjectCompileError::EmbedCycle { .. })) + ); + } + + #[test] + fn reject_duplicate_embed_symbols() { + let s = r#" +target = "evm-ethereum-london" + +func public %main() { + block0: + return; +} + +object @A { + section init { + entry %main; + embed .init as &x; + embed .init as &x; + } +} +"#; + + let parsed = parse_module(s).unwrap(); + let err = ResolvedProgram::resolve(&parsed.module).err().unwrap(); + assert!( + err.iter() + .any(|e| matches!(e, ObjectCompileError::DuplicateEmbedSymbol { .. })) + ); + } +} diff --git a/crates/codegen/src/optim/adce.rs b/crates/codegen/src/optim/adce.rs index 5060aa86..d57f5d1c 100644 --- a/crates/codegen/src/optim/adce.rs +++ b/crates/codegen/src/optim/adce.rs @@ -196,7 +196,7 @@ impl AdceSolver { }; inserter.set_location(CursorLocation::At(last_inst)); - let dests: Vec<_> = func + let dests = func .dfg .branch_info(last_inst) .map(|bi| bi.dests()) diff --git a/crates/codegen/src/stackalloc/mod.rs b/crates/codegen/src/stackalloc/mod.rs new file mode 100644 index 00000000..ad5a38e2 --- /dev/null +++ b/crates/codegen/src/stackalloc/mod.rs @@ -0,0 +1,48 @@ +use smallvec::SmallVec; +use sonatina_ir::{BlockId, Function, Immediate, InstId, ValueId}; + +mod stackify; +pub use stackify::{StackifyAlloc, StackifyLiveValues}; + +pub type Actions = SmallVec<[Action; 2]>; + +pub trait Allocator { + fn enter_function(&self, function: &Function) -> Actions; + + /// Returns the number of 32-byte frame slots used by this function for + /// spilling/reloading values. + fn frame_size_slots(&self) -> u32 { + 0 + } + + // xxx rename these to make it clear that these are pre- and post-insn operations + /// Return the actions required to place `vals` on the stack, + /// in the specified order. I.e. the first `Value` in `vals` + /// will be on the top of the stack. + fn read(&self, inst: InstId, vals: &[ValueId]) -> Actions; + fn write(&self, inst: InstId, val: Option) -> Actions; + + fn traverse_edge(&self, from: BlockId, to: BlockId) -> Actions; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Action { + StackDup(u8), + StackSwap(u8), + Push(Immediate), + /// For CALL: Push code offset that callee should jump to upon return + PushContinuationOffset, + Pop, + MemLoadAbs(u32), + /// Relative to `LowerBackend`-defined frame pointer + MemLoadFrameSlot(u32), + MemStoreAbs(u32), + MemStoreFrameSlot(u32), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum SpillSlotRef { + Persistent(u32), + Transient(u32), + Scratch(u32), +} diff --git a/crates/codegen/src/stackalloc/stackify/alloc.rs b/crates/codegen/src/stackalloc/stackify/alloc.rs new file mode 100644 index 00000000..18170728 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/alloc.rs @@ -0,0 +1,196 @@ +use cranelift_entity::SecondaryMap; +use sonatina_ir::{BlockId, Function, InstId, ValueId, cfg::ControlFlowGraph}; +use std::collections::BTreeMap; + +use crate::{ + bitset::BitSet, + domtree::DomTree, + liveness::Liveness, + stackalloc::{Action, Actions, Allocator, SpillSlotRef}, +}; + +use super::{builder::StackifyBuilder, trace::StackifyTrace}; + +pub struct StackifyLiveValues { + /// Values to pre-seed into the spill set so block templates (transfer regions) do not + /// attempt to carry them through internal calls. + pub values_live_across_calls: BitSet, + /// Values that must be stored in persistent frame slots (not transient/scratch) so their + /// memory homes remain valid across internal calls that may reuse/clobber transient storage. + pub values_persistent_across_calls: BitSet, + pub scratch_live_values: BitSet, +} + +#[derive(Default)] +pub struct StackifyAlloc { + pub(super) pre_actions: SecondaryMap, + pub(super) post_actions: SecondaryMap, + + /// br_table lowering uses per-case action sequences keyed by (scrutinee, case_val). + pub(super) brtable_actions: BTreeMap<(InstId, ValueId, ValueId), Actions>, + + /// Value -> frame slot index (32-byte slots). + /// + /// Slots are allocated deterministically and may be shared by multiple values as long as + /// their lifetimes do not overlap (currently: within-block reuse based on last-use tracking). + pub(super) slot_of_value: SecondaryMap>, + pub(crate) persistent_frame_slots: u32, + pub(crate) transient_frame_slots: u32, +} + +impl StackifyAlloc { + /// Compute stack allocation for a single function using the transfer-region stackify spec. + pub fn for_function( + func: &Function, + cfg: &ControlFlowGraph, + dom: &DomTree, + liveness: &Liveness, + reach_depth: u8, + ) -> Self { + let builder = StackifyBuilder::new(func, cfg, dom, liveness, reach_depth); + builder.compute() + } + + pub fn for_function_with_values_live_across_calls( + func: &Function, + cfg: &ControlFlowGraph, + dom: &DomTree, + liveness: &Liveness, + reach_depth: u8, + values_live_across_calls: BitSet, + ) -> Self { + let builder = StackifyBuilder::new(func, cfg, dom, liveness, reach_depth) + .with_values_live_across_calls(values_live_across_calls); + builder.compute() + } + + pub fn for_function_with_values_live_across_calls_and_scratch_spills( + func: &Function, + cfg: &ControlFlowGraph, + dom: &DomTree, + liveness: &Liveness, + reach_depth: u8, + live_values: StackifyLiveValues, + scratch_spill_slots: u32, + ) -> Self { + let StackifyLiveValues { + values_live_across_calls, + values_persistent_across_calls, + scratch_live_values, + } = live_values; + let builder = StackifyBuilder::new(func, cfg, dom, liveness, reach_depth) + .with_values_live_across_calls(values_live_across_calls) + .with_values_persistent_across_calls(values_persistent_across_calls) + .with_scratch_live_values(scratch_live_values) + .with_scratch_spills(scratch_spill_slots); + builder.compute() + } + + /// Compute stack allocation for a single function and return a human-oriented trace of the + /// planning decisions made during the final fixed-point iteration. + pub fn for_function_with_trace( + func: &Function, + cfg: &ControlFlowGraph, + dom: &DomTree, + liveness: &Liveness, + reach_depth: u8, + ) -> (Self, String) { + let builder = StackifyBuilder::new(func, cfg, dom, liveness, reach_depth); + let mut trace = StackifyTrace::default(); + let alloc = builder.compute_with_observer(&mut trace); + let trace = trace.render(func, &alloc); + (alloc, trace) + } + + pub fn for_function_with_trace_and_values_live_across_calls( + func: &Function, + cfg: &ControlFlowGraph, + dom: &DomTree, + liveness: &Liveness, + reach_depth: u8, + values_live_across_calls: BitSet, + ) -> (Self, String) { + let builder = StackifyBuilder::new(func, cfg, dom, liveness, reach_depth) + .with_values_live_across_calls(values_live_across_calls); + let mut trace = StackifyTrace::default(); + let alloc = builder.compute_with_observer(&mut trace); + let trace = trace.render(func, &alloc); + (alloc, trace) + } + + pub fn for_function_with_trace_values_live_across_calls_and_scratch_spills( + func: &Function, + cfg: &ControlFlowGraph, + dom: &DomTree, + liveness: &Liveness, + reach_depth: u8, + live_values: StackifyLiveValues, + scratch_spill_slots: u32, + ) -> (Self, String) { + let StackifyLiveValues { + values_live_across_calls, + values_persistent_across_calls, + scratch_live_values, + } = live_values; + let builder = StackifyBuilder::new(func, cfg, dom, liveness, reach_depth) + .with_values_live_across_calls(values_live_across_calls) + .with_values_persistent_across_calls(values_persistent_across_calls) + .with_scratch_live_values(scratch_live_values) + .with_scratch_spills(scratch_spill_slots); + let mut trace = StackifyTrace::default(); + let alloc = builder.compute_with_observer(&mut trace); + let trace = trace.render(func, &alloc); + (alloc, trace) + } + + pub(crate) fn uses_scratch_spills(&self) -> bool { + self.slot_of_value + .values() + .any(|slot| matches!(*slot, Some(SpillSlotRef::Scratch(_)))) + } +} + +impl Allocator for StackifyAlloc { + fn enter_function(&self, function: &Function) -> Actions { + let mut act = Actions::new(); + for (idx, &arg) in function.arg_values.iter().enumerate() { + let Some(slot) = self.slot_of_value[arg] else { + continue; + }; + debug_assert!( + idx < super::DUP_MAX, + "function arg depth exceeds DUP16 reach" + ); + act.push(Action::StackDup(idx as u8)); + match slot { + SpillSlotRef::Persistent(slot) => act.push(Action::MemStoreFrameSlot(slot)), + SpillSlotRef::Transient(slot) => act.push(Action::MemStoreFrameSlot( + self.persistent_frame_slots + slot, + )), + SpillSlotRef::Scratch(slot) => act.push(Action::MemStoreAbs(slot * 32)), + } + } + act + } + + fn frame_size_slots(&self) -> u32 { + self.persistent_frame_slots + self.transient_frame_slots + } + + fn read(&self, inst: InstId, vals: &[ValueId]) -> Actions { + if let [scrutinee, case_val] = vals + && let Some(act) = self.brtable_actions.get(&(inst, *scrutinee, *case_val)) + { + return act.clone(); + } + self.pre_actions[inst].clone() + } + + fn write(&self, inst: InstId, _val: Option) -> Actions { + self.post_actions[inst].clone() + } + + fn traverse_edge(&self, _from: BlockId, _to: BlockId) -> Actions { + Actions::new() + } +} diff --git a/crates/codegen/src/stackalloc/stackify/builder.rs b/crates/codegen/src/stackalloc/stackify/builder.rs new file mode 100644 index 00000000..03d0949d --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/builder.rs @@ -0,0 +1,380 @@ +use cranelift_entity::SecondaryMap; +use smallvec::SmallVec; +use sonatina_ir::{BlockId, Function, ValueId, cfg::ControlFlowGraph}; +use std::collections::BTreeMap; + +use crate::{ + bitset::BitSet, + cfg_scc::CfgSccAnalysis, + domtree::DomTree, + liveness::{InstLiveness, Liveness}, + stackalloc::SpillSlotRef, +}; + +use super::{ + alloc::StackifyAlloc, + flow_templates::solve_templates_from_flow, + iteration::IterationPlanner, + slots::{FreeSlotPools, SpillSlotPools, TRANSIENT_SLOT_TAG}, + spill::SpillSet, + sym_stack::SymStack, + templates::{ + DefInfo, compute_def_info, compute_dom_depth, compute_phi_out_sources, compute_phi_results, + function_has_internal_return, + }, + trace::{NullObserver, StackifyObserver}, +}; + +#[derive(Clone, Copy, Debug)] +pub(super) struct StackifyReachability { + pub(super) dup_max: usize, + pub(super) swap_max: usize, +} + +impl StackifyReachability { + pub(super) fn new(reach_depth: u8) -> Self { + assert!( + (1..=super::DUP_MAX as u8).contains(&reach_depth), + "stackify reach_depth must be in 1..={}", + super::DUP_MAX + ); + + let dup_max = reach_depth as usize; + let swap_max = (dup_max + 1).min(super::SWAP_MAX); + + Self { dup_max, swap_max } + } +} + +pub(super) struct StackifyBuilder<'a> { + func: &'a Function, + cfg: &'a ControlFlowGraph, + dom: &'a DomTree, + liveness: &'a Liveness, + reach: StackifyReachability, + values_live_across_calls_override: Option>, + values_persistent_across_calls_override: Option>, + scratch_live_values_override: Option>, + scratch_spill_slots: u32, +} + +pub(super) struct StackifyContext<'a> { + pub(super) func: &'a Function, + pub(super) cfg: &'a ControlFlowGraph, + pub(super) dom: &'a DomTree, + pub(super) liveness: &'a Liveness, + pub(super) values_live_across_calls: BitSet, + pub(super) values_persistent_across_calls: BitSet, + pub(super) scratch_live_values: BitSet, + pub(super) scratch_spill_slots: u32, + pub(super) entry: BlockId, + pub(super) scc: CfgSccAnalysis, + pub(super) dom_depth: SecondaryMap, + pub(super) def_info: SecondaryMap>, + pub(super) phi_results: SecondaryMap>, + pub(super) phi_out_sources: SecondaryMap>, + pub(super) has_internal_return: bool, + pub(super) reach: StackifyReachability, +} + +impl<'a> StackifyBuilder<'a> { + pub(super) fn new( + func: &'a Function, + cfg: &'a ControlFlowGraph, + dom: &'a DomTree, + liveness: &'a Liveness, + reach_depth: u8, + ) -> Self { + Self { + func, + cfg, + dom, + liveness, + reach: StackifyReachability::new(reach_depth), + values_live_across_calls_override: None, + values_persistent_across_calls_override: None, + scratch_live_values_override: None, + scratch_spill_slots: 0, + } + } + + pub(super) fn with_values_live_across_calls( + mut self, + values_live_across_calls: BitSet, + ) -> Self { + self.values_live_across_calls_override = Some(values_live_across_calls); + self + } + + pub(super) fn with_values_persistent_across_calls( + mut self, + values_persistent_across_calls: BitSet, + ) -> Self { + self.values_persistent_across_calls_override = Some(values_persistent_across_calls); + self + } + + pub(super) fn with_scratch_live_values(mut self, scratch_live_values: BitSet) -> Self { + self.scratch_live_values_override = Some(scratch_live_values); + self + } + + pub(super) fn with_scratch_spills(mut self, scratch_spill_slots: u32) -> Self { + self.scratch_spill_slots = scratch_spill_slots; + self + } + + pub(super) fn compute(self) -> StackifyAlloc { + let mut observer = NullObserver; + self.compute_with_observer(&mut observer) + } + + pub(super) fn compute_with_observer( + self, + observer: &mut O, + ) -> StackifyAlloc { + let entry = match self.cfg.entry() { + Some(b) => b, + None => return StackifyAlloc::default(), + }; + + let mut scc = CfgSccAnalysis::new(); + scc.compute(self.cfg); + + let values_live_across_calls = + if let Some(values) = self.values_live_across_calls_override { + values + } else { + let mut inst_liveness = InstLiveness::default(); + inst_liveness.compute(self.func, self.cfg, self.liveness); + inst_liveness.call_live_values(self.func) + }; + + let values_persistent_across_calls = if let Some(values) = + self.values_persistent_across_calls_override + { + values + } else { + values_live_across_calls.clone() + }; + + let scratch_live_values = if self.scratch_spill_slots == 0 { + BitSet::default() + } else if let Some(scratch_live_values) = self.scratch_live_values_override { + scratch_live_values + } else { + let mut scratch_live_values = BitSet::default(); + for value in self.func.dfg.values.keys() { + scratch_live_values.insert(value); + } + scratch_live_values + }; + + let ctx = StackifyContext { + func: self.func, + cfg: self.cfg, + dom: self.dom, + liveness: self.liveness, + values_live_across_calls, + values_persistent_across_calls, + scratch_live_values, + scratch_spill_slots: self.scratch_spill_slots, + entry, + scc, + dom_depth: compute_dom_depth(self.dom, entry), + def_info: compute_def_info(self.func, entry), + phi_results: compute_phi_results(self.func), + phi_out_sources: compute_phi_out_sources(self.func, self.cfg), + // Internal-return functions expect a caller-provided return address below their args. + has_internal_return: function_has_internal_return(self.func), + reach: self.reach, + }; + + // `spill_set` is discovered via a monotone fixed point: + // - planning may demand a `MemLoadFrameSlot(v)` when `v` is unreachable by `DUP16` + // - or when a flush/rebuild needs to reconstruct a stack template + // In that case we add `v` to `spill_requests`, discard this iteration's plan, and retry. + // + // Once `v ∈ spill_set`, we emit a dominating store at its definition (or phi entry) and + // remove it from transfer regions (`T(B)` excludes `spill_set`), so future iterations + // can rely on loads being correct. + // + // Seed with values live across calls to manage stack depth. During call preparation, + // the stack holds transfer_values + CallRetAddr + args. If call-live values remain in + // the transfer region, this can exceed DUP16/SWAP16 reach (16-17 items), making values + // unreachable for the planner. Spilling them to memory keeps stack depth manageable. + let mut spill_set: BitSet = ctx.values_live_across_calls.clone(); + let mut slots: SpillSlotPools = SpillSlotPools::default(); + + loop { + let checkpoint = observer.checkpoint(); + + let (mut alloc, spill_requests) = + Self::plan_iteration(&ctx, observer, SpillSet::new(&spill_set), &mut slots); + + if spill_requests.is_subset(&spill_set) { + let persistent_frame_slots = slots.persistent.frame_size_slots(); + let transient_frame_slots = slots.transient.frame_size_slots(); + + lower_encoded_frame_slots(&mut alloc, persistent_frame_slots); + + alloc.persistent_frame_slots = persistent_frame_slots; + alloc.transient_frame_slots = transient_frame_slots; + + alloc.slot_of_value = merge_slot_maps( + slots.persistent.take_slot_map(), + slots.transient.take_slot_map(), + slots.scratch.take_slot_map(), + ); + return alloc; + } + + observer.rollback(checkpoint); + spill_set.union_with(&spill_requests); + } + } + + fn plan_iteration( + ctx: &StackifyContext<'_>, + observer: &mut O, + spill: SpillSet<'_>, + slots: &mut SpillSlotPools, + ) -> (StackifyAlloc, BitSet) { + // Function arguments that are in `spill_set` must have a stable slot from function entry. + // We allocate these up-front (fresh; no reuse possible before entry). + let mut arg_free_slots: FreeSlotPools = FreeSlotPools::default(); + for &arg in ctx.func.arg_values.iter() { + if let Some(spilled) = spill.spilled(arg) { + if ctx.values_persistent_across_calls.contains(arg) { + let _ = slots.persistent.ensure_slot( + spilled, + ctx.liveness, + &mut arg_free_slots.persistent, + ); + continue; + } + + if ctx.scratch_spill_slots != 0 + && !ctx.scratch_live_values.contains(arg) + && slots + .scratch + .try_ensure_slot( + spilled, + ctx.liveness, + &mut arg_free_slots.scratch, + Some(ctx.scratch_spill_slots), + ) + .is_some() + { + continue; + } + + let _ = slots.transient.ensure_slot( + spilled, + ctx.liveness, + &mut arg_free_slots.transient, + ); + } + } + + // Template solving may encounter temporary unreachable values while iterating toward a + // fixed point, but those requests are not necessarily required under the final chosen + // templates. Treat spill discovery as the responsibility of the final planning pass. + let mut solver_spill_requests: BitSet = BitSet::default(); + let templates = solve_templates_from_flow(ctx, spill, &mut solver_spill_requests); + + let mut alloc = StackifyAlloc { + pre_actions: SecondaryMap::new(), + post_actions: SecondaryMap::new(), + brtable_actions: BTreeMap::new(), + slot_of_value: SecondaryMap::new(), + persistent_frame_slots: 0, + transient_frame_slots: 0, + }; + + let mut spill_requests: BitSet = BitSet::default(); + + // Blocks that are reached from multi-way branches inherit a dynamic stack and + // run an entry normalization prologue (single-pred only; critical edges split). + let mut inherited_stack: BTreeMap = BTreeMap::new(); + inherited_stack.insert( + ctx.entry, + ( + ctx.entry, + SymStack::entry_stack(ctx.func, ctx.has_internal_return), + ), + ); + + let mut planner = IterationPlanner::new( + ctx, + spill, + slots, + &templates, + &mut alloc, + &mut spill_requests, + inherited_stack, + observer, + ); + planner.plan_blocks(); + + (alloc, spill_requests) + } +} + +fn merge_slot_maps( + persistent: SecondaryMap>, + transient: SecondaryMap>, + scratch: SecondaryMap>, +) -> SecondaryMap> { + let mut out: SecondaryMap> = SecondaryMap::new(); + + for (v, slot) in persistent.iter() { + if let Some(slot) = *slot { + debug_assert!(out[v].is_none(), "spill slot already assigned"); + out[v] = Some(SpillSlotRef::Persistent(slot)); + } + } + for (v, slot) in transient.iter() { + if let Some(slot) = *slot { + debug_assert!(out[v].is_none(), "spill slot already assigned"); + out[v] = Some(SpillSlotRef::Transient(slot)); + } + } + for (v, slot) in scratch.iter() { + if let Some(slot) = *slot { + debug_assert!(out[v].is_none(), "spill slot already assigned"); + out[v] = Some(SpillSlotRef::Scratch(slot)); + } + } + + out +} + +fn lower_encoded_frame_slots(alloc: &mut StackifyAlloc, persistent_frame_slots: u32) { + fn lower_actions(actions: &mut crate::stackalloc::Actions, persistent_frame_slots: u32) { + for action in actions.iter_mut() { + match action { + crate::stackalloc::Action::MemLoadFrameSlot(slot) + | crate::stackalloc::Action::MemStoreFrameSlot(slot) => { + if *slot & TRANSIENT_SLOT_TAG != 0 { + let local = *slot & !TRANSIENT_SLOT_TAG; + *slot = persistent_frame_slots + .checked_add(local) + .expect("frame slot offset overflow"); + } + } + _ => {} + } + } + } + + for (_, actions) in alloc.pre_actions.iter_mut() { + lower_actions(actions, persistent_frame_slots); + } + for (_, actions) in alloc.post_actions.iter_mut() { + lower_actions(actions, persistent_frame_slots); + } + for actions in alloc.brtable_actions.values_mut() { + lower_actions(actions, persistent_frame_slots); + } +} diff --git a/crates/codegen/src/stackalloc/stackify/flow_templates.rs b/crates/codegen/src/stackalloc/stackify/flow_templates.rs new file mode 100644 index 00000000..4c25545e --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/flow_templates.rs @@ -0,0 +1,582 @@ +use std::{cmp::Ordering, collections::BTreeMap}; + +use cranelift_entity::SecondaryMap; +use smallvec::SmallVec; +use sonatina_ir::{BlockId, ValueId, inst::control_flow::BranchKind}; + +use crate::{ + bitset::BitSet, + cfg_scc::SccId, + stackalloc::{Actions, stackify::planner::MemPlan}, +}; + +use super::{ + builder::StackifyContext, + iteration::{ + clean_dead_stack_prefix, count_block_uses, improve_reachability_before_operands, + last_use_values_in_inst, operand_order_for_evm, + }, + planner::{self, Planner}, + slots::{FreeSlotPools, SpillSlotPools}, + spill::SpillSet, + sym_stack::{StackItem, SymStack}, + templates::{BlockTemplate, canonical_transfer_order, live_in_non_params}, +}; + +type TransferOrder = SmallVec<[ValueId; 8]>; +type EdgeCandMap = BTreeMap<(BlockId, BlockId), TransferOrder>; + +#[derive(Clone, Debug, Default)] +struct LayoutState { + transfer: TransferOrder, + pinned: bool, +} + +struct FlowTemplateSolver<'a, 'ctx> { + ctx: &'a StackifyContext<'ctx>, + spill: SpillSet<'a>, + slots: &'a mut SpillSlotPools, + spill_requests: &'a mut BitSet, + params_map: &'a SecondaryMap>, + carry_in: &'a SecondaryMap>, + state: &'a mut SecondaryMap, + edge_cand: &'a mut EdgeCandMap, +} + +pub(super) fn solve_templates_from_flow( + ctx: &StackifyContext<'_>, + spill: SpillSet<'_>, + spill_requests: &mut BitSet, +) -> SecondaryMap { + let mut params_map: SecondaryMap> = SecondaryMap::new(); + let mut carry_in: SecondaryMap> = SecondaryMap::new(); + let mut state: SecondaryMap = SecondaryMap::new(); + + for block in ctx.func.layout.iter_block() { + let mut params = SmallVec::<[ValueId; 4]>::new(); + if block == ctx.entry { + params.extend(ctx.func.arg_values.iter().copied()); + } + params.extend(ctx.phi_results[block].iter().copied()); + params_map[block] = params; + + let carry = live_in_non_params( + ctx.liveness, + ctx.func, + block, + ctx.entry, + &ctx.phi_results, + spill, + ); + let transfer = canonical_transfer_order(&carry, &ctx.dom_depth, &ctx.def_info); + carry_in[block] = carry; + state[block] = LayoutState { + transfer, + pinned: false, + }; + } + + let mut edge_cand: EdgeCandMap = EdgeCandMap::new(); + + let mut scratch_slots = SpillSlotPools::default(); + + let mut solver = FlowTemplateSolver { + ctx, + spill: SpillSet::new(spill.bitset()), + slots: &mut scratch_slots, + spill_requests, + params_map: ¶ms_map, + carry_in: &carry_in, + state: &mut state, + edge_cand: &mut edge_cand, + }; + + for &scc in ctx.scc.topo_order() { + solver.solve_scc(scc); + } + + let mut templates: SecondaryMap = SecondaryMap::new(); + for block in ctx.func.layout.iter_block() { + let params = params_map[block].clone(); + let transfer = if ctx.scc.is_reachable(block) { + state[block].transfer.clone() + } else { + canonical_transfer_order(&carry_in[block], &ctx.dom_depth, &ctx.def_info) + }; + templates[block] = BlockTemplate { params, transfer }; + } + + templates +} + +fn choose_transfer( + ctx: &StackifyContext<'_>, + block: BlockId, + candidates: &[(BlockId, &TransferOrder)], +) -> TransferOrder { + debug_assert!(!candidates.is_empty()); + + let first = candidates[0].1; + if candidates.iter().all(|(_, cand)| *cand == first) { + return first.clone(); + } + + if let Some((_pred, cand)) = candidates + .iter() + .filter(|(pred, _)| ctx.dom.dominates(block, *pred)) + .min_by_key(|(pred, _)| pred.as_u32()) + { + return (*cand).clone(); + } + + candidates + .iter() + .min_by(|(a_pred, a), (b_pred, b)| { + lex_cmp(a, b).then_with(|| a_pred.as_u32().cmp(&b_pred.as_u32())) + }) + .map(|(_, cand)| (*cand).clone()) + .unwrap_or_default() +} + +fn lex_cmp(a: &[ValueId], b: &[ValueId]) -> Ordering { + a.iter() + .map(|v| v.as_u32()) + .cmp(b.iter().map(|v| v.as_u32())) +} + +fn project_transfer(stack: &SymStack, carry_in: &BitSet) -> TransferOrder { + let mut out = TransferOrder::new(); + let mut seen: BitSet = BitSet::default(); + + let limit = stack.len_above_func_ret(); + for i in 0..limit { + let Some(StackItem::Value(v)) = stack.item_at(i) else { + continue; + }; + if carry_in.contains(*v) && seen.insert(*v) { + out.push(*v); + } + } + + out +} + +fn with_planner<'a, 'ctx, F>( + ctx: &'a StackifyContext<'ctx>, + mem: MemPlan<'a>, + stack: &'a mut SymStack, + actions: &'a mut Actions, + f: F, +) where + F: FnOnce(&mut Planner<'a, 'ctx>), +{ + let mut planner = Planner::new(ctx, stack, actions, mem); + f(&mut planner); +} + +impl<'a, 'ctx> FlowTemplateSolver<'a, 'ctx> { + fn template_for(&self, block: BlockId) -> BlockTemplate { + BlockTemplate { + params: self.params_map[block].clone(), + transfer: (&*self.state)[block].transfer.clone(), + } + } + + fn simulate_block_and_record_edges(&mut self, block: BlockId, template: &BlockTemplate) { + let ctx = self.ctx; + let spill = self.spill; + let slots = &mut *self.slots; + let spill_requests = &mut *self.spill_requests; + let carry_in = self.carry_in; + let edge_cand = &mut *self.edge_cand; + + let mut free_slots: FreeSlotPools = FreeSlotPools::default(); + let mut actions: Actions = Actions::new(); + + let (mut remaining_uses, mut live_future) = count_block_uses(ctx.func, block); + + let mut live_out = ctx.liveness.block_live_outs(block).clone(); + live_out.union_with(&ctx.phi_out_sources[block]); + + let mut stack = SymStack::from_template(template, ctx.has_internal_return); + + let empty_last_use: BitSet = BitSet::default(); + + for inst in ctx.func.layout.iter_inst(block) { + if ctx.func.dfg.is_phi(inst) { + continue; + } + + actions.clear(); + + let is_call = ctx.func.dfg.is_call(inst); + let is_normal = + ctx.func.dfg.branch_info(inst).is_none() && !ctx.func.dfg.is_return(inst); + + let mut args = SmallVec::<[ValueId; 8]>::new(); + let mut consume_last_use: BitSet = BitSet::default(); + if is_normal { + args = operand_order_for_evm(ctx.func, inst); + consume_last_use = + last_use_values_in_inst(ctx.func, &args, &remaining_uses, &live_out); + } + let last_use = if is_normal { + &consume_last_use + } else { + &empty_last_use + }; + + clean_dead_stack_prefix(ctx.reach, &mut stack, &live_future, &live_out, &mut actions); + + if is_normal { + improve_reachability_before_operands( + ctx.func, + &args, + ctx.reach, + &mut stack, + &live_future, + &live_out, + &mut actions, + ); + } + + if is_call { + stack.push_call_continuation(&mut actions); + } + + if let Some(branch) = ctx.func.dfg.branch_info(inst) { + match branch.branch_kind() { + BranchKind::Jump(jump) => { + let dest = *jump.dest(); + edge_cand.insert((block, dest), project_transfer(&stack, &carry_in[dest])); + return; + } + BranchKind::Br(br) => { + let cond = *br.cond(); + let consume_last_use = + last_use_values_in_inst(ctx.func, &[cond], &remaining_uses, &live_out); + + improve_reachability_before_operands( + ctx.func, + &[cond], + ctx.reach, + &mut stack, + &live_future, + &live_out, + &mut actions, + ); + + let mem = planner::MemPlan::new( + spill, + spill_requests, + ctx, + &mut free_slots, + slots, + ); + with_planner(ctx, mem, &mut stack, &mut actions, |planner| { + planner.prepare_operands(&[cond], &consume_last_use) + }); + + // The backend consumes the condition value for the actual branch. + let mut post_branch_stack = stack.clone(); + post_branch_stack.pop_operand(); + + for succ in branch.dests().iter().copied() { + edge_cand.insert( + (block, succ), + project_transfer(&post_branch_stack, &carry_in[succ]), + ); + } + return; + } + BranchKind::BrTable(table) => { + let scrutinee = *table.scrutinee(); + + improve_reachability_before_operands( + ctx.func, + &[scrutinee], + ctx.reach, + &mut stack, + &live_future, + &live_out, + &mut actions, + ); + + // As in final planning, all taken paths inherit the base stack state. + for &(_, dest) in table.table().iter() { + edge_cand + .insert((block, dest), project_transfer(&stack, &carry_in[dest])); + } + if let Some(default) = table.default() { + edge_cand.insert( + (block, *default), + project_transfer(&stack, &carry_in[*default]), + ); + } + return; + } + } + } + + if ctx.func.dfg.is_return(inst) { + let mem = planner::MemPlan::new(spill, spill_requests, ctx, &mut free_slots, slots); + with_planner(ctx, mem, &mut stack, &mut actions, |planner| { + planner.plan_internal_return(inst) + }); + return; + } + + // Normal instruction. + let mem = planner::MemPlan::new(spill, spill_requests, ctx, &mut free_slots, slots); + + with_planner(ctx, mem, &mut stack, &mut actions, |planner| { + planner.prepare_operands_for_inst(inst, &mut args, last_use) + }); + + let res = ctx.func.dfg.inst_result(inst); + + for &v in args.iter() { + if !ctx.func.dfg.value_is_imm(v) + && let Some(n) = remaining_uses.get_mut(&v) + { + let before = *n; + *n = n.saturating_sub(1); + if before != 0 && *n == 0 { + live_future.remove(v); + if !live_out.contains(v) { + slots + .persistent + .release_if_assigned(v, &mut free_slots.persistent); + slots + .scratch + .release_if_assigned(v, &mut free_slots.scratch); + slots + .transient + .release_if_assigned(v, &mut free_slots.transient); + } + } + } + } + + stack.pop_n_operands(args.len()); + + if is_call { + stack.remove_call_ret_addr(); + } + + if let Some(res) = res { + stack.push_value(res); + if live_future.contains(res) || live_out.contains(res) { + let mem = + planner::MemPlan::new(spill, spill_requests, ctx, &mut free_slots, slots); + with_planner(ctx, mem, &mut stack, &mut actions, |planner| { + planner.emit_store_if_spilled(res) + }); + } + } + } + } + + fn solve_scc(&mut self, scc: SccId) { + let ctx = self.ctx; + let data = ctx.scc.scc_data(scc); + + if !data.is_cycle { + for &block in &data.blocks_rpo { + self.state[block].transfer = choose_transfer_from_preds( + ctx, + block, + self.carry_in, + &*self.edge_cand, + None, + None, + ); + let tmpl = self.template_for(block); + self.simulate_block_and_record_edges(block, &tmpl); + } + return; + } + + self.seed_cyclic_scc(scc); + + const MAX_ITER: u32 = 32; + for _iter in 0..MAX_ITER { + for &block in &data.blocks_rpo { + let tmpl = self.template_for(block); + self.simulate_block_and_record_edges(block, &tmpl); + } + + let mut changed = false; + + // For single-entry cyclic SCCs (reducible loops), pin the header transfer once we have + // evidence of a real join conflict, preferring the backedge-derived candidate. This + // biases the fixed point toward the loop's steady-state layout instead of the function + // entry edge and avoids oscillation between competing layouts. + if let Some(header) = data.header() + && header != ctx.entry + && !self.state[header].pinned + && let Some(backedge_cand) = + best_backedge_candidate(ctx, scc, header, &*self.edge_cand) + && header_has_conflict(ctx, header, &*self.edge_cand) + { + if self.state[header].transfer != backedge_cand { + self.state[header].transfer = backedge_cand; + changed = true; + } + self.state[header].pinned = true; + } + + for &block in &data.blocks_rpo { + if self.state[block].pinned { + continue; + } + let fallback = self.state[block].transfer.clone(); + let new_t = choose_transfer_from_preds( + ctx, + block, + self.carry_in, + &*self.edge_cand, + Some(scc), + Some(&fallback), + ); + if new_t != fallback { + self.state[block].transfer = new_t; + changed = true; + } + } + if !changed { + return; + } + } + + for &block in &data.blocks_rpo { + if self.state[block].pinned { + continue; + } + let fallback = self.state[block].transfer.clone(); + let cand = choose_transfer_from_preds( + ctx, + block, + self.carry_in, + &*self.edge_cand, + Some(scc), + Some(&fallback), + ); + self.state[block].transfer = if cand.is_empty() { + canonical_transfer_order(&self.carry_in[block], &ctx.dom_depth, &ctx.def_info) + } else { + cand + }; + } + } + + fn seed_cyclic_scc(&mut self, scc: SccId) { + let ctx = self.ctx; + let carry_in = self.carry_in; + let edge_cand = &*self.edge_cand; + + let data = ctx.scc.scc_data(scc); + for &block in &data.blocks_rpo { + self.state[block].pinned = false; + if block == ctx.entry { + self.state[block].transfer = + canonical_transfer_order(&carry_in[block], &ctx.dom_depth, &ctx.def_info); + continue; + } + + let external: Vec<(BlockId, &TransferOrder)> = reachable_preds(ctx, block) + .filter(|pred| ctx.scc.scc_of(*pred) != Some(scc)) + .map(|pred| (pred, edge_candidate(edge_cand, pred, block))) + .collect(); + + if data.entry_blocks.contains(&block) && !external.is_empty() { + self.state[block].transfer = choose_transfer(ctx, block, &external); + } else { + self.state[block].transfer = + canonical_transfer_order(&carry_in[block], &ctx.dom_depth, &ctx.def_info); + } + } + } +} + +fn edge_candidate(edge_cand: &EdgeCandMap, pred: BlockId, succ: BlockId) -> &TransferOrder { + edge_cand + .get(&(pred, succ)) + .unwrap_or_else(|| panic!("missing edge candidate for {pred:?}->{succ:?}")) +} + +fn reachable_preds<'a>( + ctx: &'a StackifyContext<'_>, + block: BlockId, +) -> impl Iterator + 'a { + ctx.cfg + .preds_of(block) + .copied() + .filter(move |pred| ctx.scc.is_reachable(*pred)) +} + +fn choose_transfer_from_preds( + ctx: &StackifyContext<'_>, + block: BlockId, + carry_in: &SecondaryMap>, + edge_cand: &EdgeCandMap, + scc: Option, + fallback: Option<&TransferOrder>, +) -> TransferOrder { + if block == ctx.entry { + return canonical_transfer_order(&carry_in[block], &ctx.dom_depth, &ctx.def_info); + } + + let mut all: Vec<(BlockId, &TransferOrder)> = Vec::new(); + let mut external: Vec<(BlockId, &TransferOrder)> = Vec::new(); + + for pred in reachable_preds(ctx, block) { + let cand = edge_candidate(edge_cand, pred, block); + all.push((pred, cand)); + if let Some(scc) = scc + && ctx.scc.scc_of(pred) != Some(scc) + { + external.push((pred, cand)); + } + } + + if all.is_empty() { + return fallback.cloned().unwrap_or_else(|| { + canonical_transfer_order(&carry_in[block], &ctx.dom_depth, &ctx.def_info) + }); + } + + if let Some(scc) = scc { + let data = ctx.scc.scc_data(scc); + if data.is_multi_entry() && data.entry_blocks.contains(&block) && !external.is_empty() { + return choose_transfer(ctx, block, &external); + } + } + + choose_transfer(ctx, block, &all) +} + +fn best_backedge_candidate( + ctx: &StackifyContext<'_>, + scc: SccId, + block: BlockId, + edge_cand: &EdgeCandMap, +) -> Option { + reachable_preds(ctx, block) + .filter(|pred| ctx.scc.scc_of(*pred) == Some(scc) && ctx.dom.dominates(block, *pred)) + .min_by_key(|pred| pred.as_u32()) + .map(|pred| edge_candidate(edge_cand, pred, block).clone()) +} + +fn header_has_conflict(ctx: &StackifyContext<'_>, block: BlockId, edge_cand: &EdgeCandMap) -> bool { + let mut first: Option<&TransferOrder> = None; + for pred in reachable_preds(ctx, block) { + let cand = edge_candidate(edge_cand, pred, block); + match first { + None => first = Some(cand), + Some(first_cand) if first_cand != cand => return true, + Some(_) => {} + } + } + false +} diff --git a/crates/codegen/src/stackalloc/stackify/iteration.rs b/crates/codegen/src/stackalloc/stackify/iteration.rs new file mode 100644 index 00000000..ec558d19 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/iteration.rs @@ -0,0 +1,822 @@ +use cranelift_entity::SecondaryMap; +use smallvec::SmallVec; +use sonatina_ir::{BlockId, Function, Immediate, InstId, ValueId, inst::control_flow::BranchKind}; +use std::collections::BTreeMap; + +use crate::{bitset::BitSet, stackalloc::Actions}; + +use super::{ + alloc::StackifyAlloc, + builder::{StackifyContext, StackifyReachability}, + planner::{self, Planner}, + slots::{FreeSlotPools, SpillSlotPools}, + spill::SpillSet, + sym_stack::{StackItem, SymStack}, + templates::BlockTemplate, + trace::StackifyObserver, +}; + +pub(super) struct IterationPlanner<'a, 'ctx, O: StackifyObserver> { + ctx: &'a StackifyContext<'ctx>, + spill: SpillSet<'a>, + slots: &'a mut SpillSlotPools, + templates: &'a SecondaryMap, + alloc: &'a mut StackifyAlloc, + spill_requests: &'a mut BitSet, + inherited_stack: BTreeMap, + observer: &'a mut O, +} + +struct BlockPlanState { + block: BlockId, + free_slots: FreeSlotPools, + prologue: Actions, + injected_prologue: bool, + remaining_uses: BTreeMap, + live_future: BitSet, + live_out: BitSet, + stack: SymStack, +} + +enum InstOutcome { + Continue, + TerminateBlock, +} + +impl<'a, 'ctx, O: StackifyObserver> IterationPlanner<'a, 'ctx, O> { + #[allow(clippy::too_many_arguments)] + pub(super) fn new( + ctx: &'a StackifyContext<'ctx>, + spill: SpillSet<'a>, + slots: &'a mut SpillSlotPools, + templates: &'a SecondaryMap, + alloc: &'a mut StackifyAlloc, + spill_requests: &'a mut BitSet, + inherited_stack: BTreeMap, + observer: &'a mut O, + ) -> Self { + Self { + ctx, + spill, + slots, + templates, + alloc, + spill_requests, + inherited_stack, + observer, + } + } + + fn with_planner( + &mut self, + stack: &mut SymStack, + actions: &mut Actions, + free_slots: &mut FreeSlotPools, + f: impl FnOnce(&mut Planner) -> R, + ) -> R { + let mem = planner::MemPlan::new( + self.spill, + &mut *self.spill_requests, + self.ctx, + free_slots, + &mut *self.slots, + ); + let mut planner = Planner::new(self.ctx, stack, actions, mem); + f(&mut planner) + } + + fn with_pre_actions_planner( + &mut self, + stack: &mut SymStack, + inst: InstId, + free_slots: &mut FreeSlotPools, + f: impl FnOnce(&mut Planner) -> R, + ) -> R { + let actions = &mut self.alloc.pre_actions[inst]; + let mem = planner::MemPlan::new( + self.spill, + &mut *self.spill_requests, + self.ctx, + free_slots, + &mut *self.slots, + ); + let mut planner = Planner::new(self.ctx, stack, actions, mem); + f(&mut planner) + } + + fn with_post_actions_planner( + &mut self, + stack: &mut SymStack, + inst: InstId, + free_slots: &mut FreeSlotPools, + f: impl FnOnce(&mut Planner) -> R, + ) -> R { + let actions = &mut self.alloc.post_actions[inst]; + let mem = planner::MemPlan::new( + self.spill, + &mut *self.spill_requests, + self.ctx, + free_slots, + &mut *self.slots, + ); + let mut planner = Planner::new(self.ctx, stack, actions, mem); + f(&mut planner) + } + + pub(super) fn plan_blocks(&mut self) { + for &block in self.ctx.dom.rpo() { + if block != self.ctx.entry && !self.ctx.dom.is_reachable(block) { + continue; + } + + self.observer + .on_block_header(self.ctx.func, block, &self.templates[block]); + self.plan_block(block); + } + } + + fn plan_block(&mut self, block: BlockId) { + let mut free_slots: FreeSlotPools = FreeSlotPools::default(); + let mut prologue: Actions = Actions::new(); + let injected_prologue = false; + + // Track per-block remaining uses to implement `PopDeadTops`. + let (remaining_uses, live_future) = count_block_uses(self.ctx.func, block); + let mut live_out = self.ctx.liveness.block_live_outs(block).clone(); + live_out.union_with(&self.ctx.phi_out_sources[block]); + + let stack = if let Some((pred, mut inh)) = self.inherited_stack.remove(&block) { + // Dynamic entry stack (single predecessor). + if block != self.ctx.entry { + debug_assert_eq!( + self.ctx.cfg.pred_num_of(block), + 1, + "inherited stack implies single-predecessor block" + ); + self.observer.on_block_inherited( + self.ctx.func, + block, + pred, + &inh, + &live_future, + &live_out, + ); + let templates = self.templates; + self.with_planner(&mut inh, &mut prologue, &mut free_slots, |planner| { + planner.plan_edge_fixup(templates, pred, block); + }); + self.observer.on_block_prologue(&prologue); + } + inh + } else { + SymStack::from_template(&self.templates[block], self.ctx.has_internal_return) + }; + + let mut state = BlockPlanState { + block, + free_slots, + prologue, + injected_prologue, + remaining_uses, + live_future, + live_out, + stack, + }; + + let empty_last_use: BitSet = BitSet::default(); + for inst in self.ctx.func.layout.iter_inst(block) { + if self.ctx.func.dfg.is_phi(inst) { + continue; + } + + match self.plan_inst(&mut state, inst, &empty_last_use) { + InstOutcome::Continue => {} + InstOutcome::TerminateBlock => break, + } + } + + // If the block had no lowered instructions, inject prologue into the terminator. + if !state.prologue.is_empty() + && !state.injected_prologue + && let Some(term) = self.ctx.func.layout.last_inst_of(block) + { + self.alloc.pre_actions[term].extend_from_slice(&state.prologue); + } + } + + fn plan_inst( + &mut self, + state: &mut BlockPlanState, + inst: InstId, + empty_last_use: &BitSet, + ) -> InstOutcome { + // Inject prologue actions once, at the first lowered instruction. + if !state.prologue.is_empty() && !state.injected_prologue { + self.alloc.pre_actions[inst].extend_from_slice(&state.prologue); + state.injected_prologue = true; + } + + let is_call = self.ctx.func.dfg.is_call(inst); + let is_normal = + self.ctx.func.dfg.branch_info(inst).is_none() && !self.ctx.func.dfg.is_return(inst); + + let mut args = SmallVec::<[ValueId; 8]>::new(); + let mut consume_last_use: BitSet = BitSet::default(); + if is_normal { + args = operand_order_for_evm(self.ctx.func, inst); + consume_last_use = last_use_values_in_inst( + self.ctx.func, + &args, + &state.remaining_uses, + &state.live_out, + ); + } + let last_use = if is_normal { + &consume_last_use + } else { + empty_last_use + }; + self.observer.on_inst_start( + self.ctx.func, + inst, + &state.stack, + &state.live_future, + &state.live_out, + last_use, + ); + + // Stable cleanup: pop dead values (and dead chains under the top live value). + // + // For calls, do this before pushing the continuation target so we can still + // pop dead values that were on top of the caller's stack segment. + let before_cleanup_len = self.alloc.pre_actions[inst].len(); + clean_dead_stack_prefix( + self.ctx.reach, + &mut state.stack, + &state.live_future, + &state.live_out, + &mut self.alloc.pre_actions[inst], + ); + + // Try to improve operand reachability before operand preparation: + // - do nothing if all operands are already `DUP16`-reachable + // - otherwise, if an operand is close (within a small depth window), delete dead values, + // redundant duplicates, and (small) immediates above it to pull it back into reach + // This helps avoid unnecessary spill-set growth. + if is_normal { + improve_reachability_before_operands( + self.ctx.func, + &args, + self.ctx.reach, + &mut state.stack, + &state.live_future, + &state.live_out, + &mut self.alloc.pre_actions[inst], + ); + } + let after_cleanup_len = self.alloc.pre_actions[inst].len(); + self.observer.on_inst_actions( + "cleanup", + &self.alloc.pre_actions[inst][before_cleanup_len..after_cleanup_len], + None, + ); + + // Calls push a continuation target before argument setup (the backend consumes + // `Action::PushContinuationOffset`). + if is_call { + state + .stack + .push_call_continuation(&mut self.alloc.pre_actions[inst]); + } + + if let Some(branch) = self.ctx.func.dfg.branch_info(inst) { + match branch.branch_kind() { + BranchKind::Jump(jump) => { + // Fix up the stack so the successor observes its chosen entry template + // (including any phi results). + let dest = *jump.dest(); + let templates = self.templates; + let src = state.block; + self.with_pre_actions_planner( + &mut state.stack, + inst, + &mut state.free_slots, + |p| { + p.plan_edge_fixup(templates, src, dest); + }, + ); + + self.observer.on_inst_actions( + "exit", + &self.alloc.pre_actions[inst][after_cleanup_len..], + Some(dest), + ); + self.observer.on_inst_jump(inst, dest); + return InstOutcome::TerminateBlock; + } + BranchKind::Br(br) => { + // Ensure the branch condition is on top for the backend's JUMPI sequence. + // We intentionally do not canonicalize the transfer region here: branch + // targets are single-predecessor blocks (after critical-edge splitting) + // and will run an entry prologue to normalize to their templates. + let cond = *br.cond(); + let consume_last_use = last_use_values_in_inst( + self.ctx.func, + &[cond], + &state.remaining_uses, + &state.live_out, + ); + + improve_reachability_before_operands( + self.ctx.func, + &[cond], + self.ctx.reach, + &mut state.stack, + &state.live_future, + &state.live_out, + &mut self.alloc.pre_actions[inst], + ); + self.with_pre_actions_planner( + &mut state.stack, + inst, + &mut state.free_slots, + |p| { + p.prepare_operands(&[cond], &consume_last_use); + }, + ); + + self.observer.on_inst_actions( + "pre", + &self.alloc.pre_actions[inst][after_cleanup_len..], + None, + ); + let dests = branch.dests(); + self.observer + .on_inst_br(self.ctx.func, inst, cond, dests.as_slice()); + + // The backend consumes the condition value for the actual branch. + let mut post_branch_stack = state.stack.clone(); + post_branch_stack.pop_operand(); + + for succ in dests.iter().copied() { + debug_assert_eq!( + self.ctx.cfg.pred_num_of(succ), + 1, + "no critical edges: branch target must be single-pred" + ); + self.inherited_stack + .entry(succ) + .or_insert_with(|| (state.block, post_branch_stack.clone())); + } + return InstOutcome::TerminateBlock; + } + BranchKind::BrTable(table) => { + // Build per-case compare actions. As with `br`, we normalize successor entry + // stacks in their block prologues, so we keep the current stack order here. + let scrutinee = *table.scrutinee(); + + improve_reachability_before_operands( + self.ctx.func, + &[scrutinee], + self.ctx.reach, + &mut state.stack, + &state.live_future, + &state.live_out, + &mut self.alloc.pre_actions[inst], + ); + + // `br_table` lowering uses per-case `Allocator::read()` calls. Treat any + // actions accumulated for this terminator as a "one-time prefix" executed + // by the first case. + let base_actions = self.alloc.pre_actions[inst].clone(); + self.alloc.pre_actions[inst].clear(); + + // The br_table lowering emits `EQ; JUMPI` for each case in order. + for (case_idx, &(case_val, dest)) in table.table().iter().enumerate() { + let mut case_actions = Actions::new(); + let mut case_stack = state.stack.clone(); + + // Put [scrutinee, case_val] on stack for EQ (order doesn't matter). + if case_idx == 0 { + case_actions.extend_from_slice(&base_actions); + } + self.with_planner( + &mut case_stack, + &mut case_actions, + &mut state.free_slots, + |p| { + let consume_last_use = BitSet::::default(); + p.prepare_operands(&[scrutinee, case_val], &consume_last_use); + }, + ); + self.alloc + .brtable_actions + .insert((inst, scrutinee, case_val), case_actions); + + // Destination blocks inherit the base stack state (the compare chain + // restores it on all non-taken paths). + debug_assert_eq!( + self.ctx.cfg.pred_num_of(dest), + 1, + "no critical edges: br_table target must be single-pred" + ); + self.inherited_stack + .entry(dest) + .or_insert_with(|| (state.block, state.stack.clone())); + } + + if let Some(default) = table.default() { + debug_assert_eq!( + self.ctx.cfg.pred_num_of(*default), + 1, + "no critical edges: br_table default must be single-pred" + ); + self.inherited_stack + .entry(*default) + .or_insert_with(|| (state.block, state.stack.clone())); + } + + self.observer.on_inst_br_table(inst); + return InstOutcome::TerminateBlock; + } + } + } + + if self.ctx.func.dfg.is_return(inst) { + // Internal return: ensure only (return_val?, ret_addr) remain above the opaque caller + // stack segment. + self.with_pre_actions_planner(&mut state.stack, inst, &mut state.free_slots, |p| { + p.plan_internal_return(inst); + }); + self.observer.on_inst_actions( + "return", + &self.alloc.pre_actions[inst][after_cleanup_len..], + None, + ); + self.observer + .on_inst_return(self.ctx.func, inst, self.ctx.func.dfg.as_return(inst)); + return InstOutcome::TerminateBlock; + } + + // Normal instruction. + self.with_pre_actions_planner(&mut state.stack, inst, &mut state.free_slots, |p| { + p.prepare_operands_for_inst(inst, &mut args, &consume_last_use); + }); + + self.observer.on_inst_actions( + "pre", + &self.alloc.pre_actions[inst][after_cleanup_len..], + None, + ); + let res = self.ctx.func.dfg.inst_result(inst); + self.observer + .on_inst_normal(self.ctx.func, inst, &args, res); + + // Update remaining uses. + for &v in args.iter() { + if !self.ctx.func.dfg.value_is_imm(v) + && let Some(n) = state.remaining_uses.get_mut(&v) + { + let before = *n; + *n = n.saturating_sub(1); + if before != 0 && *n == 0 { + state.live_future.remove(v); + if !state.live_out.contains(v) { + self.slots + .persistent + .release_if_assigned(v, &mut state.free_slots.persistent); + self.slots + .scratch + .release_if_assigned(v, &mut state.free_slots.scratch); + self.slots + .transient + .release_if_assigned(v, &mut state.free_slots.transient); + } + } + } + } + + let arity = args.len(); + state.stack.pop_n_operands(arity); + + // Call consumes the temporary continuation target (not an SSA value). + if is_call { + state.stack.remove_call_ret_addr(); + } + + if let Some(res) = res { + state.stack.push_value(res); + let res_live = state.live_future.contains(res) || state.live_out.contains(res); + if res_live { + self.with_post_actions_planner( + &mut state.stack, + inst, + &mut state.free_slots, + |planner| { + planner.emit_store_if_spilled(res); + }, + ); + } + } + + self.observer + .on_inst_actions("post", &self.alloc.post_actions[inst], None); + + InstOutcome::Continue + } +} + +pub(super) fn count_block_uses( + func: &Function, + block: BlockId, +) -> (BTreeMap, BitSet) { + let mut counts: BTreeMap = BTreeMap::new(); + for inst in func.layout.iter_inst(block) { + if func.dfg.is_phi(inst) { + continue; + } + for v in func.dfg.inst(inst).collect_values() { + if func.dfg.value_is_imm(v) { + continue; + } + *counts.entry(v).or_insert(0) += 1; + } + } + let live_future: BitSet = counts.keys().copied().collect(); + (counts, live_future) +} + +fn pop_dead_tops( + stack: &mut SymStack, + live_future: &BitSet, + live_out: &BitSet, + actions: &mut Actions, +) { + while let Some(StackItem::Value(v)) = stack.top() { + if live_future.contains(*v) || live_out.contains(*v) { + break; + } + stack.pop(actions); + } +} + +pub(super) fn clean_dead_stack_prefix( + reach: StackifyReachability, + stack: &mut SymStack, + live_future: &BitSet, + live_out: &BitSet, + actions: &mut Actions, +) { + // Two local cleanups: + // - pop any dead values that reach the top + // - if a live value is on top and there is a contiguous dead chain directly beneath it, + // swap the live value down and pop the dead chain off the top. + loop { + let before_len = stack.len(); + pop_dead_tops(stack, live_future, live_out, actions); + + // If the top is not a normal value, don't try to reorder under it. + let Some(StackItem::Value(top)) = stack.top() else { + break; + }; + if !live_future.contains(*top) && !live_out.contains(*top) { + // Should have been handled by `pop_dead_tops`, but keep looping defensively. + continue; + } + + let is_dead = |v: ValueId| !live_future.contains(v) && !live_out.contains(v); + let dead_run = stack + .iter() + .skip(1) + .take_while(|&v| matches!(v, StackItem::Value(v) if is_dead(*v))) + .count(); + if dead_run == 0 { + break; + } + + // Swap the top live value with the deepest dead value in the contiguous chain (within + // SWAP16 reach), then pop that chunk off. Repeat until the chain is gone. + let mut remaining = dead_run; + while remaining > 0 { + let swap_depth = remaining.min(reach.swap_max.saturating_sub(1)); + stack.swap(swap_depth, actions); + stack.pop_n(swap_depth, actions); + remaining -= swap_depth; + } + + if stack.len() == before_len { + break; + } + } +} + +pub(super) fn operand_order_for_evm(func: &Function, inst: InstId) -> SmallVec<[ValueId; 8]> { + // This IR mostly already stores operands in the order expected by the EVM backend + // (e.g. `mstore addr value`, `gt lhs rhs`, `shl bits value`). + // + // Keeping this as a dedicated hook makes the required operand conventions explicit. + func.dfg.inst(inst).collect_values().into_iter().collect() +} + +pub(super) fn last_use_values_in_inst( + func: &Function, + args: &[ValueId], + remaining_uses: &BTreeMap, + live_out: &BitSet, +) -> BitSet { + let mut inst_counts: BTreeMap = BTreeMap::new(); + for &v in args.iter() { + if func.dfg.value_is_imm(v) { + continue; + } + *inst_counts.entry(v).or_insert(0) += 1; + } + + let mut last_use: BitSet = BitSet::default(); + for (v, count) in inst_counts { + let rem = remaining_uses.get(&v).copied().unwrap_or(0); + if rem == count && !live_out.contains(v) { + last_use.insert(v); + } + } + last_use +} + +pub(super) fn improve_reachability_before_operands( + func: &Function, + args: &[ValueId], + reach: StackifyReachability, + stack: &mut SymStack, + live_future: &BitSet, + live_out: &BitSet, + actions: &mut Actions, +) { + const AGGRESSIVE_REACHABILITY_DEPTH: usize = 20; + + let mut protected_args: BitSet = BitSet::default(); + for &arg in args.iter() { + if !func.dfg.value_is_imm(arg) { + protected_args.insert(arg); + } + } + + // If at least one operand is on the stack but unreachable by `DUP16`, attempt a more + // aggressive cleanup to bring it back into reach. This extends slightly past `SWAP16` reach + // by allowing deletions that shift the stack (e.g. popping dead/cheap values above an + // operand at depth 18-20). + let mut needs_aggressive = false; + for &arg in args.iter() { + if !func.dfg.value_is_imm(arg) + && stack.find_reachable_value(arg, reach.dup_max).is_none() + && stack + .find_reachable_value(arg, AGGRESSIVE_REACHABILITY_DEPTH) + .is_some() + { + needs_aggressive = true; + break; + } + } + if !needs_aggressive { + return; + } + + // Bound the amount of cleanup we do per instruction to avoid pathological swap chains. + const MAX_DELETIONS: usize = 8; + let mut deletions: usize = 0; + + while deletions < MAX_DELETIONS { + let mut progressed = false; + + for &arg in args.iter() { + if !func.dfg.value_is_imm(arg) + && stack.find_reachable_value(arg, reach.dup_max).is_none() + && let Some(pos) = stack.find_reachable_value(arg, AGGRESSIVE_REACHABILITY_DEPTH) + && let Some(victim) = choose_reachability_victim( + func, + stack, + pos, + &protected_args, + reach, + live_future, + live_out, + ) + { + stack.stable_delete_at_depth(victim + 1, actions); + deletions += 1; + progressed = true; + break; + } + } + + if !progressed { + break; + } + } +} + +fn choose_reachability_victim( + func: &Function, + stack: &SymStack, + above: usize, + protected_args: &BitSet, + reach: StackifyReachability, + live_future: &BitSet, + live_out: &BitSet, +) -> Option { + let limit = stack.len_above_func_ret().min(reach.swap_max); + let above = above.min(limit); + + // 1) Prefer deleting dead values, starting from the shallowest depth to minimize `SWAP*` + // chains. This includes immediates: if they're dead, removing them cannot introduce new + // rematerialization cost. + for (i, item) in stack.iter().take(above).enumerate() { + if let StackItem::Value(v) = item + && !protected_args.contains(*v) + && !live_future.contains(*v) + && !live_out.contains(*v) + { + return Some(i); + } + } + + // 2) Then evict "cheap" immediates (they are always rematerializable). + for (i, item) in stack.iter().take(above).enumerate() { + if let StackItem::Value(v) = item + && !protected_args.contains(*v) + && func.dfg.value_is_imm(*v) + && is_evictable_imm(func, *v) + { + return Some(i); + } + } + + // 3) Then delete redundant duplicates of non-operands (keeping the shallowest copy). + let mut first_index: BTreeMap = BTreeMap::new(); + for (i, item) in stack.iter().take(above).enumerate() { + let StackItem::Value(v) = item else { + continue; + }; + first_index.entry(*v).or_insert(i); + } + + for (i, item) in stack.iter().take(above).enumerate() { + if let StackItem::Value(v) = item + && !protected_args.contains(*v) + && let Some(&first) = first_index.get(v) + && first != i + { + return Some(i); + } + } + + None +} + +fn is_evictable_imm(func: &Function, v: ValueId) -> bool { + const MAX_PUSH_DATA_BYTES: usize = 2; // <= PUSH2 + let Some(imm) = func.dfg.value_imm(v) else { + return false; + }; + imm_push_data_len(imm) <= MAX_PUSH_DATA_BYTES +} + +fn imm_push_data_len(imm: Immediate) -> usize { + if imm.is_zero() { + return 0; + } + + fn shrink_len(bytes: &[u8]) -> usize { + debug_assert!(!bytes.is_empty()); + + let is_neg = (bytes[0] & 0x80) != 0; + let skip = if is_neg { 0xff } else { 0x00 }; + + let mut idx: usize = 0; + while idx < bytes.len() && bytes[idx] == skip { + idx += 1; + } + let mut len = bytes.len().saturating_sub(idx); + if len == 0 { + len = 1; + } + + // Negative numbers need a leading 1 bit for sign-extension. + if is_neg { + let first = bytes.get(idx).copied().unwrap_or(0xff); + if first < 0x80 { + len = len.saturating_add(1); + } + } + len + } + + match imm { + Immediate::I1(v) => v as usize, + Immediate::I8(v) => shrink_len(&v.to_be_bytes()), + Immediate::I16(v) => shrink_len(&v.to_be_bytes()), + Immediate::I32(v) => shrink_len(&v.to_be_bytes()), + Immediate::I64(v) => shrink_len(&v.to_be_bytes()), + Immediate::I128(v) => shrink_len(&v.to_be_bytes()), + Immediate::I256(v) => shrink_len(&v.to_u256().to_big_endian()), + } +} diff --git a/crates/codegen/src/stackalloc/stackify/mod.rs b/crates/codegen/src/stackalloc/stackify/mod.rs new file mode 100644 index 00000000..60ac63fc --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/mod.rs @@ -0,0 +1,118 @@ +//! Stack allocation via deterministic block-entry templates and edge fixups. +//! +//! - Each block `B` has a unique entry template `StackIn(B) = P(B) ++ T(B)`. +//! - `P(B)` is a parameter prefix (phi results; plus function args for the entry block). +//! - `T(B)` is a transfer region: live-in, non-phi values in a chosen order. +//! - `T(B)` is derived from simulated predecessor stacks (`cand(pred→B)`), not heuristics. +//! - Layouts are solved in reachable-CFG SCC topo order; cyclic SCCs use a fixed point. +//! - For merge blocks, all incoming edges are normalized to the same `StackIn(B)` (often a no-op). +//! - When a value cannot be duplicated from within `DUP16` reach, it is added to `spill_set`, +//! assigned a frame slot, and reloaded from memory; `spill_set` is discovered via a +//! monotone fixed point. +//! +//! Notes specific to this codebase: +//! - Critical edges must be split before running this allocator. +//! - Internal calls rely on an implicit return address value on the EVM stack. +//! The allocator models this as a special stack item barrier to avoid popping +//! into the caller's preserved stack segment. + +mod alloc; +mod builder; +mod flow_templates; +mod iteration; +mod planner; +mod slots; +mod spill; +mod sym_stack; +mod templates; +mod trace; + +pub use alloc::{StackifyAlloc, StackifyLiveValues}; + +use builder::StackifyContext; + +const DUP_MAX: usize = 16; // DUP16 duplicates stack[15] +const SWAP_MAX: usize = 17; // SWAP16 swaps stack[0] and stack[16] +/// Maximum `SWAP*` chain length used to consume a last-use operand directly from the stack. +/// +/// This is a purely local heuristic: we rotate a last-use value up (preserving the current +/// operand prefix order) so the instruction consumes it, avoiding a `DUP*` + later cleanup. +const CONSUME_LAST_USE_MAX_SWAPS: usize = 3; + +#[cfg(test)] +mod tests { + use super::StackifyAlloc; + use crate::{ + critical_edge::CriticalEdgeSplitter, + domtree::DomTree, + liveness::{InstLiveness, Liveness}, + stackalloc::SpillSlotRef, + }; + use sonatina_ir::cfg::ControlFlowGraph; + use sonatina_parser::parse_module; + + #[test] + fn spill_slots_split_by_call_liveness() { + const SRC: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test_files/evm/spill.sntn" + )); + + let parsed = parse_module(SRC).unwrap(); + let spill_func = parsed + .debug + .func_order + .iter() + .copied() + .find(|&f| parsed.module.ctx.func_sig(f, |sig| sig.name() == "spill")) + .expect("missing spill func"); + + parsed.module.func_store.modify(spill_func, |function| { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + + let mut splitter = CriticalEdgeSplitter::new(); + splitter.run(function, &mut cfg); + + let mut liveness = Liveness::new(); + liveness.compute(function, &cfg); + + let mut dom = DomTree::new(); + dom.compute(&cfg); + + let alloc = StackifyAlloc::for_function(function, &cfg, &dom, &liveness, 16); + + let mut inst_live = InstLiveness::new(); + inst_live.compute(function, &cfg, &liveness); + let call_live = inst_live.call_live_values(function); + assert!( + !call_live.is_empty(), + "expected some values live-out at calls" + ); + + let mut saw_persistent = false; + let mut saw_any = false; + + for (v, slot) in alloc.slot_of_value.iter() { + let Some(slot) = *slot else { + continue; + }; + saw_any = true; + let expect_persistent = call_live.contains(v); + match (expect_persistent, slot) { + (true, SpillSlotRef::Persistent(_)) => saw_persistent = true, + (false, SpillSlotRef::Transient(_) | SpillSlotRef::Scratch(_)) => {} + (true, SpillSlotRef::Transient(_) | SpillSlotRef::Scratch(_)) => { + panic!("expected spilled value {v:?} to be persistent"); + } + (false, SpillSlotRef::Persistent(_)) => { + panic!("expected spilled value {v:?} to be transient"); + } + } + } + + assert!(saw_any, "expected at least one spilled value"); + assert!(saw_persistent, "expected at least one persistent spill"); + }); + } +} diff --git a/crates/codegen/src/stackalloc/stackify/planner/control_flow.rs b/crates/codegen/src/stackalloc/stackify/planner/control_flow.rs new file mode 100644 index 00000000..41663dab --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/planner/control_flow.rs @@ -0,0 +1,136 @@ +use cranelift_entity::SecondaryMap; +use smallvec::SmallVec; +use sonatina_ir::{BlockId, InstId, ValueId}; + +use super::{ + super::{ + sym_stack::StackItem, + templates::{BlockTemplate, phi_args_for_edge}, + }, + Planner, +}; + +impl<'a, 'ctx: 'a> Planner<'a, 'ctx> { + pub(in super::super) fn plan_edge_fixup( + &mut self, + templates: &SecondaryMap, + pred: BlockId, + succ: BlockId, + ) { + let tmpl = &templates[succ]; + let phi_results = &self.ctx.phi_results[succ]; + + let phi_srcs = phi_args_for_edge(self.ctx.func, pred, succ); + debug_assert_eq!( + phi_srcs.len(), + phi_results.len(), + "phi source/result arity mismatch for edge {pred:?}->{succ:?}" + ); + + // Normalize the predecessor stack directly to the successor entry template: + // + // StackIn(succ) = P(succ) ++ T(succ) + // + // Where `P(succ)` includes: + // - function args (entry block only) + // - phi results (replaced here by per-edge phi sources, then renamed in-place) + let phi_count = phi_results.len(); + debug_assert!( + phi_count <= tmpl.params.len(), + "template params missing phi results for block {succ:?}" + ); + let args_prefix_len = tmpl.params.len() - phi_count; + debug_assert_eq!( + &tmpl.params.as_slice()[args_prefix_len..], + phi_results.as_slice(), + "template phi prefix mismatch for block {succ:?}" + ); + + let mut desired: SmallVec<[ValueId; 16]> = SmallVec::new(); + desired.extend(tmpl.params.iter().take(args_prefix_len).copied()); + desired.extend(phi_srcs.iter().copied()); + desired.extend(tmpl.transfer.iter().copied()); + + self.normalize_to_exact(desired.as_slice()); + + // Rename phi-source placeholders to phi results, then emit spill stores without + // disturbing the final entry layout. + for (idx, (&phi_res, &src)) in phi_results.iter().zip(phi_srcs.iter()).enumerate() { + let depth = args_prefix_len + idx; + debug_assert_eq!( + self.stack.item_at(depth), + Some(&StackItem::Value(src)), + "edge normalization failed to place phi source at depth {depth} for {pred:?}->{succ:?}" + ); + self.stack.rename_value_at_depth(depth, phi_res); + self.emit_store_if_spilled_at_depth(phi_res, depth); + } + } + + pub fn plan_internal_return(&mut self, inst: InstId) { + let Some(ret_val) = self.ctx.func.dfg.as_return(inst) else { + // No return value: pop everything above the function return address. + self.stack.clear_above_func_ret(self.actions); + return; + }; + + if self.ctx.func.dfg.value_is_imm(ret_val) { + let imm = self + .ctx + .func + .dfg + .value_imm(ret_val) + .expect("imm value missing payload"); + + // If an identical immediate is already on the stack, reuse it instead of pushing a + // duplicate constant. + let limit = self.stack.len_above_func_ret(); + let mut existing_pos: Option = None; + for (idx, item) in self.stack.iter().take(limit).enumerate() { + let StackItem::Value(v) = item else { + continue; + }; + let Some(stack_imm) = self.ctx.func.dfg.value_imm(*v) else { + continue; + }; + if stack_imm == imm { + existing_pos = Some(idx); + break; + } + } + + if let Some(pos) = existing_pos { + self.stack.pop_n(pos, self.actions); + self.stack.rename_top_value(ret_val); + self.delete_between_top_and_func_ret(); + return; + } + + // Immediate return: clear the callee stack segment, then push the immediate. + self.stack.clear_above_func_ret(self.actions); + self.stack.push_imm(ret_val, imm, self.actions); + return; + } + + // Prefer using an existing stack copy of the return value (if any) to avoid + // forcing it into `spill_set`. + // + // Step 1: pop values until either the return value or the return-address barrier is on top. + loop { + match self.stack.top() { + Some(StackItem::Value(v)) if *v == ret_val => break, + Some(StackItem::FuncRetAddr) | None => break, + Some(_) => { + self.stack.pop(self.actions); + } + } + } + + // Step 2: if the return value wasn't present on the stack, reload it from `spill_set`. + if self.stack.top() != Some(&StackItem::Value(ret_val)) { + self.push_value_from_spill_slot_or_mark(ret_val, ret_val); + } + + self.delete_between_top_and_func_ret(); + } +} diff --git a/crates/codegen/src/stackalloc/stackify/planner/mod.rs b/crates/codegen/src/stackalloc/stackify/planner/mod.rs new file mode 100644 index 00000000..f395f0a6 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/planner/mod.rs @@ -0,0 +1,193 @@ +mod control_flow; +mod normalize; +mod operand_prep; + +use crate::{ + bitset::BitSet, + liveness::Liveness, + stackalloc::{Action, Actions}, +}; +use sonatina_ir::ValueId; + +use super::{ + StackifyContext, + slots::{FreeSlotPools, SpillSlotPools, TRANSIENT_SLOT_TAG}, + spill::{SpillDiscovery, SpillSet}, + sym_stack::SymStack, +}; + +pub(super) struct MemPlan<'a> { + values_persistent_across_calls: &'a BitSet, + scratch_live_values: &'a BitSet, + scratch_spill_slots: u32, + free_slots: &'a mut FreeSlotPools, + slots: &'a mut SpillSlotPools, + liveness: &'a Liveness, + spill: SpillDiscovery<'a>, +} + +impl<'a> MemPlan<'a> { + pub(super) fn new( + spill: SpillSet<'a>, + spill_requests: &'a mut BitSet, + ctx: &'a StackifyContext<'_>, + free_slots: &'a mut FreeSlotPools, + slots: &'a mut SpillSlotPools, + ) -> Self { + Self { + values_persistent_across_calls: &ctx.values_persistent_across_calls, + scratch_live_values: &ctx.scratch_live_values, + scratch_spill_slots: ctx.scratch_spill_slots, + free_slots, + slots, + liveness: ctx.liveness, + spill: SpillDiscovery::new(spill, spill_requests), + } + } + + pub(super) fn spill_set(&self) -> SpillSet<'a> { + self.spill.spill_set() + } + + pub(super) fn spill_requests(&self) -> &BitSet { + self.spill.spill_requests() + } + + pub(super) fn free_slots(&self) -> &FreeSlotPools { + &*self.free_slots + } + + pub(super) fn slot_state(&self) -> &SpillSlotPools { + &*self.slots + } + + fn load_frame_slot_or_placeholder(&mut self, v: ValueId) -> Action { + let Some(spilled) = self.spill.spilled(v) else { + self.spill.request_spill(v); + // This fixed-point iteration will be discarded; the real slot assignment happens at + // `v`'s definition once it becomes part of `spill_set`. + return Action::MemLoadFrameSlot(0); + }; + + let persistent = self.values_persistent_across_calls.contains(v); + + if persistent { + let slot = self.slots.persistent.ensure_slot( + spilled, + self.liveness, + &mut self.free_slots.persistent, + ); + return Action::MemLoadFrameSlot(slot); + } + + if self.scratch_spill_slots != 0 + && !self.scratch_live_values.contains(v) + && let Some(slot) = self.slots.scratch.try_ensure_slot( + spilled, + self.liveness, + &mut self.free_slots.scratch, + Some(self.scratch_spill_slots), + ) + { + return Action::MemLoadAbs(slot * 32); + } + + let slot = self.slots.transient.ensure_slot( + spilled, + self.liveness, + &mut self.free_slots.transient, + ); + Action::MemLoadFrameSlot(TRANSIENT_SLOT_TAG | slot) + } + + fn emit_store_if_spilled_at_depth(&mut self, v: ValueId, depth: u8, actions: &mut Actions) { + let Some(spilled) = self.spill.spilled(v) else { + return; + }; + + let persistent = self.values_persistent_across_calls.contains(v); + actions.push(Action::StackDup(depth)); + + if persistent { + let slot = self.slots.persistent.ensure_slot( + spilled, + self.liveness, + &mut self.free_slots.persistent, + ); + actions.push(Action::MemStoreFrameSlot(slot)); + return; + } + + if self.scratch_spill_slots != 0 + && !self.scratch_live_values.contains(v) + && let Some(slot) = self.slots.scratch.try_ensure_slot( + spilled, + self.liveness, + &mut self.free_slots.scratch, + Some(self.scratch_spill_slots), + ) + { + actions.push(Action::MemStoreAbs(slot * 32)); + return; + } + + let slot = self.slots.transient.ensure_slot( + spilled, + self.liveness, + &mut self.free_slots.transient, + ); + actions.push(Action::MemStoreFrameSlot(TRANSIENT_SLOT_TAG | slot)); + } +} + +/// Planning context for a single instruction/edge. +/// +/// This bundles the symbolic stack + output action list with the current fixed-point +/// iteration's `spill_set` and frame-slot allocation state, avoiding large `&mut` +/// argument lists throughout the planner. +pub(super) struct Planner<'a, 'ctx: 'a> { + ctx: &'a StackifyContext<'ctx>, + stack: &'a mut SymStack, + actions: &'a mut Actions, + mem: MemPlan<'a>, +} + +impl<'a, 'ctx: 'a> Planner<'a, 'ctx> { + pub(super) fn new( + ctx: &'a StackifyContext<'ctx>, + stack: &'a mut SymStack, + actions: &'a mut Actions, + mem: MemPlan<'a>, + ) -> Self { + Self { + ctx, + stack, + actions, + mem, + } + } + + pub(super) fn emit_store_if_spilled(&mut self, v: ValueId) { + self.mem.emit_store_if_spilled_at_depth(v, 0, self.actions); + } + + pub(super) fn emit_store_if_spilled_at_depth(&mut self, v: ValueId, depth: usize) { + if !self.mem.spill_set().contains(v) { + return; + } + debug_assert!(depth < self.ctx.reach.dup_max, "DUP out of range"); + self.mem + .emit_store_if_spilled_at_depth(v, depth as u8, self.actions); + } + + fn push_value_from_spill_slot_or_mark(&mut self, load_from: ValueId, stack_as: ValueId) { + debug_assert!( + !self.ctx.func.dfg.value_is_imm(load_from), + "immediates must be pushed via `Action::Push`" + ); + + let act = self.mem.load_frame_slot_or_placeholder(load_from); + self.actions.push(act); + self.stack.push_value(stack_as); + } +} diff --git a/crates/codegen/src/stackalloc/stackify/planner/normalize.rs b/crates/codegen/src/stackalloc/stackify/planner/normalize.rs new file mode 100644 index 00000000..72a6feb2 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/planner/normalize.rs @@ -0,0 +1,283 @@ +use sonatina_ir::ValueId; +use std::collections::BTreeMap; + +use super::{ + super::sym_stack::{StackItem, SymStack}, + Planner, +}; + +impl<'a, 'ctx: 'a> Planner<'a, 'ctx> { + pub(super) fn normalize_to_exact(&mut self, desired: &[ValueId]) { + // Contract: rewrite the current symbolic stack (above the function return barrier, if any) + // so that it matches `desired` exactly (top-first). + if matches_exact(self.stack, desired) { + return; + } + if self.try_normalize_to_exact(desired) { + return; + } + self.flush_rebuild(desired); + } + + fn delete_to_match_multiset(&mut self, req: &BTreeMap) -> bool { + let limit = self.stack.len_above_func_ret(); + let mut keep: Vec = vec![false; limit]; + + // Choose the deepest occurrences to keep. This tends to keep values that are already + // "out of reach" (e.g. deep transfer values) while discarding dead prefixes, which helps + // avoid long swap+pop chains when cleaning up edge stacks. + let mut remaining: BTreeMap = req.clone(); + for depth in (0..limit).rev() { + let Some(StackItem::Value(v)) = self.stack.item_at(depth) else { + return false; + }; + let need = remaining.get(v).copied().unwrap_or(0); + if need == 0 { + continue; + } + keep[depth] = true; + if need == 1 { + remaining.remove(v); + } else { + remaining.insert(*v, need - 1); + } + } + + self.delete_unkept_values(&mut keep); + true + } + + fn delete_unkept_values(&mut self, keep: &mut Vec) { + // Delete all values marked as "not kept" using only SWAPs + POPs. + // + // Key optimization: if we have a kept prefix followed by a long contiguous run of + // deletable values (and more kept values below), we can sink the kept prefix below the + // run with O(k) swaps and then pop the entire run without per-item swaps. + while keep.iter().any(|k| !*k) { + if !keep[0] { + self.stack.pop(self.actions); + keep.remove(0); + continue; + } + + let keep_prefix_len = keep.iter().take_while(|k| **k).count(); + debug_assert!(keep_prefix_len > 0); + + let mut run_len: usize = 0; + for k in keep.iter().skip(keep_prefix_len) { + if *k { + break; + } + run_len += 1; + } + + let can_bulk_delete = run_len != 0 + && keep_prefix_len + run_len < keep.len() + && run_len > keep_prefix_len.saturating_mul(2).saturating_sub(1); + if can_bulk_delete { + let mut prefix = keep_prefix_len; + let mut run = run_len; + while prefix != 0 { + // Swap the top kept value with the bottom of the deletable run, + // making a deletable value immediately available to pop. + let sink_depth = prefix + run - 1; + self.stack.swap(sink_depth, self.actions); + keep.swap(0, sink_depth); + + debug_assert!(!keep[0], "bulk delete expected deletable value on top"); + self.stack.pop(self.actions); + keep.remove(0); + + run = run.saturating_sub(1); + prefix = prefix.saturating_sub(1); + } + + for _ in 0..run { + debug_assert!(!keep[0], "bulk delete expected deletable prefix"); + self.stack.pop(self.actions); + keep.remove(0); + } + continue; + } + + // Fallback: swap the first deletable value to the top and pop it. + let pos = keep + .iter() + .position(|k| !*k) + .expect("keep mask had deletable values"); + debug_assert!(pos != 0); + self.stack.swap(pos, self.actions); + keep.swap(0, pos); + self.stack.pop(self.actions); + keep.remove(0); + } + } + + fn try_normalize_to_exact(&mut self, desired: &[ValueId]) -> bool { + if desired.len() > self.ctx.reach.swap_max { + return false; + } + + let mut limit = self.stack.len_above_func_ret(); + if limit > self.ctx.reach.swap_max { + return false; + } + + // If the desired stack begins with one or more immediates, delay pushing them until after + // the existing (non-immediate) stack prefix is normalized. This avoids permuting around + // freshly-pushed constants (e.g. a loop-entry phi source `0`). + let imm_prefix_len = desired + .iter() + .take_while(|v| self.ctx.func.dfg.value_is_imm(**v)) + .count(); + if imm_prefix_len != 0 { + let tail = &desired[imm_prefix_len..]; + if !self.try_normalize_to_exact(tail) { + return false; + } + + for &v in desired[..imm_prefix_len].iter().rev() { + let imm = self + .ctx + .func + .dfg + .value_imm(v) + .expect("imm value missing payload"); + self.stack.push_imm(v, imm, self.actions); + } + return true; + } + + let mut req: BTreeMap = BTreeMap::new(); + for &v in desired.iter() { + *req.entry(v).or_insert(0) += 1; + } + + // 1) Remove any extra values (including duplicates) so the stack multiset matches `req`. + if !self.delete_to_match_multiset(&req) { + return false; + } + + // 2) Materialize any missing required multiplicities (DUP, PUSH, or MLOAD). + limit = self.stack.len_above_func_ret(); + if limit > self.ctx.reach.swap_max { + return false; + } + + let mut cur: BTreeMap = BTreeMap::new(); + for item in self.stack.iter().take(limit) { + let StackItem::Value(v) = item else { + continue; + }; + *cur.entry(*v).or_insert(0) += 1; + } + + for (&v, &need) in req.iter() { + let have = cur.get(&v).copied().unwrap_or(0); + if have >= need { + continue; + } + for _ in have..need { + if self.ctx.func.dfg.value_is_imm(v) { + let imm = self + .ctx + .func + .dfg + .value_imm(v) + .expect("imm value missing payload"); + self.stack.push_imm(v, imm, self.actions); + } else if let Some(pos) = self.stack.find_reachable_value(v, self.ctx.reach.dup_max) + { + self.stack.dup(pos, &mut *self.actions); + } else { + self.push_value_from_spill_slot_or_mark(v, v); + } + } + } + + // 3) Permute to the exact required order (bottom-up) using only SWAPs. + if self.stack.len_above_func_ret() != desired.len() { + return false; + } + let base_len = self.stack.common_suffix_len(desired); + let permute_limit = desired.len().saturating_sub(base_len); + for (depth, &want) in desired.iter().take(permute_limit).enumerate().rev() { + if self.stack.item_at(depth) == Some(&StackItem::Value(want)) { + continue; + } + + let Some(pos) = self.stack.find_reachable_value(want, depth + 1) else { + return false; + }; + + if pos != 0 { + self.stack.swap(pos, self.actions); + } + self.stack.swap(depth, self.actions); + + debug_assert!(self.stack.item_at(depth) == Some(&StackItem::Value(want))); + } + + true + } + + fn flush_rebuild(&mut self, desired: &[ValueId]) { + let base_len = self.stack.common_suffix_len(desired); + + // Pop everything above the common base suffix. + while self.stack.len_above_func_ret() > base_len { + self.stack.pop(self.actions); + } + + // Rebuild the remaining prefix bottom-to-top (push reverse so final is top-first). + for &v in desired[..desired.len().saturating_sub(base_len)] + .iter() + .rev() + { + if self.ctx.func.dfg.value_is_imm(v) { + let imm = self + .ctx + .func + .dfg + .value_imm(v) + .expect("imm value missing payload"); + self.stack.push_imm(v, imm, self.actions); + } else { + self.push_value_from_spill_slot_or_mark(v, v); + } + } + } + + /// Delete everything between the current top value and the function return barrier, preserving + /// the top value. + /// + /// This is a specialized cleanup used at internal returns. When there is more than one value + /// between `[ret_val]` and `FuncRetAddr`, we swap the return value down as close as possible + /// (up to `SWAP16`) and then pop a run of intermediates, repeating until the barrier is + /// directly below the top. + pub(super) fn delete_between_top_and_func_ret(&mut self) { + loop { + let Some(barrier_pos) = self.stack.func_ret_index() else { + break; + }; + if barrier_pos <= 1 { + break; + } + let between = barrier_pos.saturating_sub(1); + let swap_depth = between.min(self.ctx.reach.dup_max); + debug_assert!(swap_depth >= 1); + self.stack.swap(swap_depth, self.actions); + self.stack.pop_n(swap_depth, self.actions); + } + } +} + +fn matches_exact(stack: &SymStack, desired: &[ValueId]) -> bool { + let limit = stack.len_above_func_ret(); + limit == desired.len() + && stack + .iter() + .take(limit) + .zip(desired.iter().copied()) + .all(|(item, v)| item == &StackItem::Value(v)) +} diff --git a/crates/codegen/src/stackalloc/stackify/planner/operand_prep.rs b/crates/codegen/src/stackalloc/stackify/planner/operand_prep.rs new file mode 100644 index 00000000..6a8004db --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/planner/operand_prep.rs @@ -0,0 +1,487 @@ +use crate::{ + bitset::BitSet, + stackalloc::{Action, Actions}, +}; +use smallvec::SmallVec; +use sonatina_ir::{Function, InstId, ValueId}; +use std::collections::{BinaryHeap, HashMap}; + +use super::{ + super::{CONSUME_LAST_USE_MAX_SWAPS, sym_stack::StackItem}, + MemPlan, Planner, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct OperandPrepMetric { + /// Newly-discovered spill-set values (preferred to minimize). + spill_requests: usize, + /// Number of frame-slot loads emitted by operand preparation (preferred to minimize). + mem_loads: usize, + /// Number of `SWAP*` actions emitted by operand preparation (preferred to minimize). + swaps: usize, + /// Total actions emitted by operand preparation (tie-breaker). + actions: usize, +} + +struct BinaryOperandPrepPlan { + actions: Actions, + /// Stack above the function return barrier, after applying `actions`. + final_stack: Vec, + metric: OperandPrepMetric, +} + +impl<'a, 'ctx: 'a> Planner<'a, 'ctx> { + fn inst_is_commutative(&self, inst: InstId) -> bool { + use sonatina_ir::{ + InstDowncast, + inst::{arith, cmp, logic}, + }; + + let isb = self.ctx.func.inst_set(); + let inst = self.ctx.func.dfg.inst(inst); + + <&arith::Add as InstDowncast>::downcast(isb, inst).is_some() + || <&arith::Mul as InstDowncast>::downcast(isb, inst).is_some() + || <&logic::And as InstDowncast>::downcast(isb, inst).is_some() + || <&logic::Or as InstDowncast>::downcast(isb, inst).is_some() + || <&logic::Xor as InstDowncast>::downcast(isb, inst).is_some() + || <&cmp::Eq as InstDowncast>::downcast(isb, inst).is_some() + || <&cmp::Ne as InstDowncast>::downcast(isb, inst).is_some() + } + + pub(in super::super) fn prepare_operands_for_inst( + &mut self, + inst: InstId, + args: &mut SmallVec<[ValueId; 8]>, + consume_last_use: &BitSet, + ) { + // If all operands are last-use values that are already in the required order on the + // current stack top, avoid the operand-preparation machinery entirely. The instruction + // will consume them directly. + if !args.is_empty() + && args.iter().all(|&v| consume_last_use.contains(v)) + && self.stack_prefix_matches(args) + { + return; + } + + // For binary ops, solve operand preparation via a small exact search over `DUP*`/`SWAP*` + // and choose the cheapest plan. This models "consume in place" correctly and avoids + // redundant swap chains (common with the greedy per-operand preparation). + if args.len() == 2 && args[0] != args[1] { + let commutative = self.inst_is_commutative(inst); + if self.try_prepare_operands_binary_via_search(args, consume_last_use, commutative) { + return; + } + } + + // For commutative binary ops, try both operand orders and choose the cheaper + // operand-preparation plan. This is purely a bytecode optimization (SSA semantics are + // unchanged). + if args.len() == 2 && args[0] != args[1] && self.inst_is_commutative(inst) { + // Fast path: if the operands are last-use and already occupy the top two stack slots + // (in either order), use that order and consume them as-is. + if consume_last_use.contains(args[0]) && consume_last_use.contains(args[1]) { + if self.stack_prefix_matches(args) { + return; + } + let mut swapped: SmallVec<[ValueId; 8]> = args.clone(); + swapped.swap(0, 1); + if self.stack_prefix_matches(&swapped) { + args.swap(0, 1); + return; + } + } + + let original = self.simulate_operand_prep_metric(args, consume_last_use); + + let mut swapped: SmallVec<[ValueId; 8]> = args.clone(); + swapped.swap(0, 1); + let swapped_metric = self.simulate_operand_prep_metric(&swapped, consume_last_use); + + if swapped_metric < original { + args.swap(0, 1); + } + } + self.prepare_operands(args, consume_last_use); + } + + fn try_prepare_operands_binary_via_search( + &mut self, + args: &mut SmallVec<[ValueId; 8]>, + consume_last_use: &BitSet, + commutative: bool, + ) -> bool { + debug_assert_eq!(args.len(), 2); + let order = [args[0], args[1]]; + let mut best: Option<(BinaryOperandPrepPlan, bool)> = self + .search_binary_operand_prep(order, consume_last_use) + .map(|p| (p, false)); + + if commutative { + let swapped_order = [args[1], args[0]]; + if let Some(swapped) = self.search_binary_operand_prep(swapped_order, consume_last_use) + { + let better = match &best { + None => true, + Some((cur, _)) => swapped.metric < cur.metric, + }; + if better { + best = Some((swapped, true)); + } + } + } + + let Some((plan, swapped)) = best else { + return false; + }; + + if swapped { + args.swap(0, 1); + } + self.actions.extend_from_slice(&plan.actions); + self.stack.replace_above_func_ret(plan.final_stack); + true + } + + fn search_binary_operand_prep( + &self, + target: [ValueId; 2], + consume_last_use: &BitSet, + ) -> Option { + const MAX_ACTIONS: usize = 12; + + let above_len = self.stack.len_above_func_ret(); + let initial: Vec = self.stack.iter().take(above_len).cloned().collect(); + + // Only optimize when both operands can be produced without touching memory: either an + // immediate, or already present within `SWAP16` reach. + for v in target { + if !self.ctx.func.dfg.value_is_imm(v) { + let reachable = initial + .iter() + .take(self.ctx.reach.swap_max) + .any(|i| matches!(i, StackItem::Value(x) if *x == v)); + if !reachable { + return None; + } + } + } + + let metric0 = OperandPrepMetric { + spill_requests: 0, + mem_loads: 0, + swaps: 0, + actions: 0, + }; + + fn preserve_needed( + func: &Function, + consume_last_use: &BitSet, + v: ValueId, + ) -> bool { + !consume_last_use.contains(v) && !func.dfg.value_is_imm(v) + } + + fn is_goal( + func: &Function, + state: &[StackItem], + target: [ValueId; 2], + consume_last_use: &BitSet, + ) -> bool { + if state.len() < 2 { + return false; + } + if state[0] != StackItem::Value(target[0]) || state[1] != StackItem::Value(target[1]) { + return false; + } + for v in target { + if preserve_needed(func, consume_last_use, v) { + let copies = state + .iter() + .filter(|i| matches!(i, StackItem::Value(x) if *x == v)) + .count(); + if copies < 2 { + return false; + } + } + } + true + } + + #[derive(Clone)] + struct HeapItem { + metric: OperandPrepMetric, + id: u64, + state: Vec, + } + + impl PartialEq for HeapItem { + fn eq(&self, other: &Self) -> bool { + self.metric == other.metric && self.id == other.id + } + } + + impl Eq for HeapItem {} + + impl PartialOrd for HeapItem { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for HeapItem { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Reverse the order for a min-heap; use a deterministic FIFO-ish tie-break. + other + .metric + .cmp(&self.metric) + .then_with(|| other.id.cmp(&self.id)) + } + } + + let mut best: HashMap, OperandPrepMetric> = HashMap::new(); + let mut prev: HashMap, (Vec, Action)> = HashMap::new(); + let mut heap: BinaryHeap = BinaryHeap::new(); + + best.insert(initial.clone(), metric0); + heap.push(HeapItem { + metric: metric0, + id: 0, + state: initial.clone(), + }); + let mut next_id: u64 = 1; + + while let Some(item) = heap.pop() { + let Some(&cur_best) = best.get(&item.state) else { + continue; + }; + if item.metric != cur_best || item.metric.actions > MAX_ACTIONS { + continue; + } + + if is_goal(self.ctx.func, &item.state, target, consume_last_use) { + let mut actions: Vec = Vec::new(); + let mut cur = item.state.clone(); + while let Some((p, a)) = prev.get(&cur) { + actions.push(*a); + cur = p.clone(); + } + actions.reverse(); + let mut out: Actions = Actions::new(); + out.extend(actions.into_iter()); + return Some(BinaryOperandPrepPlan { + actions: out, + final_stack: item.state, + metric: item.metric, + }); + } + + // Neighbors: SWAP*, then DUP* for non-last-use operands, then PUSH for immediates. + let max_swap = self + .ctx + .reach + .swap_max + .saturating_sub(1) + .min(item.state.len().saturating_sub(1)); + for k in 1..=max_swap { + let mut next_state = item.state.clone(); + next_state.swap(0, k); + + let next_metric = OperandPrepMetric { + swaps: item.metric.swaps + 1, + actions: item.metric.actions + 1, + ..item.metric + }; + + let update = best + .get(&next_state) + .map(|m| next_metric < *m) + .unwrap_or(true); + if update { + best.insert(next_state.clone(), next_metric); + prev.insert( + next_state.clone(), + (item.state.clone(), Action::StackSwap(k as u8)), + ); + heap.push(HeapItem { + metric: next_metric, + id: next_id, + state: next_state, + }); + next_id += 1; + } + } + + for &v in target.iter() { + let max_dup = self.ctx.reach.dup_max.min(item.state.len()); + if preserve_needed(self.ctx.func, consume_last_use, v) + && let Some(pos) = (0..max_dup).find(|&i| item.state[i] == StackItem::Value(v)) + { + let mut next_state = item.state.clone(); + next_state.insert(0, StackItem::Value(v)); + + let next_metric = OperandPrepMetric { + actions: item.metric.actions + 1, + ..item.metric + }; + let update = best + .get(&next_state) + .map(|m| next_metric < *m) + .unwrap_or(true); + if update { + best.insert(next_state.clone(), next_metric); + prev.insert( + next_state.clone(), + (item.state.clone(), Action::StackDup(pos as u8)), + ); + heap.push(HeapItem { + metric: next_metric, + id: next_id, + state: next_state, + }); + next_id += 1; + } + } + } + + for &v in target.iter() { + if self.ctx.func.dfg.value_is_imm(v) { + let imm = self + .ctx + .func + .dfg + .value_imm(v) + .expect("imm value missing payload"); + let mut next_state = item.state.clone(); + next_state.insert(0, StackItem::Value(v)); + + let next_metric = OperandPrepMetric { + actions: item.metric.actions + 1, + ..item.metric + }; + let update = best + .get(&next_state) + .map(|m| next_metric < *m) + .unwrap_or(true); + if update { + best.insert(next_state.clone(), next_metric); + prev.insert(next_state.clone(), (item.state.clone(), Action::Push(imm))); + heap.push(HeapItem { + metric: next_metric, + id: next_id, + state: next_state, + }); + next_id += 1; + } + } + } + } + + None + } + + fn stack_prefix_matches(&self, args: &[ValueId]) -> bool { + if self.stack.len_above_func_ret() < args.len() { + return false; + } + self.stack + .iter() + .take(args.len()) + .zip(args.iter().copied()) + .all(|(item, arg)| item == &StackItem::Value(arg)) + } + + fn simulate_operand_prep_metric( + &self, + args: &[ValueId], + consume_last_use: &BitSet, + ) -> OperandPrepMetric { + let mut sim_stack = (*self.stack).clone(); + let mut sim_actions: Actions = Actions::new(); + let mut sim_spill_requests = self.mem.spill_requests().clone(); + let before_spill_requests = sim_spill_requests.len(); + + let mut sim_free_slots = self.mem.free_slots().clone(); + let mut sim_slots = self.mem.slot_state().clone(); + + { + let mem = MemPlan::new( + self.mem.spill_set(), + &mut sim_spill_requests, + self.ctx, + &mut sim_free_slots, + &mut sim_slots, + ); + let mut planner = Planner::new(self.ctx, &mut sim_stack, &mut sim_actions, mem); + planner.prepare_operands(args, consume_last_use); + } + + let mem_loads = sim_actions + .iter() + .filter(|a| matches!(a, Action::MemLoadFrameSlot(_) | Action::MemLoadAbs(_))) + .count(); + let swaps = sim_actions + .iter() + .filter(|a| matches!(a, Action::StackSwap(_))) + .count(); + + OperandPrepMetric { + spill_requests: sim_spill_requests + .len() + .saturating_sub(before_spill_requests), + mem_loads, + swaps, + actions: sim_actions.len(), + } + } + + pub(in super::super) fn prepare_operands( + &mut self, + args: &[ValueId], + consume_last_use: &BitSet, + ) { + // Iterate in reverse so the final stack order is `args[0]` on top, then `args[1]`, ... + let mut prepared: usize = 0; + let mut consumed_from_stack: BitSet = BitSet::default(); + for &v in args.iter().rev() { + if self.ctx.func.dfg.value_is_imm(v) { + let imm = self + .ctx + .func + .dfg + .value_imm(v) + .expect("imm value missing payload"); + self.stack.push_imm(v, imm, self.actions); + prepared += 1; + continue; + } + + // If this is a last-use, prefer consuming an existing stack copy (stable-rotated + // to the top) instead of duplicating it, but only when the swap chain is small. + // + // This is equivalent to a stable delete, except the pop is performed by the + // instruction consuming its operands. + if consume_last_use.contains(v) + && !consumed_from_stack.contains(v) + && let Some(pos) = + self.stack + .find_reachable_value_from(v, prepared, self.ctx.reach.swap_max) + && pos <= CONSUME_LAST_USE_MAX_SWAPS + { + self.stack.stable_rotate_to_top(pos, self.actions); + consumed_from_stack.insert(v); + prepared += 1; + continue; + } + + if let Some(pos) = self.stack.find_reachable_value(v, self.ctx.reach.dup_max) { + self.stack.dup(pos, self.actions); + prepared += 1; + } else { + self.push_value_from_spill_slot_or_mark(v, v); + prepared += 1; + } + } + } +} diff --git a/crates/codegen/src/stackalloc/stackify/slots.rs b/crates/codegen/src/stackalloc/stackify/slots.rs new file mode 100644 index 00000000..0e6386b4 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/slots.rs @@ -0,0 +1,155 @@ +use cranelift_entity::SecondaryMap; +use sonatina_ir::{BlockId, ValueId}; +use std::collections::BTreeSet; + +use crate::{bitset::BitSet, liveness::Liveness}; + +use super::spill::SpilledValueId; + +pub(super) const TRANSIENT_SLOT_TAG: u32 = 1 << 31; + +#[derive(Default, Clone)] +pub(super) struct SlotPool { + slot_of: SecondaryMap>, + next_slot: u32, + slot_live_blocks: Vec>, +} + +impl SlotPool { + pub(super) fn frame_size_slots(&self) -> u32 { + self.next_slot + } + + pub(super) fn snapshot_slot_map(&self) -> &SecondaryMap> { + &self.slot_of + } + + pub(super) fn take_slot_map(&mut self) -> SecondaryMap> { + std::mem::take(&mut self.slot_of) + } + + pub(super) fn try_ensure_slot( + &mut self, + v: SpilledValueId, + liveness: &Liveness, + free_slots: &mut FreeSlots, + max_slots: Option, + ) -> Option { + let v = v.value(); + if let Some(slot) = self.slot_of[v] { + return Some(slot); + } + + // Prefer reusing a slot that has been freed within this block (exact last-use tracking). + let slot = if let Some(slot) = free_slots.take_released() { + slot + } else { + let live_blocks = &liveness.val_live_blocks[v]; + let mut found: Option = None; + for candidate in 0..self.next_slot { + let idx = candidate as usize; + debug_assert!( + idx < self.slot_live_blocks.len(), + "slot_live_blocks out of sync: candidate={candidate} len={}", + self.slot_live_blocks.len() + ); + if self.slot_live_blocks[idx].is_disjoint(live_blocks) { + found = Some(candidate); + break; + } + } + if let Some(slot) = found { + slot + } else { + let can_grow = max_slots.is_none_or(|max| self.next_slot < max); + if !can_grow { + return None; + } + + let slot = self.next_slot; + self.next_slot = self + .next_slot + .checked_add(1) + .expect("frame slot index overflow"); + self.slot_live_blocks.push(BitSet::default()); + slot + } + }; + + self.slot_of[v] = Some(slot); + + // Track the live-block union for this slot to support cross-block reuse checks. + let idx = slot as usize; + debug_assert!( + idx < self.slot_live_blocks.len(), + "slot_live_blocks missing slot" + ); + self.slot_live_blocks[idx].union_with(&liveness.val_live_blocks[v]); + Some(slot) + } + + /// Return the frame slot for `v`, allocating one if needed. + /// + /// Allocation is deterministic: we reuse the lowest-numbered currently-free slot, falling back + /// to `next_slot` growth. In addition to within-block reuse, we also allow cross-block reuse + /// when liveness indicates two values' live block sets are disjoint (see + /// `Liveness::val_live_blocks`). + pub(super) fn ensure_slot( + &mut self, + v: SpilledValueId, + liveness: &Liveness, + free_slots: &mut FreeSlots, + ) -> u32 { + self.try_ensure_slot(v, liveness, free_slots, None) + .expect("frame slot allocation failed") + } + + pub(super) fn slot_of_value(&self, v: ValueId) -> Option { + self.snapshot_slot_map()[v] + } + + pub(super) fn release_if_assigned(&self, v: ValueId, free_slots: &mut FreeSlots) { + if let Some(slot) = self.slot_of_value(v) { + free_slots.release(slot); + } + } +} + +#[derive(Default, Clone)] +pub(super) struct FreeSlotPools { + pub(super) persistent: FreeSlots, + pub(super) transient: FreeSlots, + pub(super) scratch: FreeSlots, +} + +#[derive(Default, Clone)] +pub(super) struct SpillSlotPools { + pub(super) persistent: SlotPool, + pub(super) transient: SlotPool, + pub(super) scratch: SlotPool, +} + +/// Per-block free list for frame slots. +/// +/// A slot becomes available once the last use of the associated value occurs within the block and +/// the value is not live-out. New `spill_set` values do not allocate slots in discarded +/// fixed-point iterations; their slot is assigned at their definition once they become part of +/// `spill_set`. +#[derive(Default, Clone)] +pub(super) struct FreeSlots { + slots: BTreeSet, +} + +impl FreeSlots { + pub(super) fn release(&mut self, slot: u32) { + self.slots.insert(slot); + } + + pub(super) fn take_released(&mut self) -> Option { + if let Some(&slot) = self.slots.iter().next() { + self.slots.remove(&slot); + return Some(slot); + } + None + } +} diff --git a/crates/codegen/src/stackalloc/stackify/spill.rs b/crates/codegen/src/stackalloc/stackify/spill.rs new file mode 100644 index 00000000..b95aec98 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/spill.rs @@ -0,0 +1,66 @@ +use crate::bitset::BitSet; +use sonatina_ir::ValueId; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(super) struct SpilledValueId { + value: ValueId, +} + +impl SpilledValueId { + pub(super) fn value(self) -> ValueId { + self.value + } +} + +#[derive(Clone, Copy, Debug)] +pub(super) struct SpillSet<'a> { + spill_set: &'a BitSet, +} + +impl<'a> SpillSet<'a> { + pub(super) fn new(spill_set: &'a BitSet) -> Self { + Self { spill_set } + } + + pub(super) fn bitset(&self) -> &'a BitSet { + self.spill_set + } + + pub(super) fn contains(&self, v: ValueId) -> bool { + self.spill_set.contains(v) + } + + pub(super) fn spilled(&self, v: ValueId) -> Option { + self.contains(v).then_some(SpilledValueId { value: v }) + } +} + +pub(super) struct SpillDiscovery<'a> { + spill: SpillSet<'a>, + spill_requests: &'a mut BitSet, +} + +impl<'a> SpillDiscovery<'a> { + pub(super) fn new(spill: SpillSet<'a>, spill_requests: &'a mut BitSet) -> Self { + Self { + spill, + spill_requests, + } + } + + pub(super) fn spill_set(&self) -> SpillSet<'a> { + self.spill + } + + pub(super) fn spilled(&self, v: ValueId) -> Option { + self.spill.spilled(v) + } + + pub(super) fn request_spill(&mut self, v: ValueId) -> bool { + self.spill_requests.insert(v) + } + + pub(super) fn spill_requests(&self) -> &BitSet { + &*self.spill_requests + } +} diff --git a/crates/codegen/src/stackalloc/stackify/sym_stack.rs b/crates/codegen/src/stackalloc/stackify/sym_stack.rs new file mode 100644 index 00000000..9676f012 --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/sym_stack.rs @@ -0,0 +1,242 @@ +use crate::stackalloc::{Action, Actions}; +use sonatina_ir::{Function, Immediate, ValueId}; +use std::collections::VecDeque; + +use super::templates::BlockTemplate; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(super) enum StackItem { + Value(ValueId), + /// Implicit return address for the current function. + FuncRetAddr, + /// Temporary continuation address pushed for an internal `call`. + CallRetAddr, +} + +#[derive(Clone, Debug)] +pub(super) struct SymStack { + /// Top-first. + items: VecDeque, +} + +impl SymStack { + pub(super) fn entry_stack(func: &Function, has_internal_return: bool) -> Self { + let mut items: VecDeque = func + .arg_values + .iter() + .copied() + .map(StackItem::Value) + .collect(); + if has_internal_return { + items.push_back(StackItem::FuncRetAddr); + } + Self { items } + } + + pub(super) fn from_template(template: &BlockTemplate, has_internal_return: bool) -> Self { + let mut items: VecDeque = template + .params + .iter() + .copied() + .chain(template.transfer.iter().copied()) + .map(StackItem::Value) + .collect(); + if has_internal_return { + items.push_back(StackItem::FuncRetAddr); + } + Self { items } + } + + pub(super) fn func_ret_index(&self) -> Option { + self.items.iter().position(|i| *i == StackItem::FuncRetAddr) + } + + pub(super) fn len_above_func_ret(&self) -> usize { + self.func_ret_index().unwrap_or(self.items.len()) + } + + pub(super) fn common_suffix_len(&self, desired: &[ValueId]) -> usize { + let limit = self.len_above_func_ret(); + self.items + .iter() + .take(limit) + .rev() + .zip(desired.iter().rev().copied()) + .take_while(|&(item, want)| item == &StackItem::Value(want)) + .count() + } + + pub(super) fn len(&self) -> usize { + self.items.len() + } + + pub(super) fn top(&self) -> Option<&StackItem> { + self.items.front() + } + + pub(super) fn item_at(&self, idx: usize) -> Option<&StackItem> { + self.items.get(idx) + } + + pub(super) fn iter(&self) -> impl Iterator + '_ { + self.items.iter() + } + + pub(super) fn find_reachable_value(&self, v: ValueId, max_depth: usize) -> Option { + let limit = self.len_above_func_ret().min(max_depth); + self.items + .iter() + .take(limit) + .position(|item| item == &StackItem::Value(v)) + } + + pub(super) fn find_reachable_value_from( + &self, + v: ValueId, + start: usize, + max_depth: usize, + ) -> Option { + let limit = self.len_above_func_ret().min(max_depth); + self.items + .iter() + .skip(start) + .take(limit.saturating_sub(start)) + .position(|item| item == &StackItem::Value(v)) + .map(|off| start + off) + } + + pub(super) fn push_value(&mut self, v: ValueId) { + self.items.push_front(StackItem::Value(v)); + } + + pub(super) fn push_imm(&mut self, stack_as: ValueId, imm: Immediate, actions: &mut Actions) { + actions.push(Action::Push(imm)); + self.push_value(stack_as); + } + + pub(super) fn rename_top_value(&mut self, v: ValueId) { + let Some(StackItem::Value(top)) = self.items.front_mut() else { + panic!("expected StackItem::Value on top of stack") + }; + *top = v; + } + + pub(super) fn rename_value_at_depth(&mut self, depth: usize, v: ValueId) { + debug_assert!(depth < self.len_above_func_ret()); + let Some(StackItem::Value(slot)) = self.items.get_mut(depth) else { + panic!("expected StackItem::Value at depth {depth}") + }; + *slot = v; + } + + /// Duplicate `stack[pos]` to the top (`DUP{pos+1}`). + pub(super) fn dup(&mut self, pos: usize, actions: &mut Actions) { + debug_assert!(pos < super::DUP_MAX, "DUP out of range"); + debug_assert!(pos < self.len_above_func_ret()); + + let item = self.items[pos].clone(); + self.items.push_front(item); + actions.push(Action::StackDup(pos as u8)); + } + + /// Delete `stack[depth-1]` (1-indexed), preserving the relative order of the remaining items. + pub(super) fn stable_delete_at_depth(&mut self, depth: usize, actions: &mut Actions) { + debug_assert!((1..=super::SWAP_MAX).contains(&depth)); + debug_assert!(depth <= self.len_above_func_ret()); + + for k in 1..depth { + self.swap(k, actions); + } + self.pop(actions); + } + + pub(super) fn pop(&mut self, actions: &mut Actions) { + self.pop_operand(); + actions.push(Action::Pop); + } + + pub(super) fn pop_n(&mut self, n: usize, actions: &mut Actions) { + for _ in 0..n { + self.pop(actions); + } + } + + pub(super) fn swap(&mut self, depth: usize, actions: &mut Actions) { + if depth == 0 { + return; + } + debug_assert!(depth <= 16, "SWAP out of range"); + debug_assert!(depth < self.len_above_func_ret()); + + actions.push(Action::StackSwap(depth as u8)); + self.items.swap(0, depth); + } + + pub(super) fn stable_rotate_to_top(&mut self, pos: usize, actions: &mut Actions) { + if pos == 0 { + return; + } + debug_assert!(pos <= 16, "SWAP out of range"); + debug_assert!(pos < self.len_above_func_ret()); + for k in 1..=pos { + self.swap(k, actions); + } + } + + pub(super) fn clear_above_func_ret(&mut self, actions: &mut Actions) { + while let Some(top) = self.items.front() { + if *top == StackItem::FuncRetAddr { + break; + } + self.pop(actions); + } + } + + pub(super) fn replace_above_func_ret(&mut self, above: Vec) { + let Some(ret_pos) = self.func_ret_index() else { + self.items = above.into_iter().collect(); + return; + }; + + let suffix = self.items.split_off(ret_pos); + let mut items: VecDeque = above.into_iter().collect(); + items.extend(suffix); + self.items = items; + } + + pub(super) fn push_call_ret_addr(&mut self) { + self.items.push_front(StackItem::CallRetAddr); + } + + pub(super) fn push_call_continuation(&mut self, actions: &mut Actions) { + actions.push(Action::PushContinuationOffset); + self.push_call_ret_addr(); + } + + pub(super) fn remove_call_ret_addr(&mut self) { + let Some(pos) = self.items.iter().position(|i| *i == StackItem::CallRetAddr) else { + panic!("expected StackItem::CallRetAddr") + }; + self.items.remove(pos); + } + + pub(super) fn pop_operand(&mut self) { + match self.items.front() { + Some(StackItem::FuncRetAddr) => { + panic!("attempted to pop below function return barrier") + } + Some(_) => { + self.items.pop_front(); + } + None => { + panic!("stack underflow") + } + } + } + + pub(super) fn pop_n_operands(&mut self, n: usize) { + for _ in 0..n { + self.pop_operand(); + } + } +} diff --git a/crates/codegen/src/stackalloc/stackify/templates.rs b/crates/codegen/src/stackalloc/stackify/templates.rs new file mode 100644 index 00000000..ad31e41b --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/templates.rs @@ -0,0 +1,193 @@ +use cranelift_entity::SecondaryMap; +use smallvec::SmallVec; +use sonatina_ir::{BlockId, Function, ValueId, cfg::ControlFlowGraph}; + +use crate::{bitset::BitSet, domtree::DomTree, liveness::Liveness}; + +use super::spill::SpillSet; + +#[derive(Clone, Copy, Debug)] +pub(super) struct DefInfo { + def_block: BlockId, + /// Monotone per-block index; larger means "later". + def_index: u32, +} + +#[derive(Clone, Debug, Default)] +pub(super) struct BlockTemplate { + /// Parameter prefix (phi results; plus function args for the entry block). + pub(super) params: SmallVec<[ValueId; 4]>, + /// Canonical transfer region (top-first). + pub(super) transfer: SmallVec<[ValueId; 8]>, +} + +pub(super) fn compute_dom_depth(dom: &DomTree, entry: BlockId) -> SecondaryMap { + let mut depth = SecondaryMap::::new(); + depth[entry] = 0; + for &b in dom.rpo().iter().skip(1) { + let d = dom + .idom_of(b) + .and_then(|idom| depth.get(idom).copied()) + .unwrap_or(0); + depth[b] = d.saturating_add(1); + } + depth +} + +pub(super) fn compute_def_info( + func: &Function, + entry: BlockId, +) -> SecondaryMap> { + let mut info: SecondaryMap> = SecondaryMap::new(); + + // Treat function arguments as "defined" in the entry block, before any instruction. + for (idx, &arg) in func.arg_values.iter().enumerate() { + info[arg] = Some(DefInfo { + def_block: entry, + def_index: idx as u32, + }); + } + + for block in func.layout.iter_block() { + let mut idx: u32 = if block == entry { + func.arg_values.len() as u32 + } else { + 0 + }; + for inst in func.layout.iter_inst(block) { + let Some(res) = func.dfg.inst_result(inst) else { + continue; + }; + info[res] = Some(DefInfo { + def_block: block, + def_index: idx, + }); + idx = idx.saturating_add(1); + } + } + + info +} + +pub(super) fn compute_phi_results( + func: &Function, +) -> SecondaryMap> { + let mut phi_results: SecondaryMap> = SecondaryMap::new(); + for block in func.layout.iter_block() { + let mut results = SmallVec::<[ValueId; 4]>::new(); + for inst in func.layout.iter_inst(block) { + if !func.dfg.is_phi(inst) { + break; + } + if let Some(res) = func.dfg.inst_result(inst) { + results.push(res); + } + } + phi_results[block] = results; + } + phi_results +} + +pub(super) fn canonical_transfer_order( + carry: &BitSet, + dom_depth: &SecondaryMap, + def_info: &SecondaryMap>, +) -> SmallVec<[ValueId; 8]> { + sort_values_desc(carry.iter().collect(), dom_depth, def_info) + .into_iter() + .collect() +} + +pub(super) fn live_in_non_params( + liveness: &Liveness, + func: &Function, + block: BlockId, + entry: BlockId, + phi_results: &SecondaryMap>, + spill: SpillSet<'_>, +) -> BitSet { + let mut set = liveness.block_live_ins(block).clone(); + // Remove parameters (phi results; and args for the entry block). + for &p in phi_results[block].iter() { + set.remove(p); + } + if block == entry { + for &a in func.arg_values.iter() { + set.remove(a); + } + } + // Exclude memory-only (spilled) values. These will be loaded from memory when needed, + // not carried on the stack. + set.difference_with(spill.bitset()); + set +} + +fn value_key( + v: ValueId, + dom_depth: &SecondaryMap, + def_info: &SecondaryMap>, +) -> (u32, u32, u32, u32) { + let Some(info) = def_info[v] else { + return (0, 0, 0, v.as_u32()); + }; + let dd = dom_depth.get(info.def_block).copied().unwrap_or(0); + (dd, info.def_block.as_u32(), info.def_index, v.as_u32()) +} + +pub(super) fn function_has_internal_return(func: &Function) -> bool { + for block in func.layout.iter_block() { + for inst in func.layout.iter_inst(block) { + if func.dfg.is_return(inst) { + return true; + } + } + } + false +} + +fn sort_values_desc( + mut values: Vec, + dom_depth: &SecondaryMap, + def_info: &SecondaryMap>, +) -> Vec { + values.sort_by(|a, b| { + value_key(*b, dom_depth, def_info).cmp(&value_key(*a, dom_depth, def_info)) + }); + values +} + +pub(super) fn compute_phi_out_sources( + func: &Function, + cfg: &ControlFlowGraph, +) -> SecondaryMap> { + let mut sets: SecondaryMap> = SecondaryMap::new(); + for block in func.layout.iter_block() { + let mut set: BitSet = BitSet::default(); + for succ in cfg.succs_of(block) { + for v in phi_args_for_edge(func, block, *succ) { + if !func.dfg.value_is_imm(v) { + set.insert(v); + } + } + } + sets[block] = set; + } + sets +} + +pub(super) fn phi_args_for_edge( + func: &Function, + pred: BlockId, + succ: BlockId, +) -> SmallVec<[ValueId; 4]> { + func.layout + .iter_inst(succ) + .map_while(|inst| { + func.dfg.cast_phi(inst).and_then(|phi| { + phi.args() + .iter() + .find_map(|(val, block)| (*block == pred).then_some(*val)) + }) + }) + .collect() +} diff --git a/crates/codegen/src/stackalloc/stackify/trace.rs b/crates/codegen/src/stackalloc/stackify/trace.rs new file mode 100644 index 00000000..49f275fe --- /dev/null +++ b/crates/codegen/src/stackalloc/stackify/trace.rs @@ -0,0 +1,407 @@ +use std::{collections::BTreeMap, fmt::Write}; + +use sonatina_ir::{BlockId, Function, InstId, ValueId}; + +use crate::{bitset::BitSet, stackalloc::SpillSlotRef}; + +use super::{ + super::Action, + StackifyAlloc, + slots::TRANSIENT_SLOT_TAG, + sym_stack::{StackItem, SymStack}, + templates::BlockTemplate, +}; + +/// Optional observer hooks for tracing stackify planning. +/// +/// `StackifyAlloc::for_function_with_trace` is allowed to run multiple fixed-point iterations. +/// Observers must support `checkpoint`/`rollback` so unsuccessful iterations can be discarded. +pub(super) trait StackifyObserver { + type Checkpoint: Copy; + + fn checkpoint(&mut self) -> Self::Checkpoint; + fn rollback(&mut self, checkpoint: Self::Checkpoint); + + fn on_block_header(&mut self, _func: &Function, _block: BlockId, _template: &BlockTemplate) {} + + fn on_block_inherited( + &mut self, + _func: &Function, + _block: BlockId, + _pred: BlockId, + _inherited_stack: &SymStack, + _live_future: &BitSet, + _live_out: &BitSet, + ) { + } + + fn on_block_prologue(&mut self, _actions: &[Action]) {} + + fn on_inst_start( + &mut self, + _func: &Function, + _inst: InstId, + _stack: &SymStack, + _live_future: &BitSet, + _live_out: &BitSet, + _last_use: &BitSet, + ) { + } + + fn on_inst_actions( + &mut self, + _label: &'static str, + _actions: &[Action], + _dest: Option, + ) { + } + + fn on_inst_normal( + &mut self, + _func: &Function, + _inst: InstId, + _args: &[ValueId], + _res: Option, + ) { + } + + fn on_inst_jump(&mut self, _inst: InstId, _dest: BlockId) {} + + fn on_inst_br(&mut self, _func: &Function, _inst: InstId, _cond: ValueId, _dests: &[BlockId]) {} + + fn on_inst_br_table(&mut self, _inst: InstId) {} + + fn on_inst_return(&mut self, _func: &Function, _inst: InstId, _ret: Option) {} +} + +pub(super) struct NullObserver; + +impl StackifyObserver for NullObserver { + type Checkpoint = (); + + fn checkpoint(&mut self) -> Self::Checkpoint {} + + fn rollback(&mut self, _checkpoint: Self::Checkpoint) {} +} + +/// A snapshot-oriented trace collector for stackify planning. +/// +/// The `render` output is intentionally stable and human-oriented: it prints per-block templates, +/// symbolic stacks at each instruction (dead values marked with `x`), and labels action groups +/// such as cleanup, prelude, exit normalization, and internal returns. +#[derive(Default)] +pub(super) struct StackifyTrace { + out: String, + action_chunks: Vec, +} + +struct ActionChunk { + placeholder: String, + actions: crate::stackalloc::Actions, +} + +impl StackifyTrace { + pub(super) fn render(self, func: &Function, alloc: &StackifyAlloc) -> String { + if func.layout.iter_block().next().is_none() { + return "\n".to_string(); + } + + let mut out = String::new(); + let _ = writeln!(&mut out, "STACKIFY"); + + // Print spill-set slots in slot order. + let mut spill_set_by_slot: BTreeMap<(u8, u32), Vec> = BTreeMap::new(); + for (v, slot) in alloc.slot_of_value.iter() { + if let Some(slot) = *slot { + let slot = match slot { + SpillSlotRef::Scratch(slot) => (0, slot), + SpillSlotRef::Persistent(slot) => (1, slot), + SpillSlotRef::Transient(slot) => (2, alloc.persistent_frame_slots + slot), + }; + spill_set_by_slot.entry(slot).or_default().push(v); + } + } + if !spill_set_by_slot.is_empty() { + let _ = write!(&mut out, "spill_set: "); + for (i, ((kind, slot), mut values)) in spill_set_by_slot.into_iter().enumerate() { + values.sort_by_key(|v| v.as_u32()); + if i != 0 { + let _ = write!(&mut out, ", "); + } + let slot_label = if kind == 0 { + format!("s{slot}") + } else { + slot.to_string() + }; + if values.len() == 1 { + let _ = write!(&mut out, "{slot_label}={}", fmt_value(func, values[0])); + } else { + let _ = write!(&mut out, "{slot_label}={}", fmt_values(func, &values)); + } + } + let _ = writeln!(&mut out); + } + + let _ = writeln!(&mut out, "trace:"); + let mut trace = self.out; + for chunk in self.action_chunks { + let formatted = fmt_actions(&chunk.actions, alloc.persistent_frame_slots); + trace = trace.replace(&chunk.placeholder, &formatted); + } + out.push_str(&trace); + out + } + + fn push_actions_placeholder(&mut self, actions: &[Action]) -> String { + let idx = self.action_chunks.len(); + let placeholder = format!("@@ACTIONS:{idx}@@"); + let mut stored = crate::stackalloc::Actions::new(); + stored.extend_from_slice(actions); + self.action_chunks.push(ActionChunk { + placeholder: placeholder.clone(), + actions: stored, + }); + placeholder + } +} + +impl StackifyObserver for StackifyTrace { + type Checkpoint = (usize, usize); + + fn checkpoint(&mut self) -> Self::Checkpoint { + (self.out.len(), self.action_chunks.len()) + } + + fn rollback(&mut self, checkpoint: Self::Checkpoint) { + let (out_len, chunk_len) = checkpoint; + self.out.truncate(out_len); + self.action_chunks.truncate(chunk_len); + } + + fn on_block_header(&mut self, func: &Function, block: BlockId, template: &BlockTemplate) { + let _ = writeln!( + &mut self.out, + " {block:?} P={} T={}", + fmt_values(func, &template.params), + fmt_values(func, &template.transfer) + ); + } + + fn on_block_inherited( + &mut self, + func: &Function, + _block: BlockId, + pred: BlockId, + inherited_stack: &SymStack, + live_future: &BitSet, + live_out: &BitSet, + ) { + let _ = writeln!( + &mut self.out, + " inherited from {pred:?}: {}", + fmt_stack(func, inherited_stack, live_future, live_out) + ); + } + + fn on_block_prologue(&mut self, actions: &[Action]) { + if actions.is_empty() { + return; + } + let placeholder = self.push_actions_placeholder(actions); + let _ = writeln!(&mut self.out, " prologue: {placeholder}"); + } + + fn on_inst_start( + &mut self, + func: &Function, + _inst: InstId, + stack: &SymStack, + live_future: &BitSet, + live_out: &BitSet, + last_use: &BitSet, + ) { + let stack_start = fmt_stack(func, stack, live_future, live_out); + let last_use_list: Vec = last_use.iter().collect(); + if last_use_list.is_empty() { + let _ = writeln!(&mut self.out, " - stack={stack_start}"); + } else { + let _ = writeln!( + &mut self.out, + " - stack={stack_start}, last_use={}", + fmt_values(func, &last_use_list) + ); + } + } + + fn on_inst_actions(&mut self, label: &'static str, actions: &[Action], dest: Option) { + if actions.is_empty() { + return; + } + let placeholder = self.push_actions_placeholder(actions); + match (label, dest) { + ("exit", Some(dest)) => { + let _ = writeln!(&mut self.out, " exit({dest:?}): {placeholder}"); + } + _ => { + let _ = writeln!(&mut self.out, " {label}: {placeholder}"); + } + } + } + + fn on_inst_normal( + &mut self, + func: &Function, + inst: InstId, + args: &[ValueId], + res: Option, + ) { + let op_name = func.dfg.inst(inst).as_text(); + let _ = write!(&mut self.out, " {op_name} {}", fmt_values(func, args)); + if let Some(r) = res { + let _ = write!(&mut self.out, " -> {}", fmt_value(func, r)); + } + let _ = writeln!(&mut self.out); + } + + fn on_inst_jump(&mut self, _inst: InstId, dest: BlockId) { + let _ = writeln!(&mut self.out, " jump -> {dest:?}"); + } + + fn on_inst_br(&mut self, func: &Function, inst: InstId, cond: ValueId, dests: &[BlockId]) { + let op_name = func.dfg.inst(inst).as_text(); + let _ = writeln!( + &mut self.out, + " {op_name} {} -> {dests:?}", + fmt_values(func, &[cond]) + ); + } + + fn on_inst_br_table(&mut self, _inst: InstId) { + let _ = writeln!(&mut self.out, " br_table"); + } + + fn on_inst_return(&mut self, func: &Function, inst: InstId, ret: Option) { + let op_name = func.dfg.inst(inst).as_text(); + if let Some(ret) = ret { + let _ = writeln!( + &mut self.out, + " {op_name} {}", + fmt_values(func, &[ret]) + ); + } else { + let _ = writeln!(&mut self.out, " {op_name} []"); + } + } +} + +fn fmt_immediate(imm: sonatina_ir::Immediate) -> String { + use sonatina_ir::Immediate::*; + match imm { + I1(v) => format!("{v}"), + I8(v) => format!("{v}"), + I16(v) => format!("{v}"), + I32(v) => format!("{v}"), + I64(v) => format!("{v}"), + I128(v) => format!("{v}"), + I256(v) => format!("{v:?}"), + } +} + +fn fmt_value(func: &Function, v: ValueId) -> String { + if func.dfg.value_is_imm(v) { + let imm = func.dfg.value_imm(v).expect("imm value missing payload"); + return fmt_immediate(imm); + } + format!("v{}", v.as_u32()) +} + +fn fmt_values(func: &Function, values: &[ValueId]) -> String { + let mut s = String::new(); + s.push('['); + for (i, &v) in values.iter().enumerate() { + if i != 0 { + s.push_str(", "); + } + s.push_str(&fmt_value(func, v)); + } + s.push(']'); + s +} + +fn fmt_stack( + func: &Function, + stack: &SymStack, + live_future: &BitSet, + live_out: &BitSet, +) -> String { + let mut s = String::new(); + s.push('['); + for (i, item) in stack.iter().enumerate() { + if i != 0 { + s.push_str(", "); + } + match item { + StackItem::Value(v) => { + let v = *v; + let mut v_s = fmt_value(func, v); + if !func.dfg.value_is_imm(v) && !live_future.contains(v) && !live_out.contains(v) { + v_s.push('x'); + } + s.push_str(&v_s); + } + StackItem::FuncRetAddr => s.push_str(""), + StackItem::CallRetAddr => s.push_str(""), + } + } + s.push(']'); + s +} + +fn fmt_actions(actions: &[Action], persistent_frame_slots: u32) -> String { + fn decode_slot(slot: u32, persistent_frame_slots: u32) -> u32 { + if slot & TRANSIENT_SLOT_TAG != 0 { + persistent_frame_slots + .checked_add(slot & !TRANSIENT_SLOT_TAG) + .expect("frame slot overflow") + } else { + slot + } + } + + let mut s = String::new(); + s.push('['); + for (i, a) in actions.iter().enumerate() { + if i != 0 { + s.push_str(", "); + } + match *a { + Action::StackDup(n) => { + let _ = write!(&mut s, "DUP{}", n as u32 + 1); + } + Action::StackSwap(n) => { + let _ = write!(&mut s, "SWAP{n}"); + } + Action::Push(imm) => { + let _ = write!(&mut s, "PUSH({})", fmt_immediate(imm)); + } + Action::PushContinuationOffset => s.push_str("PUSH_CONT"), + Action::Pop => s.push_str("POP"), + Action::MemLoadAbs(addr) => { + let _ = write!(&mut s, "MLOAD_ABS({addr})"); + } + Action::MemLoadFrameSlot(slot) => { + let slot = decode_slot(slot, persistent_frame_slots); + let _ = write!(&mut s, "MLOAD_SLOT({slot})"); + } + Action::MemStoreAbs(addr) => { + let _ = write!(&mut s, "MSTORE_ABS({addr})"); + } + Action::MemStoreFrameSlot(slot) => { + let slot = decode_slot(slot, persistent_frame_slots); + let _ = write!(&mut s, "MSTORE_SLOT({slot})"); + } + } + } + s.push(']'); + s +} diff --git a/crates/codegen/test_files/evm/add.snap b/crates/codegen/test_files/evm/add.snap new file mode 100644 index 00000000..1620aaba --- /dev/null +++ b/crates/codegen/test_files/evm/add.snap @@ -0,0 +1,109 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/add.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: add scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0] + pre: [DUP1, PUSH(224)] + shr [224, v0] -> v1 + - stack=[v1, v0], last_use=[v0] + pre: [SWAP1, PUSH(32)] + shl [32, v0] -> v2 + - stack=[v2, v1], last_use=[v2] + pre: [PUSH(224)] + shr [224, v2] -> v3 + - stack=[v3, v1], last_use=[v1, v3] + pre: [PUSH_CONT, SWAP2] + call [v1, v3] -> v4 + - stack=[v4], last_use=[v4] + pre: [PUSH(0)] + mstore [0, v4] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %add(v0.i32, v1.i32) -> i32 + block0 P=[v0, v1] T=[] + - stack=[v0, v1, ], last_use=[v0, v1] + add [v0, v1] -> v2 + - stack=[v2, ] + return [v2] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + DUP1 // v1.i32 = shr 224.i32 v0; + PUSH1 0xe0 (224) + SHR + SWAP1 // v2.i32 = shl 32.i32 v0; + PUSH1 0x20 (32) + SHL + PUSH1 0xe0 (224) // v3.i32 = shr 224.i32 v2; + SHR + PUSH1 `pc + (4)` // v4.i32 = call %add v1 v3; + SWAP2 + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v4 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func public %add(v0.i32, v1.i32) -> i32 +add: + block0: + JUMPDEST + ADD // v2.i32 = add v0 v1; + SWAP1 // return v2; + JUMP + + + +--------------- + +// section runtime +0x5b5f358060e01c9060201b60e01c601491601b565b5f5260205ff35b019056 + + +Success { reason: Return, gas_used: 21132, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000021) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 80 DUP1 [0xb00000016000000000000000000000000000000000000000000000000] + 4 60 PUSH1 0xe0 [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 6 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 7 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xb] + 8 60 PUSH1 0x20 [0xb, 0xb00000016000000000000000000000000000000000000000000000000] + 10 1b SHL [0xb, 0xb00000016000000000000000000000000000000000000000000000000, 0x20] + 11 60 PUSH1 0xe0 [0xb, 0x1600000000000000000000000000000000000000000000000000000000] + 13 1c SHR [0xb, 0x1600000000000000000000000000000000000000000000000000000000, 0xe0] + 14 60 PUSH1 0x14 [0xb, 0x16] + 16 91 SWAP2 [0xb, 0x16, 0x14] + 17 60 PUSH1 0x1b [0x14, 0x16, 0xb] + 19 56 JUMP [0x14, 0x16, 0xb, 0x1b] + 27 5b JUMPDEST [0x14, 0x16, 0xb] + 28 01 ADD [0x14, 0x16, 0xb] + 29 90 SWAP1 [0x14, 0x21] + 30 56 JUMP [0x21, 0x14] + 20 5b JUMPDEST [0x21] + 21 5f PUSH0 [0x21] + 22 52 MSTORE [0x21, 0x0] + 23 60 PUSH1 0x20 [] + 25 5f PUSH0 [0x20] + 26 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/add.sntn b/crates/codegen/test_files/evm/add.sntn new file mode 100644 index 00000000..c279ba3f --- /dev/null +++ b/crates/codegen/test_files/evm/add.sntn @@ -0,0 +1,25 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i32 = shr 224.i32 v0; + v2.i32 = shl 32.i32 v0; + v3.i32 = shr 224.i32 v2; + v4.i32 = call %add v1 v3; + + mstore 0.i32 v4 i32; + evm_return 0.i8 32.i8; +} + +func public %add(v0.i32, v1.i32) -> i32 { + block0: + v2.i32 = add v0 v1; + return v2; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/alloca_diamond_reuse.snap b/crates/codegen/test_files/evm/alloca_diamond_reuse.snap new file mode 100644 index 00000000..5f043e19 --- /dev/null +++ b/crates/codegen/test_files/evm/alloca_diamond_reuse.snap @@ -0,0 +1,133 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/alloca_diamond_reuse.sntn +--- +evm mem plan: dyn_base=0xe0 static_base=0xc0 +evm mem plan: main scheme=StaticTree base_words=0 persistent_words=0 alloca_words=1 persistent_alloca_words=0 + alloca v1 class=Transient offset_words=0 size_words=1 addr=0xc0 + alloca v2 class=Transient offset_words=0 size_words=1 addr=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %main(v0.i1) -> i256 + block0 P=[v0] T=[] + - stack=[v0, ] + br [v0] -> [block1, block2] + block1 P=[] T=[] + inherited from block0: [] + - stack=[] + alloca [] -> v1 + - stack=[v1, ], last_use=[v1] + pre: [PUSH(I256 { is_negative: false, abs: 1 }), SWAP1] + mstore [v1, I256 { is_negative: false, abs: 1 }] + - stack=[] + jump -> block3 + block2 P=[] T=[] + inherited from block0: [] + - stack=[] + alloca [] -> v2 + - stack=[v2, ], last_use=[v2] + pre: [PUSH(I256 { is_negative: false, abs: 2 }), SWAP1] + mstore [v2, I256 { is_negative: false, abs: 2 }] + - stack=[] + jump -> block3 + block3 P=[] T=[] + - stack=[] + return: [PUSH(I256 { is_negative: false, abs: 0 })] + return [I256 { is_negative: false, abs: 0 }] + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT, PUSH(true)] + call [true] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %main(v0.i1) -> i256 +main: + block0: + JUMPDEST + ISZERO // br v0 block1 block2; + PUSH1 block2 + JUMPI + block1: + JUMPDEST + PUSH1 0xc0 (192) // v1.*i256 = alloca i256; + PUSH1 0x1 (1) // mstore v1 1.i256 i256; + SWAP1 + MSTORE + PUSH1 block3 // jump block3; + JUMP + block2: + JUMPDEST + PUSH1 0xc0 (192) // v2.*i256 = alloca i256; + PUSH1 0x2 (2) // mstore v2 2.i256 i256; + SWAP1 + MSTORE + block3: + JUMPDEST + PUSH0 // return 0.i256; + SWAP1 + JUMP + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (6)` // v0.i256 = call %main 1.i1; + PUSH1 0x1 (1) + PUSH0 + SIGNEXTEND + PUSH1 FuncRef(0) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b600a60015f0b6011565b5f5260205ff35b156020575b60c0600190526027565b60c0600290525b5f9056 + + +Success { reason: Return, gas_used: 21168, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000000) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x0a [] + 3 60 PUSH1 0x01 [0xa] + 5 5f PUSH0 [0xa, 0x1] + 6 0b SIGNEXTEND [0xa, 0x1, 0x0] + 7 60 PUSH1 0x11 [0xa, 0x1] + 9 56 JUMP [0xa, 0x1, 0x11] + 17 5b JUMPDEST [0xa, 0x1] + 18 15 ISZERO [0xa, 0x1] + 19 60 PUSH1 0x20 [0xa, 0x0] + 21 57 JUMPI [0xa, 0x0, 0x20] + 22 5b JUMPDEST [0xa] + 23 60 PUSH1 0xc0 [0xa] + 25 60 PUSH1 0x01 [0xa, 0xc0] + 27 90 SWAP1 [0xa, 0xc0, 0x1] + 28 52 MSTORE [0xa, 0x1, 0xc0] + 29 60 PUSH1 0x27 [0xa] + 31 56 JUMP [0xa, 0x27] + 39 5b JUMPDEST [0xa] + 40 5f PUSH0 [0xa] + 41 90 SWAP1 [0xa, 0x0] + 42 56 JUMP [0x0, 0xa] + 10 5b JUMPDEST [0x0] + 11 5f PUSH0 [0x0] + 12 52 MSTORE [0x0, 0x0] + 13 60 PUSH1 0x20 [] + 15 5f PUSH0 [0x20] + 16 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/alloca_diamond_reuse.sntn b/crates/codegen/test_files/evm/alloca_diamond_reuse.sntn new file mode 100644 index 00000000..2358c5ad --- /dev/null +++ b/crates/codegen/test_files/evm/alloca_diamond_reuse.sntn @@ -0,0 +1,33 @@ +target = "evm-ethereum-osaka" + +func public %main(v0.i1) -> i256 { + block0: + br v0 block1 block2; + + block1: + v1.*i256 = alloca i256; + mstore v1 1.i256 i256; + jump block3; + + block2: + v2.*i256 = alloca i256; + mstore v2 2.i256 i256; + jump block3; + + block3: + return 0.i256; +} + +func public %entry() { + block0: + v0.i256 = call %main 1.i1; + mstore 0.i32 v0 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} + diff --git a/crates/codegen/test_files/evm/alloca_loop_reuse.snap b/crates/codegen/test_files/evm/alloca_loop_reuse.snap new file mode 100644 index 00000000..ac202aa5 --- /dev/null +++ b/crates/codegen/test_files/evm/alloca_loop_reuse.snap @@ -0,0 +1,185 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/alloca_loop_reuse.sntn +--- +evm mem plan: dyn_base=0x100 static_base=0xc0 +evm mem plan: main scheme=StaticTree base_words=0 persistent_words=0 alloca_words=2 persistent_alloca_words=0 + alloca v0 class=Transient offset_words=0 size_words=1 addr=0xc0 + alloca v3 class=Transient offset_words=1 size_words=1 addr=0xe0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %main() -> i256 + block0 P=[] T=[] + - stack=[] + alloca [] -> v0 + - stack=[v0, ] + exit(block1): [PUSH(0)] + jump -> block1 + block1 P=[v1] T=[v0] + - stack=[v1, v0, ] + pre: [PUSH(I256 { is_negative: false, abs: 0 }), DUP3] + mstore [v0, I256 { is_negative: false, abs: 0 }] + - stack=[v1, v0, ] + pre: [PUSH(2), DUP2] + lt [v1, 2] -> v2 + - stack=[v2, v1, v0, ] + br [v2] -> [block2, block3] + block2 P=[] T=[v1, v0] + inherited from block1: [v1, v0, ] + - stack=[v1, v0, ] + alloca [] -> v3 + - stack=[v3, v1, v0, ], last_use=[v3] + pre: [PUSH(I256 { is_negative: false, abs: 1 }), SWAP1] + mstore [v3, I256 { is_negative: false, abs: 1 }] + - stack=[v1, v0, ], last_use=[v1] + pre: [PUSH(1)] + add [1, v1] -> v4 + - stack=[v4, v0, ] + jump -> block1 + block3 P=[] T=[] + inherited from block1: [v1x, v0x, ] + prologue: [POP, POP] + - stack=[] + return: [PUSH(I256 { is_negative: false, abs: 0 })] + return [I256 { is_negative: false, abs: 0 }] + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT] + call [] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %main() -> i256 +main: + block0: + JUMPDEST + PUSH1 0xc0 (192) // v0.*i256 = alloca i256; + PUSH0 // jump block1; + block1: + JUMPDEST + PUSH0 // mstore v0 0.i256 i256; + DUP3 + MSTORE + PUSH1 0x2 (2) // v2.i1 = lt v1 2.i32; + DUP2 + LT + ISZERO // br v2 block2 block3; + PUSH1 block3 + JUMPI + block2: + JUMPDEST + PUSH1 0xe0 (224) // v3.*i256 = alloca i256; + PUSH1 0x1 (1) // mstore v3 1.i256 i256; + SWAP1 + MSTORE + PUSH1 0x1 (1) // v4.i32 = add v1 1.i32; + ADD + PUSH1 block1 // jump block1; + JUMP + block3: + JUMPDEST + POP // return 0.i256; + POP + PUSH0 + SWAP1 + JUMP + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (3)` // v0.i256 = call %main; + PUSH1 FuncRef(0) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b6006600d565b5f5260205ff35b60c05f5b5f82526002811015602a575b60e0600190526001016011565b50505f9056 + + +Success { reason: Return, gas_used: 21292, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000000) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x06 [] + 3 60 PUSH1 0x0d [0x6] + 5 56 JUMP [0x6, 0xd] + 13 5b JUMPDEST [0x6] + 14 60 PUSH1 0xc0 [0x6] + 16 5f PUSH0 [0x6, 0xc0] + 17 5b JUMPDEST [0x6, 0xc0, 0x0] + 18 5f PUSH0 [0x6, 0xc0, 0x0] + 19 82 DUP3 [0x6, 0xc0, 0x0, 0x0] + 20 52 MSTORE [0x6, 0xc0, 0x0, 0x0, 0xc0] + 21 60 PUSH1 0x02 [0x6, 0xc0, 0x0] + 23 81 DUP2 [0x6, 0xc0, 0x0, 0x2] + 24 10 LT [0x6, 0xc0, 0x0, 0x2, 0x0] + 25 15 ISZERO [0x6, 0xc0, 0x0, 0x1] + 26 60 PUSH1 0x2a [0x6, 0xc0, 0x0, 0x0] + 28 57 JUMPI [0x6, 0xc0, 0x0, 0x0, 0x2a] + 29 5b JUMPDEST [0x6, 0xc0, 0x0] + 30 60 PUSH1 0xe0 [0x6, 0xc0, 0x0] + 32 60 PUSH1 0x01 [0x6, 0xc0, 0x0, 0xe0] + 34 90 SWAP1 [0x6, 0xc0, 0x0, 0xe0, 0x1] + 35 52 MSTORE [0x6, 0xc0, 0x0, 0x1, 0xe0] + 36 60 PUSH1 0x01 [0x6, 0xc0, 0x0] + 38 01 ADD [0x6, 0xc0, 0x0, 0x1] + 39 60 PUSH1 0x11 [0x6, 0xc0, 0x1] + 41 56 JUMP [0x6, 0xc0, 0x1, 0x11] + 17 5b JUMPDEST [0x6, 0xc0, 0x1] + 18 5f PUSH0 [0x6, 0xc0, 0x1] + 19 82 DUP3 [0x6, 0xc0, 0x1, 0x0] + 20 52 MSTORE [0x6, 0xc0, 0x1, 0x0, 0xc0] + 21 60 PUSH1 0x02 [0x6, 0xc0, 0x1] + 23 81 DUP2 [0x6, 0xc0, 0x1, 0x2] + 24 10 LT [0x6, 0xc0, 0x1, 0x2, 0x1] + 25 15 ISZERO [0x6, 0xc0, 0x1, 0x1] + 26 60 PUSH1 0x2a [0x6, 0xc0, 0x1, 0x0] + 28 57 JUMPI [0x6, 0xc0, 0x1, 0x0, 0x2a] + 29 5b JUMPDEST [0x6, 0xc0, 0x1] + 30 60 PUSH1 0xe0 [0x6, 0xc0, 0x1] + 32 60 PUSH1 0x01 [0x6, 0xc0, 0x1, 0xe0] + 34 90 SWAP1 [0x6, 0xc0, 0x1, 0xe0, 0x1] + 35 52 MSTORE [0x6, 0xc0, 0x1, 0x1, 0xe0] + 36 60 PUSH1 0x01 [0x6, 0xc0, 0x1] + 38 01 ADD [0x6, 0xc0, 0x1, 0x1] + 39 60 PUSH1 0x11 [0x6, 0xc0, 0x2] + 41 56 JUMP [0x6, 0xc0, 0x2, 0x11] + 17 5b JUMPDEST [0x6, 0xc0, 0x2] + 18 5f PUSH0 [0x6, 0xc0, 0x2] + 19 82 DUP3 [0x6, 0xc0, 0x2, 0x0] + 20 52 MSTORE [0x6, 0xc0, 0x2, 0x0, 0xc0] + 21 60 PUSH1 0x02 [0x6, 0xc0, 0x2] + 23 81 DUP2 [0x6, 0xc0, 0x2, 0x2] + 24 10 LT [0x6, 0xc0, 0x2, 0x2, 0x2] + 25 15 ISZERO [0x6, 0xc0, 0x2, 0x0] + 26 60 PUSH1 0x2a [0x6, 0xc0, 0x2, 0x1] + 28 57 JUMPI [0x6, 0xc0, 0x2, 0x1, 0x2a] + 42 5b JUMPDEST [0x6, 0xc0, 0x2] + 43 50 POP [0x6, 0xc0, 0x2] + 44 50 POP [0x6, 0xc0] + 45 5f PUSH0 [0x6] + 46 90 SWAP1 [0x6, 0x0] + 47 56 JUMP [0x0, 0x6] + 6 5b JUMPDEST [0x0] + 7 5f PUSH0 [0x0] + 8 52 MSTORE [0x0, 0x0] + 9 60 PUSH1 0x20 [] + 11 5f PUSH0 [0x20] + 12 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/alloca_loop_reuse.sntn b/crates/codegen/test_files/evm/alloca_loop_reuse.sntn new file mode 100644 index 00000000..c160bf8f --- /dev/null +++ b/crates/codegen/test_files/evm/alloca_loop_reuse.sntn @@ -0,0 +1,36 @@ +target = "evm-ethereum-osaka" + +func public %main() -> i256 { + block0: + v0.*i256 = alloca i256; + jump block1; + + block1: + v1.i32 = phi (0.i32 block0) (v4 block2); + mstore v0 0.i256 i256; + v2.i1 = lt v1 2.i32; + br v2 block2 block3; + + block2: + v3.*i256 = alloca i256; + mstore v3 1.i256 i256; + v4.i32 = add v1 1.i32; + jump block1; + + block3: + return 0.i256; +} + +func public %entry() { + block0: + v0.i256 = call %main; + mstore 0.i32 v0 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} + diff --git a/crates/codegen/test_files/evm/alloca_mem.snap b/crates/codegen/test_files/evm/alloca_mem.snap new file mode 100644 index 00000000..8361e006 --- /dev/null +++ b/crates/codegen/test_files/evm/alloca_mem.snap @@ -0,0 +1,191 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/alloca_mem.sntn +--- +evm mem plan: dyn_base=0xe0 static_base=0xc0 +evm mem plan: helper scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: main scheme=StaticTree base_words=0 persistent_words=0 alloca_words=1 persistent_alloca_words=0 + alloca v0 class=Transient offset_words=0 size_words=1 addr=0xc0 + alloca v4 class=Transient offset_words=0 size_words=1 addr=0xc0 + alloca v6 class=Transient offset_words=0 size_words=1 addr=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %helper() -> i256 + block0 P=[] T=[] + - stack=[] + return: [PUSH(I256 { is_negative: false, abs: 7 })] + return [I256 { is_negative: false, abs: 7 }] + +// func public %main() -> i256 +spill_set: 0=v0 + block0 P=[] T=[] + - stack=[] + alloca [] -> v0 + post: [DUP1, MSTORE_SLOT(0)] + - stack=[v0, ] + pre: [PUSH(I256 { is_negative: false, abs: 11 }), DUP2] + mstore [v0, I256 { is_negative: false, abs: 11 }] + - stack=[v0, ] + pre: [DUP1] + mload [v0] -> v1 + - stack=[v1x, v0, ] + cleanup: [POP] + pre: [PUSH_CONT] + call [] -> v2 + - stack=[v2, v0, ], last_use=[v2] + pre: [DUP2] + mstore [v0, v2] + - stack=[v0, ], last_use=[v0] + mload [v0] -> v3 + - stack=[v3, ] + alloca [] -> v4 + - stack=[v4, v3, ] + pre: [PUSH(I256 { is_negative: false, abs: 1 }), DUP2] + mstore [v4, I256 { is_negative: false, abs: 1 }] + - stack=[v4, v3, ], last_use=[v4] + mload [v4] -> v5 + - stack=[v5, v3, ] + alloca [] -> v6 + - stack=[v6, v5, v3, ] + pre: [PUSH(I256 { is_negative: false, abs: 2 }), DUP2] + mstore [v6, I256 { is_negative: false, abs: 2 }] + - stack=[v6, v5, v3, ], last_use=[v6] + mload [v6] -> v7 + - stack=[v7, v5, v3, ], last_use=[v3, v5] + pre: [SWAP2] + add [v3, v5] -> v8 + - stack=[v8, v7, ], last_use=[v7, v8] + add [v8, v7] -> v9 + - stack=[v9, ] + return [v9] + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT] + call [] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %helper() -> i256 +helper: + block0: + JUMPDEST + PUSH1 0x7 (7) // return 7.i256; + SWAP1 + JUMP + +// func public %main() -> i256 +main: + block0: + JUMPDEST + PUSH1 0xc0 (192) // v0.*i256 = alloca i256; + DUP1 + PUSH0 + MSTORE + PUSH1 0xb (11) // mstore v0 11.i256 i256; + DUP2 + MSTORE + DUP1 // v1.i256 = mload v0 i256; + MLOAD + POP // v2.i256 = call %helper; + PUSH1 `pc + (3)` + PUSH1 FuncRef(0) + JUMP + JUMPDEST + DUP2 // mstore v0 v2 i256; + MSTORE + MLOAD // v3.i256 = mload v0 i256; + PUSH1 0xc0 (192) // v4.*i256 = alloca i256; + PUSH1 0x1 (1) // mstore v4 1.i256 i256; + DUP2 + MSTORE + MLOAD // v5.i256 = mload v4 i256; + PUSH1 0xc0 (192) // v6.*i256 = alloca i256; + PUSH1 0x2 (2) // mstore v6 2.i256 i256; + DUP2 + MSTORE + MLOAD // v7.i256 = mload v6 i256; + SWAP2 // v8.i256 = add v3 v5; + ADD + ADD // v9.i256 = add v8 v7; + SWAP1 // return v9; + JUMP + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (3)` // v0.i256 = call %main; + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b6006600d565b5f5260205ff35b60c0805f52600b8152805150601f6036565b81525160c0600181525160c0600281525191010190565b60079056 + + +Success { reason: Return, gas_used: 21221, gas_refunded: 0, logs: [], output: Call(0x000000000000000000000000000000000000000000000000000000000000000a) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x06 [] + 3 60 PUSH1 0x0d [0x6] + 5 56 JUMP [0x6, 0xd] + 13 5b JUMPDEST [0x6] + 14 60 PUSH1 0xc0 [0x6] + 16 80 DUP1 [0x6, 0xc0] + 17 5f PUSH0 [0x6, 0xc0, 0xc0] + 18 52 MSTORE [0x6, 0xc0, 0xc0, 0x0] + 19 60 PUSH1 0x0b [0x6, 0xc0] + 21 81 DUP2 [0x6, 0xc0, 0xb] + 22 52 MSTORE [0x6, 0xc0, 0xb, 0xc0] + 23 80 DUP1 [0x6, 0xc0] + 24 51 MLOAD [0x6, 0xc0, 0xc0] + 25 50 POP [0x6, 0xc0, 0xb] + 26 60 PUSH1 0x1f [0x6, 0xc0] + 28 60 PUSH1 0x36 [0x6, 0xc0, 0x1f] + 30 56 JUMP [0x6, 0xc0, 0x1f, 0x36] + 54 5b JUMPDEST [0x6, 0xc0, 0x1f] + 55 60 PUSH1 0x07 [0x6, 0xc0, 0x1f] + 57 90 SWAP1 [0x6, 0xc0, 0x1f, 0x7] + 58 56 JUMP [0x6, 0xc0, 0x7, 0x1f] + 31 5b JUMPDEST [0x6, 0xc0, 0x7] + 32 81 DUP2 [0x6, 0xc0, 0x7] + 33 52 MSTORE [0x6, 0xc0, 0x7, 0xc0] + 34 51 MLOAD [0x6, 0xc0] + 35 60 PUSH1 0xc0 [0x6, 0x7] + 37 60 PUSH1 0x01 [0x6, 0x7, 0xc0] + 39 81 DUP2 [0x6, 0x7, 0xc0, 0x1] + 40 52 MSTORE [0x6, 0x7, 0xc0, 0x1, 0xc0] + 41 51 MLOAD [0x6, 0x7, 0xc0] + 42 60 PUSH1 0xc0 [0x6, 0x7, 0x1] + 44 60 PUSH1 0x02 [0x6, 0x7, 0x1, 0xc0] + 46 81 DUP2 [0x6, 0x7, 0x1, 0xc0, 0x2] + 47 52 MSTORE [0x6, 0x7, 0x1, 0xc0, 0x2, 0xc0] + 48 51 MLOAD [0x6, 0x7, 0x1, 0xc0] + 49 91 SWAP2 [0x6, 0x7, 0x1, 0x2] + 50 01 ADD [0x6, 0x2, 0x1, 0x7] + 51 01 ADD [0x6, 0x2, 0x8] + 52 90 SWAP1 [0x6, 0xa] + 53 56 JUMP [0xa, 0x6] + 6 5b JUMPDEST [0xa] + 7 5f PUSH0 [0xa] + 8 52 MSTORE [0xa, 0x0] + 9 60 PUSH1 0x20 [] + 11 5f PUSH0 [0x20] + 12 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/alloca_mem.sntn b/crates/codegen/test_files/evm/alloca_mem.sntn new file mode 100644 index 00000000..134c1aab --- /dev/null +++ b/crates/codegen/test_files/evm/alloca_mem.sntn @@ -0,0 +1,43 @@ +target = "evm-ethereum-osaka" + +func public %helper() -> i256 { + block0: + return 7.i256; +} + +func public %main() -> i256 { + block0: + v0.*i256 = alloca i256; + mstore v0 11.i256 i256; + v1.i256 = mload v0 i256; + + v2.i256 = call %helper; + mstore v0 v2 i256; + v3.i256 = mload v0 i256; + + v4.*i256 = alloca i256; + mstore v4 1.i256 i256; + v5.i256 = mload v4 i256; + + v6.*i256 = alloca i256; + mstore v6 2.i256 i256; + v7.i256 = mload v6 i256; + + v8.i256 = add v3 v5; + v9.i256 = add v8 v7; + return v9; +} + +func public %entry() { + block0: + v0.i256 = call %main; + mstore 0.i32 v0 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} + diff --git a/crates/codegen/test_files/evm/backedge_prefer.snap b/crates/codegen/test_files/evm/backedge_prefer.snap new file mode 100644 index 00000000..f03e35cf --- /dev/null +++ b/crates/codegen/test_files/evm/backedge_prefer.snap @@ -0,0 +1,194 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/backedge_prefer.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: prefer_backedge_layout scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT, PUSH(22), PUSH(11)] + call [11, 22] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func private %prefer_backedge_layout(v0.i32, v1.i32) -> i32 + block0 P=[v0, v1] T=[] + - stack=[v0, v1, ] + exit(block1): [SWAP1, PUSH(0)] + jump -> block1 + block1 P=[v2] T=[v1, v0] + - stack=[v2, v1, v0, ] + pre: [PUSH(3), DUP2] + lt [v2, 3] -> v3 + - stack=[v3, v2, v1, v0, ] + br [v3] -> [block2, block4] + block2 P=[] T=[v2, v1, v0] + inherited from block1: [v2, v1, v0, ] + - stack=[v2, v1, v0, ] + pre: [DUP2, DUP4] + add [v0, v1] -> v4 + - stack=[v4x, v2, v1, v0, ], last_use=[v2] + cleanup: [POP] + pre: [PUSH(1)] + add [1, v2] -> v5 + - stack=[v5, v1, v0, ] + jump -> block3 + block3 P=[] T=[v5, v1, v0] + - stack=[v5, v1, v0, ] + jump -> block1 + block4 P=[] T=[v2] + inherited from block1: [v2, v1x, v0x, ] + prologue: [SWAP1, POP, SWAP1, POP] + - stack=[v2, ] + return [v2] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (5)` // v0.i32 = call %prefer_backedge_layout 11.i32 22.i32; + PUSH1 0x16 (22) + PUSH1 0xb (11) + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func private %prefer_backedge_layout(v0.i32, v1.i32) -> i32 +prefer_backedge_layout: + block0: + JUMPDEST + SWAP1 // jump block1; + PUSH0 + block1: + JUMPDEST + PUSH1 0x3 (3) // v3.i1 = lt v2 3.i32; + DUP2 + LT + ISZERO // br v3 block2 block4; + PUSH1 block4 + JUMPI + block2: + JUMPDEST + DUP2 // v4.i32 = add v0 v1; + DUP4 + ADD + POP // v5.i32 = add v2 1.i32; + PUSH1 0x1 (1) + ADD + block3: + JUMPDEST + PUSH1 block1 // jump block1; + JUMP + block4: + JUMPDEST + SWAP1 // return v2; + POP + SWAP1 + POP + SWAP1 + JUMP + + + +--------------- + +// section runtime +0x5b600a6016600b6011565b5f5260205ff35b905f5b60038110156029575b818301506001015b6014565b905090509056 + + +Success { reason: Return, gas_used: 21313, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000003) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x0a [] + 3 60 PUSH1 0x16 [0xa] + 5 60 PUSH1 0x0b [0xa, 0x16] + 7 60 PUSH1 0x11 [0xa, 0x16, 0xb] + 9 56 JUMP [0xa, 0x16, 0xb, 0x11] + 17 5b JUMPDEST [0xa, 0x16, 0xb] + 18 90 SWAP1 [0xa, 0x16, 0xb] + 19 5f PUSH0 [0xa, 0xb, 0x16] + 20 5b JUMPDEST [0xa, 0xb, 0x16, 0x0] + 21 60 PUSH1 0x03 [0xa, 0xb, 0x16, 0x0] + 23 81 DUP2 [0xa, 0xb, 0x16, 0x0, 0x3] + 24 10 LT [0xa, 0xb, 0x16, 0x0, 0x3, 0x0] + 25 15 ISZERO [0xa, 0xb, 0x16, 0x0, 0x1] + 26 60 PUSH1 0x29 [0xa, 0xb, 0x16, 0x0, 0x0] + 28 57 JUMPI [0xa, 0xb, 0x16, 0x0, 0x0, 0x29] + 29 5b JUMPDEST [0xa, 0xb, 0x16, 0x0] + 30 81 DUP2 [0xa, 0xb, 0x16, 0x0] + 31 83 DUP4 [0xa, 0xb, 0x16, 0x0, 0x16] + 32 01 ADD [0xa, 0xb, 0x16, 0x0, 0x16, 0xb] + 33 50 POP [0xa, 0xb, 0x16, 0x0, 0x21] + 34 60 PUSH1 0x01 [0xa, 0xb, 0x16, 0x0] + 36 01 ADD [0xa, 0xb, 0x16, 0x0, 0x1] + 37 5b JUMPDEST [0xa, 0xb, 0x16, 0x1] + 38 60 PUSH1 0x14 [0xa, 0xb, 0x16, 0x1] + 40 56 JUMP [0xa, 0xb, 0x16, 0x1, 0x14] + 20 5b JUMPDEST [0xa, 0xb, 0x16, 0x1] + 21 60 PUSH1 0x03 [0xa, 0xb, 0x16, 0x1] + 23 81 DUP2 [0xa, 0xb, 0x16, 0x1, 0x3] + 24 10 LT [0xa, 0xb, 0x16, 0x1, 0x3, 0x1] + 25 15 ISZERO [0xa, 0xb, 0x16, 0x1, 0x1] + 26 60 PUSH1 0x29 [0xa, 0xb, 0x16, 0x1, 0x0] + 28 57 JUMPI [0xa, 0xb, 0x16, 0x1, 0x0, 0x29] + 29 5b JUMPDEST [0xa, 0xb, 0x16, 0x1] + 30 81 DUP2 [0xa, 0xb, 0x16, 0x1] + 31 83 DUP4 [0xa, 0xb, 0x16, 0x1, 0x16] + 32 01 ADD [0xa, 0xb, 0x16, 0x1, 0x16, 0xb] + 33 50 POP [0xa, 0xb, 0x16, 0x1, 0x21] + 34 60 PUSH1 0x01 [0xa, 0xb, 0x16, 0x1] + 36 01 ADD [0xa, 0xb, 0x16, 0x1, 0x1] + 37 5b JUMPDEST [0xa, 0xb, 0x16, 0x2] + 38 60 PUSH1 0x14 [0xa, 0xb, 0x16, 0x2] + 40 56 JUMP [0xa, 0xb, 0x16, 0x2, 0x14] + 20 5b JUMPDEST [0xa, 0xb, 0x16, 0x2] + 21 60 PUSH1 0x03 [0xa, 0xb, 0x16, 0x2] + 23 81 DUP2 [0xa, 0xb, 0x16, 0x2, 0x3] + 24 10 LT [0xa, 0xb, 0x16, 0x2, 0x3, 0x2] + 25 15 ISZERO [0xa, 0xb, 0x16, 0x2, 0x1] + 26 60 PUSH1 0x29 [0xa, 0xb, 0x16, 0x2, 0x0] + 28 57 JUMPI [0xa, 0xb, 0x16, 0x2, 0x0, 0x29] + 29 5b JUMPDEST [0xa, 0xb, 0x16, 0x2] + 30 81 DUP2 [0xa, 0xb, 0x16, 0x2] + 31 83 DUP4 [0xa, 0xb, 0x16, 0x2, 0x16] + 32 01 ADD [0xa, 0xb, 0x16, 0x2, 0x16, 0xb] + 33 50 POP [0xa, 0xb, 0x16, 0x2, 0x21] + 34 60 PUSH1 0x01 [0xa, 0xb, 0x16, 0x2] + 36 01 ADD [0xa, 0xb, 0x16, 0x2, 0x1] + 37 5b JUMPDEST [0xa, 0xb, 0x16, 0x3] + 38 60 PUSH1 0x14 [0xa, 0xb, 0x16, 0x3] + 40 56 JUMP [0xa, 0xb, 0x16, 0x3, 0x14] + 20 5b JUMPDEST [0xa, 0xb, 0x16, 0x3] + 21 60 PUSH1 0x03 [0xa, 0xb, 0x16, 0x3] + 23 81 DUP2 [0xa, 0xb, 0x16, 0x3, 0x3] + 24 10 LT [0xa, 0xb, 0x16, 0x3, 0x3, 0x3] + 25 15 ISZERO [0xa, 0xb, 0x16, 0x3, 0x0] + 26 60 PUSH1 0x29 [0xa, 0xb, 0x16, 0x3, 0x1] + 28 57 JUMPI [0xa, 0xb, 0x16, 0x3, 0x1, 0x29] + 41 5b JUMPDEST [0xa, 0xb, 0x16, 0x3] + 42 90 SWAP1 [0xa, 0xb, 0x16, 0x3] + 43 50 POP [0xa, 0xb, 0x3, 0x16] + 44 90 SWAP1 [0xa, 0xb, 0x3] + 45 50 POP [0xa, 0x3, 0xb] + 46 90 SWAP1 [0xa, 0x3] + 47 56 JUMP [0x3, 0xa] + 10 5b JUMPDEST [0x3] + 11 5f PUSH0 [0x3] + 12 52 MSTORE [0x3, 0x0] + 13 60 PUSH1 0x20 [] + 15 5f PUSH0 [0x20] + 16 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/backedge_prefer.sntn b/crates/codegen/test_files/evm/backedge_prefer.sntn new file mode 100644 index 00000000..f4c09721 --- /dev/null +++ b/crates/codegen/test_files/evm/backedge_prefer.sntn @@ -0,0 +1,36 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i32 = call %prefer_backedge_layout 11.i32 22.i32; + + mstore 0.i32 v0 i32; + evm_return 0.i8 32.i8; +} + +func private %prefer_backedge_layout(v0.i32, v1.i32) -> i32 { + block0: + jump block1; + + block1: + v2.i32 = phi (0.i32 block0) (v5 block3); + v3.i1 = lt v2 3.i32; + br v3 block2 block4; + + block2: + v4.i32 = add v0 v1; + v5.i32 = add v2 1.i32; + jump block3; + + block3: + jump block1; + + block4: + return v2; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/br_table.snap b/crates/codegen/test_files/evm/br_table.snap new file mode 100644 index 00000000..94f1c100 --- /dev/null +++ b/crates/codegen/test_files/evm/br_table.snap @@ -0,0 +1,160 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/br_table.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: dispatch scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0] + pre: [DUP1, PUSH(248)] + shr [248, v0] -> v1 + - stack=[v1, v0], last_use=[v0] + pre: [SWAP1, PUSH(224)] + shr [224, v0] -> v2 + - stack=[v2, v1], last_use=[v1, v2] + pre: [PUSH_CONT, SWAP2] + call [v1, v2] -> v3 + - stack=[v3], last_use=[v3] + pre: [PUSH(0)] + mstore [0, v3] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func private %dispatch(v0.i8, v1.i32) -> i32 + block0 P=[v0, v1] T=[] + - stack=[v0x, v1, ] + cleanup: [POP] + br_table + block1 P=[] T=[] + inherited from block0: [v1x, ] + prologue: [POP] + - stack=[] + return: [PUSH(100)] + return [100] + block2 P=[] T=[] + inherited from block0: [v1x, ] + prologue: [POP] + - stack=[] + return: [PUSH(200)] + return [200] + block3 P=[] T=[] + inherited from block0: [v1x, ] + prologue: [POP] + - stack=[] + return: [PUSH(300)] + return [300] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + DUP1 // v1.i8 = shr 248.i32 v0; + PUSH1 0xf8 (248) + SHR + SWAP1 // v2.i32 = shr 224.i32 v0; + PUSH1 0xe0 (224) + SHR + PUSH1 `pc + (4)` // v3.i32 = call %dispatch v1 v2; + SWAP2 + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v3 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func private %dispatch(v0.i8, v1.i32) -> i32 +dispatch: + block0: + JUMPDEST + POP // br_table v1 block3 (0.i32 block1) (11.i32 block2); + PUSH0 + DUP2 + EQ + PUSH1 block1 + JUMPI + PUSH1 0xb (11) + DUP2 + EQ + PUSH1 block2 + JUMPI + PUSH1 block3 + JUMP + block1: + JUMPDEST + POP // return 100.i32; + PUSH1 0x64 (100) + SWAP1 + JUMP + block2: + JUMPDEST + POP // return 200.i32; + PUSH1 0xc8 (200) + SWAP1 + JUMP + block3: + JUMPDEST + POP // return 300.i32; + PUSH2 0x12c (300) + SWAP1 + JUMP + + + +--------------- + +// section runtime +0x5b5f358060f81c9060e01c6011916018565b5f5260205ff35b505f8114602a57600b81146030576036565b50606490565b5060c890565b5061012c9056 + + +Success { reason: Return, gas_used: 21174, gas_refunded: 0, logs: [], output: Call(0x00000000000000000000000000000000000000000000000000000000000000c8) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 80 DUP1 [0xb00000016000000000000000000000000000000000000000000000000] + 4 60 PUSH1 0xf8 [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 6 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000, 0xf8] + 7 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0x0] + 8 60 PUSH1 0xe0 [0x0, 0xb00000016000000000000000000000000000000000000000000000000] + 10 1c SHR [0x0, 0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 11 60 PUSH1 0x11 [0x0, 0xb] + 13 91 SWAP2 [0x0, 0xb, 0x11] + 14 60 PUSH1 0x18 [0x11, 0xb, 0x0] + 16 56 JUMP [0x11, 0xb, 0x0, 0x18] + 24 5b JUMPDEST [0x11, 0xb, 0x0] + 25 50 POP [0x11, 0xb, 0x0] + 26 5f PUSH0 [0x11, 0xb] + 27 81 DUP2 [0x11, 0xb, 0x0] + 28 14 EQ [0x11, 0xb, 0x0, 0xb] + 29 60 PUSH1 0x2a [0x11, 0xb, 0x0] + 31 57 JUMPI [0x11, 0xb, 0x0, 0x2a] + 32 60 PUSH1 0x0b [0x11, 0xb] + 34 81 DUP2 [0x11, 0xb, 0xb] + 35 14 EQ [0x11, 0xb, 0xb, 0xb] + 36 60 PUSH1 0x30 [0x11, 0xb, 0x1] + 38 57 JUMPI [0x11, 0xb, 0x1, 0x30] + 48 5b JUMPDEST [0x11, 0xb] + 49 50 POP [0x11, 0xb] + 50 60 PUSH1 0xc8 [0x11] + 52 90 SWAP1 [0x11, 0xc8] + 53 56 JUMP [0xc8, 0x11] + 17 5b JUMPDEST [0xc8] + 18 5f PUSH0 [0xc8] + 19 52 MSTORE [0xc8, 0x0] + 20 60 PUSH1 0x20 [] + 22 5f PUSH0 [0x20] + 23 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/br_table.sntn b/crates/codegen/test_files/evm/br_table.sntn new file mode 100644 index 00000000..fca9ad34 --- /dev/null +++ b/crates/codegen/test_files/evm/br_table.sntn @@ -0,0 +1,32 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i8 = shr 248.i32 v0; + v2.i32 = shr 224.i32 v0; + v3.i32 = call %dispatch v1 v2; + + mstore 0.i32 v3 i32; + evm_return 0.i8 32.i8; +} + +func private %dispatch(v0.i8, v1.i32) -> i32 { + block0: + br_table v1 block3 (0.i32 block1) (11.i32 block2); + + block1: + return 100.i32; + + block2: + return 200.i32; + + block3: + return 300.i32; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/call.snap b/crates/codegen/test_files/evm/call.snap new file mode 100644 index 00000000..5e4df41d --- /dev/null +++ b/crates/codegen/test_files/evm/call.snap @@ -0,0 +1,225 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/call.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: square scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: invert scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: main scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0] + pre: [DUP1, PUSH(248)] + shr [248, v0] -> v1 + - stack=[v1, v0], last_use=[v0] + pre: [SWAP1, PUSH(32)] + shl [32, v0] -> v2 + - stack=[v2, v1], last_use=[v2] + pre: [PUSH(224)] + shr [224, v2] -> v3 + - stack=[v3, v1], last_use=[v1, v3] + pre: [PUSH_CONT, SWAP2] + call [v1, v3] -> v4 + - stack=[v4], last_use=[v4] + pre: [PUSH(0)] + mstore [0, v4] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func private %square(v0.i64) -> i64 + block0 P=[v0] T=[] + - stack=[v0, ], last_use=[v0] + pre: [DUP1] + mul [v0, v0] -> v1 + - stack=[v1, ] + return [v1] + +// func private %invert(v0.i64) -> i64 + block0 P=[v0] T=[] + - stack=[v0, ], last_use=[v0] + pre: [PUSH(-1)] + mul [-1, v0] -> v1 + - stack=[v1, ] + return [v1] + +// func public %main(v0.i8, v1.i64) -> i64 + block0 P=[v0, v1] T=[] + - stack=[v0, v1, ] + br_table + block1 P=[] T=[v1] + inherited from block0: [v0x, v1, ] + prologue: [POP] + - stack=[v1, ], last_use=[v1] + pre: [PUSH_CONT, SWAP1] + call [v1] -> v2 + - stack=[v2, ] + jump -> block4 + block2 P=[] T=[v1] + inherited from block0: [v0x, v1, ] + prologue: [POP] + - stack=[v1, ], last_use=[v1] + pre: [PUSH_CONT, SWAP1] + call [v1] -> v3 + - stack=[v3, ] + jump -> block4 + block3 P=[] T=[v0] + inherited from block0: [v0, v1x, ] + prologue: [SWAP1, POP] + - stack=[v0, ] + jump -> block4 + block4 P=[v4] T=[] + - stack=[v4, ] + return [v4] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + DUP1 // v1.i8 = shr 248.i32 v0; + PUSH1 0xf8 (248) + SHR + SWAP1 // v2.i32 = shl 32.i32 v0; + PUSH1 0x20 (32) + SHL + PUSH1 0xe0 (224) // v3.i64 = shr 224.i32 v2; + SHR + PUSH1 `pc + (4)` // v4.i64 = call %main v1 v3; + SWAP2 + PUSH1 FuncRef(3) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v4 i64; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func private %square(v0.i64) -> i64 +square: + block0: + JUMPDEST + DUP1 // v1.i64 = mul v0 v0; + MUL + SWAP1 // return v1; + JUMP + +// func private %invert(v0.i64) -> i64 +invert: + block0: + JUMPDEST + PUSH1 0xff (255) // v1.i64 = mul v0 -1.i64; + PUSH0 + SIGNEXTEND + MUL + SWAP1 // return v1; + JUMP + +// func public %main(v0.i8, v1.i64) -> i64 +main: + block0: + JUMPDEST + PUSH0 // br_table v0 block3 (0.i8 block1) (1.i8 block2); + DUP2 + EQ + PUSH1 block1 + JUMPI + PUSH1 0x1 (1) + DUP2 + EQ + PUSH1 block2 + JUMPI + PUSH1 block3 + JUMP + block1: + JUMPDEST + POP // v2.i64 = call %square v1; + PUSH1 `pc + (4)` + SWAP1 + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH1 block4 // jump block4; + JUMP + block2: + JUMPDEST + POP // v3.i64 = call %invert v1; + PUSH1 `pc + (4)` + SWAP1 + PUSH1 FuncRef(2) + JUMP + JUMPDEST + PUSH1 block4 // jump block4; + JUMP + block3: + JUMPDEST + SWAP1 // jump block4; + POP + block4: + JUMPDEST + SWAP1 // return v4; + JUMP + + + +--------------- + +// section runtime +0x5b5f358060f81c9060201b60e01c601491601b565b5f5260205ff35b5f8114602c57600181146038576044565b506034906052565b6047565b50604090604a565b6047565b90505b90565b60ff5f0b0290565b80029056 + + +Success { reason: Return, gas_used: 21203, gas_refunded: 0, logs: [], output: Call(0x00000000000000000000000000000000000000000000000000000000000001e4) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 80 DUP1 [0xb00000016000000000000000000000000000000000000000000000000] + 4 60 PUSH1 0xf8 [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 6 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000, 0xf8] + 7 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0x0] + 8 60 PUSH1 0x20 [0x0, 0xb00000016000000000000000000000000000000000000000000000000] + 10 1b SHL [0x0, 0xb00000016000000000000000000000000000000000000000000000000, 0x20] + 11 60 PUSH1 0xe0 [0x0, 0x1600000000000000000000000000000000000000000000000000000000] + 13 1c SHR [0x0, 0x1600000000000000000000000000000000000000000000000000000000, 0xe0] + 14 60 PUSH1 0x14 [0x0, 0x16] + 16 91 SWAP2 [0x0, 0x16, 0x14] + 17 60 PUSH1 0x1b [0x14, 0x16, 0x0] + 19 56 JUMP [0x14, 0x16, 0x0, 0x1b] + 27 5b JUMPDEST [0x14, 0x16, 0x0] + 28 5f PUSH0 [0x14, 0x16, 0x0] + 29 81 DUP2 [0x14, 0x16, 0x0, 0x0] + 30 14 EQ [0x14, 0x16, 0x0, 0x0, 0x0] + 31 60 PUSH1 0x2c [0x14, 0x16, 0x0, 0x1] + 33 57 JUMPI [0x14, 0x16, 0x0, 0x1, 0x2c] + 44 5b JUMPDEST [0x14, 0x16, 0x0] + 45 50 POP [0x14, 0x16, 0x0] + 46 60 PUSH1 0x34 [0x14, 0x16] + 48 90 SWAP1 [0x14, 0x16, 0x34] + 49 60 PUSH1 0x52 [0x14, 0x34, 0x16] + 51 56 JUMP [0x14, 0x34, 0x16, 0x52] + 82 5b JUMPDEST [0x14, 0x34, 0x16] + 83 80 DUP1 [0x14, 0x34, 0x16] + 84 02 MUL [0x14, 0x34, 0x16, 0x16] + 85 90 SWAP1 [0x14, 0x34, 0x1e4] + 86 56 JUMP [0x14, 0x1e4, 0x34] + 52 5b JUMPDEST [0x14, 0x1e4] + 53 60 PUSH1 0x47 [0x14, 0x1e4] + 55 56 JUMP [0x14, 0x1e4, 0x47] + 71 5b JUMPDEST [0x14, 0x1e4] + 72 90 SWAP1 [0x14, 0x1e4] + 73 56 JUMP [0x1e4, 0x14] + 20 5b JUMPDEST [0x1e4] + 21 5f PUSH0 [0x1e4] + 22 52 MSTORE [0x1e4, 0x0] + 23 60 PUSH1 0x20 [] + 25 5f PUSH0 [0x20] + 26 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/call.sntn b/crates/codegen/test_files/evm/call.sntn new file mode 100644 index 00000000..ffe11bb3 --- /dev/null +++ b/crates/codegen/test_files/evm/call.sntn @@ -0,0 +1,51 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i8 = shr 248.i32 v0; + v2.i32 = shl 32.i32 v0; + v3.i64 = shr 224.i32 v2; + v4.i64 = call %main v1 v3; + + mstore 0.i32 v4 i64; + evm_return 0.i8 32.i8; +} + +func private %square(v0.i64) -> i64 { + block0: + v1.i64 = mul v0 v0; + return v1; +} + +func private %invert(v0.i64) -> i64 { + block0: + v1.i64 = mul v0 -1.i64; + return v1; +} + +func public %main(v0.i8, v1.i64) -> i64 { + block0: + br_table v0 block3 (0.i8 block1) (1.i8 block2); + + block1: + v2.i64 = call %square v1; + jump block4; + + block2: + v3.i64 = call %invert v1; + jump block4; + + block3: + jump block4; + + block4: + v4.i64 = phi (v2 block1) (v3 block2) (v0 block3); + return v4; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/casts.snap b/crates/codegen/test_files/evm/casts.snap new file mode 100644 index 00000000..affde02c --- /dev/null +++ b/crates/codegen/test_files/evm/casts.snap @@ -0,0 +1,143 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/casts.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: main scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT] + call [] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %main() -> i256 + block0 P=[] T=[] + - stack=[] + pre: [PUSH(-1)] + sext [-1] -> v0 + - stack=[v0, ] + pre: [PUSH(true)] + zext [true] -> v1 + - stack=[v1, v0, ], last_use=[v0] + pre: [SWAP1] + trunc [v0] -> v2 + - stack=[v2, v1, ], last_use=[v2] + zext [v2] -> v3 + - stack=[v3, v1, ] + pre: [PUSH(42)] + int_to_ptr [42] -> v4 + - stack=[v4, v3, v1, ], last_use=[v4] + ptr_to_int [v4] -> v5 + - stack=[v5, v3, v1, ], last_use=[v5] + zext [v5] -> v6 + - stack=[v6, v3, v1, ], last_use=[v3, v6] + add [v6, v3] -> v7 + - stack=[v7, v1, ], last_use=[v7] + bitcast [v7] -> v8 + - stack=[v8, v1, ], last_use=[v1, v8] + add [v8, v1] -> v9 + - stack=[v9, ] + return [v9] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (3)` // v0.i256 = call %main; + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func public %main() -> i256 +main: + block0: + JUMPDEST + PUSH1 0xff (255) // v0.i256 = sext -1.i8 i256; + PUSH0 + SIGNEXTEND + PUSH0 + SIGNEXTEND + PUSH1 0x1 (1) // v1.i256 = zext 1.i1 i256; + PUSH0 + SIGNEXTEND + PUSH1 0x1 (1) + AND + SWAP1 // v2.i128 = trunc v0 i128; + PUSH16 0xffffffffffffffffffffffffffffffff (340282366920938463463374607431768211455) + AND + PUSH16 0xffffffffffffffffffffffffffffffff (340282366920938463463374607431768211455) // v3.i256 = zext v2 i256; + AND + PUSH1 0x2a (42) // v4.*i8 = int_to_ptr 42.i64 *i8; + PUSH8 0xffffffffffffffff (18446744073709551615) + AND + PUSH8 0xffffffffffffffff (18446744073709551615) // v5.i64 = ptr_to_int v4 i64; + AND + PUSH8 0xffffffffffffffff (18446744073709551615) // v6.i256 = zext v5 i256; + AND + ADD // v7.i256 = add v3 v6; + ADD // v9.i256 = add v8 v1; + SWAP1 // return v9; + JUMP + + + +--------------- + +// section runtime +0x5b6006600d565b5f5260205ff35b60ff5f0b5f0b60015f0b600116906fffffffffffffffffffffffffffffffff166fffffffffffffffffffffffffffffffff16602a67ffffffffffffffff1667ffffffffffffffff1667ffffffffffffffff1601019056 + + +Success { reason: Return, gas_used: 21172, gas_refunded: 0, logs: [], output: Call(0x000000000000000000000000000000010000000000000000000000000000002a) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x06 [] + 3 60 PUSH1 0x0d [0x6] + 5 56 JUMP [0x6, 0xd] + 13 5b JUMPDEST [0x6] + 14 60 PUSH1 0xff [0x6] + 16 5f PUSH0 [0x6, 0xff] + 17 0b SIGNEXTEND [0x6, 0xff, 0x0] + 18 5f PUSH0 [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff] + 19 0b SIGNEXTEND [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x0] + 20 60 PUSH1 0x01 [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff] + 22 5f PUSH0 [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1] + 23 0b SIGNEXTEND [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1, 0x0] + 24 60 PUSH1 0x01 [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1] + 26 16 AND [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1, 0x1] + 27 90 SWAP1 [0x6, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1] + 28 6f PUSH16 0xffffffffffffffffffffffffffffffff [0x6, 0x1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff] + 45 16 AND [0x6, 0x1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffff] + 46 6f PUSH16 0xffffffffffffffffffffffffffffffff [0x6, 0x1, 0xffffffffffffffffffffffffffffffff] + 63 16 AND [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffff] + 64 60 PUSH1 0x2a [0x6, 0x1, 0xffffffffffffffffffffffffffffffff] + 66 67 PUSH8 0xffffffffffffffff [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a] + 75 16 AND [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a, 0xffffffffffffffff] + 76 67 PUSH8 0xffffffffffffffff [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a] + 85 16 AND [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a, 0xffffffffffffffff] + 86 67 PUSH8 0xffffffffffffffff [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a] + 95 16 AND [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a, 0xffffffffffffffff] + 96 01 ADD [0x6, 0x1, 0xffffffffffffffffffffffffffffffff, 0x2a] + 97 01 ADD [0x6, 0x1, 0x100000000000000000000000000000029] + 98 90 SWAP1 [0x6, 0x10000000000000000000000000000002a] + 99 56 JUMP [0x10000000000000000000000000000002a, 0x6] + 6 5b JUMPDEST [0x10000000000000000000000000000002a] + 7 5f PUSH0 [0x10000000000000000000000000000002a] + 8 52 MSTORE [0x10000000000000000000000000000002a, 0x0] + 9 60 PUSH1 0x20 [] + 11 5f PUSH0 [0x20] + 12 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/casts.sntn b/crates/codegen/test_files/evm/casts.sntn new file mode 100644 index 00000000..dfe98ffa --- /dev/null +++ b/crates/codegen/test_files/evm/casts.sntn @@ -0,0 +1,31 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = call %main; + mstore 0.i32 v0 i256; + evm_return 0.i8 32.i8; +} + +func public %main() -> i256 { + block0: + v0.i256 = sext -1.i8 i256; + v1.i256 = zext 1.i1 i256; + v2.i128 = trunc v0 i128; + v3.i256 = zext v2 i256; + + v4.*i8 = int_to_ptr 42.i64 *i8; + v5.i64 = ptr_to_int v4 i64; + v6.i256 = zext v5 i256; + + v7.i256 = add v3 v6; + v8.i256 = bitcast v7 i256; + v9.i256 = add v8 v1; + return v9; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/commutative_order.snap b/crates/codegen/test_files/evm/commutative_order.snap new file mode 100644 index 00000000..d45b0efb --- /dev/null +++ b/crates/codegen/test_files/evm/commutative_order.snap @@ -0,0 +1,161 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/commutative_order.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: comm_add16 scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT, PUSH(15), PUSH(14), PUSH(13), PUSH(12), PUSH(11), PUSH(10), PUSH(9), PUSH(8), PUSH(7), PUSH(6), PUSH(5), PUSH(4), PUSH(3), PUSH(2), PUSH(1), PUSH(0)] + call [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func private %comm_add16(v0.i32, v1.i32, v2.i32, v3.i32, v4.i32, v5.i32, v6.i32, v7.i32, v8.i32, v9.i32, v10.i32, v11.i32, v12.i32, v13.i32, v14.i32, v15.i32) -> i32 + block0 P=[v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15] T=[] + - stack=[v0, v1, v2x, v3x, v4x, v5x, v6x, v7x, v8x, v9x, v10x, v11x, v12x, v13x, v14x, v15, ], last_use=[v15] + pre: [SWAP15, PUSH(1)] + add [1, v15] -> v16 + - stack=[v16, v1, v2x, v3x, v4x, v5x, v6x, v7x, v8x, v9x, v10x, v11x, v12x, v13x, v14x, v0, ] + exit(block1): [SWAP14, POP, SWAP12, POP, POP, POP, POP, POP, POP, POP, POP, POP, POP, POP, POP, SWAP1] + jump -> block1 + block1 P=[] T=[v16, v1, v0] + - stack=[v16, v1, v0, ], last_use=[v0, v1] + pre: [SWAP2] + add [v0, v1] -> v17 + - stack=[v17x, v16, ] + cleanup: [POP] + return [v16] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (19)` // v0.i32 = call %comm_add16 0.i32 1.i32 2.i32 3.i32 4.i32 5.i32 6.i32 7.i32 8.i32 9.i32 10.i32 11.i32 12.i32 13.i32 14.i32 15.i32; + PUSH1 0xf (15) + PUSH1 0xe (14) + PUSH1 0xd (13) + PUSH1 0xc (12) + PUSH1 0xb (11) + PUSH1 0xa (10) + PUSH1 0x9 (9) + PUSH1 0x8 (8) + PUSH1 0x7 (7) + PUSH1 0x6 (6) + PUSH1 0x5 (5) + PUSH1 0x4 (4) + PUSH1 0x3 (3) + PUSH1 0x2 (2) + PUSH1 0x1 (1) + PUSH0 + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func private %comm_add16(v0.i32, v1.i32, v2.i32, v3.i32, v4.i32, v5.i32, v6.i32, v7.i32, v8.i32, v9.i32, v10.i32, v11.i32, v12.i32, v13.i32, v14.i32, v15.i32) -> i32 +comm_add16: + block0: + JUMPDEST + SWAP15 // v16.i32 = add v15 1.i32; + PUSH1 0x1 (1) + ADD + SWAP14 // jump block1; + POP + SWAP12 + POP + POP + POP + POP + POP + POP + POP + POP + POP + POP + POP + POP + SWAP1 + block1: + JUMPDEST + SWAP2 // v17.i32 = add v0 v1; + ADD + POP // return v16; + SWAP1 + JUMP + + + +--------------- + +// section runtime +0x5b6025600f600e600d600c600b600a6009600860076006600560046003600260015f602c565b5f5260205ff35b9e6001019d509b505050505050505050505050905b9101509056 + + +Success { reason: Return, gas_used: 21197, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000010) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x25 [] + 3 60 PUSH1 0x0f [0x25] + 5 60 PUSH1 0x0e [0x25, 0xf] + 7 60 PUSH1 0x0d [0x25, 0xf, 0xe] + 9 60 PUSH1 0x0c [0x25, 0xf, 0xe, 0xd] + 11 60 PUSH1 0x0b [0x25, 0xf, 0xe, 0xd, 0xc] + 13 60 PUSH1 0x0a [0x25, 0xf, 0xe, 0xd, 0xc, 0xb] + 15 60 PUSH1 0x09 [0x25, 0xf, …, 0xc, 0xb, 0xa] (len=7) + 17 60 PUSH1 0x08 [0x25, 0xf, …, 0xb, 0xa, 0x9] (len=8) + 19 60 PUSH1 0x07 [0x25, 0xf, …, 0xa, 0x9, 0x8] (len=9) + 21 60 PUSH1 0x06 [0x25, 0xf, …, 0x9, 0x8, 0x7] (len=10) + 23 60 PUSH1 0x05 [0x25, 0xf, …, 0x8, 0x7, 0x6] (len=11) + 25 60 PUSH1 0x04 [0x25, 0xf, …, 0x7, 0x6, 0x5] (len=12) + 27 60 PUSH1 0x03 [0x25, 0xf, …, 0x6, 0x5, 0x4] (len=13) + 29 60 PUSH1 0x02 [0x25, 0xf, …, 0x5, 0x4, 0x3] (len=14) + 31 60 PUSH1 0x01 [0x25, 0xf, …, 0x4, 0x3, 0x2] (len=15) + 33 5f PUSH0 [0x25, 0xf, …, 0x3, 0x2, 0x1] (len=16) + 34 60 PUSH1 0x2c [0x25, 0xf, …, 0x2, 0x1, 0x0] (len=17) + 36 56 JUMP [0x25, 0xf, …, 0x1, 0x0, 0x2c] (len=18) + 44 5b JUMPDEST [0x25, 0xf, …, 0x2, 0x1, 0x0] (len=17) + 45 9e SWAP15 [0x25, 0xf, …, 0x2, 0x1, 0x0] (len=17) + 46 60 PUSH1 0x01 [0x25, 0x0, …, 0x2, 0x1, 0xf] (len=17) + 48 01 ADD [0x25, 0x0, …, 0x1, 0xf, 0x1] (len=18) + 49 9d SWAP14 [0x25, 0x0, …, 0x2, 0x1, 0x10] (len=17) + 50 50 POP [0x25, 0x0, …, 0x2, 0x1, 0xe] (len=17) + 51 9b SWAP12 [0x25, 0x0, …, 0x3, 0x2, 0x1] (len=16) + 52 50 POP [0x25, 0x0, …, 0x3, 0x2, 0xd] (len=16) + 53 50 POP [0x25, 0x0, …, 0x4, 0x3, 0x2] (len=15) + 54 50 POP [0x25, 0x0, …, 0x5, 0x4, 0x3] (len=14) + 55 50 POP [0x25, 0x0, …, 0x6, 0x5, 0x4] (len=13) + 56 50 POP [0x25, 0x0, …, 0x7, 0x6, 0x5] (len=12) + 57 50 POP [0x25, 0x0, …, 0x8, 0x7, 0x6] (len=11) + 58 50 POP [0x25, 0x0, …, 0x9, 0x8, 0x7] (len=10) + 59 50 POP [0x25, 0x0, …, 0xa, 0x9, 0x8] (len=9) + 60 50 POP [0x25, 0x0, …, 0xb, 0xa, 0x9] (len=8) + 61 50 POP [0x25, 0x0, …, 0xc, 0xb, 0xa] (len=7) + 62 50 POP [0x25, 0x0, 0x10, 0x1, 0xc, 0xb] + 63 50 POP [0x25, 0x0, 0x10, 0x1, 0xc] + 64 90 SWAP1 [0x25, 0x0, 0x10, 0x1] + 65 5b JUMPDEST [0x25, 0x0, 0x1, 0x10] + 66 91 SWAP2 [0x25, 0x0, 0x1, 0x10] + 67 01 ADD [0x25, 0x10, 0x1, 0x0] + 68 50 POP [0x25, 0x10, 0x1] + 69 90 SWAP1 [0x25, 0x10] + 70 56 JUMP [0x10, 0x25] + 37 5b JUMPDEST [0x10] + 38 5f PUSH0 [0x10] + 39 52 MSTORE [0x10, 0x0] + 40 60 PUSH1 0x20 [] + 42 5f PUSH0 [0x20] + 43 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/commutative_order.sntn b/crates/codegen/test_files/evm/commutative_order.sntn new file mode 100644 index 00000000..1fe8d3f7 --- /dev/null +++ b/crates/codegen/test_files/evm/commutative_order.sntn @@ -0,0 +1,25 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i32 = call %comm_add16 0.i32 1.i32 2.i32 3.i32 4.i32 5.i32 6.i32 7.i32 8.i32 9.i32 10.i32 11.i32 12.i32 13.i32 14.i32 15.i32; + + mstore 0.i32 v0 i32; + evm_return 0.i8 32.i8; +} + +func private %comm_add16(v0.i32, v1.i32, v2.i32, v3.i32, v4.i32, v5.i32, v6.i32, v7.i32, v8.i32, v9.i32, v10.i32, v11.i32, v12.i32, v13.i32, v14.i32, v15.i32) -> i32 { + block0: + v16.i32 = add v15 1.i32; + jump block1; + + block1: + v17.i32 = add v0 v1; + return v16; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/const_loop.snap b/crates/codegen/test_files/evm/const_loop.snap new file mode 100644 index 00000000..8dbf38d6 --- /dev/null +++ b/crates/codegen/test_files/evm/const_loop.snap @@ -0,0 +1,119 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/const_loop.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: const_loop scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT] + call [] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %const_loop() -> i8 + block1 P=[] T=[] + - stack=[] + exit(block2): [PUSH(1)] + jump -> block2 + block2 P=[v0] T=[] + - stack=[v0, ] + pre: [PUSH(10), DUP2] + add [v0, 10] -> v1 + - stack=[v1, v0, ], last_use=[v0] + pre: [SWAP1, DUP2] + gt [v1, v0] -> v2 + - stack=[v2, v1, ] + br [v2] -> [block3, block4] + block3 P=[] T=[v1] + inherited from block2: [v1, ] + - stack=[v1, ] + return [v1] + block4 P=[] T=[v1] + inherited from block2: [v1, ] + - stack=[v1, ] + jump -> block2 + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (3)` // v0.i8 = call %const_loop; + PUSH1 FuncRef(1) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i8; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func public %const_loop() -> i8 +const_loop: + block1: + JUMPDEST + PUSH1 0x1 (1) // jump block2; + block2: + JUMPDEST + PUSH1 0xa (10) // v2.i8 = add v1 10.i8; + DUP2 + ADD + SWAP1 // v3.i1 = gt v2 v1; + DUP2 + GT + ISZERO // br v3 block3 block4; + PUSH1 block4 + JUMPI + block3: + JUMPDEST + SWAP1 // return v2; + JUMP + block4: + JUMPDEST + PUSH1 block2 // jump block2; + JUMP + + + +--------------- + +// section runtime +0x5b6006600d565b5f5260205ff35b60015b600a810190811115601f575b90565b601056 + + +Success { reason: Return, gas_used: 21136, gas_refunded: 0, logs: [], output: Call(0x000000000000000000000000000000000000000000000000000000000000000b) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x06 [] + 3 60 PUSH1 0x0d [0x6] + 5 56 JUMP [0x6, 0xd] + 13 5b JUMPDEST [0x6] + 14 60 PUSH1 0x01 [0x6] + 16 5b JUMPDEST [0x6, 0x1] + 17 60 PUSH1 0x0a [0x6, 0x1] + 19 81 DUP2 [0x6, 0x1, 0xa] + 20 01 ADD [0x6, 0x1, 0xa, 0x1] + 21 90 SWAP1 [0x6, 0x1, 0xb] + 22 81 DUP2 [0x6, 0xb, 0x1] + 23 11 GT [0x6, 0xb, 0x1, 0xb] + 24 15 ISZERO [0x6, 0xb, 0x1] + 25 60 PUSH1 0x1f [0x6, 0xb, 0x0] + 27 57 JUMPI [0x6, 0xb, 0x0, 0x1f] + 28 5b JUMPDEST [0x6, 0xb] + 29 90 SWAP1 [0x6, 0xb] + 30 56 JUMP [0xb, 0x6] + 6 5b JUMPDEST [0xb] + 7 5f PUSH0 [0xb] + 8 52 MSTORE [0xb, 0x0] + 9 60 PUSH1 0x20 [] + 11 5f PUSH0 [0x20] + 12 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/const_loop.sntn b/crates/codegen/test_files/evm/const_loop.sntn new file mode 100644 index 00000000..15893e6c --- /dev/null +++ b/crates/codegen/test_files/evm/const_loop.sntn @@ -0,0 +1,29 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i8 = call %const_loop; + + mstore 0.i32 v0 i8; + evm_return 0.i8 32.i8; +} + +func public %const_loop() -> i8 { + block1: + jump block2; + + block2: + v1.i8 = phi (1.i8 block1) (v2 block2); + v2.i8 = add v1 10.i8; + v3.i1 = gt v2 v1; + br v3 block3 block2; + + block3: + return v2; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/cross_block_slot_reuse.snap b/crates/codegen/test_files/evm/cross_block_slot_reuse.snap new file mode 100644 index 00000000..731e52b1 --- /dev/null +++ b/crates/codegen/test_files/evm/cross_block_slot_reuse.snap @@ -0,0 +1,573 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/cross_block_slot_reuse.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + jump -> block1 + block1 P=[] T=[] + - stack=[] + pre: [PUSH(I256 { is_negative: false, abs: 2 }), PUSH(I256 { is_negative: false, abs: 1 })] + add [I256 { is_negative: false, abs: 1 }, I256 { is_negative: false, abs: 2 }] -> v0 + - stack=[v0] + pre: [PUSH(I256 { is_negative: false, abs: 4 }), PUSH(I256 { is_negative: false, abs: 3 })] + add [I256 { is_negative: false, abs: 3 }, I256 { is_negative: false, abs: 4 }] -> v1 + - stack=[v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 6 }), PUSH(I256 { is_negative: false, abs: 5 })] + add [I256 { is_negative: false, abs: 5 }, I256 { is_negative: false, abs: 6 }] -> v2 + - stack=[v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 8 }), PUSH(I256 { is_negative: false, abs: 7 })] + add [I256 { is_negative: false, abs: 7 }, I256 { is_negative: false, abs: 8 }] -> v3 + - stack=[v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 10 }), PUSH(I256 { is_negative: false, abs: 9 })] + add [I256 { is_negative: false, abs: 9 }, I256 { is_negative: false, abs: 10 }] -> v4 + - stack=[v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 12 }), PUSH(I256 { is_negative: false, abs: 11 })] + add [I256 { is_negative: false, abs: 11 }, I256 { is_negative: false, abs: 12 }] -> v5 + - stack=[v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 14 }), PUSH(I256 { is_negative: false, abs: 13 })] + add [I256 { is_negative: false, abs: 13 }, I256 { is_negative: false, abs: 14 }] -> v6 + - stack=[v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 16 }), PUSH(I256 { is_negative: false, abs: 15 })] + add [I256 { is_negative: false, abs: 15 }, I256 { is_negative: false, abs: 16 }] -> v7 + - stack=[v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 18 }), PUSH(I256 { is_negative: false, abs: 17 })] + add [I256 { is_negative: false, abs: 17 }, I256 { is_negative: false, abs: 18 }] -> v8 + - stack=[v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 20 }), PUSH(I256 { is_negative: false, abs: 19 })] + add [I256 { is_negative: false, abs: 19 }, I256 { is_negative: false, abs: 20 }] -> v9 + - stack=[v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 22 }), PUSH(I256 { is_negative: false, abs: 21 })] + add [I256 { is_negative: false, abs: 21 }, I256 { is_negative: false, abs: 22 }] -> v10 + - stack=[v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 24 }), PUSH(I256 { is_negative: false, abs: 23 })] + add [I256 { is_negative: false, abs: 23 }, I256 { is_negative: false, abs: 24 }] -> v11 + - stack=[v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 26 }), PUSH(I256 { is_negative: false, abs: 25 })] + add [I256 { is_negative: false, abs: 25 }, I256 { is_negative: false, abs: 26 }] -> v12 + - stack=[v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 28 }), PUSH(I256 { is_negative: false, abs: 27 })] + add [I256 { is_negative: false, abs: 27 }, I256 { is_negative: false, abs: 28 }] -> v13 + - stack=[v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 30 }), PUSH(I256 { is_negative: false, abs: 29 })] + add [I256 { is_negative: false, abs: 29 }, I256 { is_negative: false, abs: 30 }] -> v14 + - stack=[v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 32 }), PUSH(I256 { is_negative: false, abs: 31 })] + add [I256 { is_negative: false, abs: 31 }, I256 { is_negative: false, abs: 32 }] -> v15 + - stack=[v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0] + pre: [PUSH(I256 { is_negative: false, abs: 34 }), PUSH(I256 { is_negative: false, abs: 33 })] + add [I256 { is_negative: false, abs: 33 }, I256 { is_negative: false, abs: 34 }] -> v16 + - stack=[v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v0], last_use=[v0, v16] + pre: [SWAP1, SWAP16] + add [v0, v16] -> v17 + - stack=[v17, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1, v15], last_use=[v1, v17] + pre: [SWAP1, SWAP14] + add [v1, v17] -> v18 + - stack=[v18, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v14, v15], last_use=[v2, v18] + pre: [SWAP1, SWAP12] + add [v2, v18] -> v19 + - stack=[v19, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v13, v14, v15], last_use=[v3, v19] + pre: [SWAP1, SWAP10] + add [v3, v19] -> v20 + - stack=[v20, v11, v10, v9, v8, v7, v6, v5, v4, v12, v13, v14, v15], last_use=[v4, v20] + pre: [SWAP1, SWAP8] + add [v4, v20] -> v21 + - stack=[v21, v10, v9, v8, v7, v6, v5, v11, v12, v13, v14, v15], last_use=[v5, v21] + pre: [SWAP1, SWAP6] + add [v5, v21] -> v22 + - stack=[v22, v9, v8, v7, v6, v10, v11, v12, v13, v14, v15], last_use=[v6, v22] + pre: [SWAP1, SWAP4] + add [v6, v22] -> v23 + - stack=[v23, v8, v7, v9, v10, v11, v12, v13, v14, v15], last_use=[v7, v23] + pre: [SWAP1, SWAP2] + add [v7, v23] -> v24 + - stack=[v24, v8, v9, v10, v11, v12, v13, v14, v15], last_use=[v8, v24] + add [v24, v8] -> v25 + - stack=[v25, v9, v10, v11, v12, v13, v14, v15], last_use=[v9, v25] + add [v25, v9] -> v26 + - stack=[v26, v10, v11, v12, v13, v14, v15], last_use=[v10, v26] + add [v26, v10] -> v27 + - stack=[v27, v11, v12, v13, v14, v15], last_use=[v11, v27] + add [v27, v11] -> v28 + - stack=[v28, v12, v13, v14, v15], last_use=[v12, v28] + add [v28, v12] -> v29 + - stack=[v29, v13, v14, v15], last_use=[v13, v29] + add [v29, v13] -> v30 + - stack=[v30, v14, v15], last_use=[v14, v30] + add [v30, v14] -> v31 + - stack=[v31, v15], last_use=[v15, v31] + add [v31, v15] -> v32 + - stack=[v32x] + cleanup: [POP] + jump -> block2 + block2 P=[] T=[] + - stack=[] + pre: [PUSH(I256 { is_negative: false, abs: 36 }), PUSH(I256 { is_negative: false, abs: 35 })] + add [I256 { is_negative: false, abs: 35 }, I256 { is_negative: false, abs: 36 }] -> v33 + - stack=[v33] + pre: [PUSH(I256 { is_negative: false, abs: 38 }), PUSH(I256 { is_negative: false, abs: 37 })] + add [I256 { is_negative: false, abs: 37 }, I256 { is_negative: false, abs: 38 }] -> v34 + - stack=[v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 40 }), PUSH(I256 { is_negative: false, abs: 39 })] + add [I256 { is_negative: false, abs: 39 }, I256 { is_negative: false, abs: 40 }] -> v35 + - stack=[v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 42 }), PUSH(I256 { is_negative: false, abs: 41 })] + add [I256 { is_negative: false, abs: 41 }, I256 { is_negative: false, abs: 42 }] -> v36 + - stack=[v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 44 }), PUSH(I256 { is_negative: false, abs: 43 })] + add [I256 { is_negative: false, abs: 43 }, I256 { is_negative: false, abs: 44 }] -> v37 + - stack=[v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 46 }), PUSH(I256 { is_negative: false, abs: 45 })] + add [I256 { is_negative: false, abs: 45 }, I256 { is_negative: false, abs: 46 }] -> v38 + - stack=[v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 48 }), PUSH(I256 { is_negative: false, abs: 47 })] + add [I256 { is_negative: false, abs: 47 }, I256 { is_negative: false, abs: 48 }] -> v39 + - stack=[v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 50 }), PUSH(I256 { is_negative: false, abs: 49 })] + add [I256 { is_negative: false, abs: 49 }, I256 { is_negative: false, abs: 50 }] -> v40 + - stack=[v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 52 }), PUSH(I256 { is_negative: false, abs: 51 })] + add [I256 { is_negative: false, abs: 51 }, I256 { is_negative: false, abs: 52 }] -> v41 + - stack=[v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 54 }), PUSH(I256 { is_negative: false, abs: 53 })] + add [I256 { is_negative: false, abs: 53 }, I256 { is_negative: false, abs: 54 }] -> v42 + - stack=[v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 56 }), PUSH(I256 { is_negative: false, abs: 55 })] + add [I256 { is_negative: false, abs: 55 }, I256 { is_negative: false, abs: 56 }] -> v43 + - stack=[v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 58 }), PUSH(I256 { is_negative: false, abs: 57 })] + add [I256 { is_negative: false, abs: 57 }, I256 { is_negative: false, abs: 58 }] -> v44 + - stack=[v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 60 }), PUSH(I256 { is_negative: false, abs: 59 })] + add [I256 { is_negative: false, abs: 59 }, I256 { is_negative: false, abs: 60 }] -> v45 + - stack=[v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 62 }), PUSH(I256 { is_negative: false, abs: 61 })] + add [I256 { is_negative: false, abs: 61 }, I256 { is_negative: false, abs: 62 }] -> v46 + - stack=[v46, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 64 }), PUSH(I256 { is_negative: false, abs: 63 })] + add [I256 { is_negative: false, abs: 63 }, I256 { is_negative: false, abs: 64 }] -> v47 + - stack=[v47, v46, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 66 }), PUSH(I256 { is_negative: false, abs: 65 })] + add [I256 { is_negative: false, abs: 65 }, I256 { is_negative: false, abs: 66 }] -> v48 + - stack=[v48, v47, v46, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33] + pre: [PUSH(I256 { is_negative: false, abs: 68 }), PUSH(I256 { is_negative: false, abs: 67 })] + add [I256 { is_negative: false, abs: 67 }, I256 { is_negative: false, abs: 68 }] -> v49 + - stack=[v49, v48, v47, v46, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v33], last_use=[v33, v49] + pre: [SWAP1, SWAP16] + add [v33, v49] -> v50 + - stack=[v50, v47, v46, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v34, v48], last_use=[v34, v50] + pre: [SWAP1, SWAP14] + add [v34, v50] -> v51 + - stack=[v51, v46, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v35, v47, v48], last_use=[v35, v51] + pre: [SWAP1, SWAP12] + add [v35, v51] -> v52 + - stack=[v52, v45, v44, v43, v42, v41, v40, v39, v38, v37, v36, v46, v47, v48], last_use=[v36, v52] + pre: [SWAP1, SWAP10] + add [v36, v52] -> v53 + - stack=[v53, v44, v43, v42, v41, v40, v39, v38, v37, v45, v46, v47, v48], last_use=[v37, v53] + pre: [SWAP1, SWAP8] + add [v37, v53] -> v54 + - stack=[v54, v43, v42, v41, v40, v39, v38, v44, v45, v46, v47, v48], last_use=[v38, v54] + pre: [SWAP1, SWAP6] + add [v38, v54] -> v55 + - stack=[v55, v42, v41, v40, v39, v43, v44, v45, v46, v47, v48], last_use=[v39, v55] + pre: [SWAP1, SWAP4] + add [v39, v55] -> v56 + - stack=[v56, v41, v40, v42, v43, v44, v45, v46, v47, v48], last_use=[v40, v56] + pre: [SWAP1, SWAP2] + add [v40, v56] -> v57 + - stack=[v57, v41, v42, v43, v44, v45, v46, v47, v48], last_use=[v41, v57] + add [v57, v41] -> v58 + - stack=[v58, v42, v43, v44, v45, v46, v47, v48], last_use=[v42, v58] + add [v58, v42] -> v59 + - stack=[v59, v43, v44, v45, v46, v47, v48], last_use=[v43, v59] + add [v59, v43] -> v60 + - stack=[v60, v44, v45, v46, v47, v48], last_use=[v44, v60] + add [v60, v44] -> v61 + - stack=[v61, v45, v46, v47, v48], last_use=[v45, v61] + add [v61, v45] -> v62 + - stack=[v62, v46, v47, v48], last_use=[v46, v62] + add [v62, v46] -> v63 + - stack=[v63, v47, v48], last_use=[v47, v63] + add [v63, v47] -> v64 + - stack=[v64, v48], last_use=[v48, v64] + add [v64, v48] -> v65 + - stack=[v65], last_use=[v65] + pre: [PUSH(0)] + mstore [0, v65] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %entry() +entry: + block0: + JUMPDEST + block1: + JUMPDEST + PUSH1 0x2 (2) // v0.i256 = add 1.i256 2.i256; + PUSH1 0x1 (1) + ADD + PUSH1 0x4 (4) // v1.i256 = add 3.i256 4.i256; + PUSH1 0x3 (3) + ADD + PUSH1 0x6 (6) // v2.i256 = add 5.i256 6.i256; + PUSH1 0x5 (5) + ADD + PUSH1 0x8 (8) // v3.i256 = add 7.i256 8.i256; + PUSH1 0x7 (7) + ADD + PUSH1 0xa (10) // v4.i256 = add 9.i256 10.i256; + PUSH1 0x9 (9) + ADD + PUSH1 0xc (12) // v5.i256 = add 11.i256 12.i256; + PUSH1 0xb (11) + ADD + PUSH1 0xe (14) // v6.i256 = add 13.i256 14.i256; + PUSH1 0xd (13) + ADD + PUSH1 0x10 (16) // v7.i256 = add 15.i256 16.i256; + PUSH1 0xf (15) + ADD + PUSH1 0x12 (18) // v8.i256 = add 17.i256 18.i256; + PUSH1 0x11 (17) + ADD + PUSH1 0x14 (20) // v9.i256 = add 19.i256 20.i256; + PUSH1 0x13 (19) + ADD + PUSH1 0x16 (22) // v10.i256 = add 21.i256 22.i256; + PUSH1 0x15 (21) + ADD + PUSH1 0x18 (24) // v11.i256 = add 23.i256 24.i256; + PUSH1 0x17 (23) + ADD + PUSH1 0x1a (26) // v12.i256 = add 25.i256 26.i256; + PUSH1 0x19 (25) + ADD + PUSH1 0x1c (28) // v13.i256 = add 27.i256 28.i256; + PUSH1 0x1b (27) + ADD + PUSH1 0x1e (30) // v14.i256 = add 29.i256 30.i256; + PUSH1 0x1d (29) + ADD + PUSH1 0x20 (32) // v15.i256 = add 31.i256 32.i256; + PUSH1 0x1f (31) + ADD + PUSH1 0x22 (34) // v16.i256 = add 33.i256 34.i256; + PUSH1 0x21 (33) + ADD + SWAP1 // v17.i256 = add v0 v16; + SWAP16 + ADD + SWAP1 // v18.i256 = add v17 v1; + SWAP14 + ADD + SWAP1 // v19.i256 = add v18 v2; + SWAP12 + ADD + SWAP1 // v20.i256 = add v19 v3; + SWAP10 + ADD + SWAP1 // v21.i256 = add v20 v4; + SWAP8 + ADD + SWAP1 // v22.i256 = add v21 v5; + SWAP6 + ADD + SWAP1 // v23.i256 = add v22 v6; + SWAP4 + ADD + SWAP1 // v24.i256 = add v23 v7; + SWAP2 + ADD + ADD // v25.i256 = add v24 v8; + ADD // v26.i256 = add v25 v9; + ADD // v27.i256 = add v26 v10; + ADD // v28.i256 = add v27 v11; + ADD // v29.i256 = add v28 v12; + ADD // v30.i256 = add v29 v13; + ADD // v31.i256 = add v30 v14; + ADD // v32.i256 = add v31 v15; + POP // jump block2; + block2: + JUMPDEST + PUSH1 0x24 (36) // v33.i256 = add 35.i256 36.i256; + PUSH1 0x23 (35) + ADD + PUSH1 0x26 (38) // v34.i256 = add 37.i256 38.i256; + PUSH1 0x25 (37) + ADD + PUSH1 0x28 (40) // v35.i256 = add 39.i256 40.i256; + PUSH1 0x27 (39) + ADD + PUSH1 0x2a (42) // v36.i256 = add 41.i256 42.i256; + PUSH1 0x29 (41) + ADD + PUSH1 0x2c (44) // v37.i256 = add 43.i256 44.i256; + PUSH1 0x2b (43) + ADD + PUSH1 0x2e (46) // v38.i256 = add 45.i256 46.i256; + PUSH1 0x2d (45) + ADD + PUSH1 0x30 (48) // v39.i256 = add 47.i256 48.i256; + PUSH1 0x2f (47) + ADD + PUSH1 0x32 (50) // v40.i256 = add 49.i256 50.i256; + PUSH1 0x31 (49) + ADD + PUSH1 0x34 (52) // v41.i256 = add 51.i256 52.i256; + PUSH1 0x33 (51) + ADD + PUSH1 0x36 (54) // v42.i256 = add 53.i256 54.i256; + PUSH1 0x35 (53) + ADD + PUSH1 0x38 (56) // v43.i256 = add 55.i256 56.i256; + PUSH1 0x37 (55) + ADD + PUSH1 0x3a (58) // v44.i256 = add 57.i256 58.i256; + PUSH1 0x39 (57) + ADD + PUSH1 0x3c (60) // v45.i256 = add 59.i256 60.i256; + PUSH1 0x3b (59) + ADD + PUSH1 0x3e (62) // v46.i256 = add 61.i256 62.i256; + PUSH1 0x3d (61) + ADD + PUSH1 0x40 (64) // v47.i256 = add 63.i256 64.i256; + PUSH1 0x3f (63) + ADD + PUSH1 0x42 (66) // v48.i256 = add 65.i256 66.i256; + PUSH1 0x41 (65) + ADD + PUSH1 0x44 (68) // v49.i256 = add 67.i256 68.i256; + PUSH1 0x43 (67) + ADD + SWAP1 // v50.i256 = add v33 v49; + SWAP16 + ADD + SWAP1 // v51.i256 = add v50 v34; + SWAP14 + ADD + SWAP1 // v52.i256 = add v51 v35; + SWAP12 + ADD + SWAP1 // v53.i256 = add v52 v36; + SWAP10 + ADD + SWAP1 // v54.i256 = add v53 v37; + SWAP8 + ADD + SWAP1 // v55.i256 = add v54 v38; + SWAP6 + ADD + SWAP1 // v56.i256 = add v55 v39; + SWAP4 + ADD + SWAP1 // v57.i256 = add v56 v40; + SWAP2 + ADD + ADD // v58.i256 = add v57 v41; + ADD // v59.i256 = add v58 v42; + ADD // v60.i256 = add v59 v43; + ADD // v61.i256 = add v60 v44; + ADD // v62.i256 = add v61 v45; + ADD // v63.i256 = add v62 v46; + ADD // v64.i256 = add v63 v47; + ADD // v65.i256 = add v64 v48; + PUSH0 // mstore 0.i32 v65 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b5b6002600101600460030160066005016008600701600a600901600c600b01600e600d016010600f016012601101601460130160166015016018601701601a601901601c601b01601e601d016020601f016022602101909f01909d01909b019099019097019095019093019091010101010101010101505b602460230160266025016028602701602a602901602c602b01602e602d016030602f016032603101603460330160366035016038603701603a603901603c603b01603e603d016040603f0160426041016044604301909f01909d01909b0190990190970190950190930190910101010101010101015f5260205ff3 + + +Success { reason: Return, gas_used: 21572, gas_refunded: 0, logs: [], output: Call(0x00000000000000000000000000000000000000000000000000000000000006d7) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5b JUMPDEST [] + 2 60 PUSH1 0x02 [] + 4 60 PUSH1 0x01 [0x2] + 6 01 ADD [0x2, 0x1] + 7 60 PUSH1 0x04 [0x3] + 9 60 PUSH1 0x03 [0x3, 0x4] + 11 01 ADD [0x3, 0x4, 0x3] + 12 60 PUSH1 0x06 [0x3, 0x7] + 14 60 PUSH1 0x05 [0x3, 0x7, 0x6] + 16 01 ADD [0x3, 0x7, 0x6, 0x5] + 17 60 PUSH1 0x08 [0x3, 0x7, 0xb] + 19 60 PUSH1 0x07 [0x3, 0x7, 0xb, 0x8] + 21 01 ADD [0x3, 0x7, 0xb, 0x8, 0x7] + 22 60 PUSH1 0x0a [0x3, 0x7, 0xb, 0xf] + 24 60 PUSH1 0x09 [0x3, 0x7, 0xb, 0xf, 0xa] + 26 01 ADD [0x3, 0x7, 0xb, 0xf, 0xa, 0x9] + 27 60 PUSH1 0x0c [0x3, 0x7, 0xb, 0xf, 0x13] + 29 60 PUSH1 0x0b [0x3, 0x7, 0xb, 0xf, 0x13, 0xc] + 31 01 ADD [0x3, 0x7, …, 0x13, 0xc, 0xb] (len=7) + 32 60 PUSH1 0x0e [0x3, 0x7, 0xb, 0xf, 0x13, 0x17] + 34 60 PUSH1 0x0d [0x3, 0x7, …, 0x13, 0x17, 0xe] (len=7) + 36 01 ADD [0x3, 0x7, …, 0x17, 0xe, 0xd] (len=8) + 37 60 PUSH1 0x10 [0x3, 0x7, …, 0x13, 0x17, 0x1b] (len=7) + 39 60 PUSH1 0x0f [0x3, 0x7, …, 0x17, 0x1b, 0x10] (len=8) + 41 01 ADD [0x3, 0x7, …, 0x1b, 0x10, 0xf] (len=9) + 42 60 PUSH1 0x12 [0x3, 0x7, …, 0x17, 0x1b, 0x1f] (len=8) + 44 60 PUSH1 0x11 [0x3, 0x7, …, 0x1b, 0x1f, 0x12] (len=9) + 46 01 ADD [0x3, 0x7, …, 0x1f, 0x12, 0x11] (len=10) + 47 60 PUSH1 0x14 [0x3, 0x7, …, 0x1b, 0x1f, 0x23] (len=9) + 49 60 PUSH1 0x13 [0x3, 0x7, …, 0x1f, 0x23, 0x14] (len=10) + 51 01 ADD [0x3, 0x7, …, 0x23, 0x14, 0x13] (len=11) + 52 60 PUSH1 0x16 [0x3, 0x7, …, 0x1f, 0x23, 0x27] (len=10) + 54 60 PUSH1 0x15 [0x3, 0x7, …, 0x23, 0x27, 0x16] (len=11) + 56 01 ADD [0x3, 0x7, …, 0x27, 0x16, 0x15] (len=12) + 57 60 PUSH1 0x18 [0x3, 0x7, …, 0x23, 0x27, 0x2b] (len=11) + 59 60 PUSH1 0x17 [0x3, 0x7, …, 0x27, 0x2b, 0x18] (len=12) + 61 01 ADD [0x3, 0x7, …, 0x2b, 0x18, 0x17] (len=13) + 62 60 PUSH1 0x1a [0x3, 0x7, …, 0x27, 0x2b, 0x2f] (len=12) + 64 60 PUSH1 0x19 [0x3, 0x7, …, 0x2b, 0x2f, 0x1a] (len=13) + 66 01 ADD [0x3, 0x7, …, 0x2f, 0x1a, 0x19] (len=14) + 67 60 PUSH1 0x1c [0x3, 0x7, …, 0x2b, 0x2f, 0x33] (len=13) + 69 60 PUSH1 0x1b [0x3, 0x7, …, 0x2f, 0x33, 0x1c] (len=14) + 71 01 ADD [0x3, 0x7, …, 0x33, 0x1c, 0x1b] (len=15) + 72 60 PUSH1 0x1e [0x3, 0x7, …, 0x2f, 0x33, 0x37] (len=14) + 74 60 PUSH1 0x1d [0x3, 0x7, …, 0x33, 0x37, 0x1e] (len=15) + 76 01 ADD [0x3, 0x7, …, 0x37, 0x1e, 0x1d] (len=16) + 77 60 PUSH1 0x20 [0x3, 0x7, …, 0x33, 0x37, 0x3b] (len=15) + 79 60 PUSH1 0x1f [0x3, 0x7, …, 0x37, 0x3b, 0x20] (len=16) + 81 01 ADD [0x3, 0x7, …, 0x3b, 0x20, 0x1f] (len=17) + 82 60 PUSH1 0x22 [0x3, 0x7, …, 0x37, 0x3b, 0x3f] (len=16) + 84 60 PUSH1 0x21 [0x3, 0x7, …, 0x3b, 0x3f, 0x22] (len=17) + 86 01 ADD [0x3, 0x7, …, 0x3f, 0x22, 0x21] (len=18) + 87 90 SWAP1 [0x3, 0x7, …, 0x3b, 0x3f, 0x43] (len=17) + 88 9f SWAP16 [0x3, 0x7, …, 0x3b, 0x43, 0x3f] (len=17) + 89 01 ADD [0x3f, 0x7, …, 0x3b, 0x43, 0x3] (len=17) + 90 90 SWAP1 [0x3f, 0x7, …, 0x37, 0x3b, 0x46] (len=16) + 91 9d SWAP14 [0x3f, 0x7, …, 0x37, 0x46, 0x3b] (len=16) + 92 01 ADD [0x3f, 0x3b, …, 0x37, 0x46, 0x7] (len=16) + 93 90 SWAP1 [0x3f, 0x3b, …, 0x33, 0x37, 0x4d] (len=15) + 94 9b SWAP12 [0x3f, 0x3b, …, 0x33, 0x4d, 0x37] (len=15) + 95 01 ADD [0x3f, 0x3b, …, 0x33, 0x4d, 0xb] (len=15) + 96 90 SWAP1 [0x3f, 0x3b, …, 0x2f, 0x33, 0x58] (len=14) + 97 99 SWAP10 [0x3f, 0x3b, …, 0x2f, 0x58, 0x33] (len=14) + 98 01 ADD [0x3f, 0x3b, …, 0x2f, 0x58, 0xf] (len=14) + 99 90 SWAP1 [0x3f, 0x3b, …, 0x2b, 0x2f, 0x67] (len=13) + 100 97 SWAP8 [0x3f, 0x3b, …, 0x2b, 0x67, 0x2f] (len=13) + 101 01 ADD [0x3f, 0x3b, …, 0x2b, 0x67, 0x13] (len=13) + 102 90 SWAP1 [0x3f, 0x3b, …, 0x27, 0x2b, 0x7a] (len=12) + 103 95 SWAP6 [0x3f, 0x3b, …, 0x27, 0x7a, 0x2b] (len=12) + 104 01 ADD [0x3f, 0x3b, …, 0x27, 0x7a, 0x17] (len=12) + 105 90 SWAP1 [0x3f, 0x3b, …, 0x23, 0x27, 0x91] (len=11) + 106 93 SWAP4 [0x3f, 0x3b, …, 0x23, 0x91, 0x27] (len=11) + 107 01 ADD [0x3f, 0x3b, …, 0x23, 0x91, 0x1b] (len=11) + 108 90 SWAP1 [0x3f, 0x3b, …, 0x1f, 0x23, 0xac] (len=10) + 109 91 SWAP2 [0x3f, 0x3b, …, 0x1f, 0xac, 0x23] (len=10) + 110 01 ADD [0x3f, 0x3b, …, 0x23, 0xac, 0x1f] (len=10) + 111 01 ADD [0x3f, 0x3b, …, 0x27, 0x23, 0xcb] (len=9) + 112 01 ADD [0x3f, 0x3b, …, 0x2b, 0x27, 0xee] (len=8) + 113 01 ADD [0x3f, 0x3b, …, 0x2f, 0x2b, 0x115] (len=7) + 114 01 ADD [0x3f, 0x3b, 0x37, 0x33, 0x2f, 0x140] + 115 01 ADD [0x3f, 0x3b, 0x37, 0x33, 0x16f] + 116 01 ADD [0x3f, 0x3b, 0x37, 0x1a2] + 117 01 ADD [0x3f, 0x3b, 0x1d9] + 118 01 ADD [0x3f, 0x214] + 119 50 POP [0x253] + 120 5b JUMPDEST [] + 121 60 PUSH1 0x24 [] + 123 60 PUSH1 0x23 [0x24] + 125 01 ADD [0x24, 0x23] + 126 60 PUSH1 0x26 [0x47] + 128 60 PUSH1 0x25 [0x47, 0x26] + 130 01 ADD [0x47, 0x26, 0x25] + 131 60 PUSH1 0x28 [0x47, 0x4b] + 133 60 PUSH1 0x27 [0x47, 0x4b, 0x28] + 135 01 ADD [0x47, 0x4b, 0x28, 0x27] + 136 60 PUSH1 0x2a [0x47, 0x4b, 0x4f] + 138 60 PUSH1 0x29 [0x47, 0x4b, 0x4f, 0x2a] + 140 01 ADD [0x47, 0x4b, 0x4f, 0x2a, 0x29] + 141 60 PUSH1 0x2c [0x47, 0x4b, 0x4f, 0x53] + 143 60 PUSH1 0x2b [0x47, 0x4b, 0x4f, 0x53, 0x2c] + 145 01 ADD [0x47, 0x4b, 0x4f, 0x53, 0x2c, 0x2b] + 146 60 PUSH1 0x2e [0x47, 0x4b, 0x4f, 0x53, 0x57] + 148 60 PUSH1 0x2d [0x47, 0x4b, 0x4f, 0x53, 0x57, 0x2e] + 150 01 ADD [0x47, 0x4b, …, 0x57, 0x2e, 0x2d] (len=7) + 151 60 PUSH1 0x30 [0x47, 0x4b, 0x4f, 0x53, 0x57, 0x5b] + 153 60 PUSH1 0x2f [0x47, 0x4b, …, 0x57, 0x5b, 0x30] (len=7) + 155 01 ADD [0x47, 0x4b, …, 0x5b, 0x30, 0x2f] (len=8) + 156 60 PUSH1 0x32 [0x47, 0x4b, …, 0x57, 0x5b, 0x5f] (len=7) + 158 60 PUSH1 0x31 [0x47, 0x4b, …, 0x5b, 0x5f, 0x32] (len=8) + 160 01 ADD [0x47, 0x4b, …, 0x5f, 0x32, 0x31] (len=9) + 161 60 PUSH1 0x34 [0x47, 0x4b, …, 0x5b, 0x5f, 0x63] (len=8) + 163 60 PUSH1 0x33 [0x47, 0x4b, …, 0x5f, 0x63, 0x34] (len=9) + 165 01 ADD [0x47, 0x4b, …, 0x63, 0x34, 0x33] (len=10) + 166 60 PUSH1 0x36 [0x47, 0x4b, …, 0x5f, 0x63, 0x67] (len=9) + 168 60 PUSH1 0x35 [0x47, 0x4b, …, 0x63, 0x67, 0x36] (len=10) + 170 01 ADD [0x47, 0x4b, …, 0x67, 0x36, 0x35] (len=11) + 171 60 PUSH1 0x38 [0x47, 0x4b, …, 0x63, 0x67, 0x6b] (len=10) + 173 60 PUSH1 0x37 [0x47, 0x4b, …, 0x67, 0x6b, 0x38] (len=11) + 175 01 ADD [0x47, 0x4b, …, 0x6b, 0x38, 0x37] (len=12) + 176 60 PUSH1 0x3a [0x47, 0x4b, …, 0x67, 0x6b, 0x6f] (len=11) + 178 60 PUSH1 0x39 [0x47, 0x4b, …, 0x6b, 0x6f, 0x3a] (len=12) + 180 01 ADD [0x47, 0x4b, …, 0x6f, 0x3a, 0x39] (len=13) + 181 60 PUSH1 0x3c [0x47, 0x4b, …, 0x6b, 0x6f, 0x73] (len=12) + 183 60 PUSH1 0x3b [0x47, 0x4b, …, 0x6f, 0x73, 0x3c] (len=13) + 185 01 ADD [0x47, 0x4b, …, 0x73, 0x3c, 0x3b] (len=14) + 186 60 PUSH1 0x3e [0x47, 0x4b, …, 0x6f, 0x73, 0x77] (len=13) + 188 60 PUSH1 0x3d [0x47, 0x4b, …, 0x73, 0x77, 0x3e] (len=14) + 190 01 ADD [0x47, 0x4b, …, 0x77, 0x3e, 0x3d] (len=15) + 191 60 PUSH1 0x40 [0x47, 0x4b, …, 0x73, 0x77, 0x7b] (len=14) + 193 60 PUSH1 0x3f [0x47, 0x4b, …, 0x77, 0x7b, 0x40] (len=15) + 195 01 ADD [0x47, 0x4b, …, 0x7b, 0x40, 0x3f] (len=16) + 196 60 PUSH1 0x42 [0x47, 0x4b, …, 0x77, 0x7b, 0x7f] (len=15) + 198 60 PUSH1 0x41 [0x47, 0x4b, …, 0x7b, 0x7f, 0x42] (len=16) + 200 01 ADD [0x47, 0x4b, …, 0x7f, 0x42, 0x41] (len=17) + 201 60 PUSH1 0x44 [0x47, 0x4b, …, 0x7b, 0x7f, 0x83] (len=16) + 203 60 PUSH1 0x43 [0x47, 0x4b, …, 0x7f, 0x83, 0x44] (len=17) + 205 01 ADD [0x47, 0x4b, …, 0x83, 0x44, 0x43] (len=18) + 206 90 SWAP1 [0x47, 0x4b, …, 0x7f, 0x83, 0x87] (len=17) + 207 9f SWAP16 [0x47, 0x4b, …, 0x7f, 0x87, 0x83] (len=17) + 208 01 ADD [0x83, 0x4b, …, 0x7f, 0x87, 0x47] (len=17) + 209 90 SWAP1 [0x83, 0x4b, …, 0x7b, 0x7f, 0xce] (len=16) + 210 9d SWAP14 [0x83, 0x4b, …, 0x7b, 0xce, 0x7f] (len=16) + 211 01 ADD [0x83, 0x7f, …, 0x7b, 0xce, 0x4b] (len=16) + 212 90 SWAP1 [0x83, 0x7f, …, 0x77, 0x7b, 0x119] (len=15) + 213 9b SWAP12 [0x83, 0x7f, …, 0x77, 0x119, 0x7b] (len=15) + 214 01 ADD [0x83, 0x7f, …, 0x77, 0x119, 0x4f] (len=15) + 215 90 SWAP1 [0x83, 0x7f, …, 0x73, 0x77, 0x168] (len=14) + 216 99 SWAP10 [0x83, 0x7f, …, 0x73, 0x168, 0x77] (len=14) + 217 01 ADD [0x83, 0x7f, …, 0x73, 0x168, 0x53] (len=14) + 218 90 SWAP1 [0x83, 0x7f, …, 0x6f, 0x73, 0x1bb] (len=13) + 219 97 SWAP8 [0x83, 0x7f, …, 0x6f, 0x1bb, 0x73] (len=13) + 220 01 ADD [0x83, 0x7f, …, 0x6f, 0x1bb, 0x57] (len=13) + 221 90 SWAP1 [0x83, 0x7f, …, 0x6b, 0x6f, 0x212] (len=12) + 222 95 SWAP6 [0x83, 0x7f, …, 0x6b, 0x212, 0x6f] (len=12) + 223 01 ADD [0x83, 0x7f, …, 0x6b, 0x212, 0x5b] (len=12) + 224 90 SWAP1 [0x83, 0x7f, …, 0x67, 0x6b, 0x26d] (len=11) + 225 93 SWAP4 [0x83, 0x7f, …, 0x67, 0x26d, 0x6b] (len=11) + 226 01 ADD [0x83, 0x7f, …, 0x67, 0x26d, 0x5f] (len=11) + 227 90 SWAP1 [0x83, 0x7f, …, 0x63, 0x67, 0x2cc] (len=10) + 228 91 SWAP2 [0x83, 0x7f, …, 0x63, 0x2cc, 0x67] (len=10) + 229 01 ADD [0x83, 0x7f, …, 0x67, 0x2cc, 0x63] (len=10) + 230 01 ADD [0x83, 0x7f, …, 0x6b, 0x67, 0x32f] (len=9) + 231 01 ADD [0x83, 0x7f, …, 0x6f, 0x6b, 0x396] (len=8) + 232 01 ADD [0x83, 0x7f, …, 0x73, 0x6f, 0x401] (len=7) + 233 01 ADD [0x83, 0x7f, 0x7b, 0x77, 0x73, 0x470] + 234 01 ADD [0x83, 0x7f, 0x7b, 0x77, 0x4e3] + 235 01 ADD [0x83, 0x7f, 0x7b, 0x55a] + 236 01 ADD [0x83, 0x7f, 0x5d5] + 237 01 ADD [0x83, 0x654] + 238 5f PUSH0 [0x6d7] + 239 52 MSTORE [0x6d7, 0x0] + 240 60 PUSH1 0x20 [] + 242 5f PUSH0 [0x20] + 243 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/cross_block_slot_reuse.sntn b/crates/codegen/test_files/evm/cross_block_slot_reuse.sntn new file mode 100644 index 00000000..e7a7d30d --- /dev/null +++ b/crates/codegen/test_files/evm/cross_block_slot_reuse.sntn @@ -0,0 +1,91 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + jump block1; + + block1: + v0.i256 = add 1.i256 2.i256; + v1.i256 = add 3.i256 4.i256; + v2.i256 = add 5.i256 6.i256; + v3.i256 = add 7.i256 8.i256; + v4.i256 = add 9.i256 10.i256; + v5.i256 = add 11.i256 12.i256; + v6.i256 = add 13.i256 14.i256; + v7.i256 = add 15.i256 16.i256; + v8.i256 = add 17.i256 18.i256; + v9.i256 = add 19.i256 20.i256; + v10.i256 = add 21.i256 22.i256; + v11.i256 = add 23.i256 24.i256; + v12.i256 = add 25.i256 26.i256; + v13.i256 = add 27.i256 28.i256; + v14.i256 = add 29.i256 30.i256; + v15.i256 = add 31.i256 32.i256; + v16.i256 = add 33.i256 34.i256; + + v17.i256 = add v0 v16; + + v18.i256 = add v17 v1; + v19.i256 = add v18 v2; + v20.i256 = add v19 v3; + v21.i256 = add v20 v4; + v22.i256 = add v21 v5; + v23.i256 = add v22 v6; + v24.i256 = add v23 v7; + v25.i256 = add v24 v8; + v26.i256 = add v25 v9; + v27.i256 = add v26 v10; + v28.i256 = add v27 v11; + v29.i256 = add v28 v12; + v30.i256 = add v29 v13; + v31.i256 = add v30 v14; + v32.i256 = add v31 v15; + + jump block2; + + block2: + v33.i256 = add 35.i256 36.i256; + v34.i256 = add 37.i256 38.i256; + v35.i256 = add 39.i256 40.i256; + v36.i256 = add 41.i256 42.i256; + v37.i256 = add 43.i256 44.i256; + v38.i256 = add 45.i256 46.i256; + v39.i256 = add 47.i256 48.i256; + v40.i256 = add 49.i256 50.i256; + v41.i256 = add 51.i256 52.i256; + v42.i256 = add 53.i256 54.i256; + v43.i256 = add 55.i256 56.i256; + v44.i256 = add 57.i256 58.i256; + v45.i256 = add 59.i256 60.i256; + v46.i256 = add 61.i256 62.i256; + v47.i256 = add 63.i256 64.i256; + v48.i256 = add 65.i256 66.i256; + v49.i256 = add 67.i256 68.i256; + + v50.i256 = add v33 v49; + + v51.i256 = add v50 v34; + v52.i256 = add v51 v35; + v53.i256 = add v52 v36; + v54.i256 = add v53 v37; + v55.i256 = add v54 v38; + v56.i256 = add v55 v39; + v57.i256 = add v56 v40; + v58.i256 = add v57 v41; + v59.i256 = add v58 v42; + v60.i256 = add v59 v43; + v61.i256 = add v60 v44; + v62.i256 = add v61 v45; + v63.i256 = add v62 v46; + v64.i256 = add v63 v47; + v65.i256 = add v64 v48; + + mstore 0.i32 v65 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/deep_dead.snap b/crates/codegen/test_files/evm/deep_dead.snap new file mode 100644 index 00000000..7bad7a5c --- /dev/null +++ b/crates/codegen/test_files/evm/deep_dead.snap @@ -0,0 +1,279 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/deep_dead.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0], last_use=[v0] + not [v0] -> v1 + - stack=[v1] + pre: [DUP1] + not [v1] -> v2 + - stack=[v2, v1] + pre: [DUP1] + not [v2] -> v3 + - stack=[v3, v2, v1] + pre: [DUP1] + not [v3] -> v4 + - stack=[v4, v3, v2, v1] + pre: [DUP1] + not [v4] -> v5 + - stack=[v5, v4, v3, v2, v1] + pre: [DUP1] + not [v5] -> v6 + - stack=[v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v6] -> v7 + - stack=[v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v7] -> v8 + - stack=[v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v8] -> v9 + - stack=[v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v9] -> v10 + - stack=[v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v10] -> v11 + - stack=[v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v11] -> v12 + - stack=[v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v12] -> v13 + - stack=[v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v13] -> v14 + - stack=[v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v14] -> v15 + - stack=[v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v15] -> v16 + - stack=[v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + pre: [DUP1] + not [v16] -> v17 + - stack=[v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + jump -> block1 + block1 P=[] T=[v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1] + - stack=[v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1], last_use=[v1, v2] + pre: [SWAP15, SWAP1, SWAP16] + add [v1, v2] -> v18 + - stack=[v18, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v17, v16], last_use=[v3, v18] + pre: [SWAP1, SWAP13] + add [v3, v18] -> v19 + - stack=[v19, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v15, v17, v16], last_use=[v4, v19] + pre: [SWAP1, SWAP11] + add [v4, v19] -> v20 + - stack=[v20, v13, v12, v11, v10, v9, v8, v7, v6, v5, v14, v15, v17, v16], last_use=[v5, v20] + pre: [SWAP1, SWAP9] + add [v5, v20] -> v21 + - stack=[v21, v12, v11, v10, v9, v8, v7, v6, v13, v14, v15, v17, v16], last_use=[v6, v21] + pre: [SWAP1, SWAP7] + add [v6, v21] -> v22 + - stack=[v22, v11, v10, v9, v8, v7, v12, v13, v14, v15, v17, v16], last_use=[v7, v22] + pre: [SWAP1, SWAP5] + add [v7, v22] -> v23 + - stack=[v23, v10, v9, v8, v11, v12, v13, v14, v15, v17, v16], last_use=[v8, v23] + pre: [SWAP1, SWAP3] + add [v8, v23] -> v24 + - stack=[v24, v9, v10, v11, v12, v13, v14, v15, v17, v16], last_use=[v9, v24] + add [v24, v9] -> v25 + - stack=[v25, v10, v11, v12, v13, v14, v15, v17, v16], last_use=[v10, v25] + add [v25, v10] -> v26 + - stack=[v26, v11, v12, v13, v14, v15, v17, v16], last_use=[v11, v26] + add [v26, v11] -> v27 + - stack=[v27, v12, v13, v14, v15, v17, v16], last_use=[v12, v27] + add [v27, v12] -> v28 + - stack=[v28, v13, v14, v15, v17, v16], last_use=[v13, v28] + add [v28, v13] -> v29 + - stack=[v29, v14, v15, v17, v16], last_use=[v14, v29] + add [v29, v14] -> v30 + - stack=[v30, v15, v17, v16], last_use=[v15, v30] + add [v30, v15] -> v31 + - stack=[v31, v17, v16], last_use=[v16, v31] + pre: [SWAP1, SWAP2] + add [v16, v31] -> v32 + - stack=[v32, v17], last_use=[v17, v32] + add [v32, v17] -> v33 + - stack=[v33], last_use=[v33] + pre: [PUSH(0)] + mstore [0, v33] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + NOT // v1.i256 = not v0; + DUP1 // v2.i256 = not v1; + NOT + DUP1 // v3.i256 = not v2; + NOT + DUP1 // v4.i256 = not v3; + NOT + DUP1 // v5.i256 = not v4; + NOT + DUP1 // v6.i256 = not v5; + NOT + DUP1 // v7.i256 = not v6; + NOT + DUP1 // v8.i256 = not v7; + NOT + DUP1 // v9.i256 = not v8; + NOT + DUP1 // v10.i256 = not v9; + NOT + DUP1 // v11.i256 = not v10; + NOT + DUP1 // v12.i256 = not v11; + NOT + DUP1 // v13.i256 = not v12; + NOT + DUP1 // v14.i256 = not v13; + NOT + DUP1 // v15.i256 = not v14; + NOT + DUP1 // v16.i256 = not v15; + NOT + DUP1 // v17.i256 = not v16; + NOT + block1: + JUMPDEST + SWAP15 // v18.i256 = add v1 v2; + SWAP1 + SWAP16 + ADD + SWAP1 // v19.i256 = add v18 v3; + SWAP13 + ADD + SWAP1 // v20.i256 = add v19 v4; + SWAP11 + ADD + SWAP1 // v21.i256 = add v20 v5; + SWAP9 + ADD + SWAP1 // v22.i256 = add v21 v6; + SWAP7 + ADD + SWAP1 // v23.i256 = add v22 v7; + SWAP5 + ADD + SWAP1 // v24.i256 = add v23 v8; + SWAP3 + ADD + ADD // v25.i256 = add v24 v9; + ADD // v26.i256 = add v25 v10; + ADD // v27.i256 = add v26 v11; + ADD // v28.i256 = add v27 v12; + ADD // v29.i256 = add v28 v13; + ADD // v30.i256 = add v29 v14; + ADD // v31.i256 = add v30 v15; + SWAP1 // v32.i256 = add v31 v16; + SWAP2 + ADD + ADD // v33.i256 = add v32 v17; + PUSH0 // mstore 0.i32 v33 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b5f351980198019801980198019801980198019801980198019801980198019801980195b9e909f01909c01909a0190980190960190940190920101010101010101909101015f5260205ff3 + + +Success { reason: Return, gas_used: 21274, gas_refunded: 0, logs: [], output: Call(0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff7) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 19 NOT [0xb00000016000000000000000000000000000000000000000000000000] + 4 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 5 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 6 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] + 7 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 8 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 9 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 10 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] + 11 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 12 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 13 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 14 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] + 15 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=7) + 16 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=7) + 17 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=8) + 18 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] (len=8) + 19 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=9) + 20 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=9) + 21 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=10) + 22 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] (len=10) + 23 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=11) + 24 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=11) + 25 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=12) + 26 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] (len=12) + 27 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=13) + 28 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=13) + 29 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=14) + 30 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] (len=14) + 31 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=15) + 32 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=15) + 33 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=16) + 34 80 DUP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000] (len=16) + 35 19 NOT [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=17) + 36 5b JUMPDEST [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=17) + 37 9e SWAP15 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=17) + 38 90 SWAP1 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=17) + 39 9f SWAP16 [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] (len=17) + 40 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=17) + 41 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff] (len=16) + 42 9c SWAP13 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=16) + 43 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=16) + 44 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffe] (len=15) + 45 9a SWAP11 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffe, 0xb00000016000000000000000000000000000000000000000000000000] (len=15) + 46 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffe, 0xb00000016000000000000000000000000000000000000000000000000] (len=15) + 47 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe] (len=14) + 48 98 SWAP9 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=14) + 49 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=14) + 50 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffd] (len=13) + 51 96 SWAP7 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffd, 0xb00000016000000000000000000000000000000000000000000000000] (len=13) + 52 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffd, 0xb00000016000000000000000000000000000000000000000000000000] (len=13) + 53 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd] (len=12) + 54 94 SWAP5 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=12) + 55 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] (len=12) + 56 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffc] (len=11) + 57 92 SWAP3 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffc, 0xb00000016000000000000000000000000000000000000000000000000] (len=11) + 58 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffc, 0xb00000016000000000000000000000000000000000000000000000000] (len=11) + 59 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc] (len=10) + 60 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffb] (len=9) + 61 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb] (len=8) + 62 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, …, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffffa] (len=7) + 63 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa] + 64 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff9] + 65 01 ADD [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9] + 66 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff8] + 67 91 SWAP2 [0xb00000016000000000000000000000000000000000000000000000000, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff8, 0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff] + 68 01 ADD [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff8, 0xb00000016000000000000000000000000000000000000000000000000] + 69 01 ADD [0xfffffff4ffffffe9ffffffffffffffffffffffffffffffffffffffffffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8] + 70 5f PUSH0 [0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff7] + 71 52 MSTORE [0xfffffff4ffffffe9fffffffffffffffffffffffffffffffffffffffffffffff7, 0x0] + 72 60 PUSH1 0x20 [] + 74 5f PUSH0 [0x20] + 75 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/deep_dead.sntn b/crates/codegen/test_files/evm/deep_dead.sntn new file mode 100644 index 00000000..c48c6c68 --- /dev/null +++ b/crates/codegen/test_files/evm/deep_dead.sntn @@ -0,0 +1,53 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + + v1.i256 = not v0; + v2.i256 = not v1; + v3.i256 = not v2; + v4.i256 = not v3; + v5.i256 = not v4; + v6.i256 = not v5; + v7.i256 = not v6; + v8.i256 = not v7; + v9.i256 = not v8; + v10.i256 = not v9; + v11.i256 = not v10; + v12.i256 = not v11; + v13.i256 = not v12; + v14.i256 = not v13; + v15.i256 = not v14; + v16.i256 = not v15; + v17.i256 = not v16; + + jump block1; + + block1: + v18.i256 = add v1 v2; + v19.i256 = add v18 v3; + v20.i256 = add v19 v4; + v21.i256 = add v20 v5; + v22.i256 = add v21 v6; + v23.i256 = add v22 v7; + v24.i256 = add v23 v8; + v25.i256 = add v24 v9; + v26.i256 = add v25 v10; + v27.i256 = add v26 v11; + v28.i256 = add v27 v12; + v29.i256 = add v28 v13; + v30.i256 = add v29 v14; + v31.i256 = add v30 v15; + v32.i256 = add v31 v16; + v33.i256 = add v32 v17; + + mstore 0.i32 v33 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/deploy_codecopy.snap b/crates/codegen/test_files/evm/deploy_codecopy.snap new file mode 100644 index 00000000..87d5d7a4 --- /dev/null +++ b/crates/codegen/test_files/evm/deploy_codecopy.snap @@ -0,0 +1,126 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/deploy_codecopy.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: init scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0] + pre: [DUP1, PUSH(224)] + shr [224, v0] -> v1 + - stack=[v1, v0], last_use=[v0] + pre: [SWAP1, PUSH(32)] + shl [32, v0] -> v2 + - stack=[v2, v1], last_use=[v2] + pre: [PUSH(224)] + shr [224, v2] -> v3 + - stack=[v3, v1], last_use=[v1, v3] + add [v3, v1] -> v4 + - stack=[v4], last_use=[v4] + pre: [PUSH(0)] + mstore [0, v4] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %init() + block0 P=[] T=[] + - stack=[] + sym_addr [] -> v0 + - stack=[v0] + sym_size [] -> v1 + - stack=[v1, v0], last_use=[v0] + pre: [DUP1, SWAP1, SWAP2, PUSH(I256 { is_negative: false, abs: 0 })] + evm_code_copy [I256 { is_negative: false, abs: 0 }, v0, v1] + - stack=[v1], last_use=[v1] + pre: [PUSH(I256 { is_negative: false, abs: 0 })] + evm_return [I256 { is_negative: false, abs: 0 }, v1] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + DUP1 // v1.i32 = shr 224.i32 v0; + PUSH1 0xe0 (224) + SHR + SWAP1 // v2.i32 = shl 32.i32 v0; + PUSH1 0x20 (32) + SHL + PUSH1 0xe0 (224) // v3.i32 = shr 224.i32 v2; + SHR + ADD // v4.i32 = add v1 v3; + PUSH0 // mstore 0.i32 v4 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func public %init() +init: + block0: + JUMPDEST + PUSH0 0x0 (0) // v0.i256 = sym_addr &runtime; + PUSH0 0x0 (0) // v1.i256 = sym_size &runtime; + DUP1 // evm_code_copy 0.i256 v0 v1; + SWAP1 + SWAP2 + PUSH0 + CODECOPY + PUSH0 // evm_return 0.i256 v1; + RETURN + + + +--------------- + +// section init +0x5b600c60158090915f395ff35b5f358060e01c9060201b60e01c015f5260205ff3 + +// section runtime +0x5b5f358060e01c9060201b60e01c015f5260205ff3 + + +Success { reason: Return, gas_used: 57761, gas_refunded: 0, logs: [], output: Create(0x5b5f358060e01c9060201b60e01c015f5260205ff3, Some(0xbd770416a3345f91e4b34576cb804a576fa48eb1)) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x0c [] + 3 60 PUSH1 0x15 [0xc] + 5 80 DUP1 [0xc, 0x15] + 6 90 SWAP1 [0xc, 0x15, 0x15] + 7 91 SWAP2 [0xc, 0x15, 0x15] + 8 5f PUSH0 [0x15, 0x15, 0xc] + 9 39 CODECOPY [0x15, 0x15, 0xc, 0x0] + 10 5f PUSH0 [0x15] + 11 f3 RETURN [0x15, 0x0] + + +Success { reason: Return, gas_used: 21102, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000021) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 80 DUP1 [0xb00000016000000000000000000000000000000000000000000000000] + 4 60 PUSH1 0xe0 [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 6 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 7 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xb] + 8 60 PUSH1 0x20 [0xb, 0xb00000016000000000000000000000000000000000000000000000000] + 10 1b SHL [0xb, 0xb00000016000000000000000000000000000000000000000000000000, 0x20] + 11 60 PUSH1 0xe0 [0xb, 0x1600000000000000000000000000000000000000000000000000000000] + 13 1c SHR [0xb, 0x1600000000000000000000000000000000000000000000000000000000, 0xe0] + 14 01 ADD [0xb, 0x16] + 15 5f PUSH0 [0x21] + 16 52 MSTORE [0x21, 0x0] + 17 60 PUSH1 0x20 [] + 19 5f PUSH0 [0x20] + 20 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/deploy_codecopy.sntn b/crates/codegen/test_files/evm/deploy_codecopy.sntn new file mode 100644 index 00000000..0ee24387 --- /dev/null +++ b/crates/codegen/test_files/evm/deploy_codecopy.sntn @@ -0,0 +1,30 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i32 = shr 224.i32 v0; + v2.i32 = shl 32.i32 v0; + v3.i32 = shr 224.i32 v2; + v4.i32 = add v1 v3; + mstore 0.i32 v4 i32; + evm_return 0.i8 32.i8; +} + +func public %init() { + block0: + v0.i256 = sym_addr &runtime; + v1.i256 = sym_size &runtime; + evm_code_copy 0.i256 v0 v1; + evm_return 0.i256 v1; +} + +object @Contract { + section init { + entry %init; + embed .runtime as &runtime; + } + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/diamond.snap b/crates/codegen/test_files/evm/diamond.snap new file mode 100644 index 00000000..3b2bb564 --- /dev/null +++ b/crates/codegen/test_files/evm/diamond.snap @@ -0,0 +1,108 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/diamond.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(224)] + shr [224, v0] -> v1 + - stack=[v1] + pre: [PUSH(0), DUP2] + gt [v1, 0] -> v2 + - stack=[v2, v1] + br [v2] -> [block1, block2] + block1 P=[] T=[v1] + inherited from block0: [v1] + - stack=[v1], last_use=[v1] + pre: [PUSH(1)] + add [1, v1] -> v3 + - stack=[v3] + jump -> block3 + block2 P=[] T=[v1] + inherited from block0: [v1] + - stack=[v1], last_use=[v1] + pre: [PUSH(2)] + add [2, v1] -> v4 + - stack=[v4] + jump -> block3 + block3 P=[v5] T=[] + - stack=[v5], last_use=[v5] + pre: [PUSH(0)] + mstore [0, v5] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + PUSH1 0xe0 (224) // v1.i32 = shr 224.i32 v0; + SHR + PUSH0 // v2.i1 = gt v1 0.i32; + DUP2 + GT + ISZERO // br v2 block1 block2; + PUSH1 block2 + JUMPI + block1: + JUMPDEST + PUSH1 0x1 (1) // v3.i32 = add v1 1.i32; + ADD + PUSH1 block3 // jump block3; + JUMP + block2: + JUMPDEST + PUSH1 0x2 (2) // v4.i32 = add v1 2.i32; + ADD + block3: + JUMPDEST + PUSH0 // mstore 0.i32 v5 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b5f3560e01c5f8111156014575b6001016018565b6002015b5f5260205ff3 + + +Success { reason: Return, gas_used: 21124, gas_refunded: 0, logs: [], output: Call(0x000000000000000000000000000000000000000000000000000000000000000c) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 60 PUSH1 0xe0 [0xb00000016000000000000000000000000000000000000000000000000] + 5 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 6 5f PUSH0 [0xb] + 7 81 DUP2 [0xb, 0x0] + 8 11 GT [0xb, 0x0, 0xb] + 9 15 ISZERO [0xb, 0x1] + 10 60 PUSH1 0x14 [0xb, 0x0] + 12 57 JUMPI [0xb, 0x0, 0x14] + 13 5b JUMPDEST [0xb] + 14 60 PUSH1 0x01 [0xb] + 16 01 ADD [0xb, 0x1] + 17 60 PUSH1 0x18 [0xc] + 19 56 JUMP [0xc, 0x18] + 24 5b JUMPDEST [0xc] + 25 5f PUSH0 [0xc] + 26 52 MSTORE [0xc, 0x0] + 27 60 PUSH1 0x20 [] + 29 5f PUSH0 [0x20] + 30 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/diamond.sntn b/crates/codegen/test_files/evm/diamond.sntn new file mode 100644 index 00000000..c0f00f60 --- /dev/null +++ b/crates/codegen/test_files/evm/diamond.sntn @@ -0,0 +1,29 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i32 = shr 224.i32 v0; + v2.i1 = gt v1 0.i32; + br v2 block1 block2; + + block1: + v3.i32 = add v1 1.i32; + jump block3; + + block2: + v4.i32 = add v1 2.i32; + jump block3; + + block3: + v5.i32 = phi (v3 block1) (v4 block2); + + mstore 0.i32 v5 i32; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/malloc_path_sensitive.snap b/crates/codegen/test_files/evm/malloc_path_sensitive.snap new file mode 100644 index 00000000..34cea8fe --- /dev/null +++ b/crates/codegen/test_files/evm/malloc_path_sensitive.snap @@ -0,0 +1,282 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/malloc_path_sensitive.sntn +--- +evm mem plan: dyn_base=0x1c0 static_base=0xc0 +evm mem plan: big scheme=StaticTree base_words=0 persistent_words=0 alloca_words=8 persistent_alloca_words=0 + alloca v0 class=Transient offset_words=0 size_words=8 addr=0xc0 +evm mem plan: small scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: dispatch scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %big() -> i256 + block0 P=[] T=[] + - stack=[] + alloca [] -> v0 + - stack=[v0, ], last_use=[v0] + pre: [PUSH(0), PUSH(0), SWAP1, SWAP2] + gep [v0, 0, 0] -> v1 + - stack=[v1, ] + pre: [PUSH(I256 { is_negative: false, abs: 1 }), DUP2] + mstore [v1, I256 { is_negative: false, abs: 1 }] + - stack=[v1, ], last_use=[v1] + mload [v1] -> v2 + - stack=[v2, ] + return [v2] + +// func public %small() -> i256 + block0 P=[] T=[] + - stack=[] + return: [PUSH(I256 { is_negative: false, abs: 2 })] + return [I256 { is_negative: false, abs: 2 }] + +// func public %dispatch(v0.i1) -> i256 + block0 P=[v0] T=[] + - stack=[v0, ] + br [v0] -> [block1, block2] + block1 P=[] T=[] + inherited from block0: [] + - stack=[] + pre: [PUSH(I256 { is_negative: false, abs: 32 })] + evm_malloc [I256 { is_negative: false, abs: 32 }] -> v1 + - stack=[v1x, ] + cleanup: [POP] + pre: [PUSH_CONT] + call [] -> v2 + - stack=[v2, ] + return [v2] + block2 P=[] T=[] + inherited from block0: [] + - stack=[] + pre: [PUSH(I256 { is_negative: false, abs: 32 })] + evm_malloc [I256 { is_negative: false, abs: 32 }] -> v3 + - stack=[v3x, ] + cleanup: [POP] + pre: [PUSH_CONT] + call [] -> v4 + - stack=[v4, ] + return [v4] + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT, PUSH(true)] + call [true] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %big() -> i256 +big: + block0: + JUMPDEST + PUSH1 0xc0 (192) // v0.*@big = alloca @big; + PUSH0 // v1.*i256 = gep v0 0.i8 0.i8; + PUSH0 + SWAP1 + SWAP2 + SWAP1 + POP + SWAP1 + POP + PUSH1 0x1 (1) // mstore v1 1.i256 i256; + DUP2 + MSTORE + MLOAD // v2.i256 = mload v1 i256; + SWAP1 // return v2; + JUMP + +// func public %small() -> i256 +small: + block0: + JUMPDEST + PUSH1 0x2 (2) // return 2.i256; + SWAP1 + JUMP + +// func public %dispatch(v0.i1) -> i256 +dispatch: + block0: + JUMPDEST + ISZERO // br v0 block1 block2; + PUSH1 block2 + JUMPI + block1: + JUMPDEST + PUSH1 0x20 (32) // v1.*i8 = evm_malloc 32.i256; + POP + PUSH1 0x40 (64) + MLOAD + PUSH1 0x80 (128) + MLOAD + DUP2 + DUP2 + GT + PUSH1 `pc + (5)` + JUMPI + POP + PUSH1 `pc + (5)` + JUMP + JUMPDEST + SWAP1 + POP + JUMPDEST + PUSH2 0x1c0 (448) + DUP2 + DUP2 + GT + PUSH1 `pc + (5)` + JUMPI + POP + PUSH1 `pc + (5)` + JUMP + JUMPDEST + SWAP1 + POP + JUMPDEST + POP // v2.i256 = call %big; + PUSH1 `pc + (3)` + PUSH1 FuncRef(0) + JUMP + JUMPDEST + SWAP1 // return v2; + JUMP + block2: + JUMPDEST + PUSH1 0x20 (32) // v3.*i8 = evm_malloc 32.i256; + POP + PUSH1 0x40 (64) + MLOAD + PUSH1 0x80 (128) + MLOAD + DUP2 + DUP2 + GT + PUSH1 `pc + (5)` + JUMPI + POP + PUSH1 `pc + (5)` + JUMP + JUMPDEST + SWAP1 + POP + JUMPDEST + PUSH1 0xc0 (192) + DUP2 + DUP2 + GT + PUSH1 `pc + (5)` + JUMPI + POP + PUSH1 `pc + (5)` + JUMP + JUMPDEST + SWAP1 + POP + JUMPDEST + POP // v4.i256 = call %small; + PUSH1 `pc + (3)` + PUSH1 FuncRef(1) + JUMP + JUMPDEST + SWAP1 // return v4; + JUMP + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (6)` // v0.i256 = call %dispatch 1.i1; + PUSH1 0x1 (1) + PUSH0 + SIGNEXTEND + PUSH1 FuncRef(2) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b600a60015f0b6011565b5f5260205ff35b156048575b602050604051608051818111602a5750602d565b90505b6101c0818111603b5750603e565b90505b5060456079565b90565b602050604051608051818111605c5750605f565b90505b60c0818111606c5750606f565b90505b506076608b565b90565b60c05f5f909190509050600181525190565b60029056 + + +Success { reason: Return, gas_used: 21291, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000001) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x0a [] + 3 60 PUSH1 0x01 [0xa] + 5 5f PUSH0 [0xa, 0x1] + 6 0b SIGNEXTEND [0xa, 0x1, 0x0] + 7 60 PUSH1 0x11 [0xa, 0x1] + 9 56 JUMP [0xa, 0x1, 0x11] + 17 5b JUMPDEST [0xa, 0x1] + 18 15 ISZERO [0xa, 0x1] + 19 60 PUSH1 0x48 [0xa, 0x0] + 21 57 JUMPI [0xa, 0x0, 0x48] + 22 5b JUMPDEST [0xa] + 23 60 PUSH1 0x20 [0xa] + 25 50 POP [0xa, 0x20] + 26 60 PUSH1 0x40 [0xa] + 28 51 MLOAD [0xa, 0x40] + 29 60 PUSH1 0x80 [0xa, 0x0] + 31 51 MLOAD [0xa, 0x0, 0x80] + 32 81 DUP2 [0xa, 0x0, 0x0] + 33 81 DUP2 [0xa, 0x0, 0x0, 0x0] + 34 11 GT [0xa, 0x0, 0x0, 0x0, 0x0] + 35 60 PUSH1 0x2a [0xa, 0x0, 0x0, 0x0] + 37 57 JUMPI [0xa, 0x0, 0x0, 0x0, 0x2a] + 38 50 POP [0xa, 0x0, 0x0] + 39 60 PUSH1 0x2d [0xa, 0x0] + 41 56 JUMP [0xa, 0x0, 0x2d] + 45 5b JUMPDEST [0xa, 0x0] + 46 61 PUSH2 0x01c0 [0xa, 0x0] + 49 81 DUP2 [0xa, 0x0, 0x1c0] + 50 81 DUP2 [0xa, 0x0, 0x1c0, 0x0] + 51 11 GT [0xa, 0x0, 0x1c0, 0x0, 0x1c0] + 52 60 PUSH1 0x3b [0xa, 0x0, 0x1c0, 0x1] + 54 57 JUMPI [0xa, 0x0, 0x1c0, 0x1, 0x3b] + 59 5b JUMPDEST [0xa, 0x0, 0x1c0] + 60 90 SWAP1 [0xa, 0x0, 0x1c0] + 61 50 POP [0xa, 0x1c0, 0x0] + 62 5b JUMPDEST [0xa, 0x1c0] + 63 50 POP [0xa, 0x1c0] + 64 60 PUSH1 0x45 [0xa] + 66 60 PUSH1 0x79 [0xa, 0x45] + 68 56 JUMP [0xa, 0x45, 0x79] + 121 5b JUMPDEST [0xa, 0x45] + 122 60 PUSH1 0xc0 [0xa, 0x45] + 124 5f PUSH0 [0xa, 0x45, 0xc0] + 125 5f PUSH0 [0xa, 0x45, 0xc0, 0x0] + 126 90 SWAP1 [0xa, 0x45, 0xc0, 0x0, 0x0] + 127 91 SWAP2 [0xa, 0x45, 0xc0, 0x0, 0x0] + 128 90 SWAP1 [0xa, 0x45, 0x0, 0x0, 0xc0] + 129 50 POP [0xa, 0x45, 0x0, 0xc0, 0x0] + 130 90 SWAP1 [0xa, 0x45, 0x0, 0xc0] + 131 50 POP [0xa, 0x45, 0xc0, 0x0] + 132 60 PUSH1 0x01 [0xa, 0x45, 0xc0] + 134 81 DUP2 [0xa, 0x45, 0xc0, 0x1] + 135 52 MSTORE [0xa, 0x45, 0xc0, 0x1, 0xc0] + 136 51 MLOAD [0xa, 0x45, 0xc0] + 137 90 SWAP1 [0xa, 0x45, 0x1] + 138 56 JUMP [0xa, 0x1, 0x45] + 69 5b JUMPDEST [0xa, 0x1] + 70 90 SWAP1 [0xa, 0x1] + 71 56 JUMP [0x1, 0xa] + 10 5b JUMPDEST [0x1] + 11 5f PUSH0 [0x1] + 12 52 MSTORE [0x1, 0x0] + 13 60 PUSH1 0x20 [] + 15 5f PUSH0 [0x20] + 16 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/malloc_path_sensitive.sntn b/crates/codegen/test_files/evm/malloc_path_sensitive.sntn new file mode 100644 index 00000000..45e44f87 --- /dev/null +++ b/crates/codegen/test_files/evm/malloc_path_sensitive.sntn @@ -0,0 +1,46 @@ +target = "evm-ethereum-osaka" + +type @big = { i256, i256, i256, i256, i256, i256, i256, i256 }; + +func public %big() -> i256 { + block0: + v0.*@big = alloca @big; + v1.*i256 = gep v0 0.i8 0.i8; + mstore v1 1.i256 i256; + v2.i256 = mload v1 i256; + return v2; +} + +func public %small() -> i256 { + block0: + return 2.i256; +} + +func public %dispatch(v0.i1) -> i256 { + block0: + br v0 block1 block2; + + block1: + v1.*i8 = evm_malloc 32.i256; + v2.i256 = call %big; + return v2; + + block2: + v3.*i8 = evm_malloc 32.i256; + v4.i256 = call %small; + return v4; +} + +func public %entry() { + block0: + v0.i256 = call %dispatch 1.i1; + mstore 0.i32 v0 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} + diff --git a/crates/codegen/test_files/evm/mixed_frame_recursion_reach4.snap b/crates/codegen/test_files/evm/mixed_frame_recursion_reach4.snap new file mode 100644 index 00000000..3d678935 --- /dev/null +++ b/crates/codegen/test_files/evm/mixed_frame_recursion_reach4.snap @@ -0,0 +1,719 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/mixed_frame_recursion_reach4.sntn +--- +evm mem plan: dyn_base=0xe0 static_base=0xc0 +evm mem plan: b scheme=StaticTree base_words=0 persistent_words=0 alloca_words=1 persistent_alloca_words=0 + alloca v1 class=Transient offset_words=0 size_words=1 addr=0xc0 +evm mem plan: d scheme=DynamicFrame frame_slots=6 spill_slots=5 alloca_words=1 persistent_alloca_words=0 + alloca v1 class=Transient offset_words=0 size_words=1 addr=fp+0xa0 + alloca v5 class=Transient offset_words=0 size_words=1 addr=fp+0xa0 +evm mem plan: a scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %b(v0.i256) -> i256 + block0 P=[v0] T=[] + - stack=[v0, ] + alloca [] -> v1 + - stack=[v1, v0, ], last_use=[v0] + pre: [SWAP1, DUP2] + mstore [v1, v0] + - stack=[v1, ], last_use=[v1] + mload [v1] -> v2 + - stack=[v2, ] + return [v2] + +// func public %d(v0.i256) -> i256 +spill_set: 0=v7, 1=v8, 2=v9, 3=v10, 4=v11, 5=v2 + block0 P=[v0] T=[] + - stack=[v0, ] + alloca [] -> v1 + - stack=[v1, v0, ], last_use=[v0] + pre: [SWAP1, DUP2] + mstore [v1, v0] + - stack=[v1, ], last_use=[v1] + mload [v1] -> v2 + post: [DUP1, MSTORE_SLOT(5)] + - stack=[v2, ] + pre: [PUSH(I256 { is_negative: false, abs: 0 }), DUP2] + eq [v2, I256 { is_negative: false, abs: 0 }] -> v3 + - stack=[v3, v2, ] + br [v3] -> [block1, block2] + block1 P=[] T=[] + inherited from block0: [v2, ] + prologue: [POP] + - stack=[], last_use=[v2] + pre: [PUSH_CONT, MLOAD_SLOT(5)] + call [v2] -> v4 + - stack=[v4, ] + return [v4] + block2 P=[] T=[] + inherited from block0: [v2, ] + prologue: [POP] + - stack=[] + alloca [] -> v5 + - stack=[v5, ] + pre: [PUSH(I256 { is_negative: false, abs: 0 }), DUP2] + mstore [v5, I256 { is_negative: false, abs: 0 }] + - stack=[v5, ], last_use=[v5] + mload [v5] -> v6 + - stack=[v6, ] + pre: [PUSH(I256 { is_negative: false, abs: 1 }), DUP2] + add [v6, I256 { is_negative: false, abs: 1 }] -> v7 + post: [DUP1, MSTORE_SLOT(0)] + - stack=[v7, v6, ] + pre: [PUSH(I256 { is_negative: false, abs: 2 }), DUP3] + add [v6, I256 { is_negative: false, abs: 2 }] -> v8 + post: [DUP1, MSTORE_SLOT(1)] + - stack=[v8, v7, v6, ] + pre: [PUSH(I256 { is_negative: false, abs: 3 }), DUP4] + add [v6, I256 { is_negative: false, abs: 3 }] -> v9 + post: [DUP1, MSTORE_SLOT(2)] + - stack=[v9, v8, v7, v6, ] + pre: [DUP4, PUSH(I256 { is_negative: false, abs: 4 })] + add [I256 { is_negative: false, abs: 4 }, v6] -> v10 + post: [DUP1, MSTORE_SLOT(3)] + - stack=[v10, v9, v8, v7, v6, ], last_use=[v6] + pre: [SWAP4, PUSH(I256 { is_negative: false, abs: 5 })] + add [I256 { is_negative: false, abs: 5 }, v6] -> v11 + post: [DUP1, MSTORE_SLOT(4)] + - stack=[v11, v9, v8, v7, v10, ], last_use=[v2] + pre: [PUSH(I256 { is_negative: false, abs: 1 }), MLOAD_SLOT(5)] + sub [v2, I256 { is_negative: false, abs: 1 }] -> v12 + - stack=[v12, v11, v9, v8, v7, v10, ], last_use=[v12] + pre: [PUSH_CONT, SWAP1] + call [v12] -> v13 + - stack=[v13, v11, v9, v8, v7, v10, ], last_use=[v7, v13] + pre: [SWAP1, SWAP4] + add [v7, v13] -> v14 + - stack=[v14, v9, v8, v11, v10, ], last_use=[v8, v14] + pre: [SWAP1, SWAP2] + add [v8, v14] -> v15 + - stack=[v15, v9, v11, v10, ], last_use=[v9, v15] + add [v15, v9] -> v16 + - stack=[v16, v11, v10, ], last_use=[v10, v16] + pre: [SWAP1, SWAP2] + add [v10, v16] -> v17 + - stack=[v17, v11, ], last_use=[v11, v17] + add [v17, v11] -> v18 + - stack=[v18, ] + return [v18] + +// func public %a() -> i256 + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT, PUSH(I256 { is_negative: false, abs: 2 })] + call [I256 { is_negative: false, abs: 2 }] -> v0 + - stack=[v0, ] + return [v0] + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH_CONT] + call [] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(0)] + mstore [0, v0] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %b(v0.i256) -> i256 +b: + block0: + JUMPDEST + PUSH1 0xc0 (192) // v1.*i256 = alloca i256; + SWAP1 // mstore v1 v0 i256; + DUP2 + MSTORE + MLOAD // v2.i256 = mload v1 i256; + SWAP1 // return v2; + JUMP + +// func public %d(v0.i256) -> i256 +d: + block0: + JUMPDEST + PUSH1 0x80 (128) + MLOAD + DUP1 + ISZERO + ISZERO + PUSH1 `pc + (7)` + JUMPI + POP + PUSH1 0xe0 (224) + DUP1 + PUSH1 0x80 (128) + MSTORE + JUMPDEST + PUSH1 0x40 (64) + MLOAD + DUP2 + DUP2 + GT + PUSH1 `pc + (5)` + JUMPI + POP + PUSH1 `pc + (5)` + JUMP + JUMPDEST + SWAP1 + POP + JUMPDEST + PUSH1 0xa0 (160) + MLOAD + DUP2 + MSTORE + DUP1 + PUSH1 0x20 (32) + ADD + DUP1 + PUSH1 0xa0 (160) + MSTORE + PUSH1 0xc0 (192) + ADD + PUSH1 0x80 (128) + MSTORE + POP + PUSH1 0xa0 (160) // v1.*i256 = alloca i256; + MLOAD + PUSH1 0xa0 (160) + ADD + SWAP1 // mstore v1 v0 i256; + DUP2 + MSTORE + MLOAD // v2.i256 = mload v1 i256; + DUP1 + PUSH0 + MSTORE + PUSH0 // v3.i1 = eq v2 0.i256; + DUP2 + EQ + ISZERO // br v3 block1 block2; + PUSH1 block2 + JUMPI + block1: + JUMPDEST + POP // v4.i256 = call %b v2; + PUSH1 `pc + (5)` + PUSH0 + MLOAD + PUSH1 FuncRef(0) + JUMP + JUMPDEST + PUSH1 0x20 (32) // return v4; + PUSH1 0xa0 (160) + MLOAD + SUB + DUP1 + MLOAD + PUSH1 0xa0 (160) + MSTORE + PUSH1 0x80 (128) + MSTORE + SWAP1 + JUMP + block2: + JUMPDEST + POP // v5.*i256 = alloca i256; + PUSH1 0xa0 (160) + MLOAD + PUSH1 0xa0 (160) + ADD + PUSH0 // mstore v5 0.i256 i256; + DUP2 + MSTORE + MLOAD // v6.i256 = mload v5 i256; + PUSH1 0x1 (1) // v7.i256 = add v6 1.i256; + DUP2 + ADD + DUP1 + PUSH1 0xa0 (160) + MLOAD + MSTORE + PUSH1 0x2 (2) // v8.i256 = add v6 2.i256; + DUP3 + ADD + DUP1 + PUSH1 0xa0 (160) + MLOAD + PUSH1 0x20 (32) + ADD + MSTORE + PUSH1 0x3 (3) // v9.i256 = add v6 3.i256; + DUP4 + ADD + DUP1 + PUSH1 0xa0 (160) + MLOAD + PUSH1 0x40 (64) + ADD + MSTORE + DUP4 // v10.i256 = add v6 4.i256; + PUSH1 0x4 (4) + ADD + DUP1 + PUSH1 0xa0 (160) + MLOAD + PUSH1 0x60 (96) + ADD + MSTORE + SWAP4 // v11.i256 = add v6 5.i256; + PUSH1 0x5 (5) + ADD + DUP1 + PUSH1 0xa0 (160) + MLOAD + PUSH1 0x80 (128) + ADD + MSTORE + PUSH1 0x1 (1) // v12.i256 = sub v2 1.i256; + PUSH0 + MLOAD + SUB + PUSH1 `pc + (4)` // v13.i256 = call %d v12; + SWAP1 + PUSH1 FuncRef(1) + JUMP + JUMPDEST + SWAP1 // v14.i256 = add v13 v7; + SWAP4 + ADD + SWAP1 // v15.i256 = add v14 v8; + SWAP2 + ADD + ADD // v16.i256 = add v15 v9; + SWAP1 // v17.i256 = add v16 v10; + SWAP2 + ADD + ADD // v18.i256 = add v17 v11; + PUSH1 0x20 (32) // return v18; + PUSH1 0xa0 (160) + MLOAD + SUB + DUP1 + MLOAD + PUSH1 0xa0 (160) + MSTORE + PUSH1 0x80 (128) + MSTORE + SWAP1 + JUMP + +// func public %a() -> i256 +a: + block0: + JUMPDEST + PUSH1 `pc + (4)` // v0.i256 = call %d 2.i256; + PUSH1 0x2 (2) + PUSH1 FuncRef(1) + JUMP + JUMPDEST + SWAP1 // return v0; + JUMP + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH1 `pc + (3)` // v0.i256 = call %a; + PUSH1 FuncRef(2) + JUMP + JUMPDEST + PUSH0 // mstore 0.i32 v0 i256; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b6006600d565b5f5260205ff35b601560026018565b90565b6080518015156029575060e0806080525b60405181811160375750603a565b90505b60a0518152806020018060a05260c0016080525060a05160a00190815251805f525f811415607d575b50606c5f5160e9565b602060a05103805160a05260805290565b5060a05160a0015f815251600181018060a05152600282018060a05160200152600383018060a05160400152836004018060a05160600152936005018060a0516080015260015f510360cd906018565b9093019091010190910101602060a05103805160a05260805290565b60c0908152519056 + + +Success { reason: Return, gas_used: 22334, gas_refunded: 0, logs: [], output: Call(0x000000000000000000000000000000000000000000000000000000000000001e) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 60 PUSH1 0x06 [] + 3 60 PUSH1 0x0d [0x6] + 5 56 JUMP [0x6, 0xd] + 13 5b JUMPDEST [0x6] + 14 60 PUSH1 0x15 [0x6] + 16 60 PUSH1 0x02 [0x6, 0x15] + 18 60 PUSH1 0x18 [0x6, 0x15, 0x2] + 20 56 JUMP [0x6, 0x15, 0x2, 0x18] + 24 5b JUMPDEST [0x6, 0x15, 0x2] + 25 60 PUSH1 0x80 [0x6, 0x15, 0x2] + 27 51 MLOAD [0x6, 0x15, 0x2, 0x80] + 28 80 DUP1 [0x6, 0x15, 0x2, 0x0] + 29 15 ISZERO [0x6, 0x15, 0x2, 0x0, 0x0] + 30 15 ISZERO [0x6, 0x15, 0x2, 0x0, 0x1] + 31 60 PUSH1 0x29 [0x6, 0x15, 0x2, 0x0, 0x0] + 33 57 JUMPI [0x6, 0x15, 0x2, 0x0, 0x0, 0x29] + 34 50 POP [0x6, 0x15, 0x2, 0x0] + 35 60 PUSH1 0xe0 [0x6, 0x15, 0x2] + 37 80 DUP1 [0x6, 0x15, 0x2, 0xe0] + 38 60 PUSH1 0x80 [0x6, 0x15, 0x2, 0xe0, 0xe0] + 40 52 MSTORE [0x6, 0x15, 0x2, 0xe0, 0xe0, 0x80] + 41 5b JUMPDEST [0x6, 0x15, 0x2, 0xe0] + 42 60 PUSH1 0x40 [0x6, 0x15, 0x2, 0xe0] + 44 51 MLOAD [0x6, 0x15, 0x2, 0xe0, 0x40] + 45 81 DUP2 [0x6, 0x15, 0x2, 0xe0, 0x0] + 46 81 DUP2 [0x6, 0x15, 0x2, 0xe0, 0x0, 0xe0] + 47 11 GT [0x6, 0x15, …, 0x0, 0xe0, 0x0] (len=7) + 48 60 PUSH1 0x37 [0x6, 0x15, 0x2, 0xe0, 0x0, 0x0] + 50 57 JUMPI [0x6, 0x15, …, 0x0, 0x0, 0x37] (len=7) + 51 50 POP [0x6, 0x15, 0x2, 0xe0, 0x0] + 52 60 PUSH1 0x3a [0x6, 0x15, 0x2, 0xe0] + 54 56 JUMP [0x6, 0x15, 0x2, 0xe0, 0x3a] + 58 5b JUMPDEST [0x6, 0x15, 0x2, 0xe0] + 59 60 PUSH1 0xa0 [0x6, 0x15, 0x2, 0xe0] + 61 51 MLOAD [0x6, 0x15, 0x2, 0xe0, 0xa0] + 62 81 DUP2 [0x6, 0x15, 0x2, 0xe0, 0x0] + 63 52 MSTORE [0x6, 0x15, 0x2, 0xe0, 0x0, 0xe0] + 64 80 DUP1 [0x6, 0x15, 0x2, 0xe0] + 65 60 PUSH1 0x20 [0x6, 0x15, 0x2, 0xe0, 0xe0] + 67 01 ADD [0x6, 0x15, 0x2, 0xe0, 0xe0, 0x20] + 68 80 DUP1 [0x6, 0x15, 0x2, 0xe0, 0x100] + 69 60 PUSH1 0xa0 [0x6, 0x15, 0x2, 0xe0, 0x100, 0x100] + 71 52 MSTORE [0x6, 0x15, …, 0x100, 0x100, 0xa0] (len=7) + 72 60 PUSH1 0xc0 [0x6, 0x15, 0x2, 0xe0, 0x100] + 74 01 ADD [0x6, 0x15, 0x2, 0xe0, 0x100, 0xc0] + 75 60 PUSH1 0x80 [0x6, 0x15, 0x2, 0xe0, 0x1c0] + 77 52 MSTORE [0x6, 0x15, 0x2, 0xe0, 0x1c0, 0x80] + 78 50 POP [0x6, 0x15, 0x2, 0xe0] + 79 60 PUSH1 0xa0 [0x6, 0x15, 0x2] + 81 51 MLOAD [0x6, 0x15, 0x2, 0xa0] + 82 60 PUSH1 0xa0 [0x6, 0x15, 0x2, 0x100] + 84 01 ADD [0x6, 0x15, 0x2, 0x100, 0xa0] + 85 90 SWAP1 [0x6, 0x15, 0x2, 0x1a0] + 86 81 DUP2 [0x6, 0x15, 0x1a0, 0x2] + 87 52 MSTORE [0x6, 0x15, 0x1a0, 0x2, 0x1a0] + 88 51 MLOAD [0x6, 0x15, 0x1a0] + 89 80 DUP1 [0x6, 0x15, 0x2] + 90 5f PUSH0 [0x6, 0x15, 0x2, 0x2] + 91 52 MSTORE [0x6, 0x15, 0x2, 0x2, 0x0] + 92 5f PUSH0 [0x6, 0x15, 0x2] + 93 81 DUP2 [0x6, 0x15, 0x2, 0x0] + 94 14 EQ [0x6, 0x15, 0x2, 0x0, 0x2] + 95 15 ISZERO [0x6, 0x15, 0x2, 0x0] + 96 60 PUSH1 0x7d [0x6, 0x15, 0x2, 0x1] + 98 57 JUMPI [0x6, 0x15, 0x2, 0x1, 0x7d] + 125 5b JUMPDEST [0x6, 0x15, 0x2] + 126 50 POP [0x6, 0x15, 0x2] + 127 60 PUSH1 0xa0 [0x6, 0x15] + 129 51 MLOAD [0x6, 0x15, 0xa0] + 130 60 PUSH1 0xa0 [0x6, 0x15, 0x100] + 132 01 ADD [0x6, 0x15, 0x100, 0xa0] + 133 5f PUSH0 [0x6, 0x15, 0x1a0] + 134 81 DUP2 [0x6, 0x15, 0x1a0, 0x0] + 135 52 MSTORE [0x6, 0x15, 0x1a0, 0x0, 0x1a0] + 136 51 MLOAD [0x6, 0x15, 0x1a0] + 137 60 PUSH1 0x01 [0x6, 0x15, 0x0] + 139 81 DUP2 [0x6, 0x15, 0x0, 0x1] + 140 01 ADD [0x6, 0x15, 0x0, 0x1, 0x0] + 141 80 DUP1 [0x6, 0x15, 0x0, 0x1] + 142 60 PUSH1 0xa0 [0x6, 0x15, 0x0, 0x1, 0x1] + 144 51 MLOAD [0x6, 0x15, 0x0, 0x1, 0x1, 0xa0] + 145 52 MSTORE [0x6, 0x15, 0x0, 0x1, 0x1, 0x100] + 146 60 PUSH1 0x02 [0x6, 0x15, 0x0, 0x1] + 148 82 DUP3 [0x6, 0x15, 0x0, 0x1, 0x2] + 149 01 ADD [0x6, 0x15, 0x0, 0x1, 0x2, 0x0] + 150 80 DUP1 [0x6, 0x15, 0x0, 0x1, 0x2] + 151 60 PUSH1 0xa0 [0x6, 0x15, 0x0, 0x1, 0x2, 0x2] + 153 51 MLOAD [0x6, 0x15, …, 0x2, 0x2, 0xa0] (len=7) + 154 60 PUSH1 0x20 [0x6, 0x15, …, 0x2, 0x2, 0x100] (len=7) + 156 01 ADD [0x6, 0x15, …, 0x2, 0x100, 0x20] (len=8) + 157 52 MSTORE [0x6, 0x15, …, 0x2, 0x2, 0x120] (len=7) + 158 60 PUSH1 0x03 [0x6, 0x15, 0x0, 0x1, 0x2] + 160 83 DUP4 [0x6, 0x15, 0x0, 0x1, 0x2, 0x3] + 161 01 ADD [0x6, 0x15, …, 0x2, 0x3, 0x0] (len=7) + 162 80 DUP1 [0x6, 0x15, 0x0, 0x1, 0x2, 0x3] + 163 60 PUSH1 0xa0 [0x6, 0x15, …, 0x2, 0x3, 0x3] (len=7) + 165 51 MLOAD [0x6, 0x15, …, 0x3, 0x3, 0xa0] (len=8) + 166 60 PUSH1 0x40 [0x6, 0x15, …, 0x3, 0x3, 0x100] (len=8) + 168 01 ADD [0x6, 0x15, …, 0x3, 0x100, 0x40] (len=9) + 169 52 MSTORE [0x6, 0x15, …, 0x3, 0x3, 0x140] (len=8) + 170 83 DUP4 [0x6, 0x15, 0x0, 0x1, 0x2, 0x3] + 171 60 PUSH1 0x04 [0x6, 0x15, …, 0x2, 0x3, 0x0] (len=7) + 173 01 ADD [0x6, 0x15, …, 0x3, 0x0, 0x4] (len=8) + 174 80 DUP1 [0x6, 0x15, …, 0x2, 0x3, 0x4] (len=7) + 175 60 PUSH1 0xa0 [0x6, 0x15, …, 0x3, 0x4, 0x4] (len=8) + 177 51 MLOAD [0x6, 0x15, …, 0x4, 0x4, 0xa0] (len=9) + 178 60 PUSH1 0x60 [0x6, 0x15, …, 0x4, 0x4, 0x100] (len=9) + 180 01 ADD [0x6, 0x15, …, 0x4, 0x100, 0x60] (len=10) + 181 52 MSTORE [0x6, 0x15, …, 0x4, 0x4, 0x160] (len=9) + 182 93 SWAP4 [0x6, 0x15, …, 0x2, 0x3, 0x4] (len=7) + 183 60 PUSH1 0x05 [0x6, 0x15, …, 0x2, 0x3, 0x0] (len=7) + 185 01 ADD [0x6, 0x15, …, 0x3, 0x0, 0x5] (len=8) + 186 80 DUP1 [0x6, 0x15, …, 0x2, 0x3, 0x5] (len=7) + 187 60 PUSH1 0xa0 [0x6, 0x15, …, 0x3, 0x5, 0x5] (len=8) + 189 51 MLOAD [0x6, 0x15, …, 0x5, 0x5, 0xa0] (len=9) + 190 60 PUSH1 0x80 [0x6, 0x15, …, 0x5, 0x5, 0x100] (len=9) + 192 01 ADD [0x6, 0x15, …, 0x5, 0x100, 0x80] (len=10) + 193 52 MSTORE [0x6, 0x15, …, 0x5, 0x5, 0x180] (len=9) + 194 60 PUSH1 0x01 [0x6, 0x15, …, 0x2, 0x3, 0x5] (len=7) + 196 5f PUSH0 [0x6, 0x15, …, 0x3, 0x5, 0x1] (len=8) + 197 51 MLOAD [0x6, 0x15, …, 0x5, 0x1, 0x0] (len=9) + 198 03 SUB [0x6, 0x15, …, 0x5, 0x1, 0x2] (len=9) + 199 60 PUSH1 0xcd [0x6, 0x15, …, 0x3, 0x5, 0x1] (len=8) + 201 90 SWAP1 [0x6, 0x15, …, 0x5, 0x1, 0xcd] (len=9) + 202 60 PUSH1 0x18 [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 204 56 JUMP [0x6, 0x15, …, 0xcd, 0x1, 0x18] (len=10) + 24 5b JUMPDEST [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 25 60 PUSH1 0x80 [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 27 51 MLOAD [0x6, 0x15, …, 0xcd, 0x1, 0x80] (len=10) + 28 80 DUP1 [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 29 15 ISZERO [0x6, 0x15, …, 0x1, 0x1c0, 0x1c0] (len=11) + 30 15 ISZERO [0x6, 0x15, …, 0x1, 0x1c0, 0x0] (len=11) + 31 60 PUSH1 0x29 [0x6, 0x15, …, 0x1, 0x1c0, 0x1] (len=11) + 33 57 JUMPI [0x6, 0x15, …, 0x1c0, 0x1, 0x29] (len=12) + 41 5b JUMPDEST [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 42 60 PUSH1 0x40 [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 44 51 MLOAD [0x6, 0x15, …, 0x1, 0x1c0, 0x40] (len=11) + 45 81 DUP2 [0x6, 0x15, …, 0x1, 0x1c0, 0x0] (len=11) + 46 81 DUP2 [0x6, 0x15, …, 0x1c0, 0x0, 0x1c0] (len=12) + 47 11 GT [0x6, 0x15, …, 0x0, 0x1c0, 0x0] (len=13) + 48 60 PUSH1 0x37 [0x6, 0x15, …, 0x1c0, 0x0, 0x0] (len=12) + 50 57 JUMPI [0x6, 0x15, …, 0x0, 0x0, 0x37] (len=13) + 51 50 POP [0x6, 0x15, …, 0x1, 0x1c0, 0x0] (len=11) + 52 60 PUSH1 0x3a [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 54 56 JUMP [0x6, 0x15, …, 0x1, 0x1c0, 0x3a] (len=11) + 58 5b JUMPDEST [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 59 60 PUSH1 0xa0 [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 61 51 MLOAD [0x6, 0x15, …, 0x1, 0x1c0, 0xa0] (len=11) + 62 81 DUP2 [0x6, 0x15, …, 0x1, 0x1c0, 0x100] (len=11) + 63 52 MSTORE [0x6, 0x15, …, 0x1c0, 0x100, 0x1c0] (len=12) + 64 80 DUP1 [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 65 60 PUSH1 0x20 [0x6, 0x15, …, 0x1, 0x1c0, 0x1c0] (len=11) + 67 01 ADD [0x6, 0x15, …, 0x1c0, 0x1c0, 0x20] (len=12) + 68 80 DUP1 [0x6, 0x15, …, 0x1, 0x1c0, 0x1e0] (len=11) + 69 60 PUSH1 0xa0 [0x6, 0x15, …, 0x1c0, 0x1e0, 0x1e0] (len=12) + 71 52 MSTORE [0x6, 0x15, …, 0x1e0, 0x1e0, 0xa0] (len=13) + 72 60 PUSH1 0xc0 [0x6, 0x15, …, 0x1, 0x1c0, 0x1e0] (len=11) + 74 01 ADD [0x6, 0x15, …, 0x1c0, 0x1e0, 0xc0] (len=12) + 75 60 PUSH1 0x80 [0x6, 0x15, …, 0x1, 0x1c0, 0x2a0] (len=11) + 77 52 MSTORE [0x6, 0x15, …, 0x1c0, 0x2a0, 0x80] (len=12) + 78 50 POP [0x6, 0x15, …, 0xcd, 0x1, 0x1c0] (len=10) + 79 60 PUSH1 0xa0 [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 81 51 MLOAD [0x6, 0x15, …, 0xcd, 0x1, 0xa0] (len=10) + 82 60 PUSH1 0xa0 [0x6, 0x15, …, 0xcd, 0x1, 0x1e0] (len=10) + 84 01 ADD [0x6, 0x15, …, 0x1, 0x1e0, 0xa0] (len=11) + 85 90 SWAP1 [0x6, 0x15, …, 0xcd, 0x1, 0x280] (len=10) + 86 81 DUP2 [0x6, 0x15, …, 0xcd, 0x280, 0x1] (len=10) + 87 52 MSTORE [0x6, 0x15, …, 0x280, 0x1, 0x280] (len=11) + 88 51 MLOAD [0x6, 0x15, …, 0x5, 0xcd, 0x280] (len=9) + 89 80 DUP1 [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 90 5f PUSH0 [0x6, 0x15, …, 0xcd, 0x1, 0x1] (len=10) + 91 52 MSTORE [0x6, 0x15, …, 0x1, 0x1, 0x0] (len=11) + 92 5f PUSH0 [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 93 81 DUP2 [0x6, 0x15, …, 0xcd, 0x1, 0x0] (len=10) + 94 14 EQ [0x6, 0x15, …, 0x1, 0x0, 0x1] (len=11) + 95 15 ISZERO [0x6, 0x15, …, 0xcd, 0x1, 0x0] (len=10) + 96 60 PUSH1 0x7d [0x6, 0x15, …, 0xcd, 0x1, 0x1] (len=10) + 98 57 JUMPI [0x6, 0x15, …, 0x1, 0x1, 0x7d] (len=11) + 125 5b JUMPDEST [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 126 50 POP [0x6, 0x15, …, 0x5, 0xcd, 0x1] (len=9) + 127 60 PUSH1 0xa0 [0x6, 0x15, …, 0x3, 0x5, 0xcd] (len=8) + 129 51 MLOAD [0x6, 0x15, …, 0x5, 0xcd, 0xa0] (len=9) + 130 60 PUSH1 0xa0 [0x6, 0x15, …, 0x5, 0xcd, 0x1e0] (len=9) + 132 01 ADD [0x6, 0x15, …, 0xcd, 0x1e0, 0xa0] (len=10) + 133 5f PUSH0 [0x6, 0x15, …, 0x5, 0xcd, 0x280] (len=9) + 134 81 DUP2 [0x6, 0x15, …, 0xcd, 0x280, 0x0] (len=10) + 135 52 MSTORE [0x6, 0x15, …, 0x280, 0x0, 0x280] (len=11) + 136 51 MLOAD [0x6, 0x15, …, 0x5, 0xcd, 0x280] (len=9) + 137 60 PUSH1 0x01 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=9) + 139 81 DUP2 [0x6, 0x15, …, 0xcd, 0x0, 0x1] (len=10) + 140 01 ADD [0x6, 0x15, …, 0x0, 0x1, 0x0] (len=11) + 141 80 DUP1 [0x6, 0x15, …, 0xcd, 0x0, 0x1] (len=10) + 142 60 PUSH1 0xa0 [0x6, 0x15, …, 0x0, 0x1, 0x1] (len=11) + 144 51 MLOAD [0x6, 0x15, …, 0x1, 0x1, 0xa0] (len=12) + 145 52 MSTORE [0x6, 0x15, …, 0x1, 0x1, 0x1e0] (len=12) + 146 60 PUSH1 0x02 [0x6, 0x15, …, 0xcd, 0x0, 0x1] (len=10) + 148 82 DUP3 [0x6, 0x15, …, 0x0, 0x1, 0x2] (len=11) + 149 01 ADD [0x6, 0x15, …, 0x1, 0x2, 0x0] (len=12) + 150 80 DUP1 [0x6, 0x15, …, 0x0, 0x1, 0x2] (len=11) + 151 60 PUSH1 0xa0 [0x6, 0x15, …, 0x1, 0x2, 0x2] (len=12) + 153 51 MLOAD [0x6, 0x15, …, 0x2, 0x2, 0xa0] (len=13) + 154 60 PUSH1 0x20 [0x6, 0x15, …, 0x2, 0x2, 0x1e0] (len=13) + 156 01 ADD [0x6, 0x15, …, 0x2, 0x1e0, 0x20] (len=14) + 157 52 MSTORE [0x6, 0x15, …, 0x2, 0x2, 0x200] (len=13) + 158 60 PUSH1 0x03 [0x6, 0x15, …, 0x0, 0x1, 0x2] (len=11) + 160 83 DUP4 [0x6, 0x15, …, 0x1, 0x2, 0x3] (len=12) + 161 01 ADD [0x6, 0x15, …, 0x2, 0x3, 0x0] (len=13) + 162 80 DUP1 [0x6, 0x15, …, 0x1, 0x2, 0x3] (len=12) + 163 60 PUSH1 0xa0 [0x6, 0x15, …, 0x2, 0x3, 0x3] (len=13) + 165 51 MLOAD [0x6, 0x15, …, 0x3, 0x3, 0xa0] (len=14) + 166 60 PUSH1 0x40 [0x6, 0x15, …, 0x3, 0x3, 0x1e0] (len=14) + 168 01 ADD [0x6, 0x15, …, 0x3, 0x1e0, 0x40] (len=15) + 169 52 MSTORE [0x6, 0x15, …, 0x3, 0x3, 0x220] (len=14) + 170 83 DUP4 [0x6, 0x15, …, 0x1, 0x2, 0x3] (len=12) + 171 60 PUSH1 0x04 [0x6, 0x15, …, 0x2, 0x3, 0x0] (len=13) + 173 01 ADD [0x6, 0x15, …, 0x3, 0x0, 0x4] (len=14) + 174 80 DUP1 [0x6, 0x15, …, 0x2, 0x3, 0x4] (len=13) + 175 60 PUSH1 0xa0 [0x6, 0x15, …, 0x3, 0x4, 0x4] (len=14) + 177 51 MLOAD [0x6, 0x15, …, 0x4, 0x4, 0xa0] (len=15) + 178 60 PUSH1 0x60 [0x6, 0x15, …, 0x4, 0x4, 0x1e0] (len=15) + 180 01 ADD [0x6, 0x15, …, 0x4, 0x1e0, 0x60] (len=16) + 181 52 MSTORE [0x6, 0x15, …, 0x4, 0x4, 0x240] (len=15) + 182 93 SWAP4 [0x6, 0x15, …, 0x2, 0x3, 0x4] (len=13) + 183 60 PUSH1 0x05 [0x6, 0x15, …, 0x2, 0x3, 0x0] (len=13) + 185 01 ADD [0x6, 0x15, …, 0x3, 0x0, 0x5] (len=14) + 186 80 DUP1 [0x6, 0x15, …, 0x2, 0x3, 0x5] (len=13) + 187 60 PUSH1 0xa0 [0x6, 0x15, …, 0x3, 0x5, 0x5] (len=14) + 189 51 MLOAD [0x6, 0x15, …, 0x5, 0x5, 0xa0] (len=15) + 190 60 PUSH1 0x80 [0x6, 0x15, …, 0x5, 0x5, 0x1e0] (len=15) + 192 01 ADD [0x6, 0x15, …, 0x5, 0x1e0, 0x80] (len=16) + 193 52 MSTORE [0x6, 0x15, …, 0x5, 0x5, 0x260] (len=15) + 194 60 PUSH1 0x01 [0x6, 0x15, …, 0x2, 0x3, 0x5] (len=13) + 196 5f PUSH0 [0x6, 0x15, …, 0x3, 0x5, 0x1] (len=14) + 197 51 MLOAD [0x6, 0x15, …, 0x5, 0x1, 0x0] (len=15) + 198 03 SUB [0x6, 0x15, …, 0x5, 0x1, 0x1] (len=15) + 199 60 PUSH1 0xcd [0x6, 0x15, …, 0x3, 0x5, 0x0] (len=14) + 201 90 SWAP1 [0x6, 0x15, …, 0x5, 0x0, 0xcd] (len=15) + 202 60 PUSH1 0x18 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 204 56 JUMP [0x6, 0x15, …, 0xcd, 0x0, 0x18] (len=16) + 24 5b JUMPDEST [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 25 60 PUSH1 0x80 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 27 51 MLOAD [0x6, 0x15, …, 0xcd, 0x0, 0x80] (len=16) + 28 80 DUP1 [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 29 15 ISZERO [0x6, 0x15, …, 0x0, 0x2a0, 0x2a0] (len=17) + 30 15 ISZERO [0x6, 0x15, …, 0x0, 0x2a0, 0x0] (len=17) + 31 60 PUSH1 0x29 [0x6, 0x15, …, 0x0, 0x2a0, 0x1] (len=17) + 33 57 JUMPI [0x6, 0x15, …, 0x2a0, 0x1, 0x29] (len=18) + 41 5b JUMPDEST [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 42 60 PUSH1 0x40 [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 44 51 MLOAD [0x6, 0x15, …, 0x0, 0x2a0, 0x40] (len=17) + 45 81 DUP2 [0x6, 0x15, …, 0x0, 0x2a0, 0x0] (len=17) + 46 81 DUP2 [0x6, 0x15, …, 0x2a0, 0x0, 0x2a0] (len=18) + 47 11 GT [0x6, 0x15, …, 0x0, 0x2a0, 0x0] (len=19) + 48 60 PUSH1 0x37 [0x6, 0x15, …, 0x2a0, 0x0, 0x0] (len=18) + 50 57 JUMPI [0x6, 0x15, …, 0x0, 0x0, 0x37] (len=19) + 51 50 POP [0x6, 0x15, …, 0x0, 0x2a0, 0x0] (len=17) + 52 60 PUSH1 0x3a [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 54 56 JUMP [0x6, 0x15, …, 0x0, 0x2a0, 0x3a] (len=17) + 58 5b JUMPDEST [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 59 60 PUSH1 0xa0 [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 61 51 MLOAD [0x6, 0x15, …, 0x0, 0x2a0, 0xa0] (len=17) + 62 81 DUP2 [0x6, 0x15, …, 0x0, 0x2a0, 0x1e0] (len=17) + 63 52 MSTORE [0x6, 0x15, …, 0x2a0, 0x1e0, 0x2a0] (len=18) + 64 80 DUP1 [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 65 60 PUSH1 0x20 [0x6, 0x15, …, 0x0, 0x2a0, 0x2a0] (len=17) + 67 01 ADD [0x6, 0x15, …, 0x2a0, 0x2a0, 0x20] (len=18) + 68 80 DUP1 [0x6, 0x15, …, 0x0, 0x2a0, 0x2c0] (len=17) + 69 60 PUSH1 0xa0 [0x6, 0x15, …, 0x2a0, 0x2c0, 0x2c0] (len=18) + 71 52 MSTORE [0x6, 0x15, …, 0x2c0, 0x2c0, 0xa0] (len=19) + 72 60 PUSH1 0xc0 [0x6, 0x15, …, 0x0, 0x2a0, 0x2c0] (len=17) + 74 01 ADD [0x6, 0x15, …, 0x2a0, 0x2c0, 0xc0] (len=18) + 75 60 PUSH1 0x80 [0x6, 0x15, …, 0x0, 0x2a0, 0x380] (len=17) + 77 52 MSTORE [0x6, 0x15, …, 0x2a0, 0x380, 0x80] (len=18) + 78 50 POP [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 79 60 PUSH1 0xa0 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 81 51 MLOAD [0x6, 0x15, …, 0xcd, 0x0, 0xa0] (len=16) + 82 60 PUSH1 0xa0 [0x6, 0x15, …, 0xcd, 0x0, 0x2c0] (len=16) + 84 01 ADD [0x6, 0x15, …, 0x0, 0x2c0, 0xa0] (len=17) + 85 90 SWAP1 [0x6, 0x15, …, 0xcd, 0x0, 0x360] (len=16) + 86 81 DUP2 [0x6, 0x15, …, 0xcd, 0x360, 0x0] (len=16) + 87 52 MSTORE [0x6, 0x15, …, 0x360, 0x0, 0x360] (len=17) + 88 51 MLOAD [0x6, 0x15, …, 0x5, 0xcd, 0x360] (len=15) + 89 80 DUP1 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 90 5f PUSH0 [0x6, 0x15, …, 0xcd, 0x0, 0x0] (len=16) + 91 52 MSTORE [0x6, 0x15, …, 0x0, 0x0, 0x0] (len=17) + 92 5f PUSH0 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 93 81 DUP2 [0x6, 0x15, …, 0xcd, 0x0, 0x0] (len=16) + 94 14 EQ [0x6, 0x15, …, 0x0, 0x0, 0x0] (len=17) + 95 15 ISZERO [0x6, 0x15, …, 0xcd, 0x0, 0x1] (len=16) + 96 60 PUSH1 0x7d [0x6, 0x15, …, 0xcd, 0x0, 0x0] (len=16) + 98 57 JUMPI [0x6, 0x15, …, 0x0, 0x0, 0x7d] (len=17) + 99 5b JUMPDEST [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 100 50 POP [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 101 60 PUSH1 0x6c [0x6, 0x15, …, 0x3, 0x5, 0xcd] (len=14) + 103 5f PUSH0 [0x6, 0x15, …, 0x5, 0xcd, 0x6c] (len=15) + 104 51 MLOAD [0x6, 0x15, …, 0xcd, 0x6c, 0x0] (len=16) + 105 60 PUSH1 0xe9 [0x6, 0x15, …, 0xcd, 0x6c, 0x0] (len=16) + 107 56 JUMP [0x6, 0x15, …, 0x6c, 0x0, 0xe9] (len=17) + 233 5b JUMPDEST [0x6, 0x15, …, 0xcd, 0x6c, 0x0] (len=16) + 234 60 PUSH1 0xc0 [0x6, 0x15, …, 0xcd, 0x6c, 0x0] (len=16) + 236 90 SWAP1 [0x6, 0x15, …, 0x6c, 0x0, 0xc0] (len=17) + 237 81 DUP2 [0x6, 0x15, …, 0x6c, 0xc0, 0x0] (len=17) + 238 52 MSTORE [0x6, 0x15, …, 0xc0, 0x0, 0xc0] (len=18) + 239 51 MLOAD [0x6, 0x15, …, 0xcd, 0x6c, 0xc0] (len=16) + 240 90 SWAP1 [0x6, 0x15, …, 0xcd, 0x6c, 0x0] (len=16) + 241 56 JUMP [0x6, 0x15, …, 0xcd, 0x0, 0x6c] (len=16) + 108 5b JUMPDEST [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 109 60 PUSH1 0x20 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 111 60 PUSH1 0xa0 [0x6, 0x15, …, 0xcd, 0x0, 0x20] (len=16) + 113 51 MLOAD [0x6, 0x15, …, 0x0, 0x20, 0xa0] (len=17) + 114 03 SUB [0x6, 0x15, …, 0x0, 0x20, 0x2c0] (len=17) + 115 80 DUP1 [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 116 51 MLOAD [0x6, 0x15, …, 0x0, 0x2a0, 0x2a0] (len=17) + 117 60 PUSH1 0xa0 [0x6, 0x15, …, 0x0, 0x2a0, 0x1e0] (len=17) + 119 52 MSTORE [0x6, 0x15, …, 0x2a0, 0x1e0, 0xa0] (len=18) + 120 60 PUSH1 0x80 [0x6, 0x15, …, 0xcd, 0x0, 0x2a0] (len=16) + 122 52 MSTORE [0x6, 0x15, …, 0x0, 0x2a0, 0x80] (len=17) + 123 90 SWAP1 [0x6, 0x15, …, 0x5, 0xcd, 0x0] (len=15) + 124 56 JUMP [0x6, 0x15, …, 0x5, 0x0, 0xcd] (len=15) + 205 5b JUMPDEST [0x6, 0x15, …, 0x3, 0x5, 0x0] (len=14) + 206 90 SWAP1 [0x6, 0x15, …, 0x3, 0x5, 0x0] (len=14) + 207 93 SWAP4 [0x6, 0x15, …, 0x3, 0x0, 0x5] (len=14) + 208 01 ADD [0x6, 0x15, …, 0x3, 0x0, 0x1] (len=14) + 209 90 SWAP1 [0x6, 0x15, …, 0x2, 0x3, 0x1] (len=13) + 210 91 SWAP2 [0x6, 0x15, …, 0x2, 0x1, 0x3] (len=13) + 211 01 ADD [0x6, 0x15, …, 0x3, 0x1, 0x2] (len=13) + 212 01 ADD [0x6, 0x15, …, 0x5, 0x3, 0x3] (len=12) + 213 90 SWAP1 [0x6, 0x15, …, 0x4, 0x5, 0x6] (len=11) + 214 91 SWAP2 [0x6, 0x15, …, 0x4, 0x6, 0x5] (len=11) + 215 01 ADD [0x6, 0x15, …, 0x5, 0x6, 0x4] (len=11) + 216 01 ADD [0x6, 0x15, …, 0xcd, 0x5, 0xa] (len=10) + 217 60 PUSH1 0x20 [0x6, 0x15, …, 0x5, 0xcd, 0xf] (len=9) + 219 60 PUSH1 0xa0 [0x6, 0x15, …, 0xcd, 0xf, 0x20] (len=10) + 221 51 MLOAD [0x6, 0x15, …, 0xf, 0x20, 0xa0] (len=11) + 222 03 SUB [0x6, 0x15, …, 0xf, 0x20, 0x1e0] (len=11) + 223 80 DUP1 [0x6, 0x15, …, 0xcd, 0xf, 0x1c0] (len=10) + 224 51 MLOAD [0x6, 0x15, …, 0xf, 0x1c0, 0x1c0] (len=11) + 225 60 PUSH1 0xa0 [0x6, 0x15, …, 0xf, 0x1c0, 0x100] (len=11) + 227 52 MSTORE [0x6, 0x15, …, 0x1c0, 0x100, 0xa0] (len=12) + 228 60 PUSH1 0x80 [0x6, 0x15, …, 0xcd, 0xf, 0x1c0] (len=10) + 230 52 MSTORE [0x6, 0x15, …, 0xf, 0x1c0, 0x80] (len=11) + 231 90 SWAP1 [0x6, 0x15, …, 0x5, 0xcd, 0xf] (len=9) + 232 56 JUMP [0x6, 0x15, …, 0x5, 0xf, 0xcd] (len=9) + 205 5b JUMPDEST [0x6, 0x15, …, 0x3, 0x5, 0xf] (len=8) + 206 90 SWAP1 [0x6, 0x15, …, 0x3, 0x5, 0xf] (len=8) + 207 93 SWAP4 [0x6, 0x15, …, 0x3, 0xf, 0x5] (len=8) + 208 01 ADD [0x6, 0x15, …, 0x3, 0xf, 0x1] (len=8) + 209 90 SWAP1 [0x6, 0x15, …, 0x2, 0x3, 0x10] (len=7) + 210 91 SWAP2 [0x6, 0x15, …, 0x2, 0x10, 0x3] (len=7) + 211 01 ADD [0x6, 0x15, …, 0x3, 0x10, 0x2] (len=7) + 212 01 ADD [0x6, 0x15, 0x4, 0x5, 0x3, 0x12] + 213 90 SWAP1 [0x6, 0x15, 0x4, 0x5, 0x15] + 214 91 SWAP2 [0x6, 0x15, 0x4, 0x15, 0x5] + 215 01 ADD [0x6, 0x15, 0x5, 0x15, 0x4] + 216 01 ADD [0x6, 0x15, 0x5, 0x19] + 217 60 PUSH1 0x20 [0x6, 0x15, 0x1e] + 219 60 PUSH1 0xa0 [0x6, 0x15, 0x1e, 0x20] + 221 51 MLOAD [0x6, 0x15, 0x1e, 0x20, 0xa0] + 222 03 SUB [0x6, 0x15, 0x1e, 0x20, 0x100] + 223 80 DUP1 [0x6, 0x15, 0x1e, 0xe0] + 224 51 MLOAD [0x6, 0x15, 0x1e, 0xe0, 0xe0] + 225 60 PUSH1 0xa0 [0x6, 0x15, 0x1e, 0xe0, 0x0] + 227 52 MSTORE [0x6, 0x15, 0x1e, 0xe0, 0x0, 0xa0] + 228 60 PUSH1 0x80 [0x6, 0x15, 0x1e, 0xe0] + 230 52 MSTORE [0x6, 0x15, 0x1e, 0xe0, 0x80] + 231 90 SWAP1 [0x6, 0x15, 0x1e] + 232 56 JUMP [0x6, 0x1e, 0x15] + 21 5b JUMPDEST [0x6, 0x1e] + 22 90 SWAP1 [0x6, 0x1e] + 23 56 JUMP [0x1e, 0x6] + 6 5b JUMPDEST [0x1e] + 7 5f PUSH0 [0x1e] + 8 52 MSTORE [0x1e, 0x0] + 9 60 PUSH1 0x20 [] + 11 5f PUSH0 [0x20] + 12 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/mixed_frame_recursion_reach4.sntn b/crates/codegen/test_files/evm/mixed_frame_recursion_reach4.sntn new file mode 100644 index 00000000..07a57e85 --- /dev/null +++ b/crates/codegen/test_files/evm/mixed_frame_recursion_reach4.sntn @@ -0,0 +1,64 @@ +target = "evm-ethereum-osaka" + +func public %b(v0.i256) -> i256 { + block0: + v1.*i256 = alloca i256; + mstore v1 v0 i256; + v2.i256 = mload v1 i256; + return v2; +} + +func public %d(v0.i256) -> i256 { + block0: + v1.*i256 = alloca i256; + mstore v1 v0 i256; + v2.i256 = mload v1 i256; + + v3.i1 = eq v2 0.i256; + br v3 block1 block2; + + block1: + v4.i256 = call %b v2; + return v4; + + block2: + v5.*i256 = alloca i256; + mstore v5 0.i256 i256; + v6.i256 = mload v5 i256; + + v7.i256 = add v6 1.i256; + v8.i256 = add v6 2.i256; + v9.i256 = add v6 3.i256; + v10.i256 = add v6 4.i256; + v11.i256 = add v6 5.i256; + + v12.i256 = sub v2 1.i256; + + v13.i256 = call %d v12; + + v14.i256 = add v13 v7; + v15.i256 = add v14 v8; + v16.i256 = add v15 v9; + v17.i256 = add v16 v10; + v18.i256 = add v17 v11; + return v18; +} + +func public %a() -> i256 { + block0: + v0.i256 = call %d 2.i256; + return v0; +} + +func public %entry() { + block0: + v0.i256 = call %a; + mstore 0.i32 v0 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/phi_dup.snap b/crates/codegen/test_files/evm/phi_dup.snap new file mode 100644 index 00000000..8f39397e --- /dev/null +++ b/crates/codegen/test_files/evm/phi_dup.snap @@ -0,0 +1,117 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/phi_dup.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(224)] + shr [224, v0] -> v1 + - stack=[v1] + pre: [PUSH(0), DUP2] + gt [v1, 0] -> v2 + - stack=[v2, v1] + br [v2] -> [block1, block2] + block1 P=[] T=[v1] + inherited from block0: [v1] + - stack=[v1], last_use=[v1] + pre: [PUSH(1)] + add [1, v1] -> v3 + - stack=[v3] + exit(block3): [DUP1] + jump -> block3 + block2 P=[] T=[v1] + inherited from block0: [v1] + - stack=[v1], last_use=[v1] + pre: [PUSH(2)] + add [2, v1] -> v4 + - stack=[v4] + exit(block3): [DUP1] + jump -> block3 + block3 P=[v5, v6] T=[] + - stack=[v5, v6], last_use=[v5, v6] + add [v5, v6] -> v7 + - stack=[v7], last_use=[v7] + pre: [PUSH(0)] + mstore [0, v7] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + PUSH1 0xe0 (224) // v1.i32 = shr 224.i32 v0; + SHR + PUSH0 // v2.i1 = gt v1 0.i32; + DUP2 + GT + ISZERO // br v2 block1 block2; + PUSH1 block2 + JUMPI + block1: + JUMPDEST + PUSH1 0x1 (1) // v3.i32 = add v1 1.i32; + ADD + DUP1 // jump block3; + PUSH1 block3 + JUMP + block2: + JUMPDEST + PUSH1 0x2 (2) // v4.i32 = add v1 2.i32; + ADD + DUP1 // jump block3; + block3: + JUMPDEST + ADD // v7.i32 = add v5 v6; + PUSH0 // mstore 0.i32 v7 i32; + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b5f3560e01c5f8111156015575b60010180601a565b600201805b015f5260205ff3 + + +Success { reason: Return, gas_used: 21130, gas_refunded: 0, logs: [], output: Call(0x0000000000000000000000000000000000000000000000000000000000000018) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 60 PUSH1 0xe0 [0xb00000016000000000000000000000000000000000000000000000000] + 5 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 6 5f PUSH0 [0xb] + 7 81 DUP2 [0xb, 0x0] + 8 11 GT [0xb, 0x0, 0xb] + 9 15 ISZERO [0xb, 0x1] + 10 60 PUSH1 0x15 [0xb, 0x0] + 12 57 JUMPI [0xb, 0x0, 0x15] + 13 5b JUMPDEST [0xb] + 14 60 PUSH1 0x01 [0xb] + 16 01 ADD [0xb, 0x1] + 17 80 DUP1 [0xc] + 18 60 PUSH1 0x1a [0xc, 0xc] + 20 56 JUMP [0xc, 0xc, 0x1a] + 26 5b JUMPDEST [0xc, 0xc] + 27 01 ADD [0xc, 0xc] + 28 5f PUSH0 [0x18] + 29 52 MSTORE [0x18, 0x0] + 30 60 PUSH1 0x20 [] + 32 5f PUSH0 [0x20] + 33 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/phi_dup.sntn b/crates/codegen/test_files/evm/phi_dup.sntn new file mode 100644 index 00000000..40b576e6 --- /dev/null +++ b/crates/codegen/test_files/evm/phi_dup.sntn @@ -0,0 +1,31 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i32 = shr 224.i32 v0; + v2.i1 = gt v1 0.i32; + br v2 block1 block2; + + block1: + v3.i32 = add v1 1.i32; + jump block3; + + block2: + v4.i32 = add v1 2.i32; + jump block3; + + block3: + v5.i32 = phi (v3 block1) (v4 block2); + v6.i32 = phi (v3 block1) (v4 block2); + v7.i32 = add v5 v6; + + mstore 0.i32 v7 i32; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/slot_reuse.snap b/crates/codegen/test_files/evm/slot_reuse.snap new file mode 100644 index 00000000..e4a64369 --- /dev/null +++ b/crates/codegen/test_files/evm/slot_reuse.snap @@ -0,0 +1,460 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/slot_reuse.sntn +--- +evm mem plan: dyn_base=0xc0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() +spill_set: 0=[v2, v19] + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0], last_use=[v0] + pre: [PUSH(224)] + shr [224, v0] -> v1 + - stack=[v1] + pre: [DUP1] + not [v1] -> v2 + post: [DUP1, MSTORE_SLOT(0)] + - stack=[v2, v1], last_use=[v1] + pre: [SWAP1] + not [v1] -> v3 + - stack=[v3, v2] + pre: [DUP1] + not [v3] -> v4 + - stack=[v4, v3, v2] + pre: [DUP1] + not [v4] -> v5 + - stack=[v5, v4, v3, v2] + pre: [DUP1] + not [v5] -> v6 + - stack=[v6, v5, v4, v3, v2] + pre: [DUP1] + not [v6] -> v7 + - stack=[v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v7] -> v8 + - stack=[v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v8] -> v9 + - stack=[v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v9] -> v10 + - stack=[v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v10] -> v11 + - stack=[v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v11] -> v12 + - stack=[v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v12] -> v13 + - stack=[v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v13] -> v14 + - stack=[v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v14] -> v15 + - stack=[v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v15] -> v16 + - stack=[v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v16] -> v17 + - stack=[v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2] + pre: [DUP1] + not [v17] -> v18 + - stack=[v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2], last_use=[v2] + pre: [MLOAD_SLOT(0)] + not [v2] -> v19 + post: [DUP1, MSTORE_SLOT(0)] + - stack=[v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP2] + not [v18] -> v20 + - stack=[v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v20] -> v21 + - stack=[v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v21] -> v22 + - stack=[v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v22] -> v23 + - stack=[v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v23] -> v24 + - stack=[v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v24] -> v25 + - stack=[v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v25] -> v26 + - stack=[v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v26] -> v27 + - stack=[v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v27] -> v28 + - stack=[v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v28] -> v29 + - stack=[v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v29] -> v30 + - stack=[v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v30] -> v31 + - stack=[v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v31] -> v32 + - stack=[v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v32] -> v33 + - stack=[v33, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v33] -> v34 + - stack=[v34, v33, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x] + pre: [DUP1] + not [v34] -> v35 + - stack=[v35, v34, v33, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v19] + pre: [MLOAD_SLOT(0)] + not [v19] -> v36 + - stack=[v36, v35, v34, v33, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v35, v36] + add [v36, v35] -> v37 + - stack=[v37, v34, v33, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v34, v37] + add [v37, v34] -> v38 + - stack=[v38, v33, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v33, v38] + add [v38, v33] -> v39 + - stack=[v39, v32, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v32, v39] + add [v39, v32] -> v40 + - stack=[v40, v31, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v31, v40] + add [v40, v31] -> v41 + - stack=[v41, v30, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v30, v41] + add [v41, v30] -> v42 + - stack=[v42, v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v29, v42] + add [v42, v29] -> v43 + - stack=[v43, v28, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v28, v43] + add [v43, v28] -> v44 + - stack=[v44, v27, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v27, v44] + add [v44, v27] -> v45 + - stack=[v45, v26, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v26, v45] + add [v45, v26] -> v46 + - stack=[v46, v25, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v25, v46] + add [v46, v25] -> v47 + - stack=[v47, v24, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v24, v47] + add [v47, v24] -> v48 + - stack=[v48, v23, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v23, v48] + add [v48, v23] -> v49 + - stack=[v49, v22, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v22, v49] + add [v49, v22] -> v50 + - stack=[v50, v21, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v21, v50] + add [v50, v21] -> v51 + - stack=[v51, v20, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v20, v51] + add [v51, v20] -> v52 + - stack=[v52, v19x, v18, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v18, v52] + cleanup: [SWAP1, POP] + add [v52, v18] -> v53 + - stack=[v53, v17, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v17, v53] + add [v53, v17] -> v54 + - stack=[v54, v16, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v16, v54] + add [v54, v16] -> v55 + - stack=[v55, v15, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v15, v55] + add [v55, v15] -> v56 + - stack=[v56, v14, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v14, v56] + add [v56, v14] -> v57 + - stack=[v57, v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v13, v57] + add [v57, v13] -> v58 + - stack=[v58, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v12, v58] + add [v58, v12] -> v59 + - stack=[v59, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v11, v59] + add [v59, v11] -> v60 + - stack=[v60, v10, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v10, v60] + add [v60, v10] -> v61 + - stack=[v61, v9, v8, v7, v6, v5, v4, v3, v2x], last_use=[v9, v61] + add [v61, v9] -> v62 + - stack=[v62, v8, v7, v6, v5, v4, v3, v2x], last_use=[v8, v62] + add [v62, v8] -> v63 + - stack=[v63, v7, v6, v5, v4, v3, v2x], last_use=[v7, v63] + add [v63, v7] -> v64 + - stack=[v64, v6, v5, v4, v3, v2x], last_use=[v6, v64] + add [v64, v6] -> v65 + - stack=[v65, v5, v4, v3, v2x], last_use=[v5, v65] + add [v65, v5] -> v66 + - stack=[v66, v4, v3, v2x], last_use=[v4, v66] + add [v66, v4] -> v67 + - stack=[v67, v3, v2x], last_use=[v3, v67] + add [v67, v3] -> v68 + - stack=[v68, v2x], last_use=[v68] + cleanup: [SWAP1, POP] + pre: [PUSH(0)] + mstore [0, v68] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + PUSH1 0xe0 (224) // v1.i256 = shr 224.i32 v0; + SHR + DUP1 // v2.i256 = not v1; + NOT + DUP1 + PUSH0 + MSTORE + SWAP1 // v3.i256 = not v1; + NOT + DUP1 // v4.i256 = not v3; + NOT + DUP1 // v5.i256 = not v4; + NOT + DUP1 // v6.i256 = not v5; + NOT + DUP1 // v7.i256 = not v6; + NOT + DUP1 // v8.i256 = not v7; + NOT + DUP1 // v9.i256 = not v8; + NOT + DUP1 // v10.i256 = not v9; + NOT + DUP1 // v11.i256 = not v10; + NOT + DUP1 // v12.i256 = not v11; + NOT + DUP1 // v13.i256 = not v12; + NOT + DUP1 // v14.i256 = not v13; + NOT + DUP1 // v15.i256 = not v14; + NOT + DUP1 // v16.i256 = not v15; + NOT + DUP1 // v17.i256 = not v16; + NOT + DUP1 // v18.i256 = not v17; + NOT + PUSH0 // v19.i256 = not v2; + MLOAD + NOT + DUP1 + PUSH0 + MSTORE + DUP2 // v20.i256 = not v18; + NOT + DUP1 // v21.i256 = not v20; + NOT + DUP1 // v22.i256 = not v21; + NOT + DUP1 // v23.i256 = not v22; + NOT + DUP1 // v24.i256 = not v23; + NOT + DUP1 // v25.i256 = not v24; + NOT + DUP1 // v26.i256 = not v25; + NOT + DUP1 // v27.i256 = not v26; + NOT + DUP1 // v28.i256 = not v27; + NOT + DUP1 // v29.i256 = not v28; + NOT + DUP1 // v30.i256 = not v29; + NOT + DUP1 // v31.i256 = not v30; + NOT + DUP1 // v32.i256 = not v31; + NOT + DUP1 // v33.i256 = not v32; + NOT + DUP1 // v34.i256 = not v33; + NOT + DUP1 // v35.i256 = not v34; + NOT + PUSH0 // v36.i256 = not v19; + MLOAD + NOT + ADD // v37.i256 = add v36 v35; + ADD // v38.i256 = add v37 v34; + ADD // v39.i256 = add v38 v33; + ADD // v40.i256 = add v39 v32; + ADD // v41.i256 = add v40 v31; + ADD // v42.i256 = add v41 v30; + ADD // v43.i256 = add v42 v29; + ADD // v44.i256 = add v43 v28; + ADD // v45.i256 = add v44 v27; + ADD // v46.i256 = add v45 v26; + ADD // v47.i256 = add v46 v25; + ADD // v48.i256 = add v47 v24; + ADD // v49.i256 = add v48 v23; + ADD // v50.i256 = add v49 v22; + ADD // v51.i256 = add v50 v21; + ADD // v52.i256 = add v51 v20; + SWAP1 // v53.i256 = add v52 v18; + POP + ADD + ADD // v54.i256 = add v53 v17; + ADD // v55.i256 = add v54 v16; + ADD // v56.i256 = add v55 v15; + ADD // v57.i256 = add v56 v14; + ADD // v58.i256 = add v57 v13; + ADD // v59.i256 = add v58 v12; + ADD // v60.i256 = add v59 v11; + ADD // v61.i256 = add v60 v10; + ADD // v62.i256 = add v61 v9; + ADD // v63.i256 = add v62 v8; + ADD // v64.i256 = add v63 v7; + ADD // v65.i256 = add v64 v6; + ADD // v66.i256 = add v65 v5; + ADD // v67.i256 = add v66 v4; + ADD // v68.i256 = add v67 v3; + SWAP1 // mstore 0.i32 v68 i256; + POP + PUSH0 + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + + + +--------------- + +// section runtime +0x5b5f3560e01c8019805f5290198019801980198019801980198019801980198019801980198019801980195f5119805f5281198019801980198019801980198019801980198019801980198019801980195f51190101010101010101010101010101010190500101010101010101010101010101010190505f5260205ff3 + + +Success { reason: Return, gas_used: 21417, gas_refunded: 0, logs: [], output: Call(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 60 PUSH1 0xe0 [0xb00000016000000000000000000000000000000000000000000000000] + 5 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 6 80 DUP1 [0xb] + 7 19 NOT [0xb, 0xb] + 8 80 DUP1 [0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 9 5f PUSH0 [0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 10 52 MSTORE [0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0x0] + 11 90 SWAP1 [0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 12 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] + 13 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 14 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 15 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] + 16 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] + 17 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 18 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 19 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] + 20 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] + 21 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 22 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=7) + 23 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=7) + 24 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=8) + 25 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=8) + 26 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=9) + 27 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=9) + 28 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=10) + 29 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=10) + 30 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=11) + 31 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=11) + 32 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=12) + 33 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=12) + 34 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=13) + 35 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=13) + 36 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=14) + 37 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=14) + 38 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=15) + 39 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=15) + 40 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=16) + 41 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=16) + 42 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=17) + 43 5f PUSH0 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=17) + 44 51 MLOAD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0x0] (len=18) + 45 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=18) + 46 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=18) + 47 5f PUSH0 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xb, 0xb] (len=19) + 48 52 MSTORE [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xb, 0x0] (len=20) + 49 81 DUP2 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=18) + 50 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xb, 0xb] (len=19) + 51 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=19) + 52 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=20) + 53 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=20) + 54 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=21) + 55 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=21) + 56 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=22) + 57 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=22) + 58 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=23) + 59 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=23) + 60 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=24) + 61 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=24) + 62 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=25) + 63 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=25) + 64 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=26) + 65 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=26) + 66 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=27) + 67 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=27) + 68 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=28) + 69 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=28) + 70 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=29) + 71 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=29) + 72 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=30) + 73 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=30) + 74 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=31) + 75 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=31) + 76 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=32) + 77 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=32) + 78 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=33) + 79 80 DUP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=33) + 80 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=34) + 81 5f PUSH0 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb] (len=34) + 82 51 MLOAD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0x0] (len=35) + 83 19 NOT [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xb] (len=35) + 84 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=35) + 85 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff] (len=34) + 86 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3] (len=33) + 87 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe] (len=32) + 88 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2] (len=31) + 89 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd] (len=30) + 90 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1] (len=29) + 91 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc] (len=28) + 92 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0] (len=27) + 93 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb] (len=26) + 94 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef] (len=25) + 95 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa] (len=24) + 96 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee] (len=23) + 97 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9] (len=22) + 98 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed] (len=21) + 99 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8] (len=20) + 100 90 SWAP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec] (len=19) + 101 50 POP [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec, 0xb] (len=19) + 102 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec] (len=18) + 103 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7] (len=17) + 104 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb] (len=16) + 105 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6] (len=15) + 106 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea] (len=14) + 107 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5] (len=13) + 108 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9] (len=12) + 109 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] (len=11) + 110 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8] (len=10) + 111 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3] (len=9) + 112 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7] (len=8) + 113 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, …, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2] (len=7) + 114 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6] + 115 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1] + 116 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xb, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5] + 117 01 ADD [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0] + 118 90 SWAP1 [0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4] + 119 50 POP [0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4] + 120 5f PUSH0 [0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4] + 121 52 MSTORE [0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4, 0x0] + 122 60 PUSH1 0x20 [] + 124 5f PUSH0 [0x20] + 125 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/slot_reuse.sntn b/crates/codegen/test_files/evm/slot_reuse.sntn new file mode 100644 index 00000000..f1afcb74 --- /dev/null +++ b/crates/codegen/test_files/evm/slot_reuse.sntn @@ -0,0 +1,90 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i256 = shr 224.i32 v0; + + v2.i256 = not v1; + + v3.i256 = not v1; + v4.i256 = not v3; + v5.i256 = not v4; + v6.i256 = not v5; + v7.i256 = not v6; + v8.i256 = not v7; + v9.i256 = not v8; + v10.i256 = not v9; + v11.i256 = not v10; + v12.i256 = not v11; + v13.i256 = not v12; + v14.i256 = not v13; + v15.i256 = not v14; + v16.i256 = not v15; + v17.i256 = not v16; + v18.i256 = not v17; + + v19.i256 = not v2; + + v20.i256 = not v18; + v21.i256 = not v20; + v22.i256 = not v21; + v23.i256 = not v22; + v24.i256 = not v23; + v25.i256 = not v24; + v26.i256 = not v25; + v27.i256 = not v26; + v28.i256 = not v27; + v29.i256 = not v28; + v30.i256 = not v29; + v31.i256 = not v30; + v32.i256 = not v31; + v33.i256 = not v32; + v34.i256 = not v33; + v35.i256 = not v34; + + v36.i256 = not v19; + + v37.i256 = add v36 v35; + v38.i256 = add v37 v34; + v39.i256 = add v38 v33; + v40.i256 = add v39 v32; + v41.i256 = add v40 v31; + v42.i256 = add v41 v30; + v43.i256 = add v42 v29; + v44.i256 = add v43 v28; + v45.i256 = add v44 v27; + v46.i256 = add v45 v26; + v47.i256 = add v46 v25; + v48.i256 = add v47 v24; + v49.i256 = add v48 v23; + v50.i256 = add v49 v22; + v51.i256 = add v50 v21; + v52.i256 = add v51 v20; + + v53.i256 = add v52 v18; + v54.i256 = add v53 v17; + v55.i256 = add v54 v16; + v56.i256 = add v55 v15; + v57.i256 = add v56 v14; + v58.i256 = add v57 v13; + v59.i256 = add v58 v12; + v60.i256 = add v59 v11; + v61.i256 = add v60 v10; + v62.i256 = add v61 v9; + v63.i256 = add v62 v8; + v64.i256 = add v63 v7; + v65.i256 = add v64 v6; + v66.i256 = add v65 v5; + v67.i256 = add v66 v4; + v68.i256 = add v67 v3; + + mstore 0.i32 v68 i256; + evm_return 0.i8 32.i8; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/test_files/evm/spill.snap b/crates/codegen/test_files/evm/spill.snap new file mode 100644 index 00000000..80307f19 --- /dev/null +++ b/crates/codegen/test_files/evm/spill.snap @@ -0,0 +1,590 @@ +--- +source: crates/codegen/tests/evm.rs +expression: "String::from_utf8(v).unwrap()" +input_file: test_files/evm/spill.sntn +--- +evm mem plan: dyn_base=0x2a0 static_base=0xc0 +evm mem plan: entry scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: sum8 scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 +evm mem plan: spill scheme=StaticTree base_words=0 persistent_words=0 alloca_words=0 persistent_alloca_words=0 + +// func public %entry() + block0 P=[] T=[] + - stack=[] + pre: [PUSH(0)] + evm_calldata_load [0] -> v0 + - stack=[v0] + pre: [DUP1, PUSH(224)] + shr [224, v0] -> v1 + - stack=[v1, v0], last_use=[v0] + pre: [SWAP1, PUSH(32)] + shl [32, v0] -> v2 + - stack=[v2, v1], last_use=[v2] + pre: [PUSH(224)] + shr [224, v2] -> v3 + - stack=[v3, v1], last_use=[v1, v3] + pre: [PUSH_CONT, PUSH(200), PUSH(0), PUSH(0), PUSH(0), PUSH(0), PUSH(0), DUP8, DUP10] + call [v1, v3, 0, 0, 0, 0, 0, 200] -> v4 + - stack=[v4, v3x, v1x], last_use=[v4] + cleanup: [SWAP2, POP, POP] + pre: [PUSH(0)] + mstore [0, v4] + - stack=[] + pre: [PUSH(32), PUSH(0)] + evm_return [0, 32] + +// func public %sum8(v0.i64, v1.i64, v2.i64, v3.i64, v4.i64, v5.i64, v6.i64, v7.i64) -> i64 + block0 P=[v0, v1, v2, v3, v4, v5, v6, v7] T=[] + - stack=[v0, v1, v2, v3, v4, v5, v6, v7, ], last_use=[v0, v1] + add [v0, v1] -> v8 + - stack=[v8, v2, v3, v4, v5, v6, v7, ], last_use=[v2, v3] + pre: [SWAP2] + add [v3, v2] -> v9 + - stack=[v9, v8, v4, v5, v6, v7, ], last_use=[v4, v5] + pre: [SWAP3, SWAP1, SWAP2] + add [v4, v5] -> v10 + - stack=[v10, v8, v9, v6, v7, ], last_use=[v6, v7] + pre: [SWAP4, SWAP1, SWAP3] + add [v6, v7] -> v11 + - stack=[v11, v9, v8, v10, ], last_use=[v8, v9] + pre: [SWAP2] + add [v8, v9] -> v12 + - stack=[v12, v11, v10, ], last_use=[v10, v11] + pre: [SWAP2] + add [v10, v11] -> v13 + - stack=[v13, v12, ], last_use=[v12, v13] + add [v13, v12] -> v14 + - stack=[v14, ] + return [v14] + +// func public %spill(v0.i64, v1.i64, v2.i64, v3.i64, v4.i64, v5.i64, v6.i64, v7.i64) -> i64 +spill_set: 0=v0, 1=v1, 2=v2, 3=v3, 4=v4, 5=v5, 6=v6, 7=v7, 8=v8, 9=v9, 10=v10, 11=v11, 12=v12, 13=v13, 14=v14, 15=v15, 16=v16 + block1 P=[v0, v1, v2, v3, v4, v5, v6, v7] T=[] + - stack=[v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP2, DUP2] + add [v0, v1] -> v8 + post: [DUP1, MSTORE_SLOT(8)] + - stack=[v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP5, DUP5] + add [v2, v3] -> v9 + post: [DUP1, MSTORE_SLOT(9)] + - stack=[v9, v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP8, DUP8] + add [v4, v5] -> v10 + post: [DUP1, MSTORE_SLOT(10)] + - stack=[v10, v9, v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP11, DUP11] + add [v6, v7] -> v11 + post: [DUP1, MSTORE_SLOT(11)] + - stack=[v11, v10, v9, v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP3, DUP5] + add [v8, v9] -> v12 + post: [DUP1, MSTORE_SLOT(12)] + - stack=[v12, v11, v10, v9, v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP2, DUP4] + add [v10, v11] -> v13 + post: [DUP1, MSTORE_SLOT(13)] + - stack=[v13, v12, v11, v10, v9, v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + pre: [DUP1, DUP3] + add [v12, v13] -> v14 + post: [DUP1, MSTORE_SLOT(14)] + - stack=[v14, v13, v12, v11, v10, v9, v8, v0, v1, v2, v3, v4, v5, v6, v7, ] + exit(block2): [SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, SWAP1, POP, DUP1, MSTORE_SLOT(15)] + jump -> block2 + block2 P=[v15] T=[] + - stack=[v15, ] + pre: [PUSH_CONT, MLOAD_SLOT(7), MLOAD_SLOT(6), MLOAD_SLOT(5), MLOAD_SLOT(4), MLOAD_SLOT(3), MLOAD_SLOT(2), MLOAD_SLOT(1), MLOAD_SLOT(0)] + call [v0, v1, v2, v3, v4, v5, v6, v7] -> v16 + post: [DUP1, MSTORE_SLOT(16)] + - stack=[v16, v15, ], last_use=[v15] + pre: [PUSH_CONT, SWAP1, SWAP2, MLOAD_SLOT(14), MLOAD_SLOT(13), MLOAD_SLOT(12), MLOAD_SLOT(11), MLOAD_SLOT(10), MLOAD_SLOT(9), MLOAD_SLOT(8)] + call [v8, v9, v10, v11, v12, v13, v14, v15] -> v17 + - stack=[v17, v16, ], last_use=[v16, v17] + add [v17, v16] -> v18 + - stack=[v18, ] + pre: [DUP1, MLOAD_SLOT(7)] + add [v7, v18] -> v19 + - stack=[v19, v18, ] + pre: [PUSH(1000), DUP2] + gt [v19, 1000] -> v20 + - stack=[v20, v19, v18, ] + br [v20] -> [block3, block4] + block3 P=[] T=[v18] + inherited from block2: [v19x, v18, ] + prologue: [POP] + - stack=[v18, ] + return [v18] + block4 P=[] T=[v19] + inherited from block2: [v19, v18x, ] + prologue: [SWAP1, POP] + - stack=[v19, ] + exit(block2): [DUP1, MSTORE_SLOT(15)] + jump -> block2 + +// func public %entry() +entry: + block0: + JUMPDEST + PUSH0 // v0.i256 = evm_calldata_load 0.i32; + CALLDATALOAD + DUP1 // v1.i64 = shr 224.i32 v0; + PUSH1 0xe0 (224) + SHR + SWAP1 // v2.i32 = shl 32.i32 v0; + PUSH1 0x20 (32) + SHL + PUSH1 0xe0 (224) // v3.i64 = shr 224.i32 v2; + SHR + PUSH1 `pc + (11)` // v4.i64 = call %spill v1 v3 0.i64 0.i64 0.i64 0.i64 0.i64 200.i64; + PUSH1 0xc8 (200) + PUSH0 + PUSH0 + PUSH0 + PUSH0 + PUSH0 + DUP8 + DUP10 + PUSH1 FuncRef(2) + JUMP + JUMPDEST + SWAP2 // mstore 0.i32 v4 i64; + POP + POP + PUSH0 + MSTORE + PUSH1 0x20 (32) // evm_return 0.i8 32.i8; + PUSH0 + RETURN + +// func public %sum8(v0.i64, v1.i64, v2.i64, v3.i64, v4.i64, v5.i64, v6.i64, v7.i64) -> i64 +sum8: + block0: + JUMPDEST + ADD // v8.i64 = add v0 v1; + SWAP2 // v9.i64 = add v2 v3; + ADD + SWAP3 // v10.i64 = add v4 v5; + SWAP1 + SWAP2 + ADD + SWAP4 // v11.i64 = add v6 v7; + SWAP1 + SWAP3 + ADD + SWAP2 // v12.i64 = add v8 v9; + ADD + SWAP2 // v13.i64 = add v10 v11; + ADD + ADD // v14.i64 = add v12 v13; + SWAP1 // return v14; + JUMP + +// func public %spill(v0.i64, v1.i64, v2.i64, v3.i64, v4.i64, v5.i64, v6.i64, v7.i64) -> i64 +spill: + block1: + JUMPDEST + DUP1 + PUSH0 + MSTORE + DUP2 + PUSH1 0x20 (32) + MSTORE + DUP3 + PUSH1 0xc0 (192) + MSTORE + DUP4 + PUSH1 0xe0 (224) + MSTORE + DUP5 + PUSH2 0x100 (256) + MSTORE + DUP6 + PUSH2 0x120 (288) + MSTORE + DUP7 + PUSH2 0x140 (320) + MSTORE + DUP8 + PUSH2 0x160 (352) + MSTORE + DUP2 // v8.i64 = add v0 v1; + DUP2 + ADD + DUP1 + PUSH2 0x180 (384) + MSTORE + DUP5 // v9.i64 = add v2 v3; + DUP5 + ADD + DUP1 + PUSH2 0x1a0 (416) + MSTORE + DUP8 // v10.i64 = add v4 v5; + DUP8 + ADD + DUP1 + PUSH2 0x1c0 (448) + MSTORE + DUP11 // v11.i64 = add v6 v7; + DUP11 + ADD + DUP1 + PUSH2 0x1e0 (480) + MSTORE + DUP3 // v12.i64 = add v8 v9; + DUP5 + ADD + DUP1 + PUSH2 0x200 (512) + MSTORE + DUP2 // v13.i64 = add v10 v11; + DUP4 + ADD + DUP1 + PUSH2 0x220 (544) + MSTORE + DUP1 // v14.i64 = add v12 v13; + DUP3 + ADD + DUP1 + PUSH2 0x240 (576) + MSTORE + SWAP1 // jump block2; + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + SWAP1 + POP + DUP1 + PUSH2 0x260 (608) + MSTORE + block2: + JUMPDEST + PUSH1 `pc + (19)` // v16.i64 = call %sum8 v0 v1 v2 v3 v4 v5 v6 v7; + PUSH2 0x160 (352) + MLOAD + PUSH2 0x140 (320) + MLOAD + PUSH2 0x120 (288) + MLOAD + PUSH2 0x100 (256) + MLOAD + PUSH1 0xe0 (224) + MLOAD + PUSH1 0xc0 (192) + MLOAD + PUSH1 0x20 (32) + MLOAD + PUSH0 + MLOAD + PUSH1 FuncRef(1) + JUMP + JUMPDEST + DUP1 + PUSH2 0x280 (640) + MSTORE + PUSH1 `pc + (19)` // v17.i64 = call %sum8 v8 v9 v10 v11 v12 v13 v14 v15; + SWAP1 + SWAP2 + PUSH2 0x240 (576) + MLOAD + PUSH2 0x220 (544) + MLOAD + PUSH2 0x200 (512) + MLOAD + PUSH2 0x1e0 (480) + MLOAD + PUSH2 0x1c0 (448) + MLOAD + PUSH2 0x1a0 (416) + MLOAD + PUSH2 0x180 (384) + MLOAD + PUSH1 FuncRef(1) + JUMP + JUMPDEST + ADD // v18.i64 = add v16 v17; + DUP1 // v19.i64 = add v7 v18; + PUSH2 0x160 (352) + MLOAD + ADD + PUSH2 0x3e8 (1000) // v20.i1 = gt v19 1000.i64; + DUP2 + GT + ISZERO // br v20 block3 block4; + PUSH1 block4 + JUMPI + block3: + JUMPDEST + POP // return v18; + SWAP1 + JUMP + block4: + JUMPDEST + SWAP1 // jump block2; + POP + DUP1 + PUSH2 0x260 (608) + MSTORE + PUSH1 block2 + JUMP + + + +--------------- + +// section runtime +0x5b5f358060e01c9060201b60e01c601c60c85f5f5f5f5f87896026565b9150505f5260205ff35b805f52816020528260c0528360e05284610100528561012052866101405287610160528181018061018052848401806101a052878701806101c0528a8a01806101e0528284018061020052818301806102205280820180610240529050905090509050905090509050905090509050905090509050905080610260525b60c56101605161014051610120516101005160e05160c0516020515f51610110565b806102805260ef90916102405161022051610200516101e0516101c0516101a05161018051610110565b018061016051016103e8811115610105575b5090565b9050806102605260a3565b019101929091019390920191019101019056 + + +Success { reason: Return, gas_used: 21786, gas_refunded: 0, logs: [], output: Call(0x000000000000000000000000000000000000000000000000000000000000048d) } + + pc opcode input (stack grows to the right) + 0 5b JUMPDEST [] + 1 5f PUSH0 [] + 2 35 CALLDATALOAD [0x0] + 3 80 DUP1 [0xb00000016000000000000000000000000000000000000000000000000] + 4 60 PUSH1 0xe0 [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000] + 6 1c SHR [0xb00000016000000000000000000000000000000000000000000000000, 0xb00000016000000000000000000000000000000000000000000000000, 0xe0] + 7 90 SWAP1 [0xb00000016000000000000000000000000000000000000000000000000, 0xb] + 8 60 PUSH1 0x20 [0xb, 0xb00000016000000000000000000000000000000000000000000000000] + 10 1b SHL [0xb, 0xb00000016000000000000000000000000000000000000000000000000, 0x20] + 11 60 PUSH1 0xe0 [0xb, 0x1600000000000000000000000000000000000000000000000000000000] + 13 1c SHR [0xb, 0x1600000000000000000000000000000000000000000000000000000000, 0xe0] + 14 60 PUSH1 0x1c [0xb, 0x16] + 16 60 PUSH1 0xc8 [0xb, 0x16, 0x1c] + 18 5f PUSH0 [0xb, 0x16, 0x1c, 0xc8] + 19 5f PUSH0 [0xb, 0x16, 0x1c, 0xc8, 0x0] + 20 5f PUSH0 [0xb, 0x16, 0x1c, 0xc8, 0x0, 0x0] + 21 5f PUSH0 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=7) + 22 5f PUSH0 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=8) + 23 87 DUP8 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=9) + 24 89 DUP10 [0xb, 0x16, …, 0x0, 0x0, 0x16] (len=10) + 25 60 PUSH1 0x26 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 27 56 JUMP [0xb, 0x16, …, 0x16, 0xb, 0x26] (len=12) + 38 5b JUMPDEST [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 39 80 DUP1 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 40 5f PUSH0 [0xb, 0x16, …, 0x16, 0xb, 0xb] (len=12) + 41 52 MSTORE [0xb, 0x16, …, 0xb, 0xb, 0x0] (len=13) + 42 81 DUP2 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 43 60 PUSH1 0x20 [0xb, 0x16, …, 0x16, 0xb, 0x16] (len=12) + 45 52 MSTORE [0xb, 0x16, …, 0xb, 0x16, 0x20] (len=13) + 46 82 DUP3 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 47 60 PUSH1 0xc0 [0xb, 0x16, …, 0x16, 0xb, 0x0] (len=12) + 49 52 MSTORE [0xb, 0x16, …, 0xb, 0x0, 0xc0] (len=13) + 50 83 DUP4 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 51 60 PUSH1 0xe0 [0xb, 0x16, …, 0x16, 0xb, 0x0] (len=12) + 53 52 MSTORE [0xb, 0x16, …, 0xb, 0x0, 0xe0] (len=13) + 54 84 DUP5 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 55 61 PUSH2 0x0100 [0xb, 0x16, …, 0x16, 0xb, 0x0] (len=12) + 58 52 MSTORE [0xb, 0x16, …, 0xb, 0x0, 0x100] (len=13) + 59 85 DUP6 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 60 61 PUSH2 0x0120 [0xb, 0x16, …, 0x16, 0xb, 0x0] (len=12) + 63 52 MSTORE [0xb, 0x16, …, 0xb, 0x0, 0x120] (len=13) + 64 86 DUP7 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 65 61 PUSH2 0x0140 [0xb, 0x16, …, 0x16, 0xb, 0x0] (len=12) + 68 52 MSTORE [0xb, 0x16, …, 0xb, 0x0, 0x140] (len=13) + 69 87 DUP8 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 70 61 PUSH2 0x0160 [0xb, 0x16, …, 0x16, 0xb, 0xc8] (len=12) + 73 52 MSTORE [0xb, 0x16, …, 0xb, 0xc8, 0x160] (len=13) + 74 81 DUP2 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=11) + 75 81 DUP2 [0xb, 0x16, …, 0x16, 0xb, 0x16] (len=12) + 76 01 ADD [0xb, 0x16, …, 0xb, 0x16, 0xb] (len=13) + 77 80 DUP1 [0xb, 0x16, …, 0x16, 0xb, 0x21] (len=12) + 78 61 PUSH2 0x0180 [0xb, 0x16, …, 0xb, 0x21, 0x21] (len=13) + 81 52 MSTORE [0xb, 0x16, …, 0x21, 0x21, 0x180] (len=14) + 82 84 DUP5 [0xb, 0x16, …, 0x16, 0xb, 0x21] (len=12) + 83 84 DUP5 [0xb, 0x16, …, 0xb, 0x21, 0x0] (len=13) + 84 01 ADD [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=14) + 85 80 DUP1 [0xb, 0x16, …, 0xb, 0x21, 0x0] (len=13) + 86 61 PUSH2 0x01a0 [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=14) + 89 52 MSTORE [0xb, 0x16, …, 0x0, 0x0, 0x1a0] (len=15) + 90 87 DUP8 [0xb, 0x16, …, 0xb, 0x21, 0x0] (len=13) + 91 87 DUP8 [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=14) + 92 01 ADD [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=15) + 93 80 DUP1 [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=14) + 94 61 PUSH2 0x01c0 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=15) + 97 52 MSTORE [0xb, 0x16, …, 0x0, 0x0, 0x1c0] (len=16) + 98 8a DUP11 [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=14) + 99 8a DUP11 [0xb, 0x16, …, 0x0, 0x0, 0xc8] (len=15) + 100 01 ADD [0xb, 0x16, …, 0x0, 0xc8, 0x0] (len=16) + 101 80 DUP1 [0xb, 0x16, …, 0x0, 0x0, 0xc8] (len=15) + 102 61 PUSH2 0x01e0 [0xb, 0x16, …, 0x0, 0xc8, 0xc8] (len=16) + 105 52 MSTORE [0xb, 0x16, …, 0xc8, 0xc8, 0x1e0] (len=17) + 106 82 DUP3 [0xb, 0x16, …, 0x0, 0x0, 0xc8] (len=15) + 107 84 DUP5 [0xb, 0x16, …, 0x0, 0xc8, 0x0] (len=16) + 108 01 ADD [0xb, 0x16, …, 0xc8, 0x0, 0x21] (len=17) + 109 80 DUP1 [0xb, 0x16, …, 0x0, 0xc8, 0x21] (len=16) + 110 61 PUSH2 0x0200 [0xb, 0x16, …, 0xc8, 0x21, 0x21] (len=17) + 113 52 MSTORE [0xb, 0x16, …, 0x21, 0x21, 0x200] (len=18) + 114 81 DUP2 [0xb, 0x16, …, 0x0, 0xc8, 0x21] (len=16) + 115 83 DUP4 [0xb, 0x16, …, 0xc8, 0x21, 0xc8] (len=17) + 116 01 ADD [0xb, 0x16, …, 0x21, 0xc8, 0x0] (len=18) + 117 80 DUP1 [0xb, 0x16, …, 0xc8, 0x21, 0xc8] (len=17) + 118 61 PUSH2 0x0220 [0xb, 0x16, …, 0x21, 0xc8, 0xc8] (len=18) + 121 52 MSTORE [0xb, 0x16, …, 0xc8, 0xc8, 0x220] (len=19) + 122 80 DUP1 [0xb, 0x16, …, 0xc8, 0x21, 0xc8] (len=17) + 123 82 DUP3 [0xb, 0x16, …, 0x21, 0xc8, 0xc8] (len=18) + 124 01 ADD [0xb, 0x16, …, 0xc8, 0xc8, 0x21] (len=19) + 125 80 DUP1 [0xb, 0x16, …, 0x21, 0xc8, 0xe9] (len=18) + 126 61 PUSH2 0x0240 [0xb, 0x16, …, 0xc8, 0xe9, 0xe9] (len=19) + 129 52 MSTORE [0xb, 0x16, …, 0xe9, 0xe9, 0x240] (len=20) + 130 90 SWAP1 [0xb, 0x16, …, 0x21, 0xc8, 0xe9] (len=18) + 131 50 POP [0xb, 0x16, …, 0x21, 0xe9, 0xc8] (len=18) + 132 90 SWAP1 [0xb, 0x16, …, 0xc8, 0x21, 0xe9] (len=17) + 133 50 POP [0xb, 0x16, …, 0xc8, 0xe9, 0x21] (len=17) + 134 90 SWAP1 [0xb, 0x16, …, 0x0, 0xc8, 0xe9] (len=16) + 135 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0xc8] (len=16) + 136 90 SWAP1 [0xb, 0x16, …, 0x0, 0x0, 0xe9] (len=15) + 137 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0x0] (len=15) + 138 90 SWAP1 [0xb, 0x16, …, 0x21, 0x0, 0xe9] (len=14) + 139 50 POP [0xb, 0x16, …, 0x21, 0xe9, 0x0] (len=14) + 140 90 SWAP1 [0xb, 0x16, …, 0xb, 0x21, 0xe9] (len=13) + 141 50 POP [0xb, 0x16, …, 0xb, 0xe9, 0x21] (len=13) + 142 90 SWAP1 [0xb, 0x16, …, 0x16, 0xb, 0xe9] (len=12) + 143 50 POP [0xb, 0x16, …, 0x16, 0xe9, 0xb] (len=12) + 144 90 SWAP1 [0xb, 0x16, …, 0x0, 0x16, 0xe9] (len=11) + 145 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0x16] (len=11) + 146 90 SWAP1 [0xb, 0x16, …, 0x0, 0x0, 0xe9] (len=10) + 147 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0x0] (len=10) + 148 90 SWAP1 [0xb, 0x16, …, 0x0, 0x0, 0xe9] (len=9) + 149 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0x0] (len=9) + 150 90 SWAP1 [0xb, 0x16, …, 0x0, 0x0, 0xe9] (len=8) + 151 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0x0] (len=8) + 152 90 SWAP1 [0xb, 0x16, …, 0x0, 0x0, 0xe9] (len=7) + 153 50 POP [0xb, 0x16, …, 0x0, 0xe9, 0x0] (len=7) + 154 90 SWAP1 [0xb, 0x16, 0x1c, 0xc8, 0x0, 0xe9] + 155 50 POP [0xb, 0x16, 0x1c, 0xc8, 0xe9, 0x0] + 156 90 SWAP1 [0xb, 0x16, 0x1c, 0xc8, 0xe9] + 157 50 POP [0xb, 0x16, 0x1c, 0xe9, 0xc8] + 158 80 DUP1 [0xb, 0x16, 0x1c, 0xe9] + 159 61 PUSH2 0x0260 [0xb, 0x16, 0x1c, 0xe9, 0xe9] + 162 52 MSTORE [0xb, 0x16, 0x1c, 0xe9, 0xe9, 0x260] + 163 5b JUMPDEST [0xb, 0x16, 0x1c, 0xe9] + 164 60 PUSH1 0xc5 [0xb, 0x16, 0x1c, 0xe9] + 166 61 PUSH2 0x0160 [0xb, 0x16, 0x1c, 0xe9, 0xc5] + 169 51 MLOAD [0xb, 0x16, 0x1c, 0xe9, 0xc5, 0x160] + 170 61 PUSH2 0x0140 [0xb, 0x16, 0x1c, 0xe9, 0xc5, 0xc8] + 173 51 MLOAD [0xb, 0x16, …, 0xc5, 0xc8, 0x140] (len=7) + 174 61 PUSH2 0x0120 [0xb, 0x16, …, 0xc5, 0xc8, 0x0] (len=7) + 177 51 MLOAD [0xb, 0x16, …, 0xc8, 0x0, 0x120] (len=8) + 178 61 PUSH2 0x0100 [0xb, 0x16, …, 0xc8, 0x0, 0x0] (len=8) + 181 51 MLOAD [0xb, 0x16, …, 0x0, 0x0, 0x100] (len=9) + 182 60 PUSH1 0xe0 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=9) + 184 51 MLOAD [0xb, 0x16, …, 0x0, 0x0, 0xe0] (len=10) + 185 60 PUSH1 0xc0 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=10) + 187 51 MLOAD [0xb, 0x16, …, 0x0, 0x0, 0xc0] (len=11) + 188 60 PUSH1 0x20 [0xb, 0x16, …, 0x0, 0x0, 0x0] (len=11) + 190 51 MLOAD [0xb, 0x16, …, 0x0, 0x0, 0x20] (len=12) + 191 5f PUSH0 [0xb, 0x16, …, 0x0, 0x0, 0x16] (len=12) + 192 51 MLOAD [0xb, 0x16, …, 0x0, 0x16, 0x0] (len=13) + 193 61 PUSH2 0x0110 [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=13) + 196 56 JUMP [0xb, 0x16, …, 0x16, 0xb, 0x110] (len=14) + 272 5b JUMPDEST [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=13) + 273 01 ADD [0xb, 0x16, …, 0x0, 0x16, 0xb] (len=13) + 274 91 SWAP2 [0xb, 0x16, …, 0x0, 0x0, 0x21] (len=12) + 275 01 ADD [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=12) + 276 92 SWAP3 [0xb, 0x16, …, 0x0, 0x21, 0x0] (len=11) + 277 90 SWAP1 [0xb, 0x16, …, 0x0, 0x21, 0x0] (len=11) + 278 91 SWAP2 [0xb, 0x16, …, 0x0, 0x0, 0x21] (len=11) + 279 01 ADD [0xb, 0x16, …, 0x21, 0x0, 0x0] (len=11) + 280 93 SWAP4 [0xb, 0x16, …, 0x0, 0x21, 0x0] (len=10) + 281 90 SWAP1 [0xb, 0x16, …, 0x0, 0x21, 0xc8] (len=10) + 282 92 SWAP3 [0xb, 0x16, …, 0x0, 0xc8, 0x21] (len=10) + 283 01 ADD [0xb, 0x16, …, 0x0, 0xc8, 0x0] (len=10) + 284 91 SWAP2 [0xb, 0x16, …, 0x21, 0x0, 0xc8] (len=9) + 285 01 ADD [0xb, 0x16, …, 0xc8, 0x0, 0x21] (len=9) + 286 91 SWAP2 [0xb, 0x16, …, 0x0, 0xc8, 0x21] (len=8) + 287 01 ADD [0xb, 0x16, …, 0x21, 0xc8, 0x0] (len=8) + 288 01 ADD [0xb, 0x16, …, 0xc5, 0x21, 0xc8] (len=7) + 289 90 SWAP1 [0xb, 0x16, 0x1c, 0xe9, 0xc5, 0xe9] + 290 56 JUMP [0xb, 0x16, 0x1c, 0xe9, 0xe9, 0xc5] + 197 5b JUMPDEST [0xb, 0x16, 0x1c, 0xe9, 0xe9] + 198 80 DUP1 [0xb, 0x16, 0x1c, 0xe9, 0xe9] + 199 61 PUSH2 0x0280 [0xb, 0x16, 0x1c, 0xe9, 0xe9, 0xe9] + 202 52 MSTORE [0xb, 0x16, …, 0xe9, 0xe9, 0x280] (len=7) + 203 60 PUSH1 0xef [0xb, 0x16, 0x1c, 0xe9, 0xe9] + 205 90 SWAP1 [0xb, 0x16, 0x1c, 0xe9, 0xe9, 0xef] + 206 91 SWAP2 [0xb, 0x16, 0x1c, 0xe9, 0xef, 0xe9] + 207 61 PUSH2 0x0240 [0xb, 0x16, 0x1c, 0xe9, 0xef, 0xe9] + 210 51 MLOAD [0xb, 0x16, …, 0xef, 0xe9, 0x240] (len=7) + 211 61 PUSH2 0x0220 [0xb, 0x16, …, 0xef, 0xe9, 0xe9] (len=7) + 214 51 MLOAD [0xb, 0x16, …, 0xe9, 0xe9, 0x220] (len=8) + 215 61 PUSH2 0x0200 [0xb, 0x16, …, 0xe9, 0xe9, 0xc8] (len=8) + 218 51 MLOAD [0xb, 0x16, …, 0xe9, 0xc8, 0x200] (len=9) + 219 61 PUSH2 0x01e0 [0xb, 0x16, …, 0xe9, 0xc8, 0x21] (len=9) + 222 51 MLOAD [0xb, 0x16, …, 0xc8, 0x21, 0x1e0] (len=10) + 223 61 PUSH2 0x01c0 [0xb, 0x16, …, 0xc8, 0x21, 0xc8] (len=10) + 226 51 MLOAD [0xb, 0x16, …, 0x21, 0xc8, 0x1c0] (len=11) + 227 61 PUSH2 0x01a0 [0xb, 0x16, …, 0x21, 0xc8, 0x0] (len=11) + 230 51 MLOAD [0xb, 0x16, …, 0xc8, 0x0, 0x1a0] (len=12) + 231 61 PUSH2 0x0180 [0xb, 0x16, …, 0xc8, 0x0, 0x0] (len=12) + 234 51 MLOAD [0xb, 0x16, …, 0x0, 0x0, 0x180] (len=13) + 235 61 PUSH2 0x0110 [0xb, 0x16, …, 0x0, 0x0, 0x21] (len=13) + 238 56 JUMP [0xb, 0x16, …, 0x0, 0x21, 0x110] (len=14) + 272 5b JUMPDEST [0xb, 0x16, …, 0x0, 0x0, 0x21] (len=13) + 273 01 ADD [0xb, 0x16, …, 0x0, 0x0, 0x21] (len=13) + 274 91 SWAP2 [0xb, 0x16, …, 0xc8, 0x0, 0x21] (len=12) + 275 01 ADD [0xb, 0x16, …, 0x21, 0x0, 0xc8] (len=12) + 276 92 SWAP3 [0xb, 0x16, …, 0x21, 0x21, 0xc8] (len=11) + 277 90 SWAP1 [0xb, 0x16, …, 0x21, 0x21, 0xc8] (len=11) + 278 91 SWAP2 [0xb, 0x16, …, 0x21, 0xc8, 0x21] (len=11) + 279 01 ADD [0xb, 0x16, …, 0x21, 0xc8, 0x21] (len=11) + 280 93 SWAP4 [0xb, 0x16, …, 0xc8, 0x21, 0xe9] (len=10) + 281 90 SWAP1 [0xb, 0x16, …, 0xc8, 0x21, 0xe9] (len=10) + 282 92 SWAP3 [0xb, 0x16, …, 0xc8, 0xe9, 0x21] (len=10) + 283 01 ADD [0xb, 0x16, …, 0xc8, 0xe9, 0xe9] (len=10) + 284 91 SWAP2 [0xb, 0x16, …, 0x21, 0xc8, 0x1d2] (len=9) + 285 01 ADD [0xb, 0x16, …, 0x1d2, 0xc8, 0x21] (len=9) + 286 91 SWAP2 [0xb, 0x16, …, 0xe9, 0x1d2, 0xe9] (len=8) + 287 01 ADD [0xb, 0x16, …, 0xe9, 0x1d2, 0xe9] (len=8) + 288 01 ADD [0xb, 0x16, …, 0xef, 0xe9, 0x2bb] (len=7) + 289 90 SWAP1 [0xb, 0x16, 0x1c, 0xe9, 0xef, 0x3a4] + 290 56 JUMP [0xb, 0x16, 0x1c, 0xe9, 0x3a4, 0xef] + 239 5b JUMPDEST [0xb, 0x16, 0x1c, 0xe9, 0x3a4] + 240 01 ADD [0xb, 0x16, 0x1c, 0xe9, 0x3a4] + 241 80 DUP1 [0xb, 0x16, 0x1c, 0x48d] + 242 61 PUSH2 0x0160 [0xb, 0x16, 0x1c, 0x48d, 0x48d] + 245 51 MLOAD [0xb, 0x16, 0x1c, 0x48d, 0x48d, 0x160] + 246 01 ADD [0xb, 0x16, 0x1c, 0x48d, 0x48d, 0xc8] + 247 61 PUSH2 0x03e8 [0xb, 0x16, 0x1c, 0x48d, 0x555] + 250 81 DUP2 [0xb, 0x16, 0x1c, 0x48d, 0x555, 0x3e8] + 251 11 GT [0xb, 0x16, …, 0x555, 0x3e8, 0x555] (len=7) + 252 15 ISZERO [0xb, 0x16, 0x1c, 0x48d, 0x555, 0x1] + 253 61 PUSH2 0x0105 [0xb, 0x16, 0x1c, 0x48d, 0x555, 0x0] + 256 57 JUMPI [0xb, 0x16, …, 0x555, 0x0, 0x105] (len=7) + 257 5b JUMPDEST [0xb, 0x16, 0x1c, 0x48d, 0x555] + 258 50 POP [0xb, 0x16, 0x1c, 0x48d, 0x555] + 259 90 SWAP1 [0xb, 0x16, 0x1c, 0x48d] + 260 56 JUMP [0xb, 0x16, 0x48d, 0x1c] + 28 5b JUMPDEST [0xb, 0x16, 0x48d] + 29 91 SWAP2 [0xb, 0x16, 0x48d] + 30 50 POP [0x48d, 0x16, 0xb] + 31 50 POP [0x48d, 0x16] + 32 5f PUSH0 [0x48d] + 33 52 MSTORE [0x48d, 0x0] + 34 60 PUSH1 0x20 [] + 36 5f PUSH0 [0x20] + 37 f3 RETURN [0x20, 0x0] diff --git a/crates/codegen/test_files/evm/spill.sntn b/crates/codegen/test_files/evm/spill.sntn new file mode 100644 index 00000000..0697c307 --- /dev/null +++ b/crates/codegen/test_files/evm/spill.sntn @@ -0,0 +1,57 @@ +target = "evm-ethereum-osaka" + +func public %entry() { + block0: + v0.i256 = evm_calldata_load 0.i32; + v1.i64 = shr 224.i32 v0; + v2.i32 = shl 32.i32 v0; + v3.i64 = shr 224.i32 v2; + v4.i64 = call %spill v1 v3 0.i64 0.i64 0.i64 0.i64 0.i64 200.i64; + + mstore 0.i32 v4 i64; + evm_return 0.i8 32.i8; +} + +func public %sum8(v0.i64, v1.i64, v2.i64, v3.i64, v4.i64, v5.i64, v6.i64, v7.i64) -> i64 { + block0: + v8.i64 = add v0 v1; + v9.i64 = add v2 v3; + v10.i64 = add v4 v5; + v11.i64 = add v6 v7; + v12.i64 = add v8 v9; + v13.i64 = add v10 v11; + v14.i64 = add v12 v13; + return v14; +} + +func public %spill(v0.i64, v1.i64, v2.i64, v3.i64, v4.i64, v5.i64, v6.i64, v7.i64) -> i64 { + block1: + v8.i64 = add v0 v1; + v9.i64 = add v2 v3; + v10.i64 = add v4 v5; + v11.i64 = add v6 v7; + v12.i64 = add v8 v9; + v13.i64 = add v10 v11; + v14.i64 = add v12 v13; + + jump block2; + block2: + v15.i64 = phi (v14 block1) (v19 block2); + + v16.i64 = call %sum8 v0 v1 v2 v3 v4 v5 v6 v7; + v17.i64 = call %sum8 v8 v9 v10 v11 v12 v13 v14 v15; + v18.i64 = add v16 v17; + v19.i64 = add v7 v18; + + v20.i1 = gt v19 1000.i64; + br v20 block3 block2; + + block3: + return v18; +} + +object @Contract { + section runtime { + entry %entry; + } +} diff --git a/crates/codegen/tests/build.rs b/crates/codegen/tests/build.rs new file mode 100644 index 00000000..8e048f92 --- /dev/null +++ b/crates/codegen/tests/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(test)] + println!("cargo:rerun-if-changed=./test_files"); +} diff --git a/crates/codegen/tests/evm.rs b/crates/codegen/tests/evm.rs new file mode 100644 index 00000000..aa3e37e6 --- /dev/null +++ b/crates/codegen/tests/evm.rs @@ -0,0 +1,373 @@ +use dir_test::{Fixture, dir_test}; + +use hex::ToHex; +use revm::{ + Context, EvmContext, Handler, inspector_handle_register, + interpreter::Interpreter, + primitives::{ + AccountInfo, Address, Bytecode, Bytes, Env, ExecutionResult, OsakaSpec, Output, TransactTo, + U256, + }, +}; + +use sonatina_codegen::{ + domtree::DomTree, + isa::evm::{EvmBackend, PushWidthPolicy}, + liveness::Liveness, + machinst::lower::{LowerBackend, SectionLoweringCtx}, + object::{CompileOptions, compile_object}, + stackalloc::StackifyAlloc, +}; +use sonatina_ir::{ + Function, + cfg::ControlFlowGraph, + ir_writer::{FuncWriteCtx, FunctionSignature, IrWrite}, + isa::evm::Evm, + object::{EmbedSymbol, ObjectName, SectionName}, +}; +use sonatina_parser::{ParsedModule, parse_module}; +use sonatina_triple::{Architecture, OperatingSystem, Vendor}; +use std::io::{Write, stderr}; + +fn fmt_stackify_trace(trace: &str) -> String { + let mut out = String::new(); + for line in trace.lines() { + if line == "STACKIFY" || line == "trace:" { + continue; + } + out.push_str(line); + out.push('\n'); + } + out +} + +// XXX copied from fe test-utils +#[macro_export] +macro_rules! snap_test { + ($value:expr, $fixture_path: expr) => { + let mut settings = insta::Settings::new(); + let fixture_path = ::std::path::Path::new($fixture_path); + let fixture_dir = fixture_path.parent().unwrap(); + let fixture_name = fixture_path.file_stem().unwrap().to_str().unwrap(); + + settings.set_snapshot_path(fixture_dir); + settings.set_input_file($fixture_path); + settings.set_prepend_module_to_snapshot(false); + settings.bind(|| { + insta::_macro_support::assert_snapshot( + (insta::_macro_support::AutoName, $value.as_str()).into(), + std::path::Path::new(env!("CARGO_MANIFEST_DIR")), + fixture_name, + module_path!(), + file!(), + line!(), + stringify!($value), + ) + .unwrap() + }) + }; +} + +fn parse_sona(content: &str) -> ParsedModule { + match parse_module(content) { + Ok(module) => module, + Err(errs) => { + let mut w = stderr(); + for err in errs { + err.print(&mut w, "[test]", content, true).unwrap(); + } + panic!("Failed to parse test file. See errors above.") + } + } +} + +#[dir_test( + dir: "$CARGO_MANIFEST_DIR/test_files/evm", + glob: "*.sntn" +)] +fn test_evm(fixture: Fixture<&str>) { + let parsed = parse_sona(fixture.content()); + let stackify_reach_depth = stackify_reach_depth_for_fixture(fixture.path()); + + let backend = EvmBackend::new(Evm::new(sonatina_triple::TargetTriple { + architecture: Architecture::Evm, + vendor: Vendor::Ethereum, + operating_system: OperatingSystem::Evm(sonatina_triple::EvmVersion::Osaka), + })) + .with_stackify_reach_depth(stackify_reach_depth); + + let object_name = ObjectName::from("Contract"); + let section_name = SectionName::from("snapshot"); + let embed_symbols: Vec = Vec::new(); + let section_ctx = SectionLoweringCtx { + object: &object_name, + section: §ion_name, + embed_symbols: &embed_symbols, + }; + + backend.prepare_section(&parsed.module, &parsed.debug.func_order, §ion_ctx); + + let mem_plan = backend.snapshot_mem_plan(&parsed.module, &parsed.debug.func_order); + + let mut stackify_out = Vec::new(); + let mut lowered_out = Vec::new(); + for fref in parsed.debug.func_order.iter() { + let lowered = backend + .lower_function(&parsed.module, *fref, §ion_ctx) + .unwrap(); + + parsed.module.func_store.view(*fref, |function| { + let stackify = stackify_trace_for_fn(function, stackify_reach_depth); + let ctx = FuncWriteCtx::with_debug_provider(function, *fref, &parsed.debug); + + // Snapshot format: + // - all stackify traces first + // - then lowered functions/blocks + // - then the runtime EVM trace at the bottom + write!(&mut stackify_out, "// ").unwrap(); + FunctionSignature.write(&mut stackify_out, &ctx).unwrap(); + writeln!(&mut stackify_out).unwrap(); + write!(&mut stackify_out, "{}", fmt_stackify_trace(&stackify)).unwrap(); + writeln!(&mut stackify_out).unwrap(); + + lowered.vcode.write(&mut lowered_out, &ctx).unwrap(); + writeln!(&mut lowered_out).unwrap(); + }); + } + + let mut v = Vec::new(); + if !mem_plan.is_empty() { + writeln!(&mut v, "{mem_plan}").unwrap(); + } + v.append(&mut stackify_out); + v.append(&mut lowered_out); + + write!(&mut v, "\n\n---------------\n\n").unwrap(); + let opts = CompileOptions { + fixup_policy: PushWidthPolicy::MinimalRelax, + emit_symtab: false, + }; + + let artifact = compile_object(&parsed.module, &backend, "Contract", &opts).unwrap(); + for (name, section) in &artifact.sections { + writeln!(&mut v, "// section {}", name.0.as_str()).unwrap(); + let hex = section.bytes.encode_hex::(); + writeln!(&mut v, "0x{hex}\n").unwrap(); + } + + let init = artifact + .sections + .iter() + .find(|(name, _)| name.0.as_str() == "init") + .map(|(_, s)| s.bytes.clone()); + let runtime = artifact + .sections + .iter() + .find(|(name, _)| name.0.as_str() == "runtime") + .map(|(_, s)| s.bytes.clone()) + .unwrap(); + + let calldata = [0, 0, 0, 11, 0, 0, 0, 22]; + if let Some(init) = init { + let (create_res, create_trace, db, deployed) = deploy_on_evm(&init); + writeln!(&mut v, "\n{create_res:?}").unwrap(); + writeln!(&mut v, "\n{create_trace}").unwrap(); + + let (call_res, call_trace) = call_on_evm(db, deployed, &calldata); + writeln!(&mut v, "\n{call_res:?}").unwrap(); + writeln!(&mut v, "\n{call_trace}").unwrap(); + } else { + let (res, trace) = run_on_evm(&runtime, &calldata); + writeln!(&mut v, "\n{res:?}").unwrap(); + writeln!(&mut v, "\n{trace}").unwrap(); + } + + snap_test!(String::from_utf8(v).unwrap(), fixture.path()); +} + +fn run_on_evm(bytecode: &[u8], calldata: &[u8]) -> (ExecutionResult, String) { + let mut db = revm::InMemoryDB::default(); + let revm_bytecode = Bytecode::new_raw(Bytes::copy_from_slice(bytecode)); + let test_address = Address::repeat_byte(0x12); + db.insert_account_info( + test_address, + AccountInfo { + balance: U256::ZERO, + nonce: 0, + code_hash: revm_bytecode.hash_slow(), + code: Some(revm_bytecode), + }, + ); + + let mut env = Env::default(); + env.tx.clear(); + env.tx.transact_to = TransactTo::Call(test_address); + env.tx.data = Bytes::copy_from_slice(calldata); + + let (res, trace, _db) = run_revm_tx(db, env); + (res, trace) +} + +fn deploy_on_evm(init_code: &[u8]) -> (ExecutionResult, String, revm::InMemoryDB, Address) { + let db = revm::InMemoryDB::default(); + let mut env = Env::default(); + env.tx.clear(); + env.tx.transact_to = TransactTo::Create; + env.tx.data = Bytes::copy_from_slice(init_code); + + let (res, trace, db) = run_revm_tx(db, env); + let deployed = match &res { + ExecutionResult::Success { + output: Output::Create(_, Some(addr)), + .. + } => *addr, + _ => panic!("unexpected deployment result: {res:?}"), + }; + (res, trace, db, deployed) +} + +fn call_on_evm(db: revm::InMemoryDB, addr: Address, calldata: &[u8]) -> (ExecutionResult, String) { + let mut env = Env::default(); + env.tx.clear(); + env.tx.transact_to = TransactTo::Call(addr); + env.tx.data = Bytes::copy_from_slice(calldata); + + let (res, trace, _db) = run_revm_tx(db, env); + (res, trace) +} + +fn run_revm_tx(mut db: revm::InMemoryDB, env: Env) -> (ExecutionResult, String, revm::InMemoryDB) { + let context = Context::new( + EvmContext::new_with_env(db, Box::new(env)), + TestInspector::new(vec![]), + ); + + let mut evm = revm::Evm::new(context, Handler::mainnet::()); + evm = evm + .modify() + .append_handler_register(inspector_handle_register) + .build(); + + let res = evm.transact_commit(); + let trace = String::from_utf8(evm.context.external.w).unwrap(); + db = std::mem::take(&mut evm.context.evm.inner.db); + + match res { + Ok(r) => (r, trace, db), + Err(e) => panic!("evm failure: {e}"), + } +} + +struct TestInspector { + w: W, +} + +impl TestInspector { + fn new(w: W) -> TestInspector { + Self { w } + } +} + +impl revm::Inspector for TestInspector { + fn initialize_interp(&mut self, _interp: &mut Interpreter, _context: &mut EvmContext) { + writeln!( + self.w, + "{:>6} {:<17} input (stack grows to the right)", + "pc", "opcode" + ) + .unwrap(); + } + + fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + // xxx tentatively writing input stack; clean up + let pc = interp.program_counter(); + + let op = interp.current_opcode(); + let code = unsafe { std::mem::transmute::(op) }; + + let stack = interp.stack().data(); + + write!( + self.w, + "{:>6} {:0>2} {:<12} ", + pc, + format!("{op:x}"), + code.info().name(), + ) + .unwrap(); + let imm_size = code.info().immediate_size() as usize; + if imm_size > 0 { + let imm_bytes = interp.bytecode.slice((pc + 1)..(pc + 1 + imm_size)); + writeln!(self.w, "{} {}", imm_bytes, fmt_evm_stack(stack)).unwrap(); + } else { + writeln!(self.w, "{}", fmt_evm_stack(stack)).unwrap(); + } + } + + fn step_end(&mut self, _interp: &mut Interpreter, _context: &mut EvmContext) { + // NOTE: annoying revm behavior: `interp.current_opcode()` now returns the next opcode. + } +} + +fn stackify_trace_for_fn(function: &Function, stackify_reach_depth: u8) -> String { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(function); + + let mut liveness = Liveness::new(); + liveness.compute(function, &cfg); + let mut dom = DomTree::new(); + dom.compute(&cfg); + let (_alloc, stackify) = StackifyAlloc::for_function_with_trace( + function, + &cfg, + &dom, + &liveness, + stackify_reach_depth, + ); + stackify +} + +fn stackify_reach_depth_for_fixture(path: &str) -> u8 { + let Some(stem) = std::path::Path::new(path) + .file_stem() + .and_then(|s| s.to_str()) + else { + return 16; + }; + + let Some(pos) = stem.find("reach") else { + return 16; + }; + + let digits: String = stem[pos + "reach".len()..] + .chars() + .take_while(|c| c.is_ascii_digit()) + .collect(); + if digits.is_empty() { + return 16; + } + + digits.parse().unwrap_or(16) +} + +fn fmt_evm_stack(stack: &[U256]) -> String { + const SHOW: usize = 6; + + fn fmt_u256(v: &U256) -> String { + format!("{v:#x}") + } + + let len = stack.len(); + if len == 0 { + return "[]".to_string(); + } + + if len <= SHOW { + let elems: Vec = stack.iter().map(fmt_u256).collect(); + return format!("[{}]", elems.join(", ")); + } + + let head: Vec = stack.iter().take(2).map(fmt_u256).collect(); + let tail: Vec = stack.iter().skip(len - 3).map(fmt_u256).collect(); + format!("[{}, …, {}] (len={len})", head.join(", "), tail.join(", ")) +} diff --git a/crates/interpreter/test_files/gep.sntn b/crates/interpreter/test_files/gep.sntn index 3858aa8f..9999d0e7 100644 --- a/crates/interpreter/test_files/gep.sntn +++ b/crates/interpreter/test_files/gep.sntn @@ -3,8 +3,8 @@ target = "evm-ethereum-london" type @s1 = {i32, i64, i1}; type @nested = {i32, [i16; 3], [i8; 2]}; -#[(0.i256) -> 12.i256] -#[(8.i256) -> 20.i256] +#[(0.i256) -> 64.i256] +#[(8.i256) -> 72.i256] func private %gep_basic(v0.i256) -> i256 { block0: v1.*@s1 = int_to_ptr v0 *@s1; @@ -24,7 +24,7 @@ func private %gep_ptr_ty(v0.i256) -> i256 { } -#[(0.i256) -> 11.i256] +#[(0.i256) -> 160.i256] func private %gep_aggregate(v0.i256) -> i256 { block0: v1.*@nested = int_to_ptr v0 *@nested; diff --git a/crates/ir/Cargo.toml b/crates/ir/Cargo.toml index 40295c87..cf446ea7 100644 --- a/crates/ir/Cargo.toml +++ b/crates/ir/Cargo.toml @@ -25,6 +25,7 @@ dot2 = { git = "https://github.com/sanpii/dot2.rs.git" } dashmap = { version = "6.1", features = ["rayon"] } rayon = { version = "1" } dyn-clone = "1.0" +smol_str = "0.3.1" [dev-dependencies] sonatina-parser = { path = "../parser", version = "0.0.3-alpha" } diff --git a/crates/ir/src/builder/mod.rs b/crates/ir/src/builder/mod.rs index 8e24165d..3f3369b1 100644 --- a/crates/ir/src/builder/mod.rs +++ b/crates/ir/src/builder/mod.rs @@ -1,9 +1,11 @@ mod func_builder; mod module_builder; +mod object_builder; mod ssa; pub use func_builder::FunctionBuilder; -pub use module_builder::ModuleBuilder; +pub use module_builder::{BuilderError, ModuleBuilder}; +pub use object_builder::{DeclareObjectError, ObjectBuilder, ObjectBuilderError, SectionBuilder}; pub use ssa::Variable; pub mod test_util { diff --git a/crates/ir/src/builder/module_builder.rs b/crates/ir/src/builder/module_builder.rs index c2445a1d..25260c3b 100644 --- a/crates/ir/src/builder/module_builder.rs +++ b/crates/ir/src/builder/module_builder.rs @@ -1,11 +1,12 @@ use std::sync::Arc; use dashmap::DashMap; +use rustc_hash::FxHashMap; use sonatina_triple::TargetTriple; use super::FunctionBuilder; use crate::{ - Function, GlobalVariableData, GlobalVariableRef, InstSetBase, Module, Signature, Type, + Function, GlobalVariableData, GlobalVariableRef, InstSetBase, Module, Object, Signature, Type, func_cursor::{CursorLocation, FuncCursor}, module::{FuncRef, FuncStore, ModuleCtx}, types::{CompoundType, CompoundTypeRef}, @@ -18,6 +19,8 @@ pub struct ModuleBuilder { pub ctx: ModuleCtx, + pub objects: FxHashMap, + /// Map function name -> FuncRef to avoid duplicated declaration. declared_funcs: Arc>, } @@ -25,13 +28,30 @@ pub struct ModuleBuilder { #[derive(Debug, Clone)] pub enum BuilderError { ConflictingFunctionDeclaration, + DuplicateObjectDefinition { name: String }, +} + +impl std::fmt::Display for BuilderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ConflictingFunctionDeclaration => { + write!(f, "conflicting function declaration") + } + Self::DuplicateObjectDefinition { name } => { + write!(f, "duplicate object definition: {name}") + } + } + } } +impl std::error::Error for BuilderError {} + impl ModuleBuilder { pub fn new(ctx: ModuleCtx) -> Self { Self { func_store: Arc::new(FuncStore::new()), ctx, + objects: FxHashMap::default(), declared_funcs: Arc::new(DashMap::default()), } } @@ -44,6 +64,7 @@ impl ModuleBuilder { pub fn from_module(module: Module) -> Self { let store = module.func_store; let ctx = module.ctx; + let objects = module.objects; let declared_funcs = DashMap::new(); for func_ref in store.funcs() { let name = ctx.func_sig(func_ref, |sig| sig.name().to_string()); @@ -53,6 +74,7 @@ impl ModuleBuilder { Self { func_store: Arc::new(store), ctx, + objects, declared_funcs: Arc::new(declared_funcs), } } @@ -82,6 +104,19 @@ impl ModuleBuilder { self.ctx.with_gv_store_mut(|s| s.make_gv(global)) } + pub fn declare_object(&mut self, object: Object) -> Result<(), BuilderError> { + let name = object.name.0.to_string(); + if self.objects.contains_key(&name) { + return Err(BuilderError::DuplicateObjectDefinition { name }); + } + self.objects.insert(name, object); + Ok(()) + } + + pub fn lookup_object(&self, name: &str) -> Option<&Object> { + self.objects.get(name) + } + pub fn declare_struct_type(&self, name: &str, fields: &[Type], packed: bool) -> Type { self.ctx .with_ty_store_mut(|s| s.make_struct(name, fields, packed)) @@ -150,6 +185,7 @@ impl ModuleBuilder { Module { func_store: Arc::into_inner(self.func_store).unwrap(), ctx: self.ctx, + objects: self.objects, } } @@ -161,7 +197,7 @@ impl ModuleBuilder { #[cfg(test)] mod tests { use super::*; - use crate::{Linkage, builder::test_util::test_module_builder, types::Type}; //, Signature}; + use crate::{Linkage, ObjectName, builder::test_util::test_module_builder, types::Type}; //, Signature}; #[test] fn test_declare_function_success() { @@ -191,4 +227,40 @@ mod tests { _ => panic!("Expected conflicting function declaration error"), } } + + #[test] + fn test_declare_object_success() { + let mut builder = test_module_builder(); + + let object = Object { + name: ObjectName("Factory".into()), + sections: vec![], + }; + + let result = builder.declare_object(object); + assert!(result.is_ok()); + assert!(builder.lookup_object("Factory").is_some()); + } + + #[test] + fn test_declare_object_duplicate_should_fail() { + let mut builder = test_module_builder(); + + let object1 = Object { + name: ObjectName("Factory".into()), + sections: vec![], + }; + let object2 = Object { + name: ObjectName("Factory".into()), + sections: vec![], + }; + + builder.declare_object(object1).unwrap(); + let result = builder.declare_object(object2); + + assert!( + matches!(result, Err(BuilderError::DuplicateObjectDefinition { name }) if name == "Factory"), + "Expected DuplicateObjectDefinition error for object 'Factory'" + ); + } } diff --git a/crates/ir/src/builder/object_builder.rs b/crates/ir/src/builder/object_builder.rs new file mode 100644 index 00000000..a7164fba --- /dev/null +++ b/crates/ir/src/builder/object_builder.rs @@ -0,0 +1,336 @@ +use rustc_hash::FxHashSet; + +use crate::{ + GlobalVariableRef, + module::FuncRef, + object::{Directive, Embed, EmbedSymbol, Object, ObjectName, Section, SectionName, SectionRef}, +}; + +use super::{BuilderError, ModuleBuilder}; + +#[derive(Debug, Clone)] +pub enum ObjectBuilderError { + MissingEntry { + object: ObjectName, + section: SectionName, + }, + MultipleEntries { + object: ObjectName, + section: SectionName, + }, + DuplicateEmbedSymbol { + object: ObjectName, + section: SectionName, + symbol: EmbedSymbol, + }, +} + +impl std::fmt::Display for ObjectBuilderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MissingEntry { object, section } => { + write!( + f, + "missing entry in section @{}::{}", + object.0.as_str(), + section.0.as_str() + ) + } + Self::MultipleEntries { object, section } => { + write!( + f, + "multiple entry directives in section @{}::{}", + object.0.as_str(), + section.0.as_str() + ) + } + Self::DuplicateEmbedSymbol { + object, + section, + symbol, + } => write!( + f, + "duplicate embed symbol &{} in section @{}::{}", + symbol.0.as_str(), + object.0.as_str(), + section.0.as_str() + ), + } + } +} + +impl std::error::Error for ObjectBuilderError {} + +#[derive(Debug)] +pub enum DeclareObjectError { + Object(ObjectBuilderError), + Builder(BuilderError), +} + +impl std::fmt::Display for DeclareObjectError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Object(e) => e.fmt(f), + Self::Builder(e) => e.fmt(f), + } + } +} + +impl std::error::Error for DeclareObjectError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Object(e) => Some(e), + Self::Builder(e) => Some(e), + } + } +} + +impl From for DeclareObjectError { + fn from(value: ObjectBuilderError) -> Self { + Self::Object(value) + } +} + +impl From for DeclareObjectError { + fn from(value: BuilderError) -> Self { + Self::Builder(value) + } +} + +#[derive(Debug, Clone)] +pub struct ObjectBuilder { + name: ObjectName, + sections: Vec, +} + +impl ObjectBuilder { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + sections: Vec::new(), + } + } + + pub fn name(&self) -> &ObjectName { + &self.name + } + + pub fn section(&mut self, name: impl Into) -> &mut SectionBuilder { + let name = name.into(); + if let Some(pos) = self.sections.iter().position(|s| s.name == name) { + return &mut self.sections[pos]; + } + self.sections.push(SectionBuilder::new(name)); + self.sections.last_mut().expect("just pushed") + } + + pub fn finish(self) -> Result { + for section in &self.sections { + section.validate(&self.name)?; + } + + Ok(Object { + name: self.name, + sections: self + .sections + .into_iter() + .map(|section| Section { + name: section.name, + directives: section.directives, + }) + .collect(), + }) + } + + pub fn declare(self, mb: &mut ModuleBuilder) -> Result<(), DeclareObjectError> { + let object = self.finish()?; + mb.declare_object(object)?; + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct SectionBuilder { + name: SectionName, + directives: Vec, +} + +impl SectionBuilder { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + directives: Vec::new(), + } + } + + pub fn name(&self) -> &SectionName { + &self.name + } + + pub fn directives(&self) -> &[Directive] { + &self.directives + } + + pub fn entry(&mut self, func: FuncRef) -> &mut Self { + self.directives.push(Directive::Entry(func)); + self + } + + pub fn include(&mut self, func: FuncRef) -> &mut Self { + self.directives.push(Directive::Include(func)); + self + } + + pub fn data(&mut self, gv: GlobalVariableRef) -> &mut Self { + self.directives.push(Directive::Data(gv)); + self + } + + pub fn embed(&mut self, source: SectionRef, as_symbol: impl Into) -> &mut Self { + self.directives.push(Directive::Embed(Embed { + source, + as_symbol: as_symbol.into(), + })); + self + } + + pub fn embed_local( + &mut self, + section: impl Into, + as_symbol: impl Into, + ) -> &mut Self { + self.embed(SectionRef::Local(section.into()), as_symbol) + } + + pub fn embed_external( + &mut self, + object: impl Into, + section: impl Into, + as_symbol: impl Into, + ) -> &mut Self { + self.embed( + SectionRef::External { + object: object.into(), + section: section.into(), + }, + as_symbol, + ) + } + + fn validate(&self, object: &ObjectName) -> Result<(), ObjectBuilderError> { + let mut entry_count = 0usize; + let mut embed_symbols: FxHashSet = FxHashSet::default(); + + for directive in &self.directives { + match directive { + Directive::Entry(_) => entry_count += 1, + Directive::Embed(embed) => { + if !embed_symbols.insert(embed.as_symbol.clone()) { + return Err(ObjectBuilderError::DuplicateEmbedSymbol { + object: object.clone(), + section: self.name.clone(), + symbol: embed.as_symbol.clone(), + }); + } + } + _ => {} + } + } + + if entry_count == 0 { + return Err(ObjectBuilderError::MissingEntry { + object: object.clone(), + section: self.name.clone(), + }); + } + if entry_count > 1 { + return Err(ObjectBuilderError::MultipleEntries { + object: object.clone(), + section: self.name.clone(), + }); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + Linkage, Signature, Type, + builder::test_util::test_module_builder, + global_variable::{GlobalVariableData, GvInitializer}, + }; + + #[test] + fn builds_and_declares_object() { + let mut mb = test_module_builder(); + let runtime = mb + .declare_function(Signature::new("runtime", Linkage::Public, &[], Type::Unit)) + .unwrap(); + let init = mb + .declare_function(Signature::new("init", Linkage::Public, &[], Type::Unit)) + .unwrap(); + + let gv = mb.declare_gv(GlobalVariableData::constant( + "foo".to_string(), + Type::I32, + Linkage::Public, + GvInitializer::make_imm(0i32), + )); + + let mut obj = ObjectBuilder::new("Contract"); + obj.section("runtime").entry(runtime).data(gv); + obj.section("init") + .entry(init) + .embed_local("runtime", "runtime"); + + obj.clone().declare(&mut mb).unwrap(); + let stored = mb.lookup_object("Contract").unwrap(); + assert_eq!(stored.sections.len(), 2); + } + + #[test] + fn rejects_missing_entry() { + let mut obj = ObjectBuilder::new("Contract"); + obj.section("runtime"); + + let err = obj.finish().unwrap_err(); + assert!(matches!(err, ObjectBuilderError::MissingEntry { .. })); + } + + #[test] + fn rejects_multiple_entries() { + let mb = test_module_builder(); + let runtime = mb + .declare_function(Signature::new("runtime", Linkage::Public, &[], Type::Unit)) + .unwrap(); + + let mut obj = ObjectBuilder::new("Contract"); + obj.section("runtime").entry(runtime).entry(runtime); + + let err = obj.finish().unwrap_err(); + assert!(matches!(err, ObjectBuilderError::MultipleEntries { .. })); + } + + #[test] + fn rejects_duplicate_embed_symbols() { + let mb = test_module_builder(); + let init = mb + .declare_function(Signature::new("init", Linkage::Public, &[], Type::Unit)) + .unwrap(); + + let mut obj = ObjectBuilder::new("Contract"); + obj.section("init") + .entry(init) + .embed_local("runtime", "runtime") + .embed_local("runtime", "runtime"); + + let err = obj.finish().unwrap_err(); + assert!(matches!( + err, + ObjectBuilderError::DuplicateEmbedSymbol { .. } + )); + } +} diff --git a/crates/ir/src/dfg.rs b/crates/ir/src/dfg.rs index 07b4b45b..2bec3373 100644 --- a/crates/ir/src/dfg.rs +++ b/crates/ir/src/dfg.rs @@ -148,6 +148,10 @@ impl DataFlowGraph { } } + pub fn value_is_imm(&self, value_id: ValueId) -> bool { + matches!(self.values[value_id], Value::Immediate { .. }) + } + pub fn attach_user(&mut self, inst_id: InstId) { let inst = &self.insts[inst_id]; inst.for_each_value(&mut |value| { @@ -234,6 +238,18 @@ impl DataFlowGraph { InstDowncastMut::downcast_mut(is, inst) } + pub fn cast_call(&self, inst_id: InstId) -> Option<&control_flow::Call> { + let inst = self.inst(inst_id); + let is = self.inst_set(); + InstDowncast::downcast(is, inst) + } + + pub fn cast_br_table(&self, inst_id: InstId) -> Option<&control_flow::BrTable> { + let inst = self.inst(inst_id); + let is = self.inst_set(); + InstDowncast::downcast(is, inst) + } + pub fn make_phi(&self, args: Vec<(ValueId, BlockId)>) -> Phi { Phi::new(self.inst_set().phi(), args) } @@ -266,6 +282,20 @@ impl DataFlowGraph { self.cast_phi(inst_id).is_some() } + pub fn is_call(&self, inst_id: InstId) -> bool { + self.cast_call(inst_id).is_some() + } + + pub fn is_return(&self, inst_id: InstId) -> bool { + <&control_flow::Return as InstDowncast>::downcast(self.inst_set(), self.inst(inst_id)) + .is_some() + } + + pub fn as_return(&self, inst: InstId) -> Option { + let r: &control_flow::Return = InstDowncast::downcast(self.inst_set(), self.inst(inst))?; + *r.arg() + } + pub fn rewrite_branch_dest(&mut self, inst_id: InstId, from: BlockId, to: BlockId) { let inst_set = self.ctx.inst_set; let Some(branch) = self.branch_info(inst_id) else { diff --git a/crates/ir/src/inst/control_flow.rs b/crates/ir/src/inst/control_flow.rs index 602668a0..7d0f1922 100644 --- a/crates/ir/src/inst/control_flow.rs +++ b/crates/ir/src/inst/control_flow.rs @@ -1,5 +1,5 @@ use macros::{Inst, inst_prop}; -use smallvec::SmallVec; +use smallvec::{SmallVec, smallvec}; use crate::{BlockId, Inst, InstSetBase, ValueId, module::FuncRef}; @@ -86,7 +86,7 @@ impl CallInfo for Call { /// A trait for instructions that can be used as a jump or branching. #[inst_prop] pub trait BranchInfo { - fn dests(&self) -> Vec; + fn dests(&self) -> SmallVec<[BlockId; 2]>; fn num_dests(&self) -> usize; fn remove_dest(&self, isb: &dyn InstSetBase, dest: BlockId) -> Box; fn rewrite_dest(&self, isb: &dyn InstSetBase, from: BlockId, to: BlockId) -> Box; @@ -96,8 +96,8 @@ pub trait BranchInfo { } impl BranchInfo for Jump { - fn dests(&self) -> Vec { - vec![self.dest] + fn dests(&self) -> SmallVec<[BlockId; 2]> { + smallvec![self.dest] } fn num_dests(&self) -> usize { @@ -123,8 +123,8 @@ impl BranchInfo for Jump { } impl BranchInfo for Br { - fn dests(&self) -> Vec { - vec![self.nz_dest, self.z_dest] + fn dests(&self) -> SmallVec<[BlockId; 2]> { + smallvec![self.nz_dest, self.z_dest] } fn num_dests(&self) -> usize { @@ -158,11 +158,11 @@ impl BranchInfo for Br { } impl BranchInfo for BrTable { - fn dests(&self) -> Vec { + fn dests(&self) -> SmallVec<[BlockId; 2]> { let mut dests = if let Some(default) = self.default { - vec![default] + smallvec![default] } else { - vec![] + smallvec![] }; dests.extend(self.table.iter().map(|(_, block)| *block)); diff --git a/crates/ir/src/inst/data.rs b/crates/ir/src/inst/data.rs index 234288ec..24f07efe 100644 --- a/crates/ir/src/inst/data.rs +++ b/crates/ir/src/inst/data.rs @@ -1,7 +1,59 @@ +use std::io; + use macros::Inst; use smallvec::SmallVec; -use crate::{Type, ValueId, module::FuncRef}; +use crate::{ + EmbedSymbol, GlobalVariableRef, Type, ValueId, + ir_writer::IrWrite, + module::{FuncRef, ModuleCtx}, + visitor::{Visitable, VisitableMut}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SymbolRef { + Func(FuncRef), + Global(GlobalVariableRef), + Embed(EmbedSymbol), +} + +impl IrWrite for SymbolRef +where + Ctx: AsRef, +{ + fn write(&self, w: &mut W, ctx: &Ctx) -> io::Result<()> + where + W: io::Write, + { + match self { + Self::Func(f) => f.write(w, ctx), + Self::Global(gv) => ctx + .as_ref() + .with_gv_store(|s| write!(w, "${}", s.gv_data(*gv).symbol)), + Self::Embed(sym) => write!(w, "&{}", sym.0.as_str()), + } + } +} + +impl Visitable for SymbolRef { + fn accept(&self, visitor: &mut dyn crate::visitor::Visitor) { + match self { + Self::Func(f) => f.accept(visitor), + Self::Global(gv) => gv.accept(visitor), + Self::Embed(_) => {} + } + } +} + +impl VisitableMut for SymbolRef { + fn accept_mut(&mut self, visitor: &mut dyn crate::visitor::VisitorMut) { + match self { + Self::Func(f) => f.accept_mut(visitor), + Self::Global(gv) => gv.accept_mut(visitor), + Self::Embed(_) => {} + } + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] #[inst(side_effect(super::SideEffect::Read))] @@ -46,3 +98,13 @@ pub struct ExtractValue { dest: ValueId, idx: ValueId, } + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Inst)] +pub struct SymAddr { + sym: SymbolRef, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Inst)] +pub struct SymSize { + sym: SymbolRef, +} diff --git a/crates/ir/src/inst/evm/inst_set.rs b/crates/ir/src/inst/evm/inst_set.rs index a230c9c7..2d2d5f43 100644 --- a/crates/ir/src/inst/evm/inst_set.rs +++ b/crates/ir/src/inst/evm/inst_set.rs @@ -37,6 +37,8 @@ pub struct EvmInstSet( data::Mstore, data::Gep, data::GetFunctionPtr, + data::SymAddr, + data::SymSize, data::Alloca, data::InsertValue, data::ExtractValue, @@ -54,6 +56,7 @@ pub struct EvmInstSet( evm::EvmMulMod, evm::EvmExp, evm::EvmByte, + evm::EvmClz, evm::EvmKeccak256, evm::EvmAddress, evm::EvmBalance, @@ -103,5 +106,4 @@ pub struct EvmInstSet( evm::EvmRevert, evm::EvmSelfDestruct, evm::EvmMalloc, - evm::EvmContractSize, ); diff --git a/crates/ir/src/inst/evm/mod.rs b/crates/ir/src/inst/evm/mod.rs index e3c5787e..0e641885 100644 --- a/crates/ir/src/inst/evm/mod.rs +++ b/crates/ir/src/inst/evm/mod.rs @@ -1,7 +1,7 @@ use macros::Inst; pub mod inst_set; -use crate::{module::FuncRef, value::ValueId}; +use crate::value::ValueId; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] pub struct EvmUdiv { @@ -63,6 +63,11 @@ pub struct EvmByte { value: ValueId, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] +pub struct EvmClz { + word: ValueId, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] pub struct EvmKeccak256 { addr: ValueId, @@ -167,9 +172,7 @@ pub struct EvmBlockHash { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] #[inst(side_effect(crate::inst::SideEffect::Read))] -pub struct EvmCoinBase { - block_num: ValueId, -} +pub struct EvmCoinBase {} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] #[inst(side_effect(crate::inst::SideEffect::Read))] @@ -394,10 +397,3 @@ pub struct EvmSelfDestruct { pub struct EvmMalloc { size: ValueId, } - -/// An instruction that takes the main function of a contract -/// as an argument and returns the size of the contract. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Inst)] -pub struct EvmContractSize { - contract: FuncRef, -} diff --git a/crates/ir/src/inst/inst_set.rs b/crates/ir/src/inst/inst_set.rs index d39522ac..0025ee1f 100644 --- a/crates/ir/src/inst/inst_set.rs +++ b/crates/ir/src/inst/inst_set.rs @@ -49,6 +49,8 @@ define_inst_set_base! { data::Mstore, data::Gep, data::GetFunctionPtr, + data::SymAddr, + data::SymSize, data::Alloca, data::InsertValue, data::ExtractValue, @@ -69,6 +71,7 @@ define_inst_set_base! { evm::EvmMulMod, evm::EvmExp, evm::EvmByte, + evm::EvmClz, evm::EvmKeccak256, evm::EvmAddress, evm::EvmBalance, @@ -120,7 +123,7 @@ define_inst_set_base! { evm::EvmRevert, evm::EvmSelfDestruct, evm::EvmMalloc, - evm::EvmContractSize, + } } diff --git a/crates/ir/src/inst/mod.rs b/crates/ir/src/inst/mod.rs index bd5d2409..a8edfe1b 100644 --- a/crates/ir/src/inst/mod.rs +++ b/crates/ir/src/inst/mod.rs @@ -15,9 +15,11 @@ use std::{ use dyn_clone::DynClone; use macros::inst_prop; +use rustc_hash::FxHashSet; +use smallvec::SmallVec; use crate::{ - InstSetBase, + InstSetBase, ValueId, ir_writer::{FuncWriteCtx, IrWrite}, visitor::{Visitable, VisitableMut}, }; @@ -33,6 +35,22 @@ pub trait Inst: fn side_effect(&self) -> SideEffect; fn as_text(&self) -> &'static str; fn is_terminator(&self) -> bool; + + fn collect_values(&self) -> SmallVec<[ValueId; 2]> { + let mut vs = SmallVec::new(); + + self.for_each_value(&mut |v| vs.push(v)); + vs + } + + fn collect_value_set(&self) -> FxHashSet { + let mut vs = FxHashSet::default(); + + self.for_each_value(&mut |v| { + vs.insert(v); + }); + vs + } } pub trait InstExt: Inst { diff --git a/crates/ir/src/ir_writer.rs b/crates/ir/src/ir_writer.rs index d4d5fd50..6574c932 100644 --- a/crates/ir/src/ir_writer.rs +++ b/crates/ir/src/ir_writer.rs @@ -87,6 +87,21 @@ impl<'a> ModuleWriter<'a> { writeln!(w)?; } + if !self.module.objects.is_empty() { + let mut objects: Vec<_> = self.module.objects.values().collect(); + objects.sort_by(|a, b| a.name.0.as_str().cmp(b.name.0.as_str())); + + writeln!(w)?; + for (i, object) in objects.iter().enumerate() { + object.write(w, &self.module.ctx)?; + if i + 1 != objects.len() { + writeln!(w)?; + writeln!(w)?; + } + } + writeln!(w)?; + } + Ok(()) } @@ -155,34 +170,7 @@ impl<'a> FuncWriter<'a> { } pub fn write(&mut self, w: &mut impl io::Write) -> io::Result<()> { - let func_ref = self.ctx.func_ref; - let m_ctx = self.ctx.module_ctx(); - - write!(w, "func ")?; - m_ctx.func_sig(func_ref, |sig| { - sig.linkage().write(w, &self.ctx)?; - write!(w, " %{}(", sig.name())?; - io::Result::Ok(()) - })?; - let arg_values: SmallVec<[ValueWithTy; 8]> = self - .ctx - .func - .arg_values - .iter() - .map(|value| ValueWithTy(*value)) - .collect(); - arg_values.write_with_delim(w, ", ", &self.ctx)?; - write!(w, ")")?; - - m_ctx.func_sig(func_ref, |sig| { - if !sig.ret_ty().is_unit() { - write!(w, " -> ")?; - sig.ret_ty().write(w, &self.ctx) - } else { - Ok(()) - } - })?; - + FunctionSignature.write(w, &self.ctx)?; writeln!(w, " {{")?; self.level += 1; @@ -396,6 +384,43 @@ where } } +#[derive(Clone, Copy)] +pub struct FunctionSignature; +impl IrWrite> for FunctionSignature { + fn write(&self, w: &mut W, ctx: &FuncWriteCtx) -> io::Result<()> + where + W: io::Write, + { + let func_ref = ctx.func_ref; + let m_ctx = ctx.module_ctx(); + + write!(w, "func ")?; + m_ctx.func_sig(func_ref, |sig| { + sig.linkage().write(w, ctx)?; + write!(w, " %{}(", sig.name())?; + io::Result::Ok(()) + })?; + let arg_values: SmallVec<[ValueWithTy; 8]> = ctx + .func + .arg_values + .iter() + .map(|value| ValueWithTy(*value)) + .collect(); + arg_values.write_with_delim(w, ", ", ctx)?; + write!(w, ")")?; + + m_ctx.func_sig(func_ref, |sig| { + let ret_ty = sig.ret_ty(); + if !ret_ty.is_unit() { + write!(w, " -> ")?; + ret_ty.write(w, ctx) + } else { + Ok(()) + } + }) + } +} + #[derive(Clone, Copy)] pub struct ValueWithTy(pub ValueId); impl IrWrite> for ValueWithTy { diff --git a/crates/ir/src/isa/evm.rs b/crates/ir/src/isa/evm.rs index 8e7e050e..14c5b669 100644 --- a/crates/ir/src/isa/evm.rs +++ b/crates/ir/src/isa/evm.rs @@ -40,13 +40,9 @@ impl TypeLayout for EvmTypeLayout { fn size_of(&self, ty: crate::Type, ctx: &ModuleCtx) -> Result { let size = match ty { Type::Unit => 0, - Type::I1 => 1, - Type::I8 => 1, - Type::I16 => 2, - Type::I32 => 4, - Type::I64 => 8, - Type::I128 => 16, - Type::I256 => 32, + // EVM memory is word-addressed by the MLOAD/MSTORE family. For now we model all + // scalar/pointer values as occupying one full 32-byte slot. + Type::I1 | Type::I8 | Type::I16 | Type::I32 | Type::I64 | Type::I128 | Type::I256 => 32, Type::Compound(cmpd) => { let cmpd_data = ctx.with_ty_store(|s| s.resolve_compound(cmpd).clone()); @@ -81,8 +77,9 @@ impl TypeLayout for EvmTypeLayout { Type::I256 } - fn align_of(&self, _ty: Type, _ctx: &ModuleCtx) -> Result { - Ok(1) + fn align_of(&self, ty: Type, ctx: &ModuleCtx) -> Result { + let size = self.size_of(ty, ctx)?; + if size == 0 { Ok(1) } else { Ok(32) } } fn endian(&self) -> Endian { diff --git a/crates/ir/src/lib.rs b/crates/ir/src/lib.rs index 1f891795..40d1980c 100644 --- a/crates/ir/src/lib.rs +++ b/crates/ir/src/lib.rs @@ -13,6 +13,7 @@ pub mod layout; pub mod linkage; pub mod module; pub mod module_linker; +pub mod object; pub mod types; pub mod value; pub mod visitor; @@ -33,6 +34,9 @@ pub use inst::{ pub use layout::Layout; pub use linkage::Linkage; pub use module::Module; +pub use object::{ + Directive, Embed, EmbedSymbol, Object, ObjectName, Section, SectionName, SectionRef, +}; pub use types::Type; pub use value::{Immediate, Value, ValueId}; diff --git a/crates/ir/src/module.rs b/crates/ir/src/module.rs index 4eb3f32f..ef411c8f 100644 --- a/crates/ir/src/module.rs +++ b/crates/ir/src/module.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex, RwLock}; use cranelift_entity::entity_impl; use dashmap::{DashMap, ReadOnlyView}; use rayon::{iter::IntoParallelIterator, prelude::ParallelIterator}; +use rustc_hash::FxHashMap; use sonatina_triple::TargetTriple; use crate::{ @@ -10,12 +11,14 @@ use crate::{ global_variable::GlobalVariableStore, ir_writer::IrWrite, isa::{Endian, Isa, TypeLayout, TypeLayoutError}, + object::Object, types::TypeStore, }; pub struct Module { pub func_store: FuncStore, pub ctx: ModuleCtx, + pub objects: FxHashMap, } impl Module { @@ -24,6 +27,7 @@ impl Module { Self { func_store: FuncStore::new(), ctx: ModuleCtx::new(isa), + objects: FxHashMap::default(), } } diff --git a/crates/ir/src/module_linker.rs b/crates/ir/src/module_linker.rs index 94047050..94f5af99 100644 --- a/crates/ir/src/module_linker.rs +++ b/crates/ir/src/module_linker.rs @@ -10,7 +10,7 @@ use rustc_hash::FxHashMap; use crate::{ GlobalVariableRef, Linkage, Module, Signature, Type, Value, - builder::ModuleBuilder, + builder::{BuilderError, ModuleBuilder}, module::FuncRef, types::{CompoundType, CompoundTypeRef, StructData}, visitor::VisitorMut, @@ -52,6 +52,10 @@ pub enum LinkError { InconsistentFuncSignature { name: String, }, + + DuplicateObjectDefinition { + name: String, + }, } impl LinkedModule { @@ -264,6 +268,33 @@ impl ModuleLinker { // Move functions to the linked module. for (module_ref, module) in modules { let ref_map = self.module_ref_map.get(&module_ref).unwrap(); + for (_, mut object) in module.objects { + for section in &mut object.sections { + for directive in &mut section.directives { + match directive { + crate::object::Directive::Entry(func) + | crate::object::Directive::Include(func) => { + *func = ref_map.lookup_func(*func); + } + crate::object::Directive::Data(gv) => { + *gv = ref_map.lookup_gv(*gv); + } + crate::object::Directive::Embed(_) => {} + } + } + } + + self.builder + .declare_object(object) + .map_err(|err| match err { + BuilderError::DuplicateObjectDefinition { name } => { + LinkError::DuplicateObjectDefinition { name } + } + BuilderError::ConflictingFunctionDeclaration => unreachable!( + "unexpected function declaration error while linking objects" + ), + })?; + } module.func_store.par_into_for_each(|func_ref, mut func| { // If linkage is external, we don't need to move the function definition to the // linked module. @@ -293,6 +324,10 @@ impl ModuleLinker { *item = self.ref_map.lookup_func(*item); } + fn visit_gv_ref(&mut self, item: &mut GlobalVariableRef) { + *item = self.ref_map.lookup_gv(*item); + } + fn visit_ty(&mut self, item: &mut Type) { *item = self.ref_map.lookup_type(*item); } @@ -609,7 +644,9 @@ impl ModuleLinker { #[cfg(test)] mod tests { use super::*; - use crate::{Linkage, builder::test_util::test_module_builder, types::Type}; + use crate::{ + Linkage, Object, ObjectName, builder::test_util::test_module_builder, types::Type, + }; #[test] fn test_linker_conflicting_function_signature_should_fail() { @@ -732,4 +769,35 @@ mod tests { "Expected successful link for different function names" ); } + + #[test] + fn test_linker_duplicate_objects_should_fail() { + let mod1 = { + let mut builder = test_module_builder(); + builder + .declare_object(Object { + name: ObjectName("Factory".into()), + sections: vec![], + }) + .unwrap(); + builder.build() + }; + + let mod2 = { + let mut builder = test_module_builder(); + builder + .declare_object(Object { + name: ObjectName("Factory".into()), + sections: vec![], + }) + .unwrap(); + builder.build() + }; + + let result = LinkedModule::link(vec![mod1, mod2]); + assert!( + matches!(result, Err(LinkError::DuplicateObjectDefinition { name }) if name == "Factory"), + "Expected DuplicateObjectDefinition error for object 'Factory'" + ); + } } diff --git a/crates/ir/src/object.rs b/crates/ir/src/object.rs new file mode 100644 index 00000000..85e18dbf --- /dev/null +++ b/crates/ir/src/object.rs @@ -0,0 +1,194 @@ +use std::io::{self, Write}; + +use smol_str::SmolStr; + +use crate::{ + GlobalVariableRef, + ir_writer::IrWrite, + module::{FuncRef, ModuleCtx}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Object { + pub name: ObjectName, + pub sections: Vec

, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Section { + pub name: SectionName, + pub directives: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Directive { + Entry(FuncRef), + Include(FuncRef), + Data(GlobalVariableRef), + Embed(Embed), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Embed { + pub source: SectionRef, + pub as_symbol: EmbedSymbol, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SectionRef { + Local(SectionName), + External { + object: ObjectName, + section: SectionName, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ObjectName(pub SmolStr); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SectionName(pub SmolStr); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EmbedSymbol(pub SmolStr); + +impl From<&str> for ObjectName { + fn from(value: &str) -> Self { + Self(value.into()) + } +} + +impl From for ObjectName { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl From<&str> for SectionName { + fn from(value: &str) -> Self { + Self(value.into()) + } +} + +impl From for SectionName { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl From<&str> for EmbedSymbol { + fn from(value: &str) -> Self { + Self(value.into()) + } +} + +impl From for EmbedSymbol { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl IrWrite for ObjectName { + fn write(&self, w: &mut W, _ctx: &Ctx) -> io::Result<()> { + write!(w, "@{}", self.0.as_str()) + } +} + +impl IrWrite for SectionName { + fn write(&self, w: &mut W, _ctx: &Ctx) -> io::Result<()> { + write!(w, "{}", self.0.as_str()) + } +} + +impl IrWrite for EmbedSymbol { + fn write(&self, w: &mut W, _ctx: &Ctx) -> io::Result<()> { + write!(w, "&{}", self.0.as_str()) + } +} + +impl IrWrite for SectionRef { + fn write(&self, w: &mut W, ctx: &Ctx) -> io::Result<()> { + match self { + Self::Local(section) => { + write!(w, ".")?; + section.write(w, ctx) + } + Self::External { object, section } => { + object.write(w, ctx)?; + write!(w, ".")?; + section.write(w, ctx) + } + } + } +} + +impl IrWrite for Embed { + fn write(&self, w: &mut W, ctx: &Ctx) -> io::Result<()> { + write!(w, "embed ")?; + self.source.write(w, ctx)?; + write!(w, " as ")?; + self.as_symbol.write(w, ctx)?; + write!(w, ";") + } +} + +impl IrWrite for Directive +where + Ctx: AsRef, +{ + fn write(&self, w: &mut W, ctx: &Ctx) -> io::Result<()> { + match self { + Self::Entry(func) => { + write!(w, "entry ")?; + func.write(w, ctx)?; + write!(w, ";") + } + Self::Include(func) => { + write!(w, "include ")?; + func.write(w, ctx)?; + write!(w, ";") + } + Self::Data(data) => { + write!(w, "data ")?; + ctx.as_ref() + .with_gv_store(|s| write!(w, "${}", s.gv_data(*data).symbol))?; + write!(w, ";") + } + Self::Embed(embed) => embed.write(w, ctx), + } + } +} + +impl IrWrite for Section +where + Ctx: AsRef, +{ + fn write(&self, w: &mut W, ctx: &Ctx) -> io::Result<()> { + write!(w, "section ")?; + self.name.write(w, ctx)?; + writeln!(w, " {{")?; + for directive in &self.directives { + write!(w, " ")?; + directive.write(w, ctx)?; + writeln!(w)?; + } + write!(w, " }}") + } +} + +impl IrWrite for Object +where + Ctx: AsRef, +{ + fn write(&self, w: &mut W, ctx: &Ctx) -> io::Result<()> { + write!(w, "object ")?; + self.name.write(w, ctx)?; + writeln!(w, " {{")?; + for section in &self.sections { + write!(w, " ")?; + section.write(w, ctx)?; + writeln!(w)?; + } + write!(w, "}}") + } +} diff --git a/crates/ir/src/visitor.rs b/crates/ir/src/visitor.rs index bfd29a5d..49c3d217 100644 --- a/crates/ir/src/visitor.rs +++ b/crates/ir/src/visitor.rs @@ -5,7 +5,7 @@ //! visited in order to cover the whole sonatina-IR. use smallvec::{Array, SmallVec}; -use crate::{BlockId, Type, ValueId, module::FuncRef}; +use crate::{BlockId, GlobalVariableRef, Type, ValueId, module::FuncRef}; pub trait Visitable { fn accept(&self, visitor: &mut dyn Visitor); @@ -60,6 +60,8 @@ pub trait Visitor { fn visit_block_id(&mut self, item: BlockId) {} fn visit_func_ref(&mut self, item: FuncRef) {} + + fn visit_gv_ref(&mut self, item: GlobalVariableRef) {} } #[allow(unused_variables)] @@ -71,6 +73,8 @@ pub trait VisitorMut { fn visit_block_id(&mut self, item: &mut BlockId) {} fn visit_func_ref(&mut self, item: &mut FuncRef) {} + + fn visit_gv_ref(&mut self, item: &mut GlobalVariableRef) {} } impl Visitable for Type { @@ -117,6 +121,17 @@ impl VisitableMut for FuncRef { } } +impl Visitable for GlobalVariableRef { + fn accept(&self, visitor: &mut dyn Visitor) { + visitor.visit_gv_ref(*self); + } +} +impl VisitableMut for GlobalVariableRef { + fn accept_mut(&mut self, visitor: &mut dyn VisitorMut) { + visitor.visit_gv_ref(self); + } +} + impl Visitable for Option where T: Visitable, diff --git a/crates/macros/src/inst_prop.rs b/crates/macros/src/inst_prop.rs index 8790bce2..b392ec47 100644 --- a/crates/macros/src/inst_prop.rs +++ b/crates/macros/src/inst_prop.rs @@ -35,11 +35,9 @@ impl InstProp { "`type Members = (ty_1, ty_2, .., ty_n)` or `type Members = All` is required"; for it in item_trait.items.iter() { - if let syn::TraitItem::Type(assoc_ty) = it { - if assoc_ty.ident != "Members" { - continue; - } - + if let syn::TraitItem::Type(assoc_ty) = it + && assoc_ty.ident == "Members" + { let Some((_, members)) = &assoc_ty.default else { return Err(syn::Error::new_spanned(assoc_ty, MISSING_MEMBERS)); }; diff --git a/crates/object/Cargo.toml b/crates/object/Cargo.toml deleted file mode 100644 index f95f47fd..00000000 --- a/crates/object/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "sonatina-object" -version = "0.0.3-alpha" -edition.workspace = true -authors = ["Sonatina Developers"] -license = "Apache-2.0" -readme = "../../README.md" -homepage = "https://github.com/fe-lang/sonatina/tree/main/crates/object" -repository = "https://github.com/fe-lang/sonatina" -description = "Object file for sonatina code generator" -categories = ["compilers", "wasm"] -keywords = ["compiler", "evm", "wasm", "smart-contract"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/crates/object/src/lib.rs b/crates/object/src/lib.rs deleted file mode 100644 index 1b4a90c9..00000000 --- a/crates/object/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index a5c40183..df945729 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use derive_more::Debug as Dbg; use either::Either; use hex::FromHex; -use ir::{I256, U256}; +use ir::{EmbedSymbol, I256, ObjectName, U256}; pub use ir::{Immediate, Linkage}; use pest::Parser as _; use smol_str::SmolStr; @@ -14,6 +14,7 @@ pub use sonatina_triple::{InvalidTriple, TargetTriple}; use super::{Error, syntax::Node}; use crate::{ Span, + objects::Object, syntax::{FromSyntax, Parser, Rule}, }; @@ -43,9 +44,17 @@ pub struct Module { pub declared_gvs: Vec, pub struct_types: Vec, pub functions: Vec, + pub objects: Vec, pub comments: Vec, } +#[derive(Dbg)] +pub struct ObjectDefinition { + pub object: Object, + #[debug(skip)] + pub span: Span, +} + impl FromSyntax for Module { fn from_syntax(node: &mut Node) -> Self { let target = node.single(Rule::target_triple); @@ -62,6 +71,7 @@ impl FromSyntax for Module { let mut declared_functions = vec![]; let mut declared_gvs = vec![]; let mut functions = vec![]; + let mut objects = vec![]; loop { let comments = node.map_while(|p| { @@ -84,7 +94,13 @@ impl FromSyntax for Module { func.comments = comments; functions.push(func); } - None => break, + None => { + if let Some(obj) = node.single_opt(Rule::object_definition) { + objects.push(obj); + } else { + break; + } + } } } } @@ -95,11 +111,29 @@ impl FromSyntax for Module { declared_gvs, struct_types, functions, + objects, comments: module_comments, } } } +impl FromSyntax for ObjectDefinition { + fn from_syntax(node: &mut Node) -> Self { + let obj_ident = node.get(Rule::object_identifier); + let span = { + let s = obj_ident.as_span(); + Span::from_range(s.start()..s.end()) + }; + + let mut ident_node = Node::new(obj_ident); + let name = ObjectName::from_syntax(&mut ident_node); + + let sections = node.multi(Rule::object_section); + let object = Object { name, sections }; + ObjectDefinition { object, span } + } +} + impl FromSyntax for Option { fn from_syntax(node: &mut Node) -> Self { match TargetTriple::parse(node.txt) { @@ -534,6 +568,8 @@ impl FromSyntax for InstArg { InstArgKind::ValueBlockMap(vb_map) } else if let Some(func) = node.single_opt(Rule::function_identifier) { InstArgKind::FuncRef(func) + } else if let Some(sym) = node.single_opt(Rule::embed_symbol) { + InstArgKind::EmbedSymbol(sym) } else { unreachable!() }; @@ -552,6 +588,7 @@ pub enum InstArgKind { Block(BlockId), ValueBlockMap((Value, BlockId)), FuncRef(FunctionName), + EmbedSymbol(EmbedSymbol), } impl InstArgKind { @@ -562,6 +599,7 @@ impl InstArgKind { Self::Block(_) => "block", Self::ValueBlockMap(_) => "(value, block)", Self::FuncRef(_) => "function name", + Self::EmbedSymbol(_) => "embed symbol", } .into() } diff --git a/crates/parser/src/inst/data.rs b/crates/parser/src/inst/data.rs index e3e41e07..f096dd35 100644 --- a/crates/parser/src/inst/data.rs +++ b/crates/parser/src/inst/data.rs @@ -7,10 +7,83 @@ super::impl_inst_build! {Mload, (addr: ValueId, ty: Type)} super::impl_inst_build! {Mstore, (addr: ValueId, value: ValueId, ty: Type)} super::impl_inst_build_common! {Gep, ArityBound::AtLeast(2), build_gep} super::impl_inst_build! {GetFunctionPtr, (func: FuncRef)} +super::impl_inst_build_common! {SymAddr, ArityBound::Exact(1), build_sym_addr} +super::impl_inst_build_common! {SymSize, ArityBound::Exact(1), build_sym_size} super::impl_inst_build! {Alloca, (ty: Type)} super::impl_inst_build! {InsertValue, (dest: ValueId, idx: ValueId, value: ValueId)} super::impl_inst_build! {ExtractValue, (dest: ValueId, idx: ValueId)} +fn build_sym_addr( + ctx: &mut BuildCtx, + fb: &mut FunctionBuilder, + args: &[ast::InstArg], + has_inst: &dyn HasInst, +) -> Result> { + let sym = build_symbol_ref(ctx, fb, args)?; + Ok(SymAddr::new(has_inst, sym)) +} + +fn build_sym_size( + ctx: &mut BuildCtx, + fb: &mut FunctionBuilder, + args: &[ast::InstArg], + has_inst: &dyn HasInst, +) -> Result> { + let sym = build_symbol_ref(ctx, fb, args)?; + Ok(SymSize::new(has_inst, sym)) +} + +fn build_symbol_ref( + ctx: &mut BuildCtx, + fb: &mut FunctionBuilder, + args: &[ast::InstArg], +) -> Result> { + let arg = &args[0]; + + match &arg.kind { + ast::InstArgKind::FuncRef(func) => { + Ok(SymbolRef::Func(ctx.func_ref(&mut fb.module_builder, func))) + } + + ast::InstArgKind::EmbedSymbol(sym) => Ok(SymbolRef::Embed(sym.clone())), + + ast::InstArgKind::Value(value) => match &value.kind { + ast::ValueKind::Global(name) => { + let Some(gv) = fb.module_builder.lookup_gv(&name.string) else { + ctx.errors.push(Error::Undefined( + crate::UndefinedKind::Value(name.string.clone()), + value.span, + )); + return Ok(SymbolRef::Global(ir::GlobalVariableRef::from_u32(0))); + }; + Ok(SymbolRef::Global(gv)) + } + + _ => Err(Box::new(Error::InstArgKindMismatch { + expected: "function name, embed symbol, or global value".into(), + actual: Some("value".into()), + span: arg.span, + })), + }, + + other => Err(Box::new(Error::InstArgKindMismatch { + expected: "function name, embed symbol, or global value".into(), + actual: Some( + match other { + ast::InstArgKind::Value(_) => "value", + ast::InstArgKind::Ty(_) => "type", + ast::InstArgKind::Block(_) => "block", + ast::InstArgKind::ValueBlockMap(_) => "(value, block)", + ast::InstArgKind::FuncRef(_) => "function name", + ast::InstArgKind::EmbedSymbol(_) => "embed symbol", + } + .into(), + ), + span: arg.span, + })), + } +} + fn build_gep( ctx: &mut BuildCtx, fb: &mut FunctionBuilder, diff --git a/crates/parser/src/inst/evm/mod.rs b/crates/parser/src/inst/evm/mod.rs index 8b27dc69..1878314f 100644 --- a/crates/parser/src/inst/evm/mod.rs +++ b/crates/parser/src/inst/evm/mod.rs @@ -10,6 +10,7 @@ super::impl_inst_build! {EvmAddMod, (lhs: ValueId, rhs: ValueId, modulus: ValueI super::impl_inst_build! {EvmMulMod, (lhs: ValueId, rhs: ValueId, modulus: ValueId)} super::impl_inst_build! {EvmExp, (base: ValueId, exponent: ValueId)} super::impl_inst_build! {EvmByte, (pos: ValueId, value: ValueId)} +super::impl_inst_build! {EvmClz, (word: ValueId)} super::impl_inst_build! {EvmKeccak256, (addr: ValueId, len: ValueId)} super::impl_inst_build! {EvmAddress, ()} super::impl_inst_build! {EvmBalance, (contract_addr: ValueId)} @@ -28,7 +29,7 @@ super::impl_inst_build! {EvmReturnDataSize, ()} super::impl_inst_build! {EvmReturnDataCopy, (dst_addr: ValueId, data_offset: ValueId, len: ValueId)} super::impl_inst_build! {EvmExtCodeHash, (ext_addr: ValueId)} super::impl_inst_build! {EvmBlockHash, (block_num: ValueId)} -super::impl_inst_build! {EvmCoinBase, (block_num: ValueId)} +super::impl_inst_build! {EvmCoinBase, ()} super::impl_inst_build! {EvmTimestamp, ()} super::impl_inst_build! {EvmNumber, ()} super::impl_inst_build! {EvmPrevRandao, ()} @@ -61,4 +62,3 @@ super::impl_inst_build! {EvmStaticCall, (gas: ValueId, ext_addr: ValueId, arg_ad super::impl_inst_build! {EvmRevert, (addr: ValueId, len: ValueId)} super::impl_inst_build! {EvmSelfDestruct, (addr: ValueId)} super::impl_inst_build! {EvmMalloc, (size: ValueId)} -super::impl_inst_build! {EvmContractSize, (contract: FuncRef)} diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 36028540..54847e68 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -18,11 +18,13 @@ use smol_str::SmolStr; pub mod ast; pub mod syntax; pub use error::{Error, UndefinedKind}; +pub use objects::parse_object_file; use sonatina_triple::{Architecture, TargetTriple}; pub use syntax::Span; mod error; mod inst; +pub mod objects; type Bimap = bimap::BiHashMap>; pub use pest::Parser as PestParser; @@ -36,7 +38,7 @@ pub fn parse_module(input: &str) -> Result> { let ast = ast::parse(input)?; let module_ctx = module_ctx_from_triple(ast.target.unwrap()); - let builder = ModuleBuilder::new(module_ctx); + let mut builder = ModuleBuilder::new(module_ctx); let mut ctx = BuildCtx::default(); @@ -78,28 +80,32 @@ pub fn parse_module(input: &str) -> Result> { builder.declare_function(sig).unwrap(); } - for func in ast.functions.iter() { - if !ctx.check_duplicated_func(&builder, &func.signature.name) { - continue; - } - - let sig = &func.signature; - let args = sig - .params - .iter() - .map(|decl| ctx.type_(&builder, &decl.1)) - .collect::>(); - - let ret_ty = sig - .ret_type - .as_ref() - .map(|t| ctx.type_(&builder, t)) - .unwrap_or(ir::Type::Unit); - let sig = Signature::new(&sig.name.name, sig.linkage, &args, ret_ty); + let func_order = ast + .functions + .iter() + .flat_map(|func| { + if !ctx.check_duplicated_func(&builder, &func.signature.name) { + return None; + } - // Safe to unwrap: function name checked for duplicate above - builder.declare_function(sig).unwrap(); - } + let sig = &func.signature; + let args = sig + .params + .iter() + .map(|decl| ctx.type_(&builder, &decl.1)) + .collect::>(); + + let ret_ty = sig + .ret_type + .as_ref() + .map(|t| ctx.type_(&builder, t)) + .unwrap_or(ir::Type::Unit); + let sig = Signature::new(&sig.name.name, sig.linkage, &args, ret_ty); + + // Safe to unwrap: function name checked for duplicate above + Some(builder.declare_function(sig).unwrap()) + }) + .collect(); let mut func_comments = SecondaryMap::default(); @@ -110,27 +116,46 @@ pub fn parse_module(input: &str) -> Result> { func_comments[id] = func.comments; } - if ctx.errors.is_empty() { - let module = builder.build(); - Ok(ParsedModule { - module, - debug: DebugInfo { - module_comments: ast.comments, - func_comments, - value_names: ctx.value_names, - }, - }) - } else { - Err(ctx.errors) + for obj_def in ast.objects { + let name = obj_def.object.name.0.clone(); + + if let Some(object) = ctx.lower_object(&builder, obj_def.object, obj_def.span) + && builder.declare_object(object).is_err() + { + ctx.errors + .push(Error::DuplicatedDeclaration(name, obj_def.span)); + } + } + + if !ctx.errors.is_empty() { + return Err(ctx.errors); } + + let module = builder.build(); + Ok(ParsedModule { + module, + debug: DebugInfo { + func_order, + module_comments: ast.comments, + func_comments, + value_names: ctx.value_names, + }, + }) } pub struct DebugInfo { + pub func_order: Vec, pub module_comments: Vec, pub func_comments: SecondaryMap>, pub value_names: FxHashMap>, } +impl DebugInfo { + pub fn value(&self, func: FuncRef, name: &str) -> Option { + self.value_names.get(&func)?.get_by_right(name).copied() + } +} + impl DebugProvider for DebugInfo { fn value_name(&self, _func: &Function, func_ref: FuncRef, value: ir::ValueId) -> Option<&str> { let names = self.value_names.get(&func_ref)?; @@ -438,6 +463,75 @@ impl BuildCtx { } } } + + fn lower_object( + &mut self, + mb: &ModuleBuilder, + object: crate::objects::Object, + span: Span, + ) -> Option { + let mut has_error = false; + + let mut sections = Vec::with_capacity(object.sections.len()); + for section in object.sections { + let mut directives = Vec::with_capacity(section.directives.len()); + for directive in section.directives { + let lowered = match directive { + crate::objects::Directive::Entry(name) => { + match mb.lookup_func(name.0.as_str()) { + Some(func) => ir::object::Directive::Entry(func), + None => { + self.errors.push(Error::Undefined( + UndefinedKind::Func(name.0.clone()), + span, + )); + has_error = true; + ir::object::Directive::Entry(FuncRef::from_u32(0)) + } + } + } + crate::objects::Directive::Include(name) => { + match mb.lookup_func(name.0.as_str()) { + Some(func) => ir::object::Directive::Include(func), + None => { + self.errors.push(Error::Undefined( + UndefinedKind::Func(name.0.clone()), + span, + )); + has_error = true; + ir::object::Directive::Include(FuncRef::from_u32(0)) + } + } + } + crate::objects::Directive::Data(name) => match mb.lookup_gv(name.0.as_str()) { + Some(gv) => ir::object::Directive::Data(gv), + None => { + self.errors + .push(Error::Undefined(UndefinedKind::Value(name.0.clone()), span)); + has_error = true; + ir::object::Directive::Data(GlobalVariableRef::from_u32(0)) + } + }, + crate::objects::Directive::Embed(embed) => ir::object::Directive::Embed(embed), + }; + directives.push(lowered); + } + + sections.push(ir::object::Section { + name: section.name, + directives, + }); + } + + if has_error { + return None; + } + + Some(ir::object::Object { + name: object.name, + sections, + }) + } } // TODO: Temporary stopgap solution. @@ -599,4 +693,86 @@ func public %simple(v0.i8) -> i8 { assert!(parse_module(s).is_ok()); } + + #[test] + fn test_sym_addr_sym_size_parse_and_write() { + let s = r#" +target = "evm-ethereum-london" + +global public i8 $foo = 0; + +func public %main() { + block0: + v0.i256 = sym_addr %main; + v1.i256 = sym_size %main; + v2.i256 = sym_addr $foo; + v3.i256 = sym_size $foo; + v4.i256 = sym_addr &runtime; + v5.i256 = sym_size &runtime; + return; +} +"#; + + let parsed = parse_module(s).unwrap(); + let mut w = ir::ir_writer::ModuleWriter::with_debug_provider(&parsed.module, &parsed.debug); + let printed = w.dump_string(); + assert!(printed.contains("sym_addr %main")); + assert!(printed.contains("sym_size %main")); + assert!(printed.contains("sym_addr $foo")); + assert!(printed.contains("sym_size $foo")); + assert!(printed.contains("sym_addr &runtime")); + assert!(printed.contains("sym_size &runtime")); + } + + #[test] + fn test_parse_module_includes_objects() { + let s = r#" +target = "evm-ethereum-london" + +func public %factory() { + block0: + return; +} + +object @Factory { + section runtime { entry %factory; } +} +"#; + + let parsed = parse_module(s).unwrap(); + assert_eq!(parsed.module.objects.len(), 1); + assert!(parsed.module.objects.contains_key("Factory")); + let mut w = ir::ir_writer::ModuleWriter::with_debug_provider(&parsed.module, &parsed.debug); + let printed = w.dump_string(); + assert!(printed.contains("object @Factory")); + } + + #[test] + fn test_parse_module_duplicate_objects_should_fail() { + let s = r#" +target = "evm-ethereum-london" + +func public %factory() { + block0: + return; +} + +object @Factory { + section runtime { entry %factory; } +} + +object @Factory { + section runtime { entry %factory; } +} +"#; + + let errors = match parse_module(s) { + Ok(_) => panic!("Expected duplicate object definition error"), + Err(errors) => errors, + }; + assert!(errors.iter().any(|e| matches!( + e, + Error::DuplicatedDeclaration(name, _) if name.as_str() == "Factory" + ))); + } } diff --git a/crates/parser/src/objects.rs b/crates/parser/src/objects.rs new file mode 100644 index 00000000..5f22beee --- /dev/null +++ b/crates/parser/src/objects.rs @@ -0,0 +1,226 @@ +use ir::object::{Embed, EmbedSymbol, ObjectName, SectionName, SectionRef}; +use pest::Parser as _; +use smol_str::SmolStr; + +use crate::{ + Error, + syntax::{FromSyntax, Node, Parser, Rule}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Object { + pub name: ObjectName, + pub sections: Vec
, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Section { + pub name: SectionName, + pub directives: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Directive { + Entry(FuncName), + Include(FuncName), + Data(DataName), + Embed(Embed), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FuncName(pub SmolStr); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DataName(pub SmolStr); + +pub fn parse_object_file(input: &str) -> Result, Vec> { + match Parser::parse(Rule::object_file, input) { + Err(err) => Err(vec![Error::SyntaxError(err)]), + Ok(mut pairs) => { + let pair = pairs.next().unwrap(); + debug_assert_eq!(pair.as_rule(), Rule::object_file); + let mut node = Node::new(pair); + + if node.errors.is_empty() { + Ok(node.multi(Rule::object_definition)) + } else { + Err(node.errors) + } + } + } +} + +impl FromSyntax for Object { + fn from_syntax(node: &mut Node) -> Self { + Self { + name: node.single(Rule::object_identifier), + sections: node.multi(Rule::object_section), + } + } +} + +impl FromSyntax for ObjectName { + fn from_syntax(node: &mut Node) -> Self { + Self(node.parse_str(Rule::object_name)) + } +} + +impl FromSyntax for Section { + fn from_syntax(node: &mut Node) -> Self { + Self { + name: node.single(Rule::section_name), + directives: node.multi(Rule::section_stmt), + } + } +} + +impl FromSyntax for SectionName { + fn from_syntax(node: &mut Node) -> Self { + Self(node.txt.into()) + } +} + +impl FromSyntax for Directive { + fn from_syntax(node: &mut Node) -> Self { + node.descend(); + match node.rule { + Rule::section_entry => Directive::Entry( + node.descend_into(Rule::function_identifier, |n| n.single(Rule::function_name)), + ), + Rule::section_include => Directive::Include( + node.descend_into(Rule::function_identifier, |n| n.single(Rule::function_name)), + ), + Rule::section_data => { + Directive::Data(node.descend_into(Rule::gv_identifier, |n| n.single(Rule::gv_name))) + } + Rule::section_embed => Directive::Embed(Embed::from_syntax(node)), + _ => unreachable!(), + } + } +} + +impl FromSyntax for FuncName { + fn from_syntax(node: &mut Node) -> Self { + Self(node.txt.into()) + } +} + +impl FromSyntax for DataName { + fn from_syntax(node: &mut Node) -> Self { + Self(node.txt.into()) + } +} + +impl FromSyntax for Embed { + fn from_syntax(node: &mut Node) -> Self { + Self { + source: node.single(Rule::section_ref), + as_symbol: node.single(Rule::embed_symbol), + } + } +} + +impl FromSyntax for EmbedSymbol { + fn from_syntax(node: &mut Node) -> Self { + Self(node.parse_str(Rule::embed_name)) + } +} + +impl FromSyntax for SectionRef { + fn from_syntax(node: &mut Node) -> Self { + node.descend(); + match node.rule { + Rule::local_section_ref => SectionRef::Local(node.single(Rule::section_name)), + Rule::external_section_ref => SectionRef::External { + object: node.single(Rule::object_identifier), + section: node.single(Rule::section_name), + }, + _ => unreachable!(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::object::{Embed, EmbedSymbol, ObjectName, SectionName, SectionRef}; + + #[test] + fn parse_object_file_smoke() { + let s = r#" +object @Factory { + section init { + entry %factory_init; + embed .runtime as &runtime; + } + section runtime { + entry %factory; # all functions reachable from %factory are implied + include %other_function; + data $foo; + embed @Child.init as &child_init; + } +} + +object @Child { + section runtime { entry %child_main; } + section init { entry %child_init; embed .runtime as &runtime; } +} +"#; + + let objects = parse_object_file(s).unwrap(); + assert_eq!( + objects, + vec![ + Object { + name: ObjectName("Factory".into()), + sections: vec![ + Section { + name: SectionName("init".into()), + directives: vec![ + Directive::Entry(FuncName("factory_init".into())), + Directive::Embed(Embed { + source: SectionRef::Local(SectionName("runtime".into())), + as_symbol: EmbedSymbol("runtime".into()), + }), + ], + }, + Section { + name: SectionName("runtime".into()), + directives: vec![ + Directive::Entry(FuncName("factory".into())), + Directive::Include(FuncName("other_function".into())), + Directive::Data(DataName("foo".into())), + Directive::Embed(Embed { + source: SectionRef::External { + object: ObjectName("Child".into()), + section: SectionName("init".into()), + }, + as_symbol: EmbedSymbol("child_init".into()), + }), + ], + }, + ], + }, + Object { + name: ObjectName("Child".into()), + sections: vec![ + Section { + name: SectionName("runtime".into()), + directives: vec![Directive::Entry(FuncName("child_main".into()))], + }, + Section { + name: SectionName("init".into()), + directives: vec![ + Directive::Entry(FuncName("child_init".into())), + Directive::Embed(Embed { + source: SectionRef::Local(SectionName("runtime".into())), + as_symbol: EmbedSymbol("runtime".into()), + }), + ], + }, + ], + }, + ], + ); + } +} diff --git a/crates/parser/src/sonatina.pest b/crates/parser/src/sonatina.pest index ef40acbe..d78d4858 100644 --- a/crates/parser/src/sonatina.pest +++ b/crates/parser/src/sonatina.pest @@ -1,4 +1,5 @@ -module = { SOI ~ NEWLINE* ~ target_specifier ~ (NEWLINE+ ~ declaration)* ~ (NEWLINE+ ~ function)* ~ NEWLINE* ~ EOI } +module = { SOI ~ NEWLINE* ~ target_specifier ~ (NEWLINE+ ~ declaration)* ~ (NEWLINE+ ~ function)* ~ (NEWLINE+ ~ object_definition)* ~ NEWLINE* ~ EOI } +object_file = { SOI ~ NEWLINE* ~ (object_definition ~ NEWLINE*)* ~ EOI } WHITESPACE = _{ " " | "\t" } COMMENT = { "#" ~ (!NEWLINE ~ ANY)* } @@ -33,7 +34,7 @@ function_body = _{ "{" ~ (NEWLINE+ ~ block?)* ~ "}" } block = { block_ident ~ ":" ~ (NEWLINE+ ~ stmt)* } _stmts = _{ (stmt ~ NEWLINE+)* } -gv_declaration = { "global" ~ linkage ~ gv_const? ~ type_name ~ gv_identifier ~ ("=" ~ gv_initializer)? ~ ";" } +gv_declaration = { "global" ~ linkage ~ gv_const? ~ type_name ~ gv_identifier ~ ("=" ~ gv_initializer)? ~ ";" } gv_identifier = ${ "$" ~ gv_name } gv_name = @{ ident_start_char ~ ident_body_char* } gv_const = { "const" } @@ -57,6 +58,25 @@ unit_type = { "unit" } value_declaration = ${ value_name ~ "." ~ type_name } +// Object +object_definition = { "object" ~ object_identifier ~ object_body } +object_identifier = ${ "@" ~ object_name } +object_name = @{ ident_start_char ~ ident_body_char* } +object_body = _{ "{" ~ (NEWLINE* ~ object_section ~ NEWLINE*)* ~ "}" } +object_section = { "section" ~ section_name ~ section_body } +section_name = @{ ident_start_char ~ ident_body_char* } +section_body = _{ "{" ~ (NEWLINE* ~ section_stmt ~ NEWLINE*)* ~ "}" } +section_stmt = { section_entry | section_include | section_data | section_embed } +section_entry = { "entry" ~ function_identifier ~ ";" } +section_include = { "include" ~ function_identifier ~ ";" } +section_data = { "data" ~ gv_identifier ~ ";" } +section_embed = { "embed" ~ section_ref ~ "as" ~ embed_symbol ~ ";" } +section_ref = { local_section_ref | external_section_ref } +local_section_ref = { "." ~ section_name } +external_section_ref = { object_identifier ~ "." ~ section_name } +embed_symbol = ${ "&" ~ embed_name } +embed_name = @{ ident_start_char ~ ident_body_char* } + // Stmts stmt = { (assign_stmt | inst_stmt) ~ ";" } assign_stmt = { value_declaration ~ "=" ~ inst } @@ -65,7 +85,7 @@ inst_stmt = { inst } inst = { inst_name ~ inst_arg* } inst_name = { inst_identifier } inst_identifier = @{ ident_start_char ~ ident_body_char* } -inst_arg = { value | type_name | block_ident | value_block_map | function_identifier} +inst_arg = { value | type_name | block_ident | value_block_map | function_identifier | embed_symbol } value_block_map = { "(" ~ value ~ block_ident ~ ")" } value = { value_name | imm_number | undef | global_value } diff --git a/crates/parser/test_files/syntax/module/func_type.ast.snap b/crates/parser/test_files/syntax/module/func_type.ast.snap index b0eed996..7e7d962b 100644 --- a/crates/parser/test_files/syntax/module/func_type.ast.snap +++ b/crates/parser/test_files/syntax/module/func_type.ast.snap @@ -92,5 +92,6 @@ Module { comments: [], }, ], + objects: [], comments: [], } diff --git a/crates/parser/test_files/syntax/module/global_variable.ast.snap b/crates/parser/test_files/syntax/module/global_variable.ast.snap index 71219a28..657a1d5c 100644 --- a/crates/parser/test_files/syntax/module/global_variable.ast.snap +++ b/crates/parser/test_files/syntax/module/global_variable.ast.snap @@ -440,5 +440,6 @@ Module { comments: [], }, ], + objects: [], comments: [], } diff --git a/crates/parser/test_files/syntax/module/newlines.ast.snap b/crates/parser/test_files/syntax/module/newlines.ast.snap index 70a82f01..7ee48b9b 100644 --- a/crates/parser/test_files/syntax/module/newlines.ast.snap +++ b/crates/parser/test_files/syntax/module/newlines.ast.snap @@ -107,5 +107,6 @@ Module { comments: [], }, ], + objects: [], comments: [], } diff --git a/crates/parser/test_files/syntax/module/recursive.ast.snap b/crates/parser/test_files/syntax/module/recursive.ast.snap index b84822cb..92b7c6a5 100644 --- a/crates/parser/test_files/syntax/module/recursive.ast.snap +++ b/crates/parser/test_files/syntax/module/recursive.ast.snap @@ -36,6 +36,7 @@ Module { }, ], functions: [], + objects: [], comments: [ "#! this is a module", "#! with recursive type", diff --git a/crates/parser/test_files/syntax/module/simple.ast.snap b/crates/parser/test_files/syntax/module/simple.ast.snap index d03b3e47..48807f36 100644 --- a/crates/parser/test_files/syntax/module/simple.ast.snap +++ b/crates/parser/test_files/syntax/module/simple.ast.snap @@ -1046,6 +1046,7 @@ Module { comments: [], }, ], + objects: [], comments: [ "#! this is a module", "#! with two functions", diff --git a/crates/triple/src/lib.rs b/crates/triple/src/lib.rs index b10add81..25b27777 100644 --- a/crates/triple/src/lib.rs +++ b/crates/triple/src/lib.rs @@ -125,8 +125,9 @@ impl OperatingSystem { "istanbul" => EvmVersion::Istanbul, "london" => EvmVersion::London, "paris" => EvmVersion::Paris, - "Shanghai" => EvmVersion::Shanghai, + "shanghai" => EvmVersion::Shanghai, "cancun" => EvmVersion::Cancun, + "osaka" => EvmVersion::Osaka, _ => return Err(InvalidTriple::OsNotSupported), }; Ok(Self::Evm(evm_version)) @@ -155,6 +156,7 @@ pub enum EvmVersion { Paris, Shanghai, Cancun, + Osaka, } #[derive(Debug, Clone, Error)] @@ -188,6 +190,7 @@ impl Display for EvmVersion { Self::Paris => write!(f, "paris"), Self::Shanghai => write!(f, "shanghai"), Self::Cancun => write!(f, "cancun"), + Self::Osaka => write!(f, "osaka"), } } } @@ -198,14 +201,17 @@ mod tests { #[test] fn test() { - let target = "evm-ethereum-istanbul"; - let triple = TargetTriple::parse(target).unwrap(); - - assert_eq!(triple.architecture, Architecture::Evm); - assert_eq!(triple.vendor, Vendor::Ethereum); - assert_eq!( - triple.operating_system, - OperatingSystem::Evm(EvmVersion::Istanbul) - ); + for (target, want) in [ + ("evm-ethereum-istanbul", EvmVersion::Istanbul), + ("evm-ethereum-shanghai", EvmVersion::Shanghai), + ("evm-ethereum-cancun", EvmVersion::Cancun), + ("evm-ethereum-osaka", EvmVersion::Osaka), + ] { + let triple = TargetTriple::parse(target).unwrap(); + assert_eq!(triple.architecture, Architecture::Evm); + assert_eq!(triple.vendor, Vendor::Ethereum); + assert_eq!(triple.operating_system, OperatingSystem::Evm(want)); + assert_eq!(target, triple.to_string()); + } } }