diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c9772e5..024c8246 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: # TODO: Remove the dependency on wasm-tools when have time. - name: Install wasm-tools run: | - cargo install wasm-tools --version 1.235.0 --locked + cargo install wasm-tools --version 1.244.0 --locked - name: Build run: cargo build --locked --verbose diff --git a/.gitignore b/.gitignore index 31a4b752..ac8b934c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,6 @@ fac_wirm/Cargo.lock **/out.** output -# Test wasm files -a.wasm +# Some helper files +test.wat test.wasm diff --git a/Cargo.lock b/Cargo.lock index 4d5463c2..f4bc4a09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -117,36 +126,36 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c088d3406f0c0252efa7445adfd2d05736bfb5218838f64eaf79d567077aed14" +checksum = "d6abf69c884fde2d9d4cc232a585fb18f16af3ae04c634315c84ebe158ded92d" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c03f887a763abb9c1dc08f722aa82b69067fda623b6f0273050f45f8b1a6776" +checksum = "263d31fcdf83a10267e8c38b53bc8f7688dfbc331267fd8fdf5b22e0dc47a55b" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206887a11a43f507fee320a218dc365980bfc42ec2696792079a9f8c9369e90" +checksum = "d459d5377c01c4472b71029caa2df41afaf47711676aa9b12d7414f15104637b" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac0790c83cfdab95709c5d0105fd888221e3af9049a7d7ec376ec901ab4e4dba" +checksum = "8283088d5823ba7856ab8d531b7c3654b24984748f9fd99dcf3210701fd1d065" dependencies = [ "serde", "serde_derive", @@ -154,9 +163,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a98aed2d262eda69310e84bae8e053ee4f17dbdd3347b8d9156aa618ba2de0a" +checksum = "7d3138316d8dd341d725d6ab1598750545c76ad32892837fde558edd68a01b43" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -181,9 +190,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6906852826988563e9b0a9232ad951f53a47aa41ffd02f8ac852d3f41aae836a" +checksum = "505cead19304a8dc8689e31b29038775c3f73f9d5ea7a5e33864437a1f46c6b6" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -194,24 +203,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a50105aab667b5cc845f2be37c78475d7cc127cd8ec0a31f7b2b71d526099a7" +checksum = "ce62ba94f570644ce7de6ed05bd39ca28936665dec10a2a1f6f2c531d6add45c" [[package]] name = "cranelift-control" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6adcc7aa7c0bc1727176a6f2d99c28a9e79a541ccd5ca911a0cb352da8befa36" +checksum = "db6cfe339689c3926412a7060ab00ef3b2b43d936b537e7a3f696121be9d0eaa" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981b56af777f9a34ea6dcce93255125776d391410c2a68b75bed5941b714fa15" +checksum = "625518090e912bdfe3c41464bf97ae421f6044d4ca0f5c3267dcacdb352b033d" dependencies = [ "cranelift-bitset", "serde", @@ -220,9 +229,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea982589684dfb71afecb9fc09555c3a266300a1162a60d7fa39d41a5705b1c" +checksum = "17f652d40ddf3afb55be64d8979d312b52b384a8cebbcde1dd1c2e32ebcd4466" dependencies = [ "cranelift-codegen", "log", @@ -232,15 +241,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0422686b22ed6a1f33cc40e3c43eb84b67155788568d1a5cac8439d3dca1783" +checksum = "9f512767e83015f4baf6e732cabca93cea82907e3ab237f826ef64f7ece75eb6" [[package]] name = "cranelift-native" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f697bbbe135c655ea1deb7af0bae4a5c4fae2c88fdfc0fa57b34ae58c91040" +checksum = "cb1ca6e4dca568ff988d367e4707be2362cee9782265b0a501eaf467ffd550a8" dependencies = [ "cranelift-codegen", "libc", @@ -249,9 +258,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.125.4" +version = "0.127.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718efe674f3df645462677e22a3128e890d88ba55821bb091083d257707be76c" +checksum = "97400ad8fbd3a434092fc0b486fa7784150b53187941d818b1087f3ac0a547f0" [[package]] name = "crc32fast" @@ -377,7 +386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -398,12 +407,82 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "fxprof-processed-profile" version = "0.8.1" @@ -490,6 +569,20 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -639,6 +732,22 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "pkg-config" version = "0.3.32" @@ -668,9 +777,9 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beafc309a2d35e16cc390644d88d14dfa45e45e15075ec6a9e37f6dfb43e926f" +checksum = "5de307c194cf6310d486dd5595ffc329c53b4acafd54e214752c1eb2e68be3a9" dependencies = [ "cranelift-bitset", "log", @@ -680,9 +789,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885fbb6c07454cfc8725a18a1da3cfc328ee8c53fb8d0671ea313edc8567947" +checksum = "99dca2747e910d10bafe911e172a1b35860268421c3ee5ddb7e16c35e0288b4a" dependencies = [ "proc-macro2", "quote", @@ -704,6 +813,21 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.11.0" @@ -771,7 +895,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -841,11 +965,24 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", "serde", + "unsafe-libyaml", ] [[package]] @@ -865,6 +1002,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" @@ -907,7 +1060,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -961,44 +1114,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "typenum" @@ -1024,6 +1175,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "uuid" version = "1.19.0" @@ -1100,24 +1257,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-compose" +version = "0.243.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af801b6f36459023eaec63fdbaedad2fd5a4ab7dc74ecc110a8b5d375c5775e4" +dependencies = [ + "anyhow", + "heck", + "im-rc", + "indexmap", + "log", + "petgraph", + "serde", + "serde_derive", + "serde_yaml", + "smallvec", + "wasm-encoder 0.243.0", + "wasmparser 0.243.0", + "wat", +] + [[package]] name = "wasm-encoder" -version = "0.239.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" dependencies = [ "leb128fmt", - "wasmparser 0.239.0", + "wasmparser 0.243.0", ] [[package]] name = "wasm-encoder" -version = "0.240.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser 0.240.0", + "wasmparser 0.244.0", ] [[package]] @@ -1132,9 +1310,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.239.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" dependencies = [ "bitflags", "hashbrown 0.15.5", @@ -1145,9 +1323,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.240.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", @@ -1169,31 +1347,31 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.239.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3981f3d51f39f24f5fc90f93049a90f08dbbca8deba602cd46bb8ca67a94718" +checksum = "eb2b6035559e146114c29a909a3232928ee488d6507a1504d8934e8607b36d7b" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.239.0", + "wasmparser 0.243.0", ] [[package]] name = "wasmprinter" -version = "0.240.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84d6e25c198da67d0150ee7c2c62d33d784f0a565d1e670bdf1eeccca8158bc" +checksum = "09390d7b2bd7b938e563e4bff10aa345ef2e27a3bc99135697514ef54495e68f" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.240.0", + "wasmparser 0.244.0", ] [[package]] name = "wasmtime" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81eafc07c867be94c47e0dc66355d9785e09107a18901f76a20701ba0663ad7" +checksum = "0702b64d4c3fe43ae4ce229e06af06a27783e48c519e68586d180717cdd24314" dependencies = [ "addr2line", "anyhow", @@ -1203,6 +1381,7 @@ dependencies = [ "cc", "cfg-if", "encoding_rs", + "futures", "fxprof-processed-profile", "gimli", "hashbrown 0.15.5", @@ -1224,8 +1403,10 @@ dependencies = [ "serde_json", "smallvec", "target-lexicon", - "wasm-encoder 0.239.0", - "wasmparser 0.239.0", + "tempfile", + "wasm-compose", + "wasm-encoder 0.243.0", + "wasmparser 0.243.0", "wasmtime-environ", "wasmtime-internal-cache", "wasmtime-internal-component-macro", @@ -1240,14 +1421,14 @@ dependencies = [ "wasmtime-internal-versioned-export-macros", "wasmtime-internal-winch", "wat", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "wasmtime-environ" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78587abe085a44a13c90fa16fea6db014e9883e627a7044d7f0cb397ad08d1da" +checksum = "3ffeb777a21965a85e4b1ce7b308c63ba130df91912096b49b95523bf3bdd2c7" dependencies = [ "anyhow", "cpp_demangle", @@ -1264,17 +1445,17 @@ dependencies = [ "serde_derive", "smallvec", "target-lexicon", - "wasm-encoder 0.239.0", - "wasmparser 0.239.0", - "wasmprinter 0.239.0", + "wasm-encoder 0.243.0", + "wasmparser 0.243.0", + "wasmprinter 0.243.0", "wasmtime-internal-component-util", ] [[package]] name = "wasmtime-internal-cache" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78fb9299e318b0af3efb75d88321515a20a5ccb040bcde1f0f7d46d656fa8fef" +checksum = "bf419cf9b748d443be7aead0fc85c36dcdfdb4bbbd8203b3cf47179d6de4b0dd" dependencies = [ "anyhow", "base64", @@ -1286,15 +1467,15 @@ dependencies = [ "serde_derive", "sha2", "toml", - "windows-sys 0.60.2", + "windows-sys", "zstd", ] [[package]] name = "wasmtime-internal-component-macro" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d843bb444f2d1509ea9304ad749242d1fa5de95cde67665bfcdcafa0f360925c" +checksum = "935bb8db1e2829bf26b80ed3daeed6cf9fc804f7002c90a8d17dbd5e93c69e0b" dependencies = [ "anyhow", "proc-macro2", @@ -1307,15 +1488,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801ee1a80ab66f065a88c6a62f2d495d5540d027b366757c6a53e9c42f153aef" +checksum = "52625d0c8fe2df1d7dd96d45d37dae8818c183c183a82b2368e3741ee5253859" [[package]] name = "wasmtime-internal-cranelift" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb50f1c50365c32e557266ca85acdf77696c44a3f98797ba6af58cebc6d6d1e" +checksum = "85da1ba5fee01a3ee21c4d0c8052cc9035388639fa091a969b534d4c6f8449d4" dependencies = [ "anyhow", "cfg-if", @@ -1332,7 +1513,7 @@ dependencies = [ "smallvec", "target-lexicon", "thiserror 2.0.17", - "wasmparser 0.239.0", + "wasmparser 0.243.0", "wasmtime-environ", "wasmtime-internal-math", "wasmtime-internal-unwinder", @@ -1341,9 +1522,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9308cdb17f8d51e3164185616d809e28c29a6515c03b9dd95c89436b71f6d154" +checksum = "a4c7de5a0872764c1ca640886af10a70cf7f8526386906245b43cdb345ece0e6" dependencies = [ "anyhow", "cc", @@ -1351,14 +1532,14 @@ dependencies = [ "libc", "rustix", "wasmtime-internal-versioned-export-macros", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "wasmtime-internal-jit-debug" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c9b63a22bf2a8b6a149a41c6768bc17a8b2e3288a249cb8216987fbd7128e81" +checksum = "160acd973d770d62bef1b2697d7fac83a8fe63ef966215e624382b2a9532bd58" dependencies = [ "cc", "object", @@ -1368,36 +1549,36 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb8e042b6e3de2f3d708279f89f50b4b9aa1b9bab177300cdffb0ffcd2816df5" +checksum = "cc57f590ba7ea967ea9e8c8560175c6926e5b15d11c29bbde3ad0013a29470eb" dependencies = [ "anyhow", "cfg-if", "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "wasmtime-internal-math" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1f0674f38cd7d014eb1a49ea1d1766cca1a64459e8856ee118a10005302e16" +checksum = "07612904518d47b677e8db67ca47c16d8c8cefb0099020729f886776950cb58b" dependencies = [ "libm", ] [[package]] name = "wasmtime-internal-slab" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb24b7535306713e7a250f8b71e35f05b6a5031bf9c3ed7330c308e899cbe7d3" +checksum = "bc83ff16531e1e1537e0de2b630af56a0a9e1fab864130c5b7e213da71783a0f" [[package]] name = "wasmtime-internal-unwinder" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21d5a80e2623a49cb8e8c419542337b8fe0260b162c40dcc201080a84cbe9b7c" +checksum = "47371d697244785e4bbb371229f9a2daa8628d1e03368ec895cf658370e1bf38" dependencies = [ "anyhow", "cfg-if", @@ -1408,9 +1589,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e277f734b9256359b21517c3b0c26a2a9de6c53a51b670ae55cdcde548bf4e" +checksum = "ad3cd4aff8f2e7ec658c7a0424b74ad88a6940505303fb0616323592a1c400a6" dependencies = [ "proc-macro2", "quote", @@ -1419,9 +1600,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-winch" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4dc9333737142f6ece4369c8bcdda03a11edbd43d8fbd3e15004c194b9b743" +checksum = "fc4d1e6a37b397aa0a16b3b7f3a48f317d73c81ac7801be1fa9a135f30c55421" dependencies = [ "anyhow", "cranelift-codegen", @@ -1429,7 +1610,7 @@ dependencies = [ "log", "object", "target-lexicon", - "wasmparser 0.239.0", + "wasmparser 0.243.0", "wasmtime-environ", "wasmtime-internal-cranelift", "winch-codegen", @@ -1437,9 +1618,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f758625553fe33fdce0713f63bb7784c4f5fecb7f7cd4813414519ec24b6a4c" +checksum = "73a5774737acccdc70ff27bbe9e09c45b156bd7792bb83906735a572ce122247" dependencies = [ "anyhow", "bitflags", @@ -1492,7 +1673,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1503,9 +1684,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "38.0.4" +version = "40.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0bb17ae9bf89ebc74512150e6ee0a27b1eac5ff3b54d8cec264f4b4255022d" +checksum = "dcca3ffe030cc6fe77f2055c19d850bcb2c2589fa6ddc7a8ebb446f7c2b1e715" dependencies = [ "anyhow", "cranelift-assembler-x64", @@ -1515,7 +1696,7 @@ dependencies = [ "smallvec", "target-lexicon", "thiserror 2.0.17", - "wasmparser 0.239.0", + "wasmparser 0.243.0", "wasmtime-environ", "wasmtime-internal-cranelift", "wasmtime-internal-math", @@ -1527,15 +1708,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1545,79 +1717,11 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] [[package]] name = "wirm" @@ -1627,9 +1731,9 @@ dependencies = [ "rayon", "serde_json", "tempfile", - "wasm-encoder 0.240.0", - "wasmparser 0.240.0", - "wasmprinter 0.240.0", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", + "wasmprinter 0.244.0", "wasmtime", "wat", ] @@ -1642,9 +1746,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wit-parser" -version = "0.239.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +checksum = "df983a8608e513d8997f435bb74207bf0933d0e49ca97aa9d8a6157164b9b7fc" dependencies = [ "anyhow", "id-arena", @@ -1655,7 +1759,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.239.0", + "wasmparser 0.243.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4c33c9cc..612f078b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,13 @@ log = "0.4.22" rayon = { version = "1.8", optional = true } serde_json = "1.0.121" tempfile = "3.10.1" -wasm-encoder = { version = "0.240.0", features = ["wasmparser"] } -wasmparser = "0.240.0" +wasm-encoder = { version = "0.244.0", features = ["wasmparser"] } +wasmparser = "0.244.0" [dev-dependencies] -wasmprinter = "0.240.0" -wat = "1.238.1" -wasmtime = "38.0.3" +wasmprinter = "0.244.0" +wat = "1.244.0" +wasmtime = "40.0.0" [features] default = [] diff --git a/README.md b/README.md index dafb976f..b7c91e42 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ `Wirm` is a light-weight, easy-to-use, Rust Library for performing WebAssembly transformations. It uses [wasmparser](https://docs.rs/wasmparser/0.214.0/wasmparser/) and [wasm_encoder](https://docs.rs/wasm-encoder/0.214.0/wasm_encoder/) to parse and encode Wasm components/modules and maintains its own Intermediate Representation. +`Wirm` also includes some handy visitors for walking Wasm components: +- [`walk_structural`](https://docs.rs/wirm/latest/wirm/ir/component/visitor/fn.walk_structural.html): to walk a Component using its structural (in-file) order. +- [`walk_topological`](https://docs.rs/wirm/latest/wirm/ir/component/visitor/fn.walk_topological.html): to walk a Component in topological (dependency) order (useful when traversing a component post-instrumentation). + +Several projects already leverage these visitors! +1. `Wirm` (that's right, we eat our own dogfood here): at encode time, `wirm` uses `walk_topological` to ensure that instrumented components get encoded without introducing forward references. +2. [`cviz`](https://github.com/cosmonic-labs/cviz): Uses `walk_structural` to discover the internal composition of a component. +3. [`splicer`](https://github.com/ejrgilbert/splicer): Uses `walk_structural` to split out subcomponents from their root. + ## Cargo Features ## ### Parallel Processing diff --git a/src/encode/component/assign.rs b/src/encode/component/assign.rs new file mode 100644 index 00000000..f57e0eb6 --- /dev/null +++ b/src/encode/component/assign.rs @@ -0,0 +1,319 @@ +use crate::ir::component::idx_spaces::{IndexSpaceOf, ScopeId, Space}; +use crate::ir::component::refs::{Depth, IndexedRef}; +use crate::ir::component::visitor::driver::{drive_event, VisitEvent}; +use crate::ir::component::visitor::utils::VisitCtxInner; +use crate::ir::component::visitor::{ComponentVisitor, ItemKind, VisitCtx}; +use crate::{Component, Module}; +use std::collections::HashMap; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentImport, ComponentInstance, + ComponentType, ComponentTypeDeclaration, CoreType, Instance, InstanceTypeDeclaration, + ModuleTypeDeclaration, SubType, +}; + +pub(crate) fn assign_indices<'ir>( + ctx: &mut VisitCtx<'ir>, + events: &Vec>, +) -> ActualIds { + let mut assigner = Assigner::default(); + for event in events { + drive_event(event, &mut assigner, ctx); + } + + assigner.ids +} + +#[derive(Default)] +struct Assigner { + ids: ActualIds, +} +impl Assigner { + /// When performing an ID _assignment_, we MUST consider whether the node we're assigning an ID for + /// has a nested scope! If it does, this node's ID lives in its parent index space. + fn assign_actual_id( + &mut self, + cx: &VisitCtx<'_>, + is_inner_node: bool, + space: &Space, + assumed_id: u32, + ) { + let nested = cx.inner.node_has_nested_scope.last().unwrap_or(&false); + let scope_id = if *nested && !is_inner_node { + cx.inner.scope_stack.scope_at_depth(&Depth::parent()) + } else { + cx.inner.scope_stack.curr_scope_id() + }; + self.ids + .assign_actual_id(scope_id, space, assumed_id as usize) + } +} +impl ComponentVisitor<'_> for Assigner { + fn exit_component(&mut self, cx: &VisitCtx<'_>, id: u32, component: &Component<'_>) { + self.assign_actual_id(cx, false, &component.index_space_of(), id) + } + fn visit_module(&mut self, cx: &VisitCtx<'_>, id: u32, module: &Module<'_>) { + self.assign_actual_id(cx, false, &module.index_space_of(), id) + } + fn visit_comp_type_decl( + &mut self, + cx: &VisitCtx<'_>, + _decl_idx: usize, + id: u32, + _parent: &ComponentType<'_>, + decl: &ComponentTypeDeclaration<'_>, + ) { + if matches!( + decl, + ComponentTypeDeclaration::CoreType(_) | ComponentTypeDeclaration::Type(_) + ) { + // this ID assignment will be handled by the type handler! + return; + } + self.assign_actual_id(cx, true, &decl.index_space_of(), id) + } + fn visit_inst_type_decl( + &mut self, + cx: &VisitCtx<'_>, + _decl_idx: usize, + id: u32, + _parent: &ComponentType<'_>, + decl: &InstanceTypeDeclaration<'_>, + ) { + if matches!( + decl, + InstanceTypeDeclaration::CoreType(_) | InstanceTypeDeclaration::Type(_) + ) { + // this ID assignment will be handled by the type handler! + return; + } + + self.assign_actual_id(cx, true, &decl.index_space_of(), id) + } + fn exit_comp_type(&mut self, cx: &VisitCtx<'_>, id: u32, ty: &ComponentType<'_>) { + // This node COULD have a nested scope (so pass false to is_inner_node) + self.assign_actual_id(cx, false, &ty.index_space_of(), id) + } + fn visit_comp_instance( + &mut self, + cx: &VisitCtx<'_>, + id: u32, + instance: &ComponentInstance<'_>, + ) { + self.assign_actual_id(cx, true, &instance.index_space_of(), id) + } + fn visit_canon( + &mut self, + cx: &VisitCtx<'_>, + _kind: ItemKind, + id: u32, + canon: &CanonicalFunction, + ) { + self.assign_actual_id(cx, true, &canon.index_space_of(), id) + } + fn visit_alias( + &mut self, + cx: &VisitCtx<'_>, + _kind: ItemKind, + id: u32, + alias: &ComponentAlias<'_>, + ) { + self.assign_actual_id(cx, true, &alias.index_space_of(), id) + } + fn visit_comp_import( + &mut self, + cx: &VisitCtx<'_>, + _kind: ItemKind, + id: u32, + import: &ComponentImport<'_>, + ) { + self.assign_actual_id(cx, true, &import.index_space_of(), id) + } + fn visit_comp_export( + &mut self, + cx: &VisitCtx<'_>, + _kind: ItemKind, + id: u32, + export: &ComponentExport<'_>, + ) { + self.assign_actual_id(cx, true, &export.index_space_of(), id) + } + fn visit_module_type_decl( + &mut self, + cx: &VisitCtx<'_>, + _decl_idx: usize, + id: u32, + _parent: &CoreType<'_>, + decl: &ModuleTypeDeclaration<'_>, + ) { + self.assign_actual_id(cx, true, &decl.index_space_of(), id) + } + fn enter_core_rec_group( + &mut self, + cx: &VisitCtx<'_>, + _count: usize, + _core_type: &CoreType<'_>, + ) { + // just need to make sure there's a scope built :) + // this is relevant for: (component (core rec) ) + self.ids.add_scope(cx.inner.scope_stack.curr_scope_id()); + } + fn visit_core_subtype(&mut self, cx: &VisitCtx<'_>, id: u32, subtype: &SubType) { + self.assign_actual_id(cx, true, &subtype.index_space_of(), id) + } + fn exit_core_type(&mut self, cx: &VisitCtx<'_>, id: u32, core_type: &CoreType<'_>) { + // This node COULD have a nested scope (so pass false to is_inner_node) + self.assign_actual_id(cx, false, &core_type.index_space_of(), id) + } + fn visit_core_instance(&mut self, cx: &VisitCtx<'_>, id: u32, inst: &Instance<'_>) { + self.assign_actual_id(cx, true, &inst.index_space_of(), id) + } +} + +#[derive(Clone, Default)] +pub struct ActualIds { + scopes: HashMap, +} +impl ActualIds { + pub fn add_scope(&mut self, id: ScopeId) { + self.scopes.entry(id).or_default(); + } + pub fn get_scope(&self, id: ScopeId) -> Option<&IdsForScope> { + self.scopes.get(&id) + } + pub fn assign_actual_id(&mut self, id: ScopeId, space: &Space, assumed_id: usize) { + let ids = self.scopes.entry(id).or_default(); + ids.assign_actual_id(space, assumed_id) + } + + /// Looking up a reference should always be relative to the scope of the node that + /// contained the reference! No need to think about whether the node has a nested scope. + pub fn lookup_actual_id_or_panic(&self, cx: &VisitCtxInner, r: &IndexedRef) -> usize { + let scope_id = cx.scope_stack.scope_at_depth(&r.depth); + let ids = self.scopes.get(&scope_id).unwrap_or_else(|| { + panic!("Internal error: Attempted to assign a non-existent scope: {scope_id}") + }); + ids.lookup_actual_id_or_panic(r) + } +} + +/// This is used at encode time. It tracks the actual ID that has been assigned +/// to some item by allowing for lookup of the assumed ID: `assumed_id -> actual_id` +/// This is important since we know what ID should be associated with something only at encode time, +/// since instrumentation has finished at that point and encoding of component items +/// can be done out-of-order to satisfy possible forward-references injected during instrumentation. +#[derive(Clone, Default)] +pub struct IdsForScope { + // Component-level spaces + comp: IdTracker, + comp_func: IdTracker, + comp_val: IdTracker, + comp_type: IdTracker, + comp_inst: IdTracker, + + // Core space (added by component model) + core_inst: IdTracker, // (these are module instances) + module: IdTracker, + + // Core spaces that exist at the component-level + core_type: IdTracker, + core_func: IdTracker, // these are canonical function decls! + core_memory: IdTracker, + core_table: IdTracker, + core_global: IdTracker, + core_tag: IdTracker, +} +impl IdsForScope { + pub fn assign_actual_id(&mut self, space: &Space, assumed_id: usize) { + if let Some(space) = self.get_space_mut(space) { + space.assign_actual_id(assumed_id); + } + } + + fn get_space_mut(&mut self, space: &Space) -> Option<&mut IdTracker> { + let s = match space { + Space::Comp => &mut self.comp, + Space::CompFunc => &mut self.comp_func, + Space::CompVal => &mut self.comp_val, + Space::CompType => &mut self.comp_type, + Space::CompInst => &mut self.comp_inst, + Space::CoreInst => &mut self.core_inst, + Space::CoreModule => &mut self.module, + Space::CoreType => &mut self.core_type, + Space::CoreFunc => &mut self.core_func, + Space::CoreMemory => &mut self.core_memory, + Space::CoreTable => &mut self.core_table, + Space::CoreGlobal => &mut self.core_global, + Space::CoreTag => &mut self.core_tag, + Space::NA => return None, + }; + Some(s) + } + + pub(crate) fn get_space(&self, space: &Space) -> Option<&IdTracker> { + let s = match space { + Space::Comp => &self.comp, + Space::CompFunc => &self.comp_func, + Space::CompVal => &self.comp_val, + Space::CompType => &self.comp_type, + Space::CompInst => &self.comp_inst, + Space::CoreInst => &self.core_inst, + Space::CoreModule => &self.module, + Space::CoreType => &self.core_type, + Space::CoreFunc => &self.core_func, + Space::CoreMemory => &self.core_memory, + Space::CoreTable => &self.core_table, + Space::CoreGlobal => &self.core_global, + Space::CoreTag => &self.core_tag, + Space::NA => return None, + }; + Some(s) + } + + pub(crate) fn lookup_actual_id_or_panic(&self, r: &IndexedRef) -> usize { + *self + .get_space(&r.space) + .and_then(|space| space.lookup_actual_id(r.index as usize)) + .unwrap_or_else(|| { + panic!( + "[{:?}] Internal error: Can't find assumed id {} in id-tracker", + r.space, r.index + ) + }) + } +} + +#[derive(Clone, Default)] +pub(crate) struct IdTracker { + /// This is used at encode time. It tracks the actual ID that has been assigned + /// to some item by allowing for lookup of the assumed ID: `assumed_id -> actual_id` + /// This is important since we know what ID should be associated with something only at encode time, + /// since instrumentation has finished at that point and encoding of component items + /// can be done out-of-order to satisfy possible forward-references injected during instrumentation. + actual_ids: HashMap, + + /// This is the current ID that we've reached associated with this index space. + current_id: usize, +} +impl IdTracker { + pub fn curr_id(&self) -> usize { + // This returns the ID that we've reached thus far while encoding + self.current_id + } + + pub fn assign_actual_id(&mut self, assumed_id: usize) { + let id = self.curr_id(); + + self.actual_ids.insert(assumed_id, id); + self.next(); + } + + fn next(&mut self) -> usize { + let curr = self.current_id; + self.current_id += 1; + curr + } + + pub fn lookup_actual_id(&self, id: usize) -> Option<&usize> { + self.actual_ids.get(&id) + } +} diff --git a/src/encode/component/encode.rs b/src/encode/component/encode.rs new file mode 100644 index 00000000..37492791 --- /dev/null +++ b/src/encode/component/encode.rs @@ -0,0 +1,1421 @@ +use crate::encode::component::assign::ActualIds; +use crate::encode::component::fix_indices::FixIndices; +use crate::error::Error; +use crate::error::Error::Multiple; +use crate::ir::component::idx_spaces::Space; +use crate::ir::component::visitor::driver::{drive_event, VisitEvent}; +use crate::ir::component::visitor::utils::VisitCtxInner; +use crate::ir::component::visitor::{ComponentVisitor, ItemKind, VisitCtx}; +use crate::ir::component::Names; +use crate::ir::types; +use crate::ir::types::CustomSection; +use crate::{Component, Module}; +use wasm_encoder::reencode::{Reencode, ReencodeComponent, RoundtripReencoder}; +use wasm_encoder::{ + Alias, ComponentAliasSection, ComponentDefinedTypeEncoder, ComponentFuncTypeEncoder, + ComponentTypeSection, CoreTypeEncoder, CoreTypeSection, ModuleArg, ModuleSection, NameMap, + NestedComponentSection, +}; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentDefinedType, ComponentExport, ComponentFuncType, + ComponentImport, ComponentInstance, ComponentStartFunction, ComponentType, + ComponentTypeDeclaration, CoreType, Instance, InstanceTypeDeclaration, ModuleTypeDeclaration, + RecGroup, SubType, +}; + +pub(crate) fn encode_internal<'ir>( + ids: &ActualIds, + ctx: &mut VisitCtx<'ir>, + events: &Vec>, +) -> types::Result { + let mut encoder = Encoder::new(ids); + for event in events { + drive_event(event, &mut encoder, ctx); + } + if !encoder.errors.is_empty() { + return Err(Multiple(encoder.errors)); + } + + let encoded_comp = encoder.comp_stack.pop().unwrap().component; + debug_assert!(encoder.comp_stack.is_empty()); + + Ok(encoded_comp) +} + +struct Encoder<'a> { + reencode: RoundtripReencoder, + + ids: &'a ActualIds, + comp_stack: Vec, + + // recursive def items! + type_stack: Vec, + errors: Vec, +} +impl<'a> Encoder<'a> { + pub fn new(ids: &'a ActualIds) -> Encoder<'a> { + Self { + reencode: RoundtripReencoder, + ids, + comp_stack: vec![], + + type_stack: vec![], + errors: vec![], + } + } + fn curr_comp_mut(&mut self) -> &mut wasm_encoder::Component { + &mut self.comp_stack.last_mut().unwrap().component + } + fn handle_enter_comp(&mut self) { + self.comp_stack.push(CompFrame::new()); + } + fn handle_exit_comp( + enc_comp: &mut wasm_encoder::Component, + comp: &Component<'_>, + cx: &VisitCtx<'_>, + ids: &ActualIds, + ) { + // Handle the name section + let mut name_sec = wasm_encoder::ComponentNameSection::new(); + + if let Some(comp_name) = &comp.component_name { + name_sec.component(comp_name); + } + + name_sec.core_funcs(&encode_name_section( + &comp.core_func_names, + Space::CoreFunc, + cx, + ids, + )); + name_sec.core_tables(&encode_name_section( + &comp.table_names, + Space::CoreTable, + cx, + ids, + )); + name_sec.core_memories(&encode_name_section( + &comp.memory_names, + Space::CoreMemory, + cx, + ids, + )); + name_sec.core_tags(&encode_name_section( + &comp.tag_names, + Space::CoreTag, + cx, + ids, + )); + name_sec.core_globals(&encode_name_section( + &comp.global_names, + Space::CoreGlobal, + cx, + ids, + )); + name_sec.core_types(&encode_name_section( + &comp.core_type_names, + Space::CoreType, + cx, + ids, + )); + name_sec.core_modules(&encode_name_section( + &comp.module_names, + Space::CoreModule, + cx, + ids, + )); + name_sec.core_instances(&encode_name_section( + &comp.core_instances_names, + Space::CoreInst, + cx, + ids, + )); + name_sec.funcs(&encode_name_section( + &comp.func_names, + Space::CompFunc, + cx, + ids, + )); + name_sec.values(&encode_name_section( + &comp.value_names, + Space::CompVal, + cx, + ids, + )); + name_sec.types(&encode_name_section( + &comp.type_names, + Space::CompType, + cx, + ids, + )); + name_sec.components(&encode_name_section( + &comp.components_names, + Space::Comp, + cx, + ids, + )); + name_sec.instances(&encode_name_section( + &comp.instance_names, + Space::CompInst, + cx, + ids, + )); + + // Add the name section back to the component + enc_comp.section(&name_sec); + } +} +impl ComponentVisitor<'_> for Encoder<'_> { + fn enter_root_component(&mut self, _cx: &VisitCtx<'_>, _component: &Component<'_>) { + self.handle_enter_comp(); + } + fn exit_root_component(&mut self, cx: &VisitCtx<'_>, comp: &Component<'_>) { + Self::handle_exit_comp( + &mut self.comp_stack.last_mut().unwrap().component, + comp, + cx, + self.ids, + ); + } + fn enter_component(&mut self, _cx: &VisitCtx<'_>, _id: u32, _comp: &Component<'_>) { + self.handle_enter_comp(); + } + fn exit_component(&mut self, cx: &VisitCtx<'_>, _id: u32, comp: &Component<'_>) { + let nested_comp = &mut self.comp_stack.pop().unwrap().component; + Self::handle_exit_comp(nested_comp, comp, cx, self.ids); + + self.curr_comp_mut() + .section(&NestedComponentSection(nested_comp)); + } + fn visit_module(&mut self, _: &VisitCtx<'_>, _id: u32, module: &Module<'_>) { + if let Err(e) = encode_module_section(module, self.curr_comp_mut()) { + self.errors.push(e); + } + } + fn enter_comp_type(&mut self, cx: &VisitCtx<'_>, _id: u32, ty: &ComponentType<'_>) { + // always make sure the component type section exists! + let section = curr_comp_ty_sect_mut(&mut self.comp_stack); + match self.type_stack.last_mut() { + Some(TypeFrame::Inst { ty: ity }) => { + if let Some(new_frame) = + encode_comp_ty_in_inst_ty(ty, ity, &mut self.reencode, self.ids, &cx.inner) + { + self.type_stack.push(new_frame) + } + return; + } + Some(TypeFrame::Comp { ty: cty }) => { + if let Some(new_frame) = + encode_comp_ty_in_comp_ty(ty, cty, &mut self.reencode, self.ids, &cx.inner) + { + self.type_stack.push(new_frame) + } + return; + } + Some(TypeFrame::Mod { .. }) => unreachable!(), + None => {} + } + + match ty { + ComponentType::Defined(comp_ty) => { + encode_comp_defined_ty( + comp_ty, + section.defined_type(), + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + ComponentType::Func(func_ty) => { + encode_comp_func_ty( + func_ty, + section.function(), + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + ComponentType::Resource { rep, dtor } => { + section.resource(self.reencode.val_type(*rep).unwrap(), *dtor); + } + ComponentType::Component(_) | ComponentType::Instance(_) => {} + } + if let Some(new_frame) = frame_for_comp_ty(ty) { + self.type_stack.push(new_frame); + } + } + fn visit_comp_type_decl( + &mut self, + cx: &VisitCtx<'_>, + _: usize, + _: u32, + _: &ComponentType<'_>, + decl: &ComponentTypeDeclaration<'_>, + ) { + match self.type_stack.last_mut().unwrap() { + TypeFrame::Comp { ty } => { + encode_comp_ty_decl(decl, ty, &mut self.reencode, self.ids, &cx.inner); + } + TypeFrame::Inst { .. } | TypeFrame::Mod { .. } => unreachable!(), + } + } + fn visit_inst_type_decl( + &mut self, + cx: &VisitCtx<'_>, + _: usize, + _: u32, + _: &ComponentType<'_>, + decl: &InstanceTypeDeclaration<'_>, + ) { + match self.type_stack.last_mut().unwrap() { + TypeFrame::Inst { ty } => { + encode_inst_ty_decl(decl, ty, &mut self.reencode, self.ids, &cx.inner); + } + TypeFrame::Comp { .. } | TypeFrame::Mod { .. } => unreachable!(), + } + } + fn exit_comp_type(&mut self, _: &VisitCtx<'_>, _: u32, ty: &ComponentType<'_>) { + let CompFrame { + comp_type_section, + component, + .. + } = curr_comp_frame(&mut self.comp_stack); + let section = comp_type_section.as_mut().unwrap(); + if frame_for_comp_ty(ty).is_some() { + match self.type_stack.pop().unwrap() { + TypeFrame::Comp { ty } => { + if let Some(parent) = self.type_stack.last_mut() { + parent.attach_component_type(&ty); + } else { + section.component(&ty); + } + } + TypeFrame::Inst { ty } => { + if let Some(parent) = self.type_stack.last_mut() { + parent.attach_instance_type(&ty); + } else { + // top-level type, attach to comp_type_section + section.instance(&ty); + } + } + TypeFrame::Mod { .. } => unreachable!(), + } + } + + if self.type_stack.is_empty() { + component.section(section); + *comp_type_section = None; + } + } + fn visit_comp_instance(&mut self, cx: &VisitCtx<'_>, _: u32, instance: &ComponentInstance<'_>) { + encode_comp_inst_section( + instance, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn visit_canon(&mut self, cx: &VisitCtx<'_>, _: ItemKind, _: u32, canon: &CanonicalFunction) { + encode_canon_section( + canon, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn visit_alias(&mut self, cx: &VisitCtx<'_>, _: ItemKind, _: u32, alias: &ComponentAlias<'_>) { + encode_alias_section( + alias, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn visit_comp_import( + &mut self, + cx: &VisitCtx<'_>, + _: ItemKind, + _: u32, + import: &ComponentImport<'_>, + ) { + encode_comp_import_section( + import, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn visit_comp_export( + &mut self, + cx: &VisitCtx<'_>, + _: ItemKind, + _: u32, + export: &ComponentExport<'_>, + ) { + encode_comp_export_section( + export, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn enter_core_rec_group(&mut self, cx: &VisitCtx<'_>, _: usize, ty: &CoreType<'_>) { + // always make sure the core type section exists! + let section = curr_core_ty_sect_mut(&mut self.comp_stack); + match self.type_stack.last_mut() { + Some(TypeFrame::Inst { ty: ity }) => { + if let Some(new_frame) = + encode_core_ty_from_inst_ty(ty, ity, &mut self.reencode, self.ids, &cx.inner) + { + self.type_stack.push(new_frame) + } + return; + } + Some(TypeFrame::Comp { ty: cty }) => { + if let Some(new_frame) = + encode_core_ty_from_comp_ty(ty, cty, &mut self.reencode, self.ids, &cx.inner) + { + self.type_stack.push(new_frame) + } + return; + } + Some(TypeFrame::Mod { .. }) => unreachable!(), + None => {} + } + + match ty { + CoreType::Rec(group) => { + encode_rec_group_in_core_ty( + group, + section, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + _ => unreachable!(), + } + } + fn exit_core_rec_group(&mut self, _: &VisitCtx<'_>) { + let CompFrame { + core_type_section, + component, + .. + } = curr_comp_frame(&mut self.comp_stack); + let section = core_type_section.as_mut().unwrap(); + + component.section(section); + *core_type_section = None; + } + fn enter_core_type(&mut self, cx: &VisitCtx<'_>, _id: u32, ty: &CoreType<'_>) { + // always make sure the core type section exists! + curr_core_ty_sect_mut(&mut self.comp_stack); + match self.type_stack.last_mut() { + Some(TypeFrame::Inst { ty: ity }) => { + if let Some(new_frame) = + encode_core_ty_from_inst_ty(ty, ity, &mut self.reencode, self.ids, &cx.inner) + { + self.type_stack.push(new_frame) + } + return; + } + Some(TypeFrame::Comp { ty: cty }) => { + if let Some(new_frame) = + encode_core_ty_from_comp_ty(ty, cty, &mut self.reencode, self.ids, &cx.inner) + { + self.type_stack.push(new_frame) + } + return; + } + Some(TypeFrame::Mod { .. }) => unreachable!(), + None => {} + } + + if let Some(new_frame) = frame_for_core_ty(ty) { + self.type_stack.push(new_frame) + } + } + fn visit_module_type_decl( + &mut self, + cx: &VisitCtx<'_>, + _decl_idx: usize, + _id: u32, + _parent: &CoreType<'_>, + decl: &ModuleTypeDeclaration<'_>, + ) { + match self.type_stack.last_mut().unwrap() { + TypeFrame::Mod { ty } => { + encode_module_type_decl(decl, ty, &mut self.reencode, self.ids, &cx.inner); + } + TypeFrame::Comp { .. } | TypeFrame::Inst { .. } => unreachable!(), + } + } + fn exit_core_type(&mut self, _cx: &VisitCtx<'_>, _id: u32, ty: &CoreType<'_>) { + let CompFrame { + core_type_section, + component, + .. + } = curr_comp_frame(&mut self.comp_stack); + let section = core_type_section.as_mut().unwrap(); + if frame_for_core_ty(ty).is_some() { + match self.type_stack.pop().unwrap() { + TypeFrame::Mod { ty } => { + if let Some(TypeFrame::Comp { ty: parent }) = self.type_stack.last_mut() { + parent.core_type().module(&ty); + } else if let Some(TypeFrame::Inst { ty: parent }) = self.type_stack.last_mut() + { + parent.core_type().module(&ty); + } else { + section.ty().module(&ty); + } + } + TypeFrame::Comp { .. } | TypeFrame::Inst { .. } => unreachable!(), + } + } + + if self.type_stack.is_empty() { + component.section(section); + *core_type_section = None; + } + } + fn visit_core_instance(&mut self, cx: &VisitCtx<'_>, _: u32, inst: &Instance<'_>) { + encode_inst_section( + inst, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn visit_custom_section(&mut self, cx: &VisitCtx<'_>, sect: &CustomSection<'_>) { + encode_custom_section( + sect, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } + fn visit_start_section(&mut self, cx: &VisitCtx<'_>, start: &ComponentStartFunction) { + encode_start_section( + start, + &mut self.comp_stack.last_mut().unwrap().component, + &mut self.reencode, + self.ids, + &cx.inner, + ); + } +} +struct CompFrame { + component: wasm_encoder::Component, + comp_type_section: Option, + core_type_section: Option, +} +impl CompFrame { + fn new() -> Self { + Self { + component: wasm_encoder::Component::new(), + comp_type_section: None, + core_type_section: None, + } + } +} + +enum TypeFrame { + Comp { ty: wasm_encoder::ComponentType }, + Inst { ty: wasm_encoder::InstanceType }, + Mod { ty: wasm_encoder::ModuleType }, +} +impl TypeFrame { + fn attach_component_type(&mut self, ty: &wasm_encoder::ComponentType) { + match self { + TypeFrame::Comp { ty: parent } => { + parent.ty().component(ty); // attach to enclosing ComponentType + } + TypeFrame::Inst { ty: parent } => { + parent.ty().component(ty); // attach to parent instance + } + _ => unreachable!(), + } + } + + fn attach_instance_type(&mut self, ty: &wasm_encoder::InstanceType) { + match self { + TypeFrame::Comp { ty: parent } => { + parent.ty().instance(ty); // attach to enclosing ComponentType + } + TypeFrame::Inst { ty: parent } => { + parent.ty().instance(ty); // attach to parent instance + } + _ => unreachable!(), + } + } +} +fn frame_for_comp_ty(ty: &ComponentType) -> Option { + match ty { + ComponentType::Component(_) => Some(TypeFrame::Comp { + ty: wasm_encoder::ComponentType::new(), + }), + ComponentType::Instance(_) => Some(TypeFrame::Inst { + ty: wasm_encoder::InstanceType::new(), + }), + ComponentType::Defined(_) | ComponentType::Func(_) | ComponentType::Resource { .. } => None, + } +} +fn frame_for_core_ty(core_ty: &CoreType) -> Option { + match core_ty { + CoreType::Rec(_) => None, + CoreType::Module(_) => Some(TypeFrame::Mod { + ty: wasm_encoder::ModuleType::new(), + }), + } +} + +/// Reworking the names assigned to nodes are ALWAYS the nodes WITHIN a component. +/// Since this is true, we don't have to worry about whether the node has a nested scope! +/// We're already looking up the node's ID within its containing scope :) +fn encode_name_section(names: &Names, space: Space, cx: &VisitCtx, ids: &ActualIds) -> NameMap { + let curr_scope_id = cx.inner.scope_stack.curr_scope_id(); + let actual_ids = if let Some(scope) = ids.get_scope(curr_scope_id) { + // Sometimes the scope doesn't get created (if there are no immediate IR nodes that would populate + // its own scope) + // For example: (component), OR (component (component . . . )) + scope.get_space(&space) + } else { + None + }; + + let mut enc_names = NameMap::default(); + + for (id, name) in names.names.iter() { + let actual_id = if let Some(actual_ids) = actual_ids { + *actual_ids + .lookup_actual_id(*id as usize) + .unwrap_or_else(|| { + panic!("Could not find actual ID for {id} during {space:?} name encoding") + }) as u32 + } else { + *id + }; + enc_names.append(actual_id, name) + } + enc_names +} + +fn encode_module_section( + module: &Module, + component: &mut wasm_encoder::Component, +) -> types::Result<()> { + component.section(&ModuleSection(&module.encode_internal(false)?.0)); + Ok(()) +} +fn encode_comp_inst_section( + instance: &ComponentInstance, + component: &mut wasm_encoder::Component, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let comp_inst = instance.fix(ids, cx); + let mut instances = wasm_encoder::ComponentInstanceSection::new(); + + match comp_inst { + ComponentInstance::Instantiate { + component_index, + args, + } => { + instances.instantiate( + component_index, + args.iter().map(|arg| { + ( + arg.name, + reencode.component_export_kind(arg.kind), + arg.index, + ) + }), + ); + } + ComponentInstance::FromExports(export) => { + instances.export_items(export.iter().map(|value| { + ( + value.name.0, + reencode.component_export_kind(value.kind), + value.index, + ) + })); + } + } + + component.section(&instances); +} +fn encode_canon_section( + c: &CanonicalFunction, + component: &mut wasm_encoder::Component, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let canon = c.fix(ids, cx); + let mut canon_sec = wasm_encoder::CanonicalFunctionSection::new(); + + match canon { + CanonicalFunction::Lift { + core_func_index, + type_index, + options, + } => { + canon_sec.lift( + core_func_index, + type_index, + options.iter().map(|canon| { + do_reencode( + *canon, + RoundtripReencoder::canonical_option, + reencode, + "canonical option", + ) + }), + ); + } + CanonicalFunction::Lower { + func_index, + options, + } => { + canon_sec.lower( + func_index, + options.iter().map(|canon| { + do_reencode( + *canon, + RoundtripReencoder::canonical_option, + reencode, + "canonical option", + ) + }), + ); + } + CanonicalFunction::ResourceNew { resource } => { + canon_sec.resource_new(resource); + } + CanonicalFunction::ResourceDrop { resource } => { + canon_sec.resource_drop(resource); + } + CanonicalFunction::ResourceRep { resource } => { + canon_sec.resource_rep(resource); + } + CanonicalFunction::ResourceDropAsync { resource } => { + canon_sec.resource_drop_async(resource); + } + CanonicalFunction::ThreadAvailableParallelism => { + canon_sec.thread_available_parallelism(); + } + CanonicalFunction::BackpressureDec => { + canon_sec.backpressure_dec(); + } + CanonicalFunction::BackpressureInc => { + canon_sec.backpressure_inc(); + } + CanonicalFunction::TaskReturn { result, options } => { + canon_sec.task_return( + result.map(|v| v.into()), + options.iter().map(|opt| (*opt).into()), + ); + } + CanonicalFunction::WaitableSetNew => { + canon_sec.waitable_set_new(); + } + CanonicalFunction::WaitableSetWait { + cancellable, + memory, + } => { + // NOTE: There's a discrepancy in naming here. `cancellable` refers to the same bit as `async_` + canon_sec.waitable_set_wait(cancellable, memory); + } + CanonicalFunction::WaitableSetPoll { + cancellable, + memory, + } => { + // NOTE: There's a discrepancy in naming here. `cancellable` refers to the same bit as `async_` + canon_sec.waitable_set_poll(cancellable, memory); + } + CanonicalFunction::WaitableSetDrop => { + canon_sec.waitable_set_drop(); + } + CanonicalFunction::WaitableJoin => { + canon_sec.waitable_join(); + } + CanonicalFunction::SubtaskDrop => { + canon_sec.subtask_drop(); + } + CanonicalFunction::StreamNew { ty } => { + canon_sec.stream_new(ty); + } + CanonicalFunction::StreamRead { ty, options } => { + canon_sec.stream_read(ty, options.iter().map(|opt| (*opt).into())); + } + CanonicalFunction::StreamWrite { ty, options } => { + canon_sec.stream_write(ty, options.iter().map(|opt| (*opt).into())); + } + CanonicalFunction::StreamCancelRead { async_, ty } => { + canon_sec.stream_cancel_read(ty, async_); + } + CanonicalFunction::StreamCancelWrite { async_, ty } => { + canon_sec.stream_cancel_write(ty, async_); + } + CanonicalFunction::FutureNew { ty } => { + canon_sec.future_new(ty); + } + CanonicalFunction::FutureRead { ty, options } => { + canon_sec.future_read(ty, options.iter().map(|opt| (*opt).into())); + } + CanonicalFunction::FutureWrite { ty, options } => { + canon_sec.future_write(ty, options.iter().map(|opt| (*opt).into())); + } + CanonicalFunction::FutureCancelRead { async_, ty } => { + canon_sec.future_cancel_read(ty, async_); + } + CanonicalFunction::FutureCancelWrite { async_, ty } => { + canon_sec.future_cancel_write(ty, async_); + } + CanonicalFunction::ErrorContextNew { options } => { + canon_sec.error_context_new(options.iter().map(|opt| (*opt).into())); + } + CanonicalFunction::ErrorContextDebugMessage { options } => { + canon_sec.error_context_debug_message(options.iter().map(|opt| (*opt).into())); + } + CanonicalFunction::ErrorContextDrop => { + canon_sec.error_context_drop(); + } + CanonicalFunction::ThreadSpawnRef { func_ty_index } => { + canon_sec.thread_spawn_ref(func_ty_index); + } + CanonicalFunction::ThreadSpawnIndirect { + func_ty_index, + table_index, + } => { + canon_sec.thread_spawn_indirect(func_ty_index, table_index); + } + CanonicalFunction::TaskCancel => { + canon_sec.task_cancel(); + } + CanonicalFunction::ContextGet(i) => { + canon_sec.context_get(i); + } + CanonicalFunction::ContextSet(i) => { + canon_sec.context_set(i); + } + CanonicalFunction::SubtaskCancel { async_ } => { + canon_sec.subtask_cancel(async_); + } + CanonicalFunction::StreamDropReadable { ty } => { + canon_sec.stream_drop_readable(ty); + } + CanonicalFunction::StreamDropWritable { ty } => { + canon_sec.stream_drop_writable(ty); + } + CanonicalFunction::FutureDropReadable { ty } => { + canon_sec.future_drop_readable(ty); + } + CanonicalFunction::FutureDropWritable { ty } => { + canon_sec.future_drop_writable(ty); + } + CanonicalFunction::ThreadYield { cancellable } => { + canon_sec.thread_yield(cancellable); + } + CanonicalFunction::ThreadIndex => { + canon_sec.thread_index(); + } + CanonicalFunction::ThreadNewIndirect { + func_ty_index, + table_index, + } => { + canon_sec.thread_new_indirect(func_ty_index, table_index); + } + CanonicalFunction::ThreadSwitchTo { cancellable } => { + canon_sec.thread_switch_to(cancellable); + } + CanonicalFunction::ThreadSuspend { cancellable } => { + canon_sec.thread_suspend(cancellable); + } + CanonicalFunction::ThreadResumeLater => { + canon_sec.thread_resume_later(); + } + CanonicalFunction::ThreadYieldTo { cancellable } => { + canon_sec.thread_yield_to(cancellable); + } + } + component.section(&canon_sec); +} +fn encode_alias_section( + a: &ComponentAlias, + component: &mut wasm_encoder::Component, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let alias = a.fix(ids, cx); + let new_a = into_wasm_encoder_alias(&alias, reencode); + + let mut alias_section = ComponentAliasSection::new(); + alias_section.alias(new_a); + component.section(&alias_section); +} +fn encode_comp_import_section( + i: &ComponentImport, + component: &mut wasm_encoder::Component, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let import = i.fix(ids, cx); + let mut imports = wasm_encoder::ComponentImportSection::new(); + + let ty = do_reencode( + import.ty, + RoundtripReencoder::component_type_ref, + reencode, + "component import", + ); + imports.import(import.name.0, ty); + + component.section(&imports); +} +fn encode_comp_export_section( + e: &ComponentExport, + component: &mut wasm_encoder::Component, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let export = e.fix(ids, cx); + let mut exports = wasm_encoder::ComponentExportSection::new(); + + let ty = export.ty.map(|ty| { + do_reencode( + ty, + RoundtripReencoder::component_type_ref, + reencode, + "component export", + ) + }); + + exports.export( + export.name.0, + reencode.component_export_kind(export.kind), + export.index, + ty, + ); + + component.section(&exports); +} +fn encode_inst_section( + i: &Instance, + component: &mut wasm_encoder::Component, + _: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let inst = i.fix(ids, cx); + let mut instances = wasm_encoder::InstanceSection::new(); + + match inst { + Instance::Instantiate { module_index, args } => { + instances.instantiate( + module_index, + args.iter() + .map(|arg| (arg.name, ModuleArg::Instance(arg.index))), + ); + } + Instance::FromExports(exports) => { + instances.export_items(exports.iter().map(|export| { + ( + export.name, + wasm_encoder::ExportKind::from(export.kind), + export.index, + ) + })); + } + } + + component.section(&instances); +} +fn encode_start_section( + s: &ComponentStartFunction, + component: &mut wasm_encoder::Component, + _: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let start = s.fix(ids, cx); + component.section(&wasm_encoder::ComponentStartSection { + function_index: start.func_index, + args: start.arguments.clone(), + results: start.results, + }); +} +fn encode_custom_section( + s: &CustomSection, + component: &mut wasm_encoder::Component, + _: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let custom = s.fix(ids, cx); + component.section(&wasm_encoder::CustomSection { + name: std::borrow::Cow::Borrowed(custom.name), + data: custom.data.clone(), + }); +} + +// === The inner structs === + +fn encode_comp_defined_ty( + t: &ComponentDefinedType, + enc: ComponentDefinedTypeEncoder, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let ty = t.fix(ids, cx); + match ty { + ComponentDefinedType::Primitive(p) => { + enc.primitive(wasm_encoder::PrimitiveValType::from(p)) + } + ComponentDefinedType::Record(records) => { + enc.record( + records + .iter() + .map(|(n, ty)| (*n, reencode.component_val_type(*ty))), + ); + } + ComponentDefinedType::Variant(variants) => enc.variant(variants.iter().map(|variant| { + ( + variant.name, + variant.ty.map(|ty| reencode.component_val_type(ty)), + variant.refines, + ) + })), + ComponentDefinedType::List(l) => enc.list(reencode.component_val_type(l)), + ComponentDefinedType::Tuple(tup) => enc.tuple( + tup.iter() + .map(|val_type| reencode.component_val_type(*val_type)), + ), + ComponentDefinedType::Flags(flags) => enc.flags(flags.clone().into_vec()), + ComponentDefinedType::Enum(en) => enc.enum_type(en.clone().into_vec()), + ComponentDefinedType::Option(opt) => enc.option(reencode.component_val_type(opt)), + ComponentDefinedType::Result { ok, err } => enc.result( + ok.map(|val_type| reencode.component_val_type(val_type)), + err.map(|val_type| reencode.component_val_type(val_type)), + ), + ComponentDefinedType::Own(id) => enc.own(id), + ComponentDefinedType::Borrow(id) => enc.borrow(id), + ComponentDefinedType::Future(opt) => { + enc.future(opt.map(|opt| reencode.component_val_type(opt))) + } + ComponentDefinedType::Stream(opt) => { + enc.stream(opt.map(|opt| reencode.component_val_type(opt))) + } + ComponentDefinedType::FixedSizeList(ty, i) => { + enc.fixed_size_list(reencode.component_val_type(ty), i) + } + ComponentDefinedType::Map(key_ty, val_ty) => enc.map( + reencode.component_val_type(key_ty), + reencode.component_val_type(val_ty), + ), + } +} + +fn encode_comp_func_ty( + t: &ComponentFuncType, + mut enc: ComponentFuncTypeEncoder, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let ty = t.fix(ids, cx); + enc.async_(ty.async_); + enc.params( + ty.params + .iter() + .map(|(name, ty)| (*name, reencode.component_val_type(*ty))), + ); + enc.result(ty.result.map(|v| reencode.component_val_type(v))); +} + +fn encode_comp_ty_decl( + ty: &ComponentTypeDeclaration, + new_comp_ty: &mut wasm_encoder::ComponentType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + match ty { + ComponentTypeDeclaration::Alias(a) => { + encode_alias_in_comp_ty(a, new_comp_ty, reencode, ids, cx) + } + ComponentTypeDeclaration::Export { name, ty: t } => { + let ty = t.fix(ids, cx); + let ty = do_reencode( + ty, + RoundtripReencoder::component_type_ref, + reencode, + "component type", + ); + new_comp_ty.export(name.0, ty); + } + ComponentTypeDeclaration::Import(i) => { + let imp = i.fix(ids, cx); + let ty = do_reencode( + imp.ty, + RoundtripReencoder::component_type_ref, + reencode, + "component type", + ); + new_comp_ty.import(imp.name.0, ty); + } + ComponentTypeDeclaration::CoreType(_) | ComponentTypeDeclaration::Type(_) => {} // handled explicitly in visitor + } +} +fn encode_alias_in_comp_ty( + a: &ComponentAlias, + comp_ty: &mut wasm_encoder::ComponentType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let alias = a.fix(ids, cx); + let new_a = into_wasm_encoder_alias(&alias, reencode); + comp_ty.alias(new_a); +} +fn encode_rec_group_in_core_ty( + group: &RecGroup, + enc: &mut CoreTypeSection, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + let types = into_wasm_encoder_recgroup(group, reencode, ids, cx); + + if group.is_explicit_rec_group() { + enc.ty().core().rec(types); + } else { + // it's implicit! + for subty in types { + enc.ty().core().subtype(&subty); + } + } +} + +fn encode_inst_ty_decl( + inst: &InstanceTypeDeclaration, + ity: &mut wasm_encoder::InstanceType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + match inst { + InstanceTypeDeclaration::Alias(a) => { + let alias = a.fix(ids, cx); + match alias { + ComponentAlias::InstanceExport { + kind, + instance_index, + name, + } => { + ity.alias(Alias::InstanceExport { + instance: instance_index, + kind: reencode.component_export_kind(kind), + name, + }); + } + ComponentAlias::CoreInstanceExport { + kind, + instance_index, + name, + } => { + ity.alias(Alias::CoreInstanceExport { + instance: instance_index, + kind: do_reencode( + kind, + RoundtripReencoder::export_kind, + reencode, + "export kind", + ), + name, + }); + } + ComponentAlias::Outer { kind, count, index } => { + ity.alias(Alias::Outer { + kind: reencode.component_outer_alias_kind(kind), + count, + index, + }); + } + } + } + InstanceTypeDeclaration::Export { name, ty: t } => { + let ty = t.fix(ids, cx); + ity.export( + name.0, + do_reencode( + ty, + RoundtripReencoder::component_type_ref, + reencode, + "component type", + ), + ); + } + InstanceTypeDeclaration::CoreType(_) | InstanceTypeDeclaration::Type(_) => {} // handled explicitly in visitor + } +} +fn encode_core_ty_from_inst_ty( + core_ty: &CoreType, + inst_ty: &mut wasm_encoder::InstanceType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) -> Option { + match core_ty { + CoreType::Rec(r) => { + let recgroup = r.fix(ids, cx); + for sub in recgroup.types() { + encode_subtype(sub, inst_ty.core_type().core(), reencode); + } + } + CoreType::Module(_) => {} + } + + frame_for_core_ty(core_ty) +} +fn encode_core_ty_from_comp_ty( + core_ty: &CoreType, + comp_ty: &mut wasm_encoder::ComponentType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) -> Option { + match core_ty { + CoreType::Rec(r) => { + let recgroup = r.fix(ids, cx); + for sub in recgroup.types() { + encode_subtype(sub, comp_ty.core_type().core(), reencode); + } + } + CoreType::Module(_) => {} + } + + frame_for_core_ty(core_ty) +} +fn encode_module_type_decl( + d: &ModuleTypeDeclaration, + mty: &mut wasm_encoder::ModuleType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) { + if let ModuleTypeDeclaration::Type(recgroup) = d { + // special handler for recgroups! + let types = into_wasm_encoder_recgroup(recgroup, reencode, ids, cx); + + if recgroup.is_explicit_rec_group() { + mty.ty().rec(types); + } else { + // it's implicit! + for subty in types { + mty.ty().subtype(&subty); + } + } + return; + } + + let decl = d.fix(ids, cx); + match decl { + ModuleTypeDeclaration::Type(_) => unreachable!(), + ModuleTypeDeclaration::Export { name, ty } => { + mty.export(name, reencode.entity_type(ty).unwrap()); + } + ModuleTypeDeclaration::OuterAlias { + kind: _kind, + count, + index, + } => { + mty.alias_outer_core_type(count, index); + } + ModuleTypeDeclaration::Import(import) => { + mty.import( + import.module, + import.name, + reencode.entity_type(import.ty).unwrap(), + ); + } + } +} + +fn encode_comp_ty_in_inst_ty( + t: &ComponentType, + ity: &mut wasm_encoder::InstanceType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) -> Option { + let frame = frame_for_comp_ty(t); + if frame.is_some() { + // special case for components and instances + return frame; + } + + let ty = t.fix(ids, cx); + match ty { + ComponentType::Defined(comp_ty) => { + encode_comp_defined_ty(&comp_ty, ity.ty().defined_type(), reencode, ids, cx); + } + ComponentType::Func(func_ty) => { + encode_comp_func_ty(&func_ty, ity.ty().function(), reencode, ids, cx); + } + ComponentType::Resource { rep, dtor } => { + ity.ty().resource(reencode.val_type(rep).unwrap(), dtor); + } + ComponentType::Component(_) | ComponentType::Instance(_) => unreachable!(), + } + + frame +} + +fn encode_comp_ty_in_comp_ty( + t: &ComponentType, + cty: &mut wasm_encoder::ComponentType, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) -> Option { + let frame = frame_for_comp_ty(t); + if frame.is_some() { + // special case for components and instances + return frame; + } + + let ty = t.fix(ids, cx); + match ty { + ComponentType::Defined(comp_ty) => { + encode_comp_defined_ty(&comp_ty, cty.ty().defined_type(), reencode, ids, cx); + } + ComponentType::Func(func_ty) => { + encode_comp_func_ty(&func_ty, cty.ty().function(), reencode, ids, cx); + } + ComponentType::Resource { rep, dtor } => { + cty.ty().resource(reencode.val_type(rep).unwrap(), dtor); + } + ComponentType::Component(_) | ComponentType::Instance(_) => unreachable!(), + } + + frame +} + +/// NOTE: The alias passed here should already be FIXED +fn into_wasm_encoder_alias<'a>( + alias: &ComponentAlias<'a>, + reencode: &mut RoundtripReencoder, +) -> Alias<'a> { + match alias { + ComponentAlias::InstanceExport { + kind, + instance_index, + name, + } => Alias::InstanceExport { + instance: *instance_index, + kind: reencode.component_export_kind(*kind), + name, + }, + ComponentAlias::CoreInstanceExport { + kind, + instance_index, + name, + } => Alias::CoreInstanceExport { + instance: *instance_index, + kind: do_reencode( + *kind, + RoundtripReencoder::export_kind, + reencode, + "export kind", + ), + name, + }, + ComponentAlias::Outer { kind, count, index } => Alias::Outer { + kind: reencode.component_outer_alias_kind(*kind), + count: *count, + index: *index, + }, + } +} + +pub fn into_wasm_encoder_recgroup( + group: &RecGroup, + reencode: &mut RoundtripReencoder, + ids: &ActualIds, + cx: &VisitCtxInner, +) -> Vec { + let subtypes = group + .types() + .map(|subty| { + let fixed_subty = subty.fix(ids, cx); + reencode.sub_type(fixed_subty).unwrap_or_else(|e| { + panic!( + "Internal error: Could not encode type as subtype: {:?}\n\t{e}", + subty + ) + }) + }) + .collect::>(); + + subtypes +} + +/// NOTE: The subtype passed here should already be FIXED +fn encode_subtype(subtype: &SubType, enc: CoreTypeEncoder, reencode: &mut RoundtripReencoder) { + let subty = reencode + .sub_type(subtype.to_owned()) + .unwrap_or_else(|_| panic!("Could not encode type as subtype: {:?}", subtype)); + + enc.subtype(&subty); +} + +pub(crate) fn do_reencode( + i: I, + reencode: fn(&mut RoundtripReencoder, I) -> Result, + inst: &mut RoundtripReencoder, + msg: &str, +) -> O { + match reencode(inst, i) { + Ok(o) => o, + Err(e) => panic!( + "Internal error: Couldn't encode {} due to error: {}", + msg, e + ), + } +} + +fn curr_comp_frame(comp_stack: &mut [CompFrame]) -> &mut CompFrame { + comp_stack.last_mut().unwrap() +} +fn curr_comp_ty_sect_mut(comp_stack: &mut [CompFrame]) -> &mut ComponentTypeSection { + let frame = curr_comp_frame(comp_stack); + + if frame.comp_type_section.is_none() { + frame.comp_type_section = Some(ComponentTypeSection::new()); + } + + frame.comp_type_section.as_mut().unwrap() +} +fn curr_core_ty_sect_mut(comp_stack: &mut [CompFrame]) -> &mut CoreTypeSection { + let frame = curr_comp_frame(comp_stack); + + if frame.core_type_section.is_none() { + frame.core_type_section = Some(CoreTypeSection::new()); + } + + frame.core_type_section.as_mut().unwrap() +} diff --git a/src/encode/component/fix_indices.rs b/src/encode/component/fix_indices.rs new file mode 100644 index 00000000..962e9fc5 --- /dev/null +++ b/src/encode/component/fix_indices.rs @@ -0,0 +1,1140 @@ +// I want this file to be a bunch of oneliners (easier to read)! + +use crate::encode::component::assign::ActualIds; +use crate::ir::component::refs::{ + GetArgRefs, GetCompRefs, GetFuncRef, GetFuncRefs, GetItemRef, GetMemRefs, GetModuleRefs, + GetTableRefs, GetTypeRefs, +}; +use crate::ir::component::scopes::GetScopeKind; +use crate::ir::component::visitor::utils::VisitCtxInner; +use crate::ir::types::CustomSection; +use wasmparser::{ + ArrayType, CanonicalFunction, CanonicalOption, ComponentAlias, ComponentDefinedType, + ComponentExport, ComponentFuncType, ComponentImport, ComponentInstance, + ComponentInstantiationArg, ComponentStartFunction, ComponentType, ComponentTypeDeclaration, + ComponentTypeRef, ComponentValType, CompositeInnerType, CompositeType, ContType, CoreType, + Export, FieldType, FuncType, HeapType, Import, Instance, InstanceTypeDeclaration, + InstantiationArg, ModuleTypeDeclaration, PackedIndex, PrimitiveValType, RecGroup, RefType, + StorageType, StructType, SubType, TagType, TypeRef, UnpackedIndex, ValType, VariantCase, +}; + +mod sealed { + pub trait Sealed {} +} +trait FixIndicesImpl { + fn fixme(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self; +} +pub(crate) trait FixIndices: sealed::Sealed { + fn fix(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self + where + Self: Sized; +} + +impl FixIndices for T +where + T: GetScopeKind + sealed::Sealed + FixIndicesImpl, +{ + fn fix<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self + where + Self: Sized, + { + self.fixme(ids, cx) + } +} + +impl sealed::Sealed for ComponentExport<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentExport<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + + let fixed_ty = self.ty.map(|ty| { + ty.fix(ids, cx) + }); + + ComponentExport { + name: self.name, + kind: self.kind, + index: new_id as u32, + ty: fixed_ty, + } + } +} + +impl sealed::Sealed for ComponentInstantiationArg<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentInstantiationArg<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + + ComponentInstantiationArg { + name: self.name, + kind: self.kind, + index: new_id as u32, + } + } +} + +impl sealed::Sealed for ComponentType<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentType<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ComponentType::Defined(ty) => ComponentType::Defined(ty.fix(ids, cx)), + ComponentType::Func(ty) => ComponentType::Func(ty.fix(ids, cx)), + ComponentType::Resource { rep, dtor } => { + ComponentType::Resource { + rep: rep.fix(ids, cx), + dtor: dtor.map(|_| { + ids.lookup_actual_id_or_panic( + cx, + &self.get_func_refs().first().unwrap().ref_ + ) as u32 + }) + } + } + ComponentType::Component(_) + | ComponentType::Instance(_) => unreachable!("should never be called for this variant") + } + } +} + +impl sealed::Sealed for ComponentInstance<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentInstance<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ComponentInstance::Instantiate { args, .. } => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_comp_refs().first().unwrap().ref_ + ); + + ComponentInstance::Instantiate { + component_index: new_id as u32, + args: args.iter().map( | arg| { + arg.fix(ids, cx) + }).collect(), + } + } + ComponentInstance::FromExports(export) => ComponentInstance::FromExports( + export.iter().map(|value| { + value.fix(ids, cx) + }).collect() + ) + } + } +} + +impl sealed::Sealed for CanonicalFunction {} +#[rustfmt::skip] +impl FixIndicesImpl for CanonicalFunction { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + CanonicalFunction::Lift { options: options_orig, .. } => { + let new_fid = ids.lookup_actual_id_or_panic( + cx, + &self.get_func_refs().first().unwrap().ref_ + ); + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + + CanonicalFunction::Lift { + core_func_index: new_fid as u32, + type_index: new_tid as u32, + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::Lower { options: options_orig, .. } => { + let new_fid = ids.lookup_actual_id_or_panic( + cx, + &self.get_func_refs().first().unwrap().ref_ + ); + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + + CanonicalFunction::Lower { + func_index: new_fid as u32, + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::ResourceNew { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ResourceNew { resource: new_tid as u32} + } + CanonicalFunction::ResourceDrop { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ResourceDrop { resource: new_tid as u32} + } + CanonicalFunction::ResourceRep { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ResourceRep { resource: new_tid as u32} + } + CanonicalFunction::ResourceDropAsync { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ResourceDropAsync { resource: new_tid as u32} + } + CanonicalFunction::TaskReturn { + result, + options: options_orig, + } => { + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + CanonicalFunction::TaskReturn { + result: result.map(|v| { + v.fix(ids, cx) + }), + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::WaitableSetWait { cancellable, .. } => { + let new_mid = ids.lookup_actual_id_or_panic( + cx, + &self.get_mem_refs().first().unwrap().ref_ + ); + + CanonicalFunction::WaitableSetWait { + cancellable: *cancellable, + memory: new_mid as u32, + } + } + CanonicalFunction::WaitableSetPoll { cancellable, .. } => { + let new_mid = ids.lookup_actual_id_or_panic( + cx, + &self.get_mem_refs().first().unwrap().ref_ + ); + + CanonicalFunction::WaitableSetPoll { + cancellable: *cancellable, + memory: new_mid as u32, + } + } + CanonicalFunction::StreamNew { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::StreamNew { + ty: new_tid as u32, + } + } + CanonicalFunction::StreamRead { options: options_orig, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + + CanonicalFunction::StreamRead { + ty: new_tid as u32, + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::StreamWrite { options: options_orig, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + + CanonicalFunction::StreamWrite { + ty: new_tid as u32, + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::StreamCancelRead { async_, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::StreamCancelRead { + async_: *async_, + ty: new_tid as u32, + } + } + CanonicalFunction::StreamCancelWrite { async_, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::StreamCancelWrite { + async_: *async_, + ty: new_tid as u32, + } + } + CanonicalFunction::FutureNew { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::FutureNew { + ty: new_tid as u32, + } + } + CanonicalFunction::FutureRead { options: options_orig, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + CanonicalFunction::FutureRead { + ty: new_tid as u32, + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::FutureWrite { options: options_orig, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + CanonicalFunction::FutureWrite { + ty: new_tid as u32, + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::FutureCancelRead { async_, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::FutureCancelRead { + async_: *async_, + ty: new_tid as u32, + } + } + CanonicalFunction::FutureCancelWrite { async_, .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::FutureCancelWrite { + async_: *async_, + ty: new_tid as u32, + } + } + CanonicalFunction::ErrorContextNew { options: options_orig } => { + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + CanonicalFunction::ErrorContextNew { + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::ErrorContextDebugMessage { options: options_orig } => { + let mut fixed_options = vec![]; + for opt in options_orig.iter() { + fixed_options.push(opt.fix(ids, cx)); + } + CanonicalFunction::ErrorContextDebugMessage { + options: fixed_options.into_boxed_slice() + } + } + CanonicalFunction::ThreadSpawnRef { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ThreadSpawnRef { + func_ty_index: new_tid as u32, + } + } + CanonicalFunction::ThreadSpawnIndirect { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + let new_tbl_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_tbl_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ThreadSpawnIndirect { + func_ty_index: new_tid as u32, + table_index: new_tbl_id as u32, + } + } + CanonicalFunction::ThreadNewIndirect { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + let new_tbl_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_tbl_refs().first().unwrap().ref_ + ); + + CanonicalFunction::ThreadNewIndirect { + func_ty_index: new_tid as u32, + table_index: new_tbl_id as u32, + } + } + CanonicalFunction::StreamDropReadable { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::StreamDropReadable { + ty: new_tid as u32, + } + } + CanonicalFunction::StreamDropWritable { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::StreamDropWritable { + ty: new_tid as u32, + } + } + CanonicalFunction::FutureDropReadable { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::FutureDropReadable { + ty: new_tid as u32, + } + } + CanonicalFunction::FutureDropWritable { .. } => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + CanonicalFunction::FutureDropWritable { + ty: new_tid as u32, + } + } + CanonicalFunction::ThreadAvailableParallelism + | CanonicalFunction::BackpressureInc + | CanonicalFunction::BackpressureDec + | CanonicalFunction::WaitableSetNew + | CanonicalFunction::WaitableSetDrop + | CanonicalFunction::WaitableJoin + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::TaskCancel + | CanonicalFunction::SubtaskCancel { .. } + | CanonicalFunction::ContextGet(_) + | CanonicalFunction::ContextSet(_) + | CanonicalFunction::ThreadYield { .. } + | CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater + | CanonicalFunction::ThreadYieldTo {..} + | CanonicalFunction::ErrorContextDrop => self.clone(), + } + } +} + +impl sealed::Sealed for Instance<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for Instance<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + Instance::Instantiate { args: args_orig, .. } => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_module_refs().first().unwrap().ref_ + ); + + let mut args = vec![]; + for arg in args_orig.iter() { + args.push(arg.fix(ids, cx)); + } + Instance::Instantiate { + module_index: new_id as u32, + args: args.into_boxed_slice() + } + } + Instance::FromExports(exports_orig) => { + let mut exports = vec![]; + for export in exports_orig.iter() { + exports.push(export.fix(ids, cx)); + } + Instance::FromExports(exports.into_boxed_slice()) + } + } + } +} + +impl sealed::Sealed for ComponentStartFunction {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentStartFunction { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let new_fid = ids.lookup_actual_id_or_panic( + cx, + &self.get_func_ref().ref_ + ); + + let mut new_args = vec![]; + for r in self.get_arg_refs().iter() { + let new_arg = ids.lookup_actual_id_or_panic( + cx, + &r.ref_ + ); + new_args.push(new_arg as u32) + } + + Self { + func_index: new_fid as u32, + arguments: new_args.into_boxed_slice(), + results: self.results, + } + } +} + +impl sealed::Sealed for CustomSection<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for CustomSection<'_> { + fn fixme<'a>(&self, _: &ActualIds, _: &VisitCtxInner) -> Self { + self.clone() + } +} + +impl sealed::Sealed for ComponentDefinedType<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentDefinedType<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ComponentDefinedType::Flags(_) + | ComponentDefinedType::Enum(_) => self.clone(), + ComponentDefinedType::Primitive(ty) => ComponentDefinedType::Primitive(ty.fix(ids, cx)), + ComponentDefinedType::Record(tys) => { + let mut new_tys = vec![]; + for (s, ty) in tys.iter() { + new_tys.push((*s, ty.fix(ids, cx))) + } + ComponentDefinedType::Record(new_tys.into_boxed_slice()) + }, + ComponentDefinedType::Variant(tys) => { + let mut new_tys = vec![]; + for ty in tys.iter() { + new_tys.push(ty.fix(ids, cx)) + } + ComponentDefinedType::Variant(new_tys.into_boxed_slice()) + }, + ComponentDefinedType::List(ty) => ComponentDefinedType::List(ty.fix(ids, cx)), + ComponentDefinedType::FixedSizeList(ty, len) => ComponentDefinedType::FixedSizeList(ty.fix(ids, cx), *len), + ComponentDefinedType::Tuple(tys) => { + let mut new_tys = vec![]; + for t in tys.iter() { + new_tys.push(t.fix(ids, cx)) + } + ComponentDefinedType::Tuple(new_tys.into_boxed_slice()) + } + ComponentDefinedType::Option(ty) => ComponentDefinedType::Option(ty.fix(ids, cx)), + ComponentDefinedType::Result { ok, err } => ComponentDefinedType::Result { + ok: ok.as_ref().map(|ok| ok.fix(ids, cx)), + err: err.as_ref().map(|err| err.fix(ids, cx)) + }, + ComponentDefinedType::Own(_) => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + ComponentDefinedType::Own(new_tid as u32) + }, + ComponentDefinedType::Borrow(_) => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + ComponentDefinedType::Borrow(new_tid as u32) + }, + ComponentDefinedType::Future(ty) => ComponentDefinedType::Future(ty.as_ref().map(|ty| ty.fix(ids, cx))), + ComponentDefinedType::Stream(ty) => ComponentDefinedType::Stream(ty.as_ref().map(|ty| ty.fix(ids, cx))), + ComponentDefinedType::Map(key_ty, val_ty) => ComponentDefinedType::Map( + key_ty.fix(ids, cx), + val_ty.fix(ids, cx) + ), + } + } +} + +impl sealed::Sealed for PrimitiveValType {} +#[rustfmt::skip] +impl FixIndicesImpl for PrimitiveValType { + fn fixme<'a>(&self, _: &ActualIds, _: &VisitCtxInner) -> Self { + *self + } +} + +impl sealed::Sealed for VariantCase<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for VariantCase<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + Self { + name: self.name, + ty: self.ty.map(|ty| ty.fix(ids, cx)), + refines: self.refines.map(|_| { + ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ) as u32 + }), + } + } +} + +impl sealed::Sealed for ComponentFuncType<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentFuncType<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let mut new_params = vec![]; + for (orig_name, orig_ty) in self.params.iter() { + new_params.push((*orig_name, orig_ty.fix(ids, cx))); + } + + let new_res = self.result.map(|res| res.fix(ids, cx)); + + Self { + async_: self.async_, + params: new_params.into_boxed_slice(), + result: new_res, + } + } +} + +impl sealed::Sealed for SubType {} +#[rustfmt::skip] +impl FixIndicesImpl for SubType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + Self { + is_final: self.is_final, + supertype_idx: if self.supertype_idx.is_some() { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + Some(PackedIndex::from_module_index(new_tid as u32).unwrap()) + } else { + None + }, + composite_type: self.composite_type.fix(ids, cx) + } + } +} + +impl sealed::Sealed for CompositeType {} +#[rustfmt::skip] +impl FixIndicesImpl for CompositeType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + Self { + inner: self.inner.fix(ids, cx), + shared: false, + descriptor_idx: None, + describes_idx: None, + } + } +} + +impl sealed::Sealed for CompositeInnerType {} +#[rustfmt::skip] +impl FixIndicesImpl for CompositeInnerType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + CompositeInnerType::Func(ty) => CompositeInnerType::Func(ty.fix(ids, cx)), + CompositeInnerType::Array(ty) => CompositeInnerType::Array(ArrayType(ty.0.fix(ids, cx))), + CompositeInnerType::Struct(s) => CompositeInnerType::Struct(s.fix(ids, cx)), + CompositeInnerType::Cont(_) => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + CompositeInnerType::Cont(ContType(PackedIndex::from_module_index(new_tid as u32).unwrap())) + }, + } + } +} + +impl sealed::Sealed for FuncType {} +#[rustfmt::skip] +impl FixIndicesImpl for FuncType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let mut new_params = vec![]; + for p in self.params() { + new_params.push(p.fix(ids, cx)); + } + let mut new_results = vec![]; + for r in self.results() { + new_results.push(r.fix(ids, cx)); + } + + Self::new(new_params, new_results) + } +} + +impl sealed::Sealed for FieldType {} +#[rustfmt::skip] +impl FixIndicesImpl for FieldType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + Self { + element_type: self.element_type.fix(ids, cx), + mutable: self.mutable, + } + } +} + +impl sealed::Sealed for StorageType {} +#[rustfmt::skip] +impl FixIndicesImpl for StorageType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + StorageType::I8 + | StorageType::I16 => *self, + StorageType::Val(value) => StorageType::Val(value.fix(ids, cx)) + } + } +} + +impl sealed::Sealed for StructType {} +#[rustfmt::skip] +impl FixIndicesImpl for StructType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let mut new_fields = vec![]; + for f in self.fields.iter() { + new_fields.push(f.fix(ids, cx)); + } + + Self { + fields: new_fields.into_boxed_slice() + } + } +} + +impl sealed::Sealed for ComponentTypeDeclaration<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentTypeDeclaration<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ComponentTypeDeclaration::CoreType(ty) => ComponentTypeDeclaration::CoreType(ty.fix(ids, cx)), + ComponentTypeDeclaration::Type(ty) => ComponentTypeDeclaration::Type(ty.fix(ids, cx)), + ComponentTypeDeclaration::Alias(a) => ComponentTypeDeclaration::Alias(a.fix(ids, cx)), + ComponentTypeDeclaration::Import(i) => ComponentTypeDeclaration::Import(i.fix(ids, cx)), + ComponentTypeDeclaration::Export { name, ty } => ComponentTypeDeclaration::Export { + name: *name, + ty: ty.fix(ids, cx) + }, + } + } +} + +impl sealed::Sealed for ValType {} +#[rustfmt::skip] +impl FixIndicesImpl for ValType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ValType::I32 + | ValType::I64 + | ValType::F32 + | ValType::F64 + | ValType::V128 => *self, + ValType::Ref(r) => ValType::Ref(r.fix(ids, cx)), + } + } +} + +impl sealed::Sealed for RefType {} +#[rustfmt::skip] +impl FixIndicesImpl for RefType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let refs = self.get_type_refs(); + if !refs.is_empty() { + let new_heap = match self.heap_type() { + HeapType::Concrete(_) => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &refs.first().unwrap().ref_ + ); + HeapType::Concrete(UnpackedIndex::Module(new_tid as u32)) + } + + HeapType::Exact(_) => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &refs.first().unwrap().ref_ + ); + HeapType::Exact(UnpackedIndex::Module(new_tid as u32)) + } + + HeapType::Abstract { .. } => { + // Abstract heap types never contain indices + return *self; + } + }; + + Self::new(self.is_nullable(), new_heap).unwrap() + } else { + *self + } + } +} + +impl sealed::Sealed for CoreType<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for CoreType<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match &self { + CoreType::Rec(group) => { + CoreType::Rec(group.fix(ids, cx)) + } + CoreType::Module(_) => unreachable!("Should never be called on this variant.") + } + } +} + +impl sealed::Sealed for ModuleTypeDeclaration<'_> {} +impl FixIndicesImpl for ModuleTypeDeclaration<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ModuleTypeDeclaration::Type(group) => ModuleTypeDeclaration::Type(group.fix(ids, cx)), + ModuleTypeDeclaration::Export { name, ty } => ModuleTypeDeclaration::Export { + name, + ty: ty.fix(ids, cx), + }, + ModuleTypeDeclaration::Import(import) => { + ModuleTypeDeclaration::Import(import.fix(ids, cx)) + } + ModuleTypeDeclaration::OuterAlias { kind, count, .. } => { + let new_tid = + ids.lookup_actual_id_or_panic(cx, &self.get_type_refs().first().unwrap().ref_); + + ModuleTypeDeclaration::OuterAlias { + kind: *kind, + count: *count, + index: new_tid as u32, + } + } + } + } +} + +impl sealed::Sealed for Import<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for Import<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + Self { + module: self.module, + name: self.name, + ty: self.ty.fix(ids, cx), + } + } +} + +impl sealed::Sealed for RecGroup {} +#[rustfmt::skip] +impl FixIndicesImpl for RecGroup { + fn fixme<'a>(&self, _: &ActualIds, _: &VisitCtxInner) -> Self { + // NOTE: This is kept as an opaque IR node (indices not fixed here) + // This is because wasmparser does not allow library users to create + // a new RecGroup. + // Indices will be fixed in `into_wasm_encoder_recgroup`! + self.clone() + } +} + +impl sealed::Sealed for ComponentImport<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentImport<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + Self { + name: self.name, + ty: self.ty.fix(ids, cx) + } + } +} + +impl sealed::Sealed for ComponentValType {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentValType { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + if let ComponentValType::Type(_) = self { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + ComponentValType::Type(new_tid as u32) + } else { + *self + } + } +} + +impl sealed::Sealed for ComponentAlias<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentAlias<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ComponentAlias::InstanceExport { kind, name, .. } => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + + Self::InstanceExport { + kind: *kind, + name, + instance_index: new_id as u32, + } + } + ComponentAlias::CoreInstanceExport { kind, name, .. } => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + + Self::CoreInstanceExport { + kind: *kind, + name, + instance_index: new_id as u32, + } + } + ComponentAlias::Outer { kind, count, .. } => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + + Self::Outer { + kind: *kind, + count: *count, + index: new_id as u32, + } + } + } + } +} + +impl sealed::Sealed for ComponentTypeRef {} +#[rustfmt::skip] +impl FixIndicesImpl for ComponentTypeRef { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + ComponentTypeRef::Module(_) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + ComponentTypeRef::Module(new_id as u32) + } + ComponentTypeRef::Value(ty) => { + ComponentTypeRef::Value(ty.fix(ids, cx)) + } + ComponentTypeRef::Func(_) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + ComponentTypeRef::Func(new_id as u32) + } + ComponentTypeRef::Instance(_) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + ComponentTypeRef::Instance(new_id as u32) + } + ComponentTypeRef::Component(_) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + ComponentTypeRef::Component(new_id as u32) + } + ComponentTypeRef::Type(_) => *self // nothing to do + } + } +} + +impl sealed::Sealed for CanonicalOption {} +#[rustfmt::skip] +impl FixIndicesImpl for CanonicalOption { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + CanonicalOption::Realloc(_) + | CanonicalOption::PostReturn(_) + | CanonicalOption::Callback(_) => { + let new_fid = ids.lookup_actual_id_or_panic( + cx, + &self.get_func_refs().first().unwrap().ref_ + ); + + match self { + CanonicalOption::Realloc(_) => CanonicalOption::Realloc(new_fid as u32), + CanonicalOption::PostReturn(_) => CanonicalOption::PostReturn(new_fid as u32), + CanonicalOption::Callback(_) => CanonicalOption::Callback(new_fid as u32), + _ => unreachable!(), + } + } + CanonicalOption::CoreType(_) => { + let new_tid = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + CanonicalOption::CoreType(new_tid as u32) + } + + CanonicalOption::Memory(_) => { + let new_mid = ids.lookup_actual_id_or_panic( + cx, + &self.get_mem_refs().first().unwrap().ref_ + ); + CanonicalOption::Memory(new_mid as u32) + } + CanonicalOption::UTF8 + | CanonicalOption::UTF16 + | CanonicalOption::CompactUTF16 + | CanonicalOption::Async + | CanonicalOption::Gc => *self + } + } +} + +impl sealed::Sealed for InstantiationArg<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for InstantiationArg<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + Self { + name: self.name, + kind: self.kind, + index: new_id as u32, + } + } +} + +impl sealed::Sealed for Export<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for Export<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_item_ref().ref_ + ); + + Self { + name: self.name, + kind: self.kind, + index: new_id as u32, + } + } +} + +impl sealed::Sealed for InstanceTypeDeclaration<'_> {} +#[rustfmt::skip] +impl FixIndicesImpl for InstanceTypeDeclaration<'_> { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + InstanceTypeDeclaration::CoreType(core_type) => InstanceTypeDeclaration::CoreType(core_type.fix(ids, cx)), + InstanceTypeDeclaration::Type(ty) => InstanceTypeDeclaration::Type(ty.fix(ids, cx)), + InstanceTypeDeclaration::Alias(alias) => InstanceTypeDeclaration::Alias(alias.fix(ids, cx)), + InstanceTypeDeclaration::Export { name, ty } => InstanceTypeDeclaration::Export { + name: *name, + ty: ty.fix(ids, cx) + }, + } + } +} + +impl sealed::Sealed for TypeRef {} +#[rustfmt::skip] +impl FixIndicesImpl for TypeRef { + fn fixme<'a>(&self, ids: &ActualIds, cx: &VisitCtxInner) -> Self { + match self { + TypeRef::Func(_) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + + TypeRef::Func(new_id as u32) + } + TypeRef::Tag(TagType { kind, .. }) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + TypeRef::Tag(TagType { + kind: *kind, + func_type_idx: new_id as u32, + }) + } + TypeRef::FuncExact(_) => { + let new_id = ids.lookup_actual_id_or_panic( + cx, + &self.get_type_refs().first().unwrap().ref_ + ); + TypeRef::FuncExact(new_id as u32) + } + TypeRef::Table(_) | TypeRef::Memory(_) | TypeRef::Global(_) => *self + } + } +} diff --git a/src/encode/component/mod.rs b/src/encode/component/mod.rs new file mode 100644 index 00000000..8e470e99 --- /dev/null +++ b/src/encode/component/mod.rs @@ -0,0 +1,159 @@ +use crate::encode::component::assign::assign_indices; +use crate::encode::component::encode::encode_internal; +use crate::ir::component::visitor::events_topological::get_topological_events; +use crate::ir::component::visitor::VisitCtx; +use crate::ir::types; +use crate::Component; + +mod assign; +pub(crate) mod encode; +mod fix_indices; + +/// Encode this component into its binary WebAssembly representation. +/// +/// # Overview +/// +/// Encoding proceeds in **three distinct phases**: +/// +/// 1. **Collect** +/// 2. **Assign** +/// 3. **Encode** +/// +/// These phases exist to decouple *traversal*, *index computation*, and +/// *binary emission*, which is required because instrumentation may insert, +/// reorder, or otherwise affect items that participate in index spaces. +/// +/// At a high level: +/// +/// - **Collect** walks the IR and records *what* needs to be encoded and +/// *where* indices are referenced. +/// - **Assign** resolves all logical references to their final concrete +/// indices after instrumentation. +/// - **Encode** emits the final binary using the resolved indices. +/// +/// --- +/// +/// # Phase 1: Collect +/// +/// The collect phase performs a structured traversal of the component IR. +/// During this traversal: +/// +/// - All items that participate in encoding are recorded into an internal +/// "plan" that determines section order and contents. +/// - Any IR node that *references indices* (e.g. types, instances, modules, +/// exports) is recorded along with enough context to later rewrite those +/// references. +/// +/// No indices are rewritten during this phase. +/// +/// ## Component ID Stack +/// +/// Components may be arbitrarily nested. To correctly associate items with +/// their owning component, the encoder maintains a **stack of component IDs** +/// during traversal. +/// +/// - When entering a component, its `ComponentId` is pushed onto the stack. +/// - When exiting, it is popped. +/// - The top of the stack always represents the *current component context*. +/// +/// A **component registry** maps `ComponentId` → `&Component`, allowing the +/// encoder to recover the owning component at any point without relying on +/// pointer identity for components themselves. +/// +/// --- +/// +/// # Phase 2: Assign +/// +/// The assign phase resolves all *logical* references recorded during collect +/// into *concrete* indices suitable for encoding. +/// +/// This includes: +/// +/// - Mapping original indices to their post-instrumentation positions +/// - Resolving cross-item references (e.g. type references inside signatures) +/// - Ensuring index spaces are internally consistent +/// +/// ## Scope Stack +/// +/// Many IR nodes are scoped (for example, nested types, instances, or +/// component-local definitions). During traversal, the encoder maintains a +/// **stack of scopes** representing the current lexical and component nesting. +/// +/// - Entering a scoped node pushes a new scope. +/// - Exiting the node pops the scope. +/// - At any point, the scope stack represents the active lookup context. +/// +/// ## Scope Registry +/// +/// Because scoped nodes may be deeply nested and arbitrarily structured, +/// scopes are not looked up via traversal position alone. +/// +/// Instead, the encoder uses a **scope registry**, which maps the *identity* +/// of an IR node to its associated scope. +/// +/// - Scoped IR nodes are stored behind stable pointers (e.g. `Box`). +/// - These pointers are registered exactly once when the IR is built. +/// - During assign, any IR node can query the registry to recover its scope +/// in O(1) time. +/// +/// This allows index resolution to be: +/// +/// - Independent of traversal order +/// - Robust to instrumentation +/// - Safe against reordering or insertion of unrelated nodes +/// +/// --- +/// +/// # Phase 3: Encode +/// +/// Once all indices have been assigned, the encode phase performs a final +/// traversal and emits the binary representation. +/// +/// At this point: +/// +/// - No structural mutations occur +/// - All index values are final +/// - Encoding is a pure, deterministic process +/// +/// The encoder follows the previously constructed plan to emit sections +/// in the correct order and format. +/// +/// --- +/// +/// # Design Notes +/// +/// This three-phase architecture ensures that: +/// +/// - Instrumentation can freely insert or modify IR before encoding +/// - Index correctness is guaranteed before any bytes are emitted +/// - Encoding logic remains simple and local +/// +/// The use of component IDs, scope stacks, and a scope registry allows the +/// encoder to handle arbitrarily nested components and scoped definitions +/// without relying on fragile positional assumptions. +/// +/// # Panics +/// +/// This method may panic if: +/// +/// - The component registry is inconsistent +/// - A scoped IR node is missing from the scope registry +/// - Index resolution encounters an unresolved reference +/// +/// These conditions indicate an internal bug or invalid IR construction. +pub fn encode(comp: &Component) -> types::Result> { + // Phase 1: Collect + // NOTE: I'm directly calling get_topological_events to avoid generating + // the events 2x (one per visitor used during assign and encode) + let mut events = Vec::new(); + get_topological_events(comp, &mut VisitCtx::new(comp), &mut events); + + // Phase 2: Assign indices + let ids = assign_indices(&mut VisitCtx::new(comp), &events); + + // Phase 3: Encode (pass in the root-level component's plan, assigned indices, and original->new index map) + let bytes = encode_internal(&ids, &mut VisitCtx::new(comp), &events)?; + + // Reset the index stores for any future visits! + Ok(bytes.finish()) +} diff --git a/src/encode/mod.rs b/src/encode/mod.rs new file mode 100644 index 00000000..9cea807e --- /dev/null +++ b/src/encode/mod.rs @@ -0,0 +1 @@ +pub mod component; diff --git a/src/error.rs b/src/error.rs index 7a8709f8..0d06f34d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,10 +3,15 @@ use std::ops::Range; use wasmparser::BinaryReaderError; /// Error for parsing -#[derive(Debug, Clone)] +#[derive(Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { + IO(std::io::Error), + UnknownId(String), + InvalidOperation(String), + InstrumentationError(String), BinaryReaderError(BinaryReaderError), + Multiple(Vec), UnknownVersion(u32), UnknownSection { section_id: u8, @@ -44,9 +49,28 @@ impl From for Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Error::IO(err) => { + write!(f, "IO error: {err}") + } + Error::UnknownId(err) => { + write!(f, "Unknown id: {err}") + } + Error::InvalidOperation(err) => { + write!(f, "Invalid operation: {err}") + } + Error::InstrumentationError(err) => { + write!(f, "Instrumentation error: {err}") + } Error::BinaryReaderError(err) => { write!(f, "Error from wasmparser: {}", err) } + Error::Multiple(errs) => { + write!(f, "Multiple errors:")?; + for err in errs { + write!(f, "\n\t{err}")?; + } + writeln!(f) + } Error::UnknownVersion(ver) => { write!(f, "Unknown version: {}", ver) } diff --git a/src/ir/component.rs b/src/ir/component.rs deleted file mode 100644 index b391fc34..00000000 --- a/src/ir/component.rs +++ /dev/null @@ -1,1272 +0,0 @@ -#![allow(clippy::mut_range_bound)] // see https://github.com/rust-lang/rust-clippy/issues/6072 -//! Intermediate Representation of a wasm component. - -use wasm_encoder::reencode::{Reencode, ReencodeComponent, RoundtripReencoder}; -use wasm_encoder::{ComponentAliasSection, ModuleArg, ModuleSection, NestedComponentSection}; -use wasmparser::{ - CanonicalFunction, ComponentAlias, ComponentExport, ComponentImport, ComponentInstance, - ComponentStartFunction, ComponentType, ComponentTypeDeclaration, CoreType, Encoding, Instance, - Parser, Payload, -}; - -use crate::error::Error; -use crate::ir::helpers::{ - print_alias, print_component_export, print_component_import, print_component_type, - print_core_type, -}; -use crate::ir::id::{CustomSectionID, FunctionID, GlobalID, ModuleID}; -use crate::ir::module::module_functions::FuncKind; -use crate::ir::module::module_globals::Global; -use crate::ir::module::Module; -use crate::ir::section::ComponentSection; -use crate::ir::types::{CustomSection, CustomSections}; -use crate::ir::wrappers::{ - add_to_namemap, convert_component_type, convert_instance_type, convert_module_type_declaration, - convert_results, do_reencode, process_alias, -}; - -#[derive(Debug)] -/// Intermediate Representation of a wasm component. -pub struct Component<'a> { - /// Modules - pub modules: Vec>, - ///Alias - pub alias: Vec>, - /// Core Types - pub core_types: Vec>, - /// Component Types - pub component_types: Vec>, - /// Imports - pub imports: Vec>, - /// Exports - pub exports: Vec>, - /// Core Instances - pub instances: Vec>, - /// Component Instances - pub component_instance: Vec>, - /// Canons - pub canons: Vec, - /// Custom sections - pub custom_sections: CustomSections<'a>, - /// Nested Components - pub components: Vec>, - /// Number of modules - pub num_modules: usize, - /// Component Start Section - pub start_section: Vec, - /// Sections of the Component. Represented as (#num of occurrences of a section, type of section) - pub sections: Vec<(u32, ComponentSection)>, - num_sections: usize, - - // Names - pub(crate) component_name: Option, - pub(crate) core_func_names: wasm_encoder::NameMap, - pub(crate) global_names: wasm_encoder::NameMap, - pub(crate) memory_names: wasm_encoder::NameMap, - pub(crate) tag_names: wasm_encoder::NameMap, - pub(crate) table_names: wasm_encoder::NameMap, - pub(crate) module_names: wasm_encoder::NameMap, - pub(crate) core_instances_names: wasm_encoder::NameMap, - pub(crate) core_type_names: wasm_encoder::NameMap, - pub(crate) type_names: wasm_encoder::NameMap, - pub(crate) instance_names: wasm_encoder::NameMap, - pub(crate) components_names: wasm_encoder::NameMap, - pub(crate) func_names: wasm_encoder::NameMap, - pub(crate) value_names: wasm_encoder::NameMap, -} - -impl Default for Component<'_> { - fn default() -> Self { - Component::new() - } -} - -impl<'a> Component<'a> { - /// Creates a new Empty Component - pub fn new() -> Self { - Component { - modules: vec![], - alias: vec![], - core_types: vec![], - component_types: vec![], - imports: vec![], - exports: vec![], - instances: vec![], - component_instance: vec![], - canons: vec![], - custom_sections: CustomSections::new(vec![]), - num_modules: 0, - start_section: vec![], - sections: vec![], - num_sections: 0, - components: vec![], - component_name: None, - core_func_names: wasm_encoder::NameMap::new(), - global_names: wasm_encoder::NameMap::new(), - memory_names: wasm_encoder::NameMap::new(), - tag_names: wasm_encoder::NameMap::new(), - table_names: wasm_encoder::NameMap::new(), - module_names: wasm_encoder::NameMap::new(), - core_instances_names: wasm_encoder::NameMap::new(), - core_type_names: wasm_encoder::NameMap::new(), - type_names: wasm_encoder::NameMap::new(), - instance_names: wasm_encoder::NameMap::new(), - components_names: wasm_encoder::NameMap::new(), - func_names: wasm_encoder::NameMap::new(), - value_names: wasm_encoder::NameMap::new(), - } - } - - fn add_to_own_section(&mut self, section: ComponentSection) { - if self.sections[self.num_sections - 1].1 == section { - self.sections[self.num_sections - 1].0 += 1; - } else { - self.sections.push((1, section)); - } - } - - /// Add a Module to this Component. - pub fn add_module(&mut self, module: Module<'a>) -> ModuleID { - let id = self.modules.len(); - self.modules.push(module); - self.add_to_own_section(ComponentSection::Module); - self.num_modules += 1; - - ModuleID(id as u32) - } - - /// Add a Global to this Component. - pub fn add_globals(&mut self, global: Global, module_idx: ModuleID) -> GlobalID { - self.modules[*module_idx as usize].globals.add(global) - } - - pub fn add_custom_section(&mut self, section: CustomSection<'a>) -> CustomSectionID { - let id = self.custom_sections.add(section); - self.add_to_own_section(ComponentSection::CustomSection); - - id - } - - fn add_to_sections( - sections: &mut Vec<(u32, ComponentSection)>, - section: ComponentSection, - num_sections: &mut usize, - sections_added: u32, - ) { - if *num_sections > 0 && sections[*num_sections - 1].1 == section { - sections[*num_sections - 1].0 += sections_added; - } else { - sections.push((sections_added, section)); - *num_sections += 1; - } - } - - /// Parse a `Component` from a wasm binary. - /// - /// Set enable_multi_memory to `true` to support parsing modules using multiple memories. - /// Set with_offsets to `true` to save opcode pc offset metadata during parsing - /// (can be used to determine the static pc offset inside a function body of the start of any opcode). - /// - /// # Example - /// - /// ```no_run - /// use wirm::Component; - /// - /// let file = "path_to_file"; - /// let buff = wat::parse_file(file).expect("couldn't convert the input wat to Wasm"); - /// let comp = Component::parse(&buff, false, false).unwrap(); - /// ``` - pub fn parse( - wasm: &'a [u8], - enable_multi_memory: bool, - with_offsets: bool, - ) -> Result { - let parser = Parser::new(0); - Component::parse_comp( - wasm, - enable_multi_memory, - with_offsets, - parser, - 0, - &mut vec![], - ) - } - - fn parse_comp( - wasm: &'a [u8], - enable_multi_memory: bool, - with_offsets: bool, - parser: Parser, - start: usize, - parent_stack: &mut Vec, - ) -> Result { - let mut modules = vec![]; - let mut core_types = vec![]; - let mut component_types = vec![]; - let mut imports = vec![]; - let mut exports = vec![]; - let mut instances = vec![]; - let mut canons = vec![]; - let mut alias = vec![]; - let mut component_instance = vec![]; - let mut custom_sections = vec![]; - let mut sections = vec![]; - let mut num_sections: usize = 0; - let mut components: Vec = vec![]; - let mut start_section = vec![]; - let mut stack = vec![]; - - // Names - let mut component_name: Option = None; - let mut core_func_names = wasm_encoder::NameMap::new(); - let mut global_names = wasm_encoder::NameMap::new(); - let mut tag_names = wasm_encoder::NameMap::new(); - let mut memory_names = wasm_encoder::NameMap::new(); - let mut table_names = wasm_encoder::NameMap::new(); - let mut module_names = wasm_encoder::NameMap::new(); - let mut core_instance_names = wasm_encoder::NameMap::new(); - let mut instance_names = wasm_encoder::NameMap::new(); - let mut components_names = wasm_encoder::NameMap::new(); - let mut func_names = wasm_encoder::NameMap::new(); - let mut value_names = wasm_encoder::NameMap::new(); - let mut core_type_names = wasm_encoder::NameMap::new(); - let mut type_names = wasm_encoder::NameMap::new(); - - for payload in parser.parse_all(wasm) { - let payload = payload?; - if let Payload::End(..) = payload { - if !stack.is_empty() { - stack.pop(); - } - } - if !stack.is_empty() { - continue; - } - match payload { - Payload::ComponentImportSection(import_section_reader) => { - let temp: &mut Vec = &mut import_section_reader - .into_iter() - .collect::>()?; - let l = temp.len(); - imports.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::ComponentImport, - &mut num_sections, - l as u32, - ); - } - Payload::ComponentExportSection(export_section_reader) => { - let temp: &mut Vec = &mut export_section_reader - .into_iter() - .collect::>()?; - let l = temp.len(); - exports.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::ComponentExport, - &mut num_sections, - l as u32, - ); - } - Payload::InstanceSection(instance_section_reader) => { - let temp: &mut Vec = &mut instance_section_reader - .into_iter() - .collect::>()?; - let l = temp.len(); - instances.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::CoreInstance, - &mut num_sections, - l as u32, - ); - } - Payload::CoreTypeSection(core_type_reader) => { - let temp: &mut Vec = - &mut core_type_reader.into_iter().collect::>()?; - let l = temp.len(); - core_types.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::CoreType, - &mut num_sections, - l as u32, - ); - } - Payload::ComponentTypeSection(component_type_reader) => { - let temp: &mut Vec = &mut component_type_reader - .into_iter() - .collect::>()?; - let l = temp.len(); - component_types.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::ComponentType, - &mut num_sections, - l as u32, - ); - } - Payload::ComponentInstanceSection(component_instances) => { - let temp: &mut Vec = - &mut component_instances.into_iter().collect::>()?; - let l = temp.len(); - component_instance.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::ComponentInstance, - &mut num_sections, - l as u32, - ); - } - Payload::ComponentAliasSection(alias_reader) => { - let temp: &mut Vec = - &mut alias_reader.into_iter().collect::>()?; - let l = temp.len(); - alias.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::Alias, - &mut num_sections, - l as u32, - ); - } - Payload::ComponentCanonicalSection(canon_reader) => { - let temp: &mut Vec = - &mut canon_reader.into_iter().collect::>()?; - let l = temp.len(); - canons.append(temp); - Self::add_to_sections( - &mut sections, - ComponentSection::Canon, - &mut num_sections, - l as u32, - ); - } - Payload::ModuleSection { - parser, - unchecked_range, - } => { - // Indicating the start of a new module - parent_stack.push(Encoding::Module); - stack.push(Encoding::Module); - modules.push(Module::parse_internal( - &wasm[unchecked_range.start - start..unchecked_range.end - start], - enable_multi_memory, - with_offsets, - parser, - )?); - Self::add_to_sections( - &mut sections, - ComponentSection::Module, - &mut num_sections, - 1, - ); - } - Payload::ComponentSection { - parser, - unchecked_range, - } => { - // Indicating the start of a new component - parent_stack.push(Encoding::Component); - stack.push(Encoding::Component); - let cmp = Component::parse_comp( - &wasm[unchecked_range.start - start..unchecked_range.end - start], - enable_multi_memory, - with_offsets, - parser, - unchecked_range.start, - &mut stack, - )?; - components.push(cmp); - Self::add_to_sections( - &mut sections, - ComponentSection::Component, - &mut num_sections, - 1, - ); - } - Payload::ComponentStartSection { start, range: _ } => { - start_section.push(start); - Self::add_to_sections( - &mut sections, - ComponentSection::ComponentStartSection, - &mut num_sections, - 1, - ); - } - Payload::CustomSection(custom_section_reader) => { - match custom_section_reader.as_known() { - wasmparser::KnownCustom::ComponentName(name_section_reader) => { - for subsection in name_section_reader { - #[allow(clippy::single_match)] - match subsection? { - wasmparser::ComponentName::Component { name, .. } => { - component_name = Some(name.parse().unwrap()) - } - wasmparser::ComponentName::CoreFuncs(names) => { - add_to_namemap(&mut core_func_names, names); - } - wasmparser::ComponentName::CoreGlobals(names) => { - add_to_namemap(&mut global_names, names); - } - wasmparser::ComponentName::CoreTables(names) => { - add_to_namemap(&mut table_names, names); - } - wasmparser::ComponentName::CoreModules(names) => { - add_to_namemap(&mut module_names, names); - } - wasmparser::ComponentName::CoreInstances(names) => { - add_to_namemap(&mut core_instance_names, names); - } - wasmparser::ComponentName::CoreTypes(names) => { - add_to_namemap(&mut core_type_names, names); - } - wasmparser::ComponentName::Types(names) => { - add_to_namemap(&mut type_names, names); - } - wasmparser::ComponentName::Instances(names) => { - add_to_namemap(&mut instance_names, names); - } - wasmparser::ComponentName::Components(names) => { - add_to_namemap(&mut components_names, names); - } - wasmparser::ComponentName::Funcs(names) => { - add_to_namemap(&mut func_names, names); - } - wasmparser::ComponentName::Values(names) => { - add_to_namemap(&mut value_names, names); - } - wasmparser::ComponentName::CoreMemories(names) => { - add_to_namemap(&mut memory_names, names); - } - wasmparser::ComponentName::CoreTags(names) => { - add_to_namemap(&mut tag_names, names); - } - wasmparser::ComponentName::Unknown { .. } => {} - } - } - } - _ => { - custom_sections - .push((custom_section_reader.name(), custom_section_reader.data())); - Self::add_to_sections( - &mut sections, - ComponentSection::CustomSection, - &mut num_sections, - 1, - ); - } - } - } - Payload::UnknownSection { - id, - contents: _, - range: _, - } => return Err(Error::UnknownSection { section_id: id }), - _ => {} - } - } - let num_modules = modules.len(); - Ok(Component { - modules, - alias, - core_types, - component_types, - imports, - exports, - instances, - component_instance, - canons, - custom_sections: CustomSections::new(custom_sections), - num_modules, - sections, - start_section, - num_sections, - component_name, - core_func_names, - global_names, - memory_names, - tag_names, - table_names, - module_names, - core_instances_names: core_instance_names, - core_type_names, - type_names, - instance_names, - components_names, - func_names, - components, - value_names, - }) - } - - /// Encode a `Component` to bytes.. - /// - /// # Example - /// - /// ```no_run - /// use wirm::Component; - /// - /// let file = "path_to_file"; - /// let buff = wat::parse_file(file).expect("couldn't convert the input wat to Wasm"); - /// let mut comp = Component::parse(&buff, false, false).unwrap(); - /// let result = comp.encode(); - /// ``` - pub fn encode(&mut self) -> Vec { - self.encode_comp().finish() - } - - fn encode_comp(&mut self) -> wasm_encoder::Component { - let mut component = wasm_encoder::Component::new(); - let mut reencode = wasm_encoder::reencode::RoundtripReencoder; - // NOTE: All of these are 1-indexed and not 0-indexed - let mut last_processed_module = 0; - let mut last_processed_core_ty = 0; - let mut last_processed_comp_ty = 0; - let mut last_processed_imp = 0; - let mut last_processed_exp = 0; - let mut last_processed_comp_inst = 0; - let mut last_processed_core_inst = 0; - let mut last_processed_alias = 0; - let mut last_processed_canon = 0; - let mut last_processed_custom_section = 0; - let mut last_processed_component = 0; - - for (num, section) in self.sections.iter() { - match section { - ComponentSection::Component => { - assert!( - *num as usize + last_processed_component as usize <= self.components.len() - ); - for comp_idx in last_processed_component..last_processed_component + num { - component.section(&NestedComponentSection( - &self.components[comp_idx as usize].encode_comp(), - )); - last_processed_component += 1; - } - } - ComponentSection::Module => { - assert!(*num as usize + last_processed_module as usize <= self.modules.len()); - for mod_idx in last_processed_module..last_processed_module + num { - component.section(&ModuleSection( - &self.modules[mod_idx as usize].encode_internal(false).0, - )); - last_processed_module += 1; - } - } - ComponentSection::CoreType => { - assert!( - *num as usize + last_processed_core_ty as usize <= self.core_types.len() - ); - let mut type_section = wasm_encoder::CoreTypeSection::new(); - for cty_idx in last_processed_core_ty..last_processed_core_ty + num { - match &self.core_types[cty_idx as usize] { - CoreType::Rec(recgroup) => { - let types = recgroup - .types() - .map(|ty| { - reencode.sub_type(ty.to_owned()).unwrap_or_else(|_| { - panic!("Could not encode type as subtype: {:?}", ty) - }) - }) - .collect::>(); - - if recgroup.is_explicit_rec_group() { - type_section.ty().core().rec(types); - } else { - // it's implicit! - for subty in types { - type_section.ty().core().subtype(&subty); - } - } - } - CoreType::Module(module) => { - let enc = type_section.ty(); - convert_module_type_declaration(module, enc, &mut reencode); - } - } - last_processed_core_ty += 1; - } - component.section(&type_section); - } - ComponentSection::ComponentType => { - assert!( - *num as usize + last_processed_comp_ty as usize - <= self.component_types.len() - ); - let mut component_ty_section = wasm_encoder::ComponentTypeSection::new(); - for comp_ty_idx in last_processed_comp_ty..last_processed_comp_ty + num { - match &self.component_types[comp_ty_idx as usize] { - ComponentType::Defined(comp_ty) => { - let enc = component_ty_section.defined_type(); - match comp_ty { - wasmparser::ComponentDefinedType::Primitive(p) => { - enc.primitive(wasm_encoder::PrimitiveValType::from(*p)) - } - wasmparser::ComponentDefinedType::Record(records) => { - enc.record(records.iter().map(|record| { - (record.0, reencode.component_val_type(record.1)) - })); - } - wasmparser::ComponentDefinedType::Variant(variants) => enc - .variant(variants.iter().map(|variant| { - ( - variant.name, - variant - .ty - .map(|ty| reencode.component_val_type(ty)), - variant.refines, - ) - })), - wasmparser::ComponentDefinedType::List(l) => { - enc.list(reencode.component_val_type(*l)) - } - wasmparser::ComponentDefinedType::Tuple(tup) => enc - .tuple(tup.iter().map(|val_type| { - reencode.component_val_type(*val_type) - })), - wasmparser::ComponentDefinedType::Flags(flags) => { - enc.flags(flags.clone().into_vec().into_iter()) - } - wasmparser::ComponentDefinedType::Enum(en) => { - enc.enum_type(en.clone().into_vec().into_iter()) - } - wasmparser::ComponentDefinedType::Option(opt) => { - enc.option(reencode.component_val_type(*opt)) - } - wasmparser::ComponentDefinedType::Result { ok, err } => enc - .result( - ok.map(|val_type| { - reencode.component_val_type(val_type) - }), - err.map(|val_type| { - reencode.component_val_type(val_type) - }), - ), - wasmparser::ComponentDefinedType::Own(u) => enc.own(*u), - wasmparser::ComponentDefinedType::Borrow(u) => enc.borrow(*u), - wasmparser::ComponentDefinedType::Future(opt) => match opt { - Some(u) => { - enc.future(Some(reencode.component_val_type(*u))) - } - None => enc.future(None), - }, - wasmparser::ComponentDefinedType::Stream(opt) => match opt { - Some(u) => { - enc.stream(Some(reencode.component_val_type(*u))) - } - None => enc.stream(None), - }, - wasmparser::ComponentDefinedType::FixedSizeList(ty, i) => { - enc.fixed_size_list(reencode.component_val_type(*ty), *i) - } - } - } - ComponentType::Func(func_ty) => { - let mut enc = component_ty_section.function(); - enc.params(func_ty.params.iter().map( - |p: &(&str, wasmparser::ComponentValType)| { - (p.0, reencode.component_val_type(p.1)) - }, - )); - convert_results(func_ty.result, enc, &mut reencode); - } - ComponentType::Component(comp) => { - let mut new_comp = wasm_encoder::ComponentType::new(); - for c in comp.iter() { - match c { - ComponentTypeDeclaration::CoreType(core) => match core { - CoreType::Rec(recgroup) => { - let types = recgroup - .types() - .map(|ty| { - reencode - .sub_type(ty.to_owned()) - .unwrap_or_else(|_| panic!("Could not encode type as subtype: {:?}", ty)) - }) - .collect::>(); - - if recgroup.is_explicit_rec_group() { - new_comp.core_type().core().rec(types); - } else { - // it's implicit! - for subty in types { - new_comp.core_type().core().subtype(&subty); - } - } - } - CoreType::Module(module) => { - let enc = new_comp.core_type(); - convert_module_type_declaration( - module, - enc, - &mut reencode, - ); - } - }, - ComponentTypeDeclaration::Type(typ) => { - let enc = new_comp.ty(); - convert_component_type( - &(*typ).clone(), - enc, - &mut reencode, - ); - } - ComponentTypeDeclaration::Alias(a) => { - new_comp.alias(process_alias(a, &mut reencode)); - } - ComponentTypeDeclaration::Export { name, ty } => { - let ty = do_reencode( - *ty, - RoundtripReencoder::component_type_ref, - &mut reencode, - "component type", - ); - new_comp.export(name.0, ty); - } - ComponentTypeDeclaration::Import(imp) => { - let ty = do_reencode( - imp.ty, - RoundtripReencoder::component_type_ref, - &mut reencode, - "component type", - ); - new_comp.import(imp.name.0, ty); - } - } - } - component_ty_section.component(&new_comp); - } - ComponentType::Instance(inst) => { - component_ty_section - .instance(&convert_instance_type(inst, &mut reencode)); - } - ComponentType::Resource { rep, dtor } => { - component_ty_section - .resource(reencode.val_type(*rep).unwrap(), *dtor); - } - } - last_processed_comp_ty += 1; - } - component.section(&component_ty_section); - } - ComponentSection::ComponentImport => { - assert!(*num as usize + last_processed_imp as usize <= self.imports.len()); - let mut imports = wasm_encoder::ComponentImportSection::new(); - for imp_idx in last_processed_imp..last_processed_imp + num { - let imp = &self.imports[imp_idx as usize]; - let ty = do_reencode( - imp.ty, - RoundtripReencoder::component_type_ref, - &mut reencode, - "component type", - ); - imports.import(imp.name.0, ty); - last_processed_imp += 1; - } - component.section(&imports); - } - ComponentSection::ComponentExport => { - assert!(*num as usize + last_processed_exp as usize <= self.exports.len()); - let mut exports = wasm_encoder::ComponentExportSection::new(); - for exp_idx in last_processed_exp..last_processed_exp + num { - let exp = &self.exports[exp_idx as usize]; - exports.export( - exp.name.0, - reencode.component_export_kind(exp.kind), - exp.index, - exp.ty.map(|ty| { - do_reencode( - ty, - RoundtripReencoder::component_type_ref, - &mut reencode, - "component type", - ) - }), - ); - last_processed_exp += 1; - } - component.section(&exports); - } - ComponentSection::ComponentInstance => { - assert!( - *num as usize + last_processed_comp_inst as usize - <= self.component_instance.len() - ); - let mut instances = wasm_encoder::ComponentInstanceSection::new(); - for instance_idx in last_processed_comp_inst..last_processed_comp_inst + num { - let instance = &self.component_instance[instance_idx as usize]; - match instance { - ComponentInstance::Instantiate { - component_index, - args, - } => { - instances.instantiate( - *component_index, - args.iter().map(|arg| { - ( - arg.name, - reencode.component_export_kind(arg.kind), - arg.index, - ) - }), - ); - } - ComponentInstance::FromExports(export) => { - instances.export_items(export.iter().map(|value| { - ( - value.name.0, - reencode.component_export_kind(value.kind), - value.index, - ) - })); - } - } - last_processed_comp_inst += 1; - } - component.section(&instances); - } - ComponentSection::CoreInstance => { - assert!( - *num as usize + last_processed_core_inst as usize <= self.instances.len() - ); - let mut instances = wasm_encoder::InstanceSection::new(); - for instance_idx in last_processed_core_inst..last_processed_core_inst + num { - let instance = &self.instances[instance_idx as usize]; - match instance { - Instance::Instantiate { module_index, args } => { - instances.instantiate( - *module_index, - args.iter() - .map(|arg| (arg.name, ModuleArg::Instance(arg.index))), - ); - } - Instance::FromExports(exports) => { - instances.export_items(exports.iter().map(|export| { - ( - export.name, - wasm_encoder::ExportKind::from(export.kind), - export.index, - ) - })); - } - } - last_processed_core_inst += 1; - } - component.section(&instances); - } - ComponentSection::Alias => { - assert!(*num as usize + last_processed_alias as usize <= self.alias.len()); - let mut alias = ComponentAliasSection::new(); - for a_idx in last_processed_alias..last_processed_alias + num { - let a = &self.alias[a_idx as usize]; - alias.alias(process_alias(a, &mut reencode)); - last_processed_alias += 1; - } - component.section(&alias); - } - ComponentSection::Canon => { - assert!(*num as usize + last_processed_canon as usize <= self.canons.len()); - let mut canon_sec = wasm_encoder::CanonicalFunctionSection::new(); - for canon_idx in last_processed_canon..last_processed_canon + num { - let canon = &self.canons[canon_idx as usize]; - match canon { - CanonicalFunction::Lift { - core_func_index, - type_index, - options, - } => { - canon_sec.lift( - *core_func_index, - *type_index, - options.iter().map(|canon| { - do_reencode( - *canon, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }), - ); - } - CanonicalFunction::Lower { - func_index, - options, - } => { - canon_sec.lower( - *func_index, - options.iter().map(|canon| { - do_reencode( - *canon, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }), - ); - } - CanonicalFunction::ResourceNew { resource } => { - canon_sec.resource_new(*resource); - } - CanonicalFunction::ResourceDrop { resource } => { - canon_sec.resource_drop(*resource); - } - CanonicalFunction::ResourceRep { resource } => { - canon_sec.resource_rep(*resource); - } - CanonicalFunction::ResourceDropAsync { resource } => { - canon_sec.resource_drop_async(*resource); - } - CanonicalFunction::ThreadAvailableParallelism => { - canon_sec.thread_available_parallelism(); - } - CanonicalFunction::BackpressureSet => { - canon_sec.backpressure_set(); - } - CanonicalFunction::TaskReturn { result, options } => { - let options = options - .iter() - .cloned() - .map(|v| v.into()) - .collect::>(); - let result = result.map(|v| v.into()); - canon_sec.task_return(result, options); - } - CanonicalFunction::ThreadIndex => { - canon_sec.thread_index(); - } - CanonicalFunction::ThreadNewIndirect { - func_ty_index, - table_index, - } => { - canon_sec.thread_new_indirect(*func_ty_index, *table_index); - } - CanonicalFunction::ThreadSwitchTo { cancellable } => { - canon_sec.thread_switch_to(*cancellable); - } - CanonicalFunction::ThreadSuspend { cancellable } => { - canon_sec.thread_suspend(*cancellable); - } - CanonicalFunction::ThreadResumeLater => { - canon_sec.thread_resume_later(); - } - CanonicalFunction::ThreadYield { cancellable } => { - canon_sec.thread_yield(*cancellable); - } - CanonicalFunction::ThreadYieldTo { cancellable } => { - canon_sec.thread_yield_to(*cancellable); - } - CanonicalFunction::WaitableSetNew => { - canon_sec.waitable_set_new(); - } - CanonicalFunction::WaitableSetWait { - cancellable, - memory, - } => { - canon_sec.waitable_set_wait(*cancellable, *memory); - } - CanonicalFunction::WaitableSetPoll { - cancellable, - memory, - } => { - canon_sec.waitable_set_poll(*cancellable, *memory); - } - CanonicalFunction::WaitableSetDrop => { - canon_sec.waitable_set_drop(); - } - CanonicalFunction::WaitableJoin => { - canon_sec.waitable_join(); - } - CanonicalFunction::SubtaskDrop => { - canon_sec.subtask_drop(); - } - CanonicalFunction::StreamNew { ty } => { - canon_sec.stream_new(*ty); - } - CanonicalFunction::StreamRead { ty, options } => { - canon_sec.stream_read( - *ty, - options - .into_iter() - .map(|t| { - do_reencode( - *t, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }) - .collect::>(), - ); - } - CanonicalFunction::StreamWrite { ty, options } => { - canon_sec.stream_write( - *ty, - options - .into_iter() - .map(|t| { - do_reencode( - *t, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }) - .collect::>(), - ); - } - CanonicalFunction::StreamCancelRead { ty, async_ } => { - canon_sec.stream_cancel_read(*ty, *async_); - } - CanonicalFunction::StreamCancelWrite { ty, async_ } => { - canon_sec.stream_cancel_write(*ty, *async_); - } - CanonicalFunction::FutureNew { ty } => { - canon_sec.future_new(*ty); - } - CanonicalFunction::FutureRead { ty, options } => { - canon_sec.future_read( - *ty, - options - .into_iter() - .map(|t| { - do_reencode( - *t, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }) - .collect::>(), - ); - } - CanonicalFunction::FutureWrite { ty, options } => { - canon_sec.future_write( - *ty, - options - .into_iter() - .map(|t| { - do_reencode( - *t, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }) - .collect::>(), - ); - } - CanonicalFunction::FutureCancelRead { ty, async_ } => { - canon_sec.future_cancel_read(*ty, *async_); - } - CanonicalFunction::FutureCancelWrite { ty, async_ } => { - canon_sec.future_cancel_write(*ty, *async_); - } - CanonicalFunction::ErrorContextNew { options } => { - canon_sec.error_context_new( - options - .into_iter() - .map(|t| { - do_reencode( - *t, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }) - .collect::>(), - ); - } - CanonicalFunction::ErrorContextDebugMessage { options } => { - canon_sec.error_context_debug_message( - options - .into_iter() - .map(|t| { - do_reencode( - *t, - RoundtripReencoder::canonical_option, - &mut reencode, - "canonical option", - ) - }) - .collect::>(), - ); - } - CanonicalFunction::ErrorContextDrop => { - canon_sec.error_context_drop(); - } - CanonicalFunction::ThreadSpawnRef { func_ty_index } => { - canon_sec.thread_spawn_ref(*func_ty_index); - } - CanonicalFunction::ThreadSpawnIndirect { - func_ty_index, - table_index, - } => { - canon_sec.thread_spawn_indirect(*func_ty_index, *table_index); - } - CanonicalFunction::TaskCancel => { - canon_sec.task_cancel(); - } - CanonicalFunction::ContextGet(i) => { - canon_sec.context_get(*i); - } - CanonicalFunction::ContextSet(i) => { - canon_sec.context_set(*i); - } - CanonicalFunction::SubtaskCancel { async_ } => { - canon_sec.subtask_cancel(*async_); - } - CanonicalFunction::StreamDropReadable { ty } => { - canon_sec.stream_drop_readable(*ty); - } - CanonicalFunction::StreamDropWritable { ty } => { - canon_sec.stream_drop_writable(*ty); - } - CanonicalFunction::FutureDropReadable { ty } => { - canon_sec.future_drop_readable(*ty); - } - CanonicalFunction::FutureDropWritable { ty } => { - canon_sec.future_drop_writable(*ty); - } - CanonicalFunction::BackpressureInc => {} - CanonicalFunction::BackpressureDec => {} - } - last_processed_canon += 1; - } - component.section(&canon_sec); - } - ComponentSection::ComponentStartSection => { - // Should only be 1 start section - assert_eq!(self.start_section.len(), 1); - let start_fn = &self.start_section[0]; - let start_sec = wasm_encoder::ComponentStartSection { - function_index: start_fn.func_index, - args: start_fn.arguments.iter(), - results: start_fn.results, - }; - component.section(&start_sec); - } - ComponentSection::CustomSection => { - assert!( - *num as usize + last_processed_custom_section as usize - <= self.custom_sections.len() - ); - for custom_sec_idx in - last_processed_custom_section..last_processed_custom_section + num - { - let section = &self - .custom_sections - .get_by_id(CustomSectionID(custom_sec_idx)); - component.section(&wasm_encoder::CustomSection { - name: std::borrow::Cow::Borrowed(section.name), - data: section.data.clone(), - }); - last_processed_custom_section += 1; - } - } - } - } - - // Name section - let mut name_sec = wasm_encoder::ComponentNameSection::new(); - - if let Some(comp_name) = &self.component_name { - name_sec.component(comp_name); - } - - name_sec.core_funcs(&self.core_func_names); - name_sec.core_tables(&self.table_names); - name_sec.core_memories(&self.memory_names); - name_sec.core_tags(&self.tag_names); - name_sec.core_globals(&self.global_names); - name_sec.core_types(&self.core_type_names); - name_sec.core_modules(&self.module_names); - name_sec.core_instances(&self.core_instances_names); - name_sec.funcs(&self.func_names); - name_sec.values(&self.value_names); - name_sec.types(&self.type_names); - name_sec.components(&self.components_names); - name_sec.instances(&self.instance_names); - - // Add the name section back to the component - component.section(&name_sec); - - component - } - - /// Print a rudimentary textual representation of a `Component` - pub fn print(&self) { - // Print Alias - if !self.alias.is_empty() { - eprintln!("Alias Section:"); - for alias in self.alias.iter() { - print_alias(alias); - } - eprintln!(); - } - - // Print CoreType - if !self.core_types.is_empty() { - eprintln!("Core Type Section:"); - for cty in self.core_types.iter() { - print_core_type(cty); - } - eprintln!(); - } - - // Print ComponentType - if !self.component_types.is_empty() { - eprintln!("Component Type Section:"); - for cty in self.component_types.iter() { - print_component_type(cty); - } - eprintln!(); - } - - // Print Imports - if !self.imports.is_empty() { - eprintln!("Imports Section:"); - for imp in self.imports.iter() { - print_component_import(imp); - } - eprintln!(); - } - - // Print Exports - if !self.imports.is_empty() { - eprintln!("Exports Section:"); - for exp in self.exports.iter() { - print_component_export(exp); - } - eprintln!(); - } - } - - /// Emit the Component into a wasm binary file. - pub fn emit_wasm(&mut self, file_name: &str) -> Result<(), std::io::Error> { - let comp = self.encode_comp(); - let wasm = comp.finish(); - std::fs::write(file_name, wasm)?; - Ok(()) - } - - /// Get Local Function ID by name - // Note: returned absolute id here - pub fn get_fid_by_name(&self, name: &str, module_idx: ModuleID) -> Option { - for (idx, func) in self.modules[*module_idx as usize] - .functions - .iter() - .enumerate() - { - if let FuncKind::Local(l) = &func.kind { - if let Some(n) = &l.body.name { - if n == name { - return Some(FunctionID(idx as u32)); - } - } - } - } - None - } -} diff --git a/src/ir/component/alias.rs b/src/ir/component/alias.rs new file mode 100644 index 00000000..f42bed4b --- /dev/null +++ b/src/ir/component/alias.rs @@ -0,0 +1,78 @@ +use crate::ir::id::AliasId; +use crate::ir::AppendOnlyVec; +use wasmparser::{ComponentAlias, ComponentExternalKind, ExternalKind}; + +#[derive(Debug, Default)] +pub struct Aliases<'a> { + pub items: AppendOnlyVec>, + + num_core_funcs: usize, + num_core_funcs_added: usize, + + num_funcs: usize, + num_funcs_added: usize, + pub(crate) num_types: usize, + num_types_added: usize, +} +impl<'a> Aliases<'a> { + pub fn new(items: AppendOnlyVec>) -> Self { + let (mut num_core_funcs, mut num_funcs, mut num_types) = (0, 0, 0); + for i in items.iter() { + match i { + ComponentAlias::CoreInstanceExport { kind, .. } => { + if kind == &ExternalKind::Func { + num_core_funcs += 1 + } + } + ComponentAlias::InstanceExport { kind, .. } => match kind { + ComponentExternalKind::Type => num_types += 1, + ComponentExternalKind::Func => num_funcs += 1, + _ => {} + }, + _ => {} + } + } + Self { + items, + num_core_funcs, + num_funcs, + num_types, + ..Self::default() + } + } + + pub(crate) fn add(&mut self, alias: ComponentAlias<'a>) -> (u32, AliasId) { + let ty_id = self.items.len() as u32; + let ty_inner_id = match alias { + ComponentAlias::CoreInstanceExport { kind, .. } => match kind { + ExternalKind::Func => { + self.num_core_funcs += 1; + self.num_core_funcs_added += 1; + + self.num_core_funcs - 1 + } + _ => todo!(), + }, + ComponentAlias::InstanceExport { kind, .. } => match kind { + ComponentExternalKind::Type => { + self.num_types += 1; + self.num_types_added += 1; + + self.num_types - 1 + } + ComponentExternalKind::Func => { + self.num_funcs += 1; + self.num_funcs_added += 1; + + self.num_funcs - 1 + } + + _ => todo!("haven't supported this yet: {:#?}", kind), + }, + _ => todo!(), + }; + + self.items.push(alias); + (ty_inner_id as u32, AliasId(ty_id)) + } +} diff --git a/src/ir/component/canons.rs b/src/ir/component/canons.rs new file mode 100644 index 00000000..c99abe02 --- /dev/null +++ b/src/ir/component/canons.rs @@ -0,0 +1,50 @@ +use crate::ir::id::CanonicalFuncId; +use crate::ir::AppendOnlyVec; +use wasmparser::CanonicalFunction; + +#[derive(Debug, Default)] +pub struct Canons { + pub items: AppendOnlyVec, + + pub(crate) num_lift_lower: usize, + num_lift_lower_added: usize, +} +impl Canons { + pub fn new(items: AppendOnlyVec) -> Self { + let mut num_lift_lower = 0; + for i in items.iter() { + if matches!( + i, + CanonicalFunction::Lift { .. } | CanonicalFunction::Lower { .. } + ) { + num_lift_lower += 1; + } + } + + Self { + items, + num_lift_lower, + ..Self::default() + } + } + + /// Add a new canonical function to the component. + pub(crate) fn add(&mut self, canon: CanonicalFunction) -> (u32, CanonicalFuncId) { + let fid = self.items.len() as u32; + let fid_inner = match canon { + CanonicalFunction::Lift { .. } | CanonicalFunction::Lower { .. } => { + self.num_lift_lower += 1; + self.num_lift_lower_added += 1; + + self.num_lift_lower - 1 + } + _ => todo!( + "Haven't implemented support to add this canonical function type yet: {:#?}", + canon + ), + }; + self.items.push(canon); + + (fid_inner as u32, CanonicalFuncId(fid)) + } +} diff --git a/src/ir/component/idx_spaces.rs b/src/ir/component/idx_spaces.rs new file mode 100644 index 00000000..dce0c7e0 --- /dev/null +++ b/src/ir/component/idx_spaces.rs @@ -0,0 +1,913 @@ +use crate::ir::component::refs::IndexedRef; +use crate::ir::component::section::ComponentSection; +use crate::ir::types::CustomSection; +use crate::{Component, Module}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::Debug; +use std::rc::Rc; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentExternalKind, ComponentImport, + ComponentInstance, ComponentOuterAliasKind, ComponentStartFunction, ComponentType, + ComponentTypeDeclaration, ComponentTypeRef, CoreType, ExternalKind, Import, Instance, + InstanceTypeDeclaration, InstantiationArgKind, ModuleTypeDeclaration, OuterAliasKind, RecGroup, + SubType, TypeRef, +}; + +pub(crate) type ScopeId = usize; + +/// Every IR node can have a reference to this to allow for instrumentation +/// to have access to the index stores and perform manipulations! +pub(crate) type StoreHandle = Rc>; + +#[derive(Default, Debug)] +pub(crate) struct IndexStore { + pub scopes: HashMap, + next_id: usize, +} +impl IndexStore { + /// Create a new scope in the scope store. + pub fn new_scope(&mut self) -> ScopeId { + let id = self.use_next_id(); + self.scopes.insert(id, IndexScope::new(id)); + + id + } + /// Lookup where to find an item in the component IR based on its assumed ID + /// (the ID given to the item at parse and IR-injection time). This is done WITHOUT + /// caching the found result, which is helpful when performing an operation when the + /// IndexStore cannot be mutable. + /// Returns: + /// - .0,SpaceSubtype: the space vector to look up this index in + /// - .1,usize: the index of the vector in the IR to find the item + /// - .2,Option: the index within the node to find the item (as in pointing to a certain subtype in a recgroup) + pub fn index_from_assumed_id_no_cache( + &self, + id: &ScopeId, + r: &IndexedRef, + ) -> (SpaceSubtype, usize, Option) { + self.get(id).index_from_assumed_id_no_cache(r) + } + /// Lookup where to find an item in the component IR based on its assumed ID + /// (the ID given to the item at parse and IR-injection time). The found result will + /// then be cached for faster future lookups. + /// Returns: + /// - .0,SpaceSubtype: the space vector to look up this index in + /// - .1,usize: the index of the vector in the IR to find the item + /// - .2,Option: the index within the node to find the item (as in pointing to a certain subtype in a recgroup) + pub fn index_from_assumed_id( + &mut self, + id: &ScopeId, + r: &IndexedRef, + ) -> (SpaceSubtype, usize, Option) { + self.get_mut(id).index_from_assumed_id(r) + } + /// Give an assumed ID for some IR item (done at parse and IR-injection time). + pub fn assign_assumed_id( + &mut self, + id: &ScopeId, + space: &Space, + section: &ComponentSection, + curr_idx: usize, + ) -> Option { + self.get_mut(id).assign_assumed_id(space, section, curr_idx) + } + + /// Iterate over a list of items to assign an assumed ID for. + pub fn assign_assumed_id_for( + &mut self, + id: &ScopeId, + items: &[I], + curr_idx: usize, + sections: &Vec, + ) { + self.get_mut(id) + .assign_assumed_id_for(items, curr_idx, sections) + } + /// Iterate over a list of _boxed_ items to assign an assumed ID for. + pub fn assign_assumed_id_for_boxed( + &mut self, + id: &ScopeId, + items: &[Box], + curr_idx: usize, + sections: &Vec, + ) { + self.get_mut(id) + .assign_assumed_id_for_boxed(items, curr_idx, sections) + } + /// Use up the next ID to assign in the tracker. + fn use_next_id(&mut self) -> ScopeId { + let next = self.next_id; + self.next_id += 1; + + next + } + + /// Get an index scope that can be mutated. + fn get_mut(&mut self, id: &ScopeId) -> &mut IndexScope { + self.scopes.get_mut(id).unwrap() + } + /// Get an immutable ref to an index scope. + fn get(&self, id: &ScopeId) -> &IndexScope { + self.scopes.get(id).unwrap() + } +} + +/// A single lexical index scope in a WebAssembly component. +/// +/// An `IndexScope` contains all index spaces that are *visible at one level* +/// of the component hierarchy. Each scope corresponds to a lexical boundary +/// introduced by constructs such as: +/// +/// - a `component` +/// - a `component type` +/// - a `component instance` +/// +/// Within a scope, indices are allocated monotonically and are only valid +/// relative to that scope. Nested constructs introduce *new* `IndexScope`s, +/// which may reference items in outer scopes via `(outer N ...)` declarations. +/// +/// ## Relationship to the Component Model +/// +/// In the WebAssembly Component Model, index spaces are *lexically scoped*. +/// For example: +/// +/// - Component functions, values, instances, and types each have their own +/// index spaces. +/// - Core index spaces (functions, types, memories, etc.) are also scoped when +/// introduced at the component level. +/// - Entering a nested component (or component type / instance) creates a new +/// set of index spaces that shadow outer ones. +/// +/// `IndexScope` models exactly one such lexical level. +/// +/// ## Scope Stack Usage +/// +/// `IndexScope` is intended to be used in conjunction with a stack structure +/// (e.g. `ScopeStack`), where: +/// +/// - entering a nested construct pushes a new `IndexScope` +/// - exiting the construct pops it +/// - resolving `(outer depth ...)` references indexes into the stack by depth +/// +/// This design allows encode-time traversal to correctly reindex references +/// even when IR nodes are visited in an arbitrary order (e.g. during +/// instrumentation). +/// +/// ## Encode-Time Semantics +/// +/// During encoding, the active `IndexScope` determines: +/// +/// - where newly declared items are allocated +/// - how referenced indices are remapped +/// - which outer scope to consult for `(outer ...)` references +/// +/// `IndexScope` does **not** represent all index spaces in the component; +/// it represents only those visible at a single lexical level. +/// +/// We build these index spaces following the order of the original IR, then traverse the IR out-of-order +/// based on the instrumentation injections, we must enable the lookup of spaces through assigned IDs. This +/// ensures that we do not use the wrong index space for a node in a reordered list of IR nodes. +/// +/// +/// ## Design Note +/// +/// This type intentionally separates *scope structure* from *IR structure*. +/// IR nodes do not own scopes; instead, scopes are entered and exited explicitly +/// during traversal. This keeps index resolution explicit, debuggable, and +/// faithful to the specification. +#[derive(Clone, Debug, Default)] +pub(crate) struct IndexScope { + pub(crate) id: ScopeId, + + // Component-level spaces + pub comp: IdxSpace, + pub comp_func: IdxSpace, + pub comp_val: IdxSpace, + pub comp_type: IdxSpace, + pub comp_inst: IdxSpace, + + // Core space (added by component model) + pub core_inst: IdxSpace, // (these are module instances) + pub module: IdxSpace, + + // Core spaces that exist at the component-level + pub core_type: IdxSpace, + pub core_func: IdxSpace, // these are canonical function decls! + pub core_memory: IdxSpace, + pub core_table: IdxSpace, + pub core_global: IdxSpace, + pub core_tag: IdxSpace, +} +impl IndexScope { + pub fn new(id: ScopeId) -> Self { + Self { + id, + ..Self::default() + } + } + + /// This function is called as I parse a component. This is necessary since different items encoded + /// in a component index into different namespaces. There is not a one-to-one relationship between + /// those items' indices in a vector to the index space it manipulates! + /// + /// Consider a canonical function, this can take place of an index in the core-function OR the + /// component-function index space! + pub fn assign_assumed_id_for( + &mut self, + items: &[I], + curr_idx: usize, + sections: &Vec, // one per item + ) { + debug_assert_eq!(items.len(), sections.len()); + for ((i, item), section) in items.iter().enumerate().zip(sections) { + self.assign_assumed_id(&item.index_space_of(), section, curr_idx + i); + } + } + pub fn assign_assumed_id_for_boxed( + &mut self, + items: &[Box], + curr_idx: usize, + sections: &Vec, // one per item + ) { + debug_assert_eq!(items.len(), sections.len()); + for ((i, item), section) in items.iter().enumerate().zip(sections) { + self.assign_assumed_id(&item.index_space_of(), section, curr_idx + i); + } + } + + /// This is also called as I parse a component for the same reason mentioned above in the documentation for [`IdxSpaces.assign_assumed_id_for`]. + pub fn assign_assumed_id( + &mut self, + space: &Space, + section: &ComponentSection, + curr_idx: usize, + ) -> Option { + self.get_space_mut(space) + .map(|space| space.assign_assumed_id(section, curr_idx)) + } + + pub fn lookup_assumed_id( + &self, + space: &Space, + section: &ComponentSection, + vec_idx: usize, + ) -> usize { + self.get_space(space) + .and_then(|s| s.lookup_assumed_id(section, vec_idx)) + .unwrap_or_else(|| { + panic!("[{space:?}] Internal error: No assumed ID for index: {vec_idx}") + }) + } + + pub fn lookup_assumed_id_with_subvec( + &self, + space: &Space, + section: &ComponentSection, + vec_idx: usize, + subvec_idx: usize, + ) -> usize { + self.get_space(space) + .and_then(|space| space.lookup_assumed_id_with_subvec(section, vec_idx, subvec_idx)) + .unwrap_or_else(|| { + panic!("[{space:?}] Internal error: No assumed ID for index: {vec_idx}, subvec index: {subvec_idx}") + }) + } + + pub fn index_from_assumed_id( + &mut self, + r: &IndexedRef, + ) -> (SpaceSubtype, usize, Option) { + self.get_space_mut(&r.space) + .and_then(|space| space.index_from_assumed_id(r.index as usize)) + .unwrap_or_else(|| { + panic!( + "[{:?}@scope{}] Internal error: No index for assumed ID: {}", + r.space, self.id, r.index + ) + }) + } + + pub fn index_from_assumed_id_no_cache( + &self, + r: &IndexedRef, + ) -> (SpaceSubtype, usize, Option) { + self.get_space(&r.space) + .and_then(|space| space.index_from_assumed_id_no_cache(r.index as usize)) + .unwrap_or_else(|| { + panic!( + "[{:?}@scope{}] Internal error: No index for assumed ID: {}", + r.space, self.id, r.index + ) + }) + } + + // =================== + // ==== UTILITIES ==== + // =================== + + fn get_space_mut(&mut self, space: &Space) -> Option<&mut IdxSpace> { + let s = match space { + Space::Comp => &mut self.comp, + Space::CompFunc => &mut self.comp_func, + Space::CompVal => &mut self.comp_val, + Space::CompType => &mut self.comp_type, + Space::CompInst => &mut self.comp_inst, + Space::CoreInst => &mut self.core_inst, + Space::CoreModule => &mut self.module, + Space::CoreType => &mut self.core_type, + Space::CoreFunc => &mut self.core_func, + Space::CoreMemory => &mut self.core_memory, + Space::CoreTable => &mut self.core_table, + Space::CoreGlobal => &mut self.core_global, + Space::CoreTag => &mut self.core_tag, + Space::NA => return None, + }; + Some(s) + } + + fn get_space(&self, space: &Space) -> Option<&IdxSpace> { + let s = match space { + Space::Comp => &self.comp, + Space::CompFunc => &self.comp_func, + Space::CompVal => &self.comp_val, + Space::CompType => &self.comp_type, + Space::CompInst => &self.comp_inst, + Space::CoreInst => &self.core_inst, + Space::CoreModule => &self.module, + Space::CoreType => &self.core_type, + Space::CoreFunc => &self.core_func, + Space::CoreMemory => &self.core_memory, + Space::CoreTable => &self.core_table, + Space::CoreGlobal => &self.core_global, + Space::CoreTag => &self.core_tag, + Space::NA => return None, + }; + Some(s) + } +} + +/// How we represent the assumed IDs at some index location in the IR +#[derive(Clone, Debug)] +enum AssumedIdForIdx { + /// This can be mapped to a SINGLE assumed ID + Single(usize), + /// OR multiple IDs for an index in the IR (rec groups take up a single + /// index in the core_types vector, but can have multiple core type IDs. One + /// for each rec group subtype!) + Multiple(Vec), +} +impl AssumedIdForIdx { + /// Returns whether this is a match for the passed assumed_id AND + /// the optional index in the IR's subvec + fn matches(&self, assumed_id: usize) -> (bool, Option) { + match self { + AssumedIdForIdx::Single(my_id) => return (*my_id == assumed_id, None), + AssumedIdForIdx::Multiple(sub_ids) => { + for (idx, id) in sub_ids.iter().enumerate() { + if *id == assumed_id { + return (true, Some(idx)); + } + } + } + } + (false, None) + } + fn append(&mut self, assumed_id: usize) { + match self { + Self::Single(my_id) => *self = AssumedIdForIdx::Multiple(vec![*my_id, assumed_id]), + Self::Multiple(sub_ids) => sub_ids.push(assumed_id), + } + } + fn unwrap_single(&self) -> usize { + match self { + AssumedIdForIdx::Single(my_id) => *my_id, + _ => unreachable!(), + } + } + fn unwrap_for_idx(&self, subvec_idx: usize) -> usize { + match self { + AssumedIdForIdx::Single(my_id) => { + debug_assert_eq!(subvec_idx, 0); + *my_id + } + AssumedIdForIdx::Multiple(subvec) => subvec[subvec_idx], + } + } +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct IdxSpace { + /// This is the current ID that we've reached associated with this index space. + current_id: usize, + + /// Tracks the index in the MAIN item vector to the ID we've assumed for it: `main_idx -> assumed_id` + /// This ID will be used to reference that item in the IR. + main_assumed_ids: HashMap, + + // The below maps are to track assumed IDs for item vectors that index into this index space. + /// Tracks the index in the ALIAS item vector to the ID we've assumed for it: `alias_idx -> assumed_id` + /// This ID will be used to reference that item in the IR. + alias_assumed_ids: HashMap, + /// Tracks the index in the IMPORT item vector to the ID we've assumed for it: `imports_idx -> assumed_id` + /// This ID will be used to reference that item in the IR. + imports_assumed_ids: HashMap, + /// Tracks the index in the EXPORT item vector to the ID we've assumed for it: `exports_idx -> assumed_id` + /// This ID will be used to reference that item in the IR. + exports_assumed_ids: HashMap, + + index_from_assumed_id_cache: HashMap)>, +} +impl IdxSpace { + pub fn curr_id(&self) -> usize { + // This returns the ID that we've reached thus far while encoding + self.current_id + } + + fn next(&mut self) -> usize { + let curr = self.current_id; + self.current_id += 1; + curr + } + + pub fn lookup_assumed_id(&self, section: &ComponentSection, vec_idx: usize) -> Option { + let (_group, vector) = match section { + ComponentSection::ComponentImport => ("imports", &self.imports_assumed_ids), + ComponentSection::ComponentExport => ("exports", &self.exports_assumed_ids), + ComponentSection::Alias => ("aliases", &self.alias_assumed_ids), + + ComponentSection::Component + | ComponentSection::Module + | ComponentSection::CoreType + | ComponentSection::ComponentType + | ComponentSection::CoreInstance + | ComponentSection::ComponentInstance + | ComponentSection::Canon + | ComponentSection::CustomSection + | ComponentSection::ComponentStartSection => ("main", &self.main_assumed_ids), + }; + + vector.get(&vec_idx).map(|res| res.unwrap_single()) + } + + pub fn lookup_assumed_id_with_subvec( + &self, + section: &ComponentSection, + vec_idx: usize, + subvec_idx: usize, + ) -> Option { + let (_group, vector) = match section { + ComponentSection::ComponentImport => ("imports", &self.imports_assumed_ids), + ComponentSection::ComponentExport => ("exports", &self.exports_assumed_ids), + ComponentSection::Alias => ("aliases", &self.alias_assumed_ids), + + ComponentSection::Component + | ComponentSection::Module + | ComponentSection::CoreType + | ComponentSection::ComponentType + | ComponentSection::CoreInstance + | ComponentSection::ComponentInstance + | ComponentSection::Canon + | ComponentSection::CustomSection + | ComponentSection::ComponentStartSection => ("main", &self.main_assumed_ids), + }; + + vector + .get(&vec_idx) + .map(|res| res.unwrap_for_idx(subvec_idx)) + } + + /// Returns: + /// - .0,SpaceSubtype: the space vector to look up this index in + /// - .1,usize: the index of the vector in the IR to find the item + /// - .2,Option: the index within the node to find the item (as in pointing to a certain subtype in a recgroup) + pub fn index_from_assumed_id( + &mut self, + assumed_id: usize, + ) -> Option<(SpaceSubtype, usize, Option)> { + if let Some(cached_data) = self.index_from_assumed_id_cache.get(&assumed_id) { + return Some(*cached_data); + } + + // We haven't cached this yet, we must do the less efficient logic and do a full lookup, + // then we can cache what we find! + let maps = [ + (SpaceSubtype::Main, &self.main_assumed_ids), + (SpaceSubtype::Import, &self.imports_assumed_ids), + (SpaceSubtype::Export, &self.exports_assumed_ids), + (SpaceSubtype::Alias, &self.alias_assumed_ids), + ]; + + for (subty, map) in maps.iter() { + for (idx, assumed) in map.iter() { + let (matches, opt_subidx) = assumed.matches(assumed_id); + if matches { + let result = (*subty, *idx, opt_subidx); + // cache what we found + self.index_from_assumed_id_cache.insert(assumed_id, result); + + return Some(result); + } + } + } + None + } + /// Returns: + /// - .0,SpaceSubtype: the space vector to look up this index in + /// - .1,usize: the index of the vector in the IR to find the item + /// - .2,Option: the index within the node to find the item (as in pointing to a certain subtype in a recgroup) + pub fn index_from_assumed_id_no_cache( + &self, + assumed_id: usize, + ) -> Option<(SpaceSubtype, usize, Option)> { + if let Some(cached_data) = self.index_from_assumed_id_cache.get(&assumed_id) { + return Some(*cached_data); + } + + // We haven't cached this yet, we must do the less efficient logic and do a full lookup, + // then we can cache what we find! + let maps = [ + (SpaceSubtype::Main, &self.main_assumed_ids), + (SpaceSubtype::Import, &self.imports_assumed_ids), + (SpaceSubtype::Export, &self.exports_assumed_ids), + (SpaceSubtype::Alias, &self.alias_assumed_ids), + ]; + + for (subty, map) in maps.iter() { + for (idx, assumed) in map.iter() { + let (matches, opt_subidx) = assumed.matches(assumed_id); + if matches { + let result = (*subty, *idx, opt_subidx); + return Some(result); + } + } + } + None + } + + pub fn assign_assumed_id(&mut self, section: &ComponentSection, vec_idx: usize) -> usize { + let assumed_id = self.curr_id(); + self.next(); + let to_update = match section { + ComponentSection::ComponentImport => &mut self.imports_assumed_ids, + ComponentSection::ComponentExport => &mut self.exports_assumed_ids, + ComponentSection::Alias => &mut self.alias_assumed_ids, + + ComponentSection::Component + | ComponentSection::Module + | ComponentSection::CoreType + | ComponentSection::ComponentType + | ComponentSection::CoreInstance + | ComponentSection::ComponentInstance + | ComponentSection::Canon + | ComponentSection::CustomSection + | ComponentSection::ComponentStartSection => &mut self.main_assumed_ids, + }; + to_update + .entry(vec_idx) + .and_modify(|entry| { + entry.append(assumed_id); + }) + .or_insert(AssumedIdForIdx::Single(assumed_id)); + + assumed_id + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum SpaceSubtype { + Export, + Import, + Alias, + Main, +} + +// Logic to figure out which index space is being manipulated +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Space { + // Component-level spaces + Comp, + CompFunc, + CompVal, + CompType, + CompInst, + + // Core-level spaces + CoreInst, + CoreModule, + CoreType, + CoreFunc, + CoreMemory, + CoreTable, + CoreGlobal, + CoreTag, + + // isn't part of an index space + NA, +} + +// Trait for centralizing index space mapping +pub trait IndexSpaceOf { + fn index_space_of(&self) -> Space; +} + +impl IndexSpaceOf for CustomSection<'_> { + fn index_space_of(&self) -> Space { + Space::NA + } +} + +impl IndexSpaceOf for ComponentStartFunction { + fn index_space_of(&self) -> Space { + Space::NA + } +} + +impl IndexSpaceOf for ComponentTypeRef { + fn index_space_of(&self) -> Space { + // This is the index space to use when looking up + // the IDs in this ref. + match self { + Self::Value(_) => Space::CompVal, + Self::Instance(_) => Space::CompInst, + Self::Component(_) => Space::CompType, + Self::Module(_) => Space::CoreModule, + Self::Func(_) | Self::Type(_) => Space::CompType, + } + } +} + +impl IndexSpaceOf for ComponentImport<'_> { + fn index_space_of(&self) -> Space { + // This is the index space of THIS IMPORT! + // Not what space to use for the IDs of the typeref! + match self.ty { + ComponentTypeRef::Func(_) => Space::CompFunc, + ComponentTypeRef::Value(_) => Space::CompVal, + ComponentTypeRef::Type(_) => Space::CompType, + ComponentTypeRef::Instance(_) => Space::CompInst, + ComponentTypeRef::Component(_) => Space::Comp, // verified in wat (instantiate.wast) + ComponentTypeRef::Module(_) => Space::CoreModule, + } + } +} + +impl IndexSpaceOf for ComponentExport<'_> { + fn index_space_of(&self) -> Space { + // This is the index space of THIS EXPORT! + // Not what space to use for the IDs of the typeref! + match self.kind { + ComponentExternalKind::Module => Space::CoreModule, + ComponentExternalKind::Func => Space::CompFunc, + ComponentExternalKind::Value => Space::CompVal, + ComponentExternalKind::Type => Space::CompType, + ComponentExternalKind::Instance => Space::CompInst, + ComponentExternalKind::Component => Space::CompInst, // verified in alias.wast + } + } +} + +impl IndexSpaceOf for Instance<'_> { + fn index_space_of(&self) -> Space { + Space::CoreInst + } +} + +impl<'a> IndexSpaceOf for ComponentAlias<'a> { + fn index_space_of(&self) -> Space { + match self { + // Aliasing an export of a component instance + ComponentAlias::InstanceExport { kind, .. } => match kind { + ComponentExternalKind::Func => Space::CompFunc, + ComponentExternalKind::Value => Space::CompVal, + ComponentExternalKind::Type => Space::CompType, + ComponentExternalKind::Instance => Space::CompInst, + ComponentExternalKind::Component => Space::Comp, // verified in alias.wast + ComponentExternalKind::Module => Space::CoreModule, + }, + + // Aliasing an export of a core instance + ComponentAlias::CoreInstanceExport { kind, .. } => match kind { + ExternalKind::Func => Space::CoreFunc, + ExternalKind::Memory => Space::CoreMemory, + ExternalKind::Table => Space::CoreTable, + ExternalKind::Global => Space::CoreGlobal, + ExternalKind::Tag => Space::CoreTag, + ExternalKind::FuncExact => Space::CoreFunc, + }, + + // Aliasing an outer item + ComponentAlias::Outer { kind, .. } => match kind { + ComponentOuterAliasKind::CoreModule => Space::CoreModule, + ComponentOuterAliasKind::CoreType => Space::CoreType, + ComponentOuterAliasKind::Type => Space::CompType, + ComponentOuterAliasKind::Component => Space::Comp, // verified in alias.wast + }, + } + } +} + +impl IndexSpaceOf for CanonicalFunction { + fn index_space_of(&self) -> Space { + match self { + CanonicalFunction::Lower { .. } => Space::CoreFunc, + CanonicalFunction::Lift { .. } => Space::CompFunc, + + // Resource-related functions reference a resource type + CanonicalFunction::ResourceNew { .. } + | CanonicalFunction::ResourceDrop { .. } + | CanonicalFunction::ResourceDropAsync { .. } + | CanonicalFunction::ResourceRep { .. } => Space::CoreFunc, + + // Thread spawn / new indirect → function type + CanonicalFunction::ThreadSpawnRef { .. } + | CanonicalFunction::ThreadSpawnIndirect { .. } => Space::CompFunc, + CanonicalFunction::ThreadNewIndirect { .. } => Space::CoreFunc, + + // Task-related functions operate on values + CanonicalFunction::TaskReturn { .. } + | CanonicalFunction::TaskCancel + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::SubtaskCancel { .. } => Space::CoreFunc, + + // Context access + CanonicalFunction::ContextGet(_) | CanonicalFunction::ContextSet(_) => Space::CoreFunc, + + // Stream / Future functions operate on types + CanonicalFunction::StreamCancelRead { .. } + | CanonicalFunction::StreamCancelWrite { .. } + | CanonicalFunction::FutureCancelRead { .. } + | CanonicalFunction::FutureCancelWrite { .. } + | CanonicalFunction::FutureNew { .. } + | CanonicalFunction::FutureRead { .. } + | CanonicalFunction::FutureWrite { .. } + | CanonicalFunction::FutureDropReadable { .. } + | CanonicalFunction::FutureDropWritable { .. } + | CanonicalFunction::StreamNew { .. } + | CanonicalFunction::StreamRead { .. } + | CanonicalFunction::StreamWrite { .. } + | CanonicalFunction::StreamDropReadable { .. } + | CanonicalFunction::StreamDropWritable { .. } => Space::CoreFunc, + + // Error context → operate on values + CanonicalFunction::ErrorContextNew { .. } + | CanonicalFunction::ErrorContextDebugMessage { .. } + | CanonicalFunction::ErrorContextDrop => Space::CoreFunc, + + // Waitable set → memory + CanonicalFunction::WaitableSetWait { .. } + | CanonicalFunction::WaitableSetPoll { .. } + | CanonicalFunction::WaitableSetNew + | CanonicalFunction::WaitableSetDrop + | CanonicalFunction::WaitableJoin => Space::CoreFunc, + + // Thread functions + CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater + | CanonicalFunction::ThreadYieldTo { .. } + | CanonicalFunction::ThreadYield { .. } + | CanonicalFunction::ThreadAvailableParallelism => Space::CoreFunc, + + CanonicalFunction::BackpressureInc | CanonicalFunction::BackpressureDec => { + Space::CoreFunc + } + } + } +} + +impl IndexSpaceOf for Module<'_> { + fn index_space_of(&self) -> Space { + Space::CoreModule + } +} + +impl IndexSpaceOf for Component<'_> { + fn index_space_of(&self) -> Space { + Space::Comp // verified + } +} + +impl IndexSpaceOf for CoreType<'_> { + fn index_space_of(&self) -> Space { + Space::CoreType + } +} + +impl IndexSpaceOf for RecGroup { + fn index_space_of(&self) -> Space { + Space::CoreType + } +} + +impl IndexSpaceOf for SubType { + fn index_space_of(&self) -> Space { + Space::CoreType + } +} + +impl IndexSpaceOf for ComponentType<'_> { + fn index_space_of(&self) -> Space { + Space::CompType + } +} + +impl IndexSpaceOf for ComponentInstance<'_> { + fn index_space_of(&self) -> Space { + Space::CompInst + } +} + +impl IndexSpaceOf for InstantiationArgKind { + fn index_space_of(&self) -> Space { + match self { + InstantiationArgKind::Instance => Space::CoreInst, + } + } +} + +impl IndexSpaceOf for ExternalKind { + fn index_space_of(&self) -> Space { + match self { + ExternalKind::Func => Space::CoreFunc, + ExternalKind::Table => Space::CoreTable, + ExternalKind::Memory => Space::CoreMemory, + ExternalKind::Global => Space::CoreGlobal, + ExternalKind::Tag => Space::CoreTag, + ExternalKind::FuncExact => Space::CoreFunc, + } + } +} + +impl IndexSpaceOf for ComponentExternalKind { + fn index_space_of(&self) -> Space { + match self { + ComponentExternalKind::Func => Space::CompFunc, + ComponentExternalKind::Value => Space::CompVal, + ComponentExternalKind::Type => Space::CompType, + ComponentExternalKind::Instance => Space::CompInst, + ComponentExternalKind::Component => Space::Comp, // verified in alias.wast + ComponentExternalKind::Module => Space::CoreModule, + } + } +} + +impl IndexSpaceOf for ComponentOuterAliasKind { + fn index_space_of(&self) -> Space { + match self { + ComponentOuterAliasKind::CoreModule => Space::CoreModule, + ComponentOuterAliasKind::CoreType => Space::CoreType, + ComponentOuterAliasKind::Type => Space::CompType, + ComponentOuterAliasKind::Component => Space::Comp, // verified in wat (alias.wast) + } + } +} + +impl IndexSpaceOf for ComponentTypeDeclaration<'_> { + fn index_space_of(&self) -> Space { + match self { + ComponentTypeDeclaration::CoreType(ty) => ty.index_space_of(), + ComponentTypeDeclaration::Type(ty) => ty.index_space_of(), + ComponentTypeDeclaration::Alias(alias) => alias.index_space_of(), + ComponentTypeDeclaration::Export { ty, .. } => ty.index_space_of(), + ComponentTypeDeclaration::Import(import) => import.index_space_of(), + } + } +} + +impl IndexSpaceOf for InstanceTypeDeclaration<'_> { + fn index_space_of(&self) -> Space { + match self { + InstanceTypeDeclaration::CoreType(ty) => ty.index_space_of(), + InstanceTypeDeclaration::Type(ty) => ty.index_space_of(), + InstanceTypeDeclaration::Alias(a) => a.index_space_of(), + InstanceTypeDeclaration::Export { ty, .. } => ty.index_space_of(), + } + } +} + +impl IndexSpaceOf for ModuleTypeDeclaration<'_> { + fn index_space_of(&self) -> Space { + match self { + ModuleTypeDeclaration::Type(_) => Space::CoreType, + ModuleTypeDeclaration::Export { ty, .. } => ty.index_space_of(), + ModuleTypeDeclaration::OuterAlias { kind, .. } => kind.index_space_of(), + ModuleTypeDeclaration::Import(Import { ty, .. }) => ty.index_space_of(), + } + } +} + +impl IndexSpaceOf for TypeRef { + fn index_space_of(&self) -> Space { + Space::CoreType + } +} + +impl IndexSpaceOf for OuterAliasKind { + fn index_space_of(&self) -> Space { + match self { + OuterAliasKind::Type => Space::CoreType, + } + } +} diff --git a/src/ir/component/mod.rs b/src/ir/component/mod.rs new file mode 100644 index 00000000..bd274362 --- /dev/null +++ b/src/ir/component/mod.rs @@ -0,0 +1,928 @@ +#![allow(clippy::too_many_arguments)] +//! Intermediate Representation of a wasm component. + +use crate::encode::component::encode; +use crate::error::Error; +use crate::error::Error::IO; +use crate::ir::component::alias::Aliases; +use crate::ir::component::canons::Canons; +use crate::ir::component::idx_spaces::{ + IndexSpaceOf, IndexStore, ScopeId, Space, SpaceSubtype, StoreHandle, +}; +use crate::ir::component::refs::{GetItemRef, GetTypeRefs}; +use crate::ir::component::scopes::{IndexScopeRegistry, RegistryHandle}; +use crate::ir::component::section::{ + get_sections_for_comp_ty, get_sections_for_core_ty_and_assign_top_level_ids, + populate_space_for_comp_ty, populate_space_for_core_ty, ComponentSection, +}; +use crate::ir::component::types::ComponentTypes; +use crate::ir::helpers::{ + print_alias, print_component_export, print_component_import, print_component_type, + print_core_type, +}; +use crate::ir::id::{ + AliasFuncId, AliasId, CanonicalFuncId, ComponentExportId, ComponentId, ComponentTypeFuncId, + ComponentTypeId, ComponentTypeInstanceId, CoreInstanceId, CustomSectionID, FunctionID, + GlobalID, ModuleID, +}; +use crate::ir::module::module_globals::Global; +use crate::ir::module::Module; +use crate::ir::types::{CustomSection, CustomSections}; +use crate::ir::AppendOnlyVec; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::rc::Rc; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentFuncType, ComponentImport, + ComponentInstance, ComponentStartFunction, ComponentType, CoreType, Encoding, Instance, + InstanceTypeDeclaration, NameMap, Parser, Payload, +}; + +mod alias; +mod canons; +pub(crate) mod idx_spaces; +pub mod refs; +pub(crate) mod scopes; +pub(crate) mod section; +mod types; +pub mod visitor; + +#[derive(Debug)] +/// Intermediate Representation of a wasm component. +pub struct Component<'a> { + pub id: ComponentId, + /// Nested Components + // These have scopes, but the scopes are looked up by ComponentId + pub components: AppendOnlyVec>, + /// Modules + // These have scopes, but they aren't handled by component encoding logic + pub modules: AppendOnlyVec>, + /// Component Types + // These can have scopes and need to be looked up by a pointer to the IR node --> Box the value! + pub component_types: ComponentTypes<'a>, + /// Component Instances + pub component_instance: AppendOnlyVec>, + /// Canons + pub canons: Canons, + + /// Alias + pub alias: Aliases<'a>, + /// Imports + pub imports: AppendOnlyVec>, + /// Exports + pub exports: AppendOnlyVec>, + + /// Core Types + // These can have scopes and need to be looked up by a pointer to the IR node --> Box the value! + pub core_types: AppendOnlyVec>>, + /// Core Instances + pub instances: AppendOnlyVec>, + + // Tracks the index spaces of this component. + pub(crate) space_id: ScopeId, // cached for quick lookup! + pub(crate) scope_registry: RegistryHandle, + pub(crate) index_store: StoreHandle, + + /// Custom sections + pub custom_sections: CustomSections<'a>, + /// Component Start Section + pub start_section: AppendOnlyVec, + /// Sections of the Component. Represented as (#num of occurrences of a section, type of section) + pub sections: Vec<(u32, ComponentSection)>, + num_sections: usize, + + // Names + pub(crate) component_name: Option, + pub(crate) core_func_names: Names, + pub(crate) global_names: Names, + pub(crate) memory_names: Names, + pub(crate) tag_names: Names, + pub(crate) table_names: Names, + pub(crate) module_names: Names, + pub(crate) core_instances_names: Names, + pub(crate) core_type_names: Names, + pub(crate) type_names: Names, + pub(crate) instance_names: Names, + pub(crate) components_names: Names, + pub(crate) func_names: Names, + pub(crate) value_names: Names, +} + +impl<'a> Component<'a> { + /// Emit the Component into a wasm binary file. + pub fn emit_wasm(&self, file_name: &str) -> crate::ir::types::Result<()> { + let wasm = self.encode()?; + std::fs::write(file_name, wasm).map_err(IO)?; + Ok(()) + } + + fn add_section_and_get_id( + &mut self, + space: Space, + sect: ComponentSection, + idx: usize, + ) -> usize { + // get and save off the assumed id + let assumed_id = + self.index_store + .borrow_mut() + .assign_assumed_id(&self.space_id, &space, §, idx); + + self.add_section(sect); + + assumed_id.unwrap_or(idx) + } + + fn add_section(&mut self, sect: ComponentSection) { + // add to section order list + if !self.sections.is_empty() && self.sections[self.num_sections - 1].1 == sect { + self.sections[self.num_sections - 1].0 += 1; + } else { + self.sections.push((1, sect)); + } + } + + /// Add a Module to this Component. + pub fn add_module(&mut self, module: Module<'a>) -> ModuleID { + let idx = self.modules.len(); + let id = + self.add_section_and_get_id(module.index_space_of(), ComponentSection::Module, idx); + self.modules.push(module); + + ModuleID(id as u32) + } + + /// Add a Global to this Component. + pub fn add_globals(&mut self, global: Global, module_idx: ModuleID) -> GlobalID { + self.modules[*module_idx as usize].globals.add(global) + } + + pub fn add_custom_section(&mut self, section: CustomSection<'a>) -> CustomSectionID { + let id = self.custom_sections.add(section); + self.add_section(ComponentSection::CustomSection); + + id + } + + /// Add an Import to this Component. + pub fn add_import(&mut self, import: ComponentImport<'a>) -> u32 { + let idx = self.imports.len(); + let id = self.add_section_and_get_id( + import.index_space_of(), + ComponentSection::ComponentImport, + idx, + ); + self.imports.push(import); + + id as u32 + } + + /// Add an Aliased function to this Component. + pub fn add_alias_func(&mut self, alias: ComponentAlias<'a>) -> (AliasFuncId, AliasId) { + let space = alias.index_space_of(); + let (_item_id, alias_id) = self.alias.add(alias); + let id = self.add_section_and_get_id(space, ComponentSection::Alias, *alias_id as usize); + + (AliasFuncId(id as u32), alias_id) + } + + /// Add a Canonical Function to this Component. + pub fn add_canon_func(&mut self, canon: CanonicalFunction) -> CanonicalFuncId { + let space = canon.index_space_of(); + let idx = self.canons.add(canon).1; + let id = self.add_section_and_get_id(space, ComponentSection::Canon, *idx as usize); + + CanonicalFuncId(id as u32) + } + + /// Add a Component Type to this Component. + pub(crate) fn add_component_type( + &mut self, + component_ty: ComponentType<'a>, + ) -> (u32, ComponentTypeId) { + let space = component_ty.index_space_of(); + let ids = self.component_types.add(component_ty); + let id = + self.add_section_and_get_id(space, ComponentSection::ComponentType, *ids.1 as usize); + + // Handle the index space of this node + populate_space_for_comp_ty( + self.component_types.items.last().unwrap(), + self.scope_registry.clone(), + self.index_store.clone(), + ); + + (id as u32, ids.1) + } + + /// Add a Component Type that is an Instance to this component. + pub fn add_type_instance( + &mut self, + decls: Vec>, + ) -> (ComponentTypeInstanceId, ComponentTypeId) { + let (ty_inst_id, ty_id) = + self.add_component_type(ComponentType::Instance(decls.into_boxed_slice())); + + // almost account for aliased types! + (ComponentTypeInstanceId(ty_inst_id), ty_id) + } + + /// Add a Component Type that is a Function to this component. + pub fn add_type_func( + &mut self, + ty: ComponentFuncType<'a>, + ) -> (ComponentTypeFuncId, ComponentTypeId) { + let (ty_inst_id, ty_id) = self.add_component_type(ComponentType::Func(ty)); + + // almost account for aliased types! + (ComponentTypeFuncId(ty_inst_id), ty_id) + } + + /// Add a new core instance to this component. + pub fn add_core_instance(&mut self, instance: Instance<'a>) -> CoreInstanceId { + let idx = self.instances.len(); + let id = self.add_section_and_get_id( + instance.index_space_of(), + ComponentSection::CoreInstance, + idx, + ); + self.instances.push(instance); + + CoreInstanceId(id as u32) + } + + fn add_to_sections( + has_subscope: bool, + sections: &mut Vec<(u32, ComponentSection)>, + new_sections: &[ComponentSection], + num_sections: &mut usize, + sections_added: u32, + ) { + // We can only collapse sections if the new sections don't have + // inner index spaces associated with them. + let can_collapse = !has_subscope; + + if can_collapse + && *num_sections > 0 + && sections[*num_sections - 1].1 == *new_sections.last().unwrap() + { + sections[*num_sections - 1].0 += sections_added; + return; + } + // Cannot collapse these, add one at a time! + for sect in new_sections.iter() { + sections.push((1, sect.clone())); + *num_sections += 1; + } + } + + /// Parse a `Component` from a wasm binary. + /// + /// Set enable_multi_memory to `true` to support parsing modules using multiple memories. + /// Set with_offsets to `true` to save opcode pc offset metadata during parsing + /// (can be used to determine the static pc offset inside a function body of the start of any opcode). + /// + /// # Example + /// + /// ```no_run + /// use wirm::Component; + /// + /// let file = "path_to_file"; + /// let buff = wat::parse_file(file).expect("couldn't convert the input wat to Wasm"); + /// let comp = Component::parse(&buff, false, false).unwrap(); + /// ``` + pub fn parse( + wasm: &'_ [u8], + enable_multi_memory: bool, + with_offsets: bool, + ) -> Result, Error> { + let parser = Parser::new(0); + + let registry = IndexScopeRegistry::default(); + let mut store = IndexStore::default(); + let space_id = store.new_scope(); + let mut next_comp_id = 0; + let res = Component::parse_comp( + wasm, + enable_multi_memory, + with_offsets, + parser, + 0, + &mut vec![], + space_id, + Rc::new(RefCell::new(registry)), + Rc::new(RefCell::new(store)), + &mut next_comp_id, + ); + res + } + + fn parse_comp( + wasm: &'a [u8], + enable_multi_memory: bool, + with_offsets: bool, + parser: Parser, + start: usize, + parent_stack: &mut Vec, + space_id: ScopeId, + registry_handle: RegistryHandle, + store_handle: StoreHandle, + next_comp_id: &mut u32, + ) -> Result, Error> { + let my_comp_id = ComponentId(*next_comp_id); + *next_comp_id += 1; + + let mut modules = AppendOnlyVec::default(); + let mut core_types = AppendOnlyVec::default(); + let mut component_types = AppendOnlyVec::default(); + let mut imports = AppendOnlyVec::default(); + let mut exports = AppendOnlyVec::default(); + let mut instances = AppendOnlyVec::default(); + let mut canons = AppendOnlyVec::default(); + let mut alias = AppendOnlyVec::default(); + let mut component_instance = AppendOnlyVec::default(); + let mut components = AppendOnlyVec::default(); + let mut start_section = AppendOnlyVec::default(); + let mut custom_sections = vec![]; + + let mut sections = vec![]; + let mut num_sections: usize = 0; + let mut stack = vec![]; + + // Names + let mut component_name: Option = None; + let mut core_func_names = Names::default(); + let mut global_names = Names::default(); + let mut tag_names = Names::default(); + let mut memory_names = Names::default(); + let mut table_names = Names::default(); + let mut module_names = Names::default(); + let mut core_instances_names = Names::default(); + let mut instance_names = Names::default(); + let mut components_names = Names::default(); + let mut func_names = Names::default(); + let mut value_names = Names::default(); + let mut core_type_names = Names::default(); + let mut type_names = Names::default(); + + for payload in parser.parse_all(wasm) { + let payload = payload?; + if let Payload::End(..) = payload { + if !stack.is_empty() { + stack.pop(); + } + } + if !stack.is_empty() { + continue; + } + match payload { + Payload::ComponentImportSection(import_section_reader) => { + let mut temp: Vec = import_section_reader + .into_iter() + .collect::>()?; + let l = temp.len(); + let new_sections = vec![ComponentSection::ComponentImport; l]; + store_handle.borrow_mut().assign_assumed_id_for( + &space_id, + &temp, + imports.len(), + &new_sections, + ); + imports.append(&mut temp); + Self::add_to_sections( + false, + &mut sections, + &new_sections, + &mut num_sections, + l as u32, + ); + } + Payload::ComponentExportSection(export_section_reader) => { + let mut temp: Vec = export_section_reader + .into_iter() + .collect::>()?; + let l = temp.len(); + let new_sections = vec![ComponentSection::ComponentExport; l]; + store_handle.borrow_mut().assign_assumed_id_for( + &space_id, + &temp, + exports.len(), + &new_sections, + ); + exports.append(&mut temp); + Self::add_to_sections( + false, + &mut sections, + &new_sections, + &mut num_sections, + l as u32, + ); + } + Payload::InstanceSection(instance_section_reader) => { + let mut temp: Vec = instance_section_reader + .into_iter() + .collect::>()?; + let l = temp.len(); + let new_sections = vec![ComponentSection::CoreInstance; l]; + store_handle.borrow_mut().assign_assumed_id_for( + &space_id, + &temp, + instances.len(), + &new_sections, + ); + instances.append(&mut temp); + Self::add_to_sections( + false, + &mut sections, + &new_sections, + &mut num_sections, + l as u32, + ); + } + Payload::CoreTypeSection(core_type_reader) => { + let mut temp: Vec> = core_type_reader + .into_iter() + .map(|res| res.map(Box::new)) + .collect::>()?; + + let old_len = core_types.len(); + let l = temp.len(); + core_types.append(&mut temp); + + let mut new_sects = vec![]; + let mut has_subscope = false; + for (idx, ty) in core_types[old_len..].iter().enumerate() { + let (new_sect, sect_has_subscope) = + get_sections_for_core_ty_and_assign_top_level_ids( + ty, + old_len + idx, + &space_id, + store_handle.clone(), + ); + has_subscope |= sect_has_subscope; + new_sects.push(new_sect); + } + + Self::add_to_sections( + has_subscope, + &mut sections, + &new_sects, + &mut num_sections, + l as u32, + ); + } + Payload::ComponentTypeSection(component_type_reader) => { + let mut temp: Vec> = component_type_reader + .into_iter() + .map(|res| res.map(Box::new)) + .collect::>()?; + + let old_len = component_types.len(); + let l = temp.len(); + component_types.append(&mut temp); + + let mut new_sects = vec![]; + let mut has_subscope = false; + for ty in &component_types[old_len..] { + let (new_sect, sect_has_subscope) = get_sections_for_comp_ty(ty); + has_subscope |= sect_has_subscope; + new_sects.push(new_sect); + } + + store_handle.borrow_mut().assign_assumed_id_for_boxed( + &space_id, + &component_types[old_len..], + old_len, + &new_sects, + ); + Self::add_to_sections( + has_subscope, + &mut sections, + &new_sects, + &mut num_sections, + l as u32, + ); + } + Payload::ComponentInstanceSection(component_instances) => { + let mut temp: Vec = + component_instances.into_iter().collect::>()?; + let l = temp.len(); + let new_sections = vec![ComponentSection::ComponentInstance; l]; + store_handle.borrow_mut().assign_assumed_id_for( + &space_id, + &temp, + component_instance.len(), + &new_sections, + ); + component_instance.append(&mut temp); + Self::add_to_sections( + false, + &mut sections, + &new_sections, + &mut num_sections, + l as u32, + ); + } + Payload::ComponentAliasSection(alias_reader) => { + let mut temp: Vec = + alias_reader.into_iter().collect::>()?; + let l = temp.len(); + let new_sections = vec![ComponentSection::Alias; l]; + store_handle.borrow_mut().assign_assumed_id_for( + &space_id, + &temp, + alias.len(), + &new_sections, + ); + alias.append(&mut temp); + Self::add_to_sections( + false, + &mut sections, + &new_sections, + &mut num_sections, + l as u32, + ); + } + Payload::ComponentCanonicalSection(canon_reader) => { + let mut temp: Vec = + canon_reader.into_iter().collect::>()?; + let l = temp.len(); + let new_sections = vec![ComponentSection::Canon; l]; + store_handle.borrow_mut().assign_assumed_id_for( + &space_id, + &temp, + canons.len(), + &new_sections, + ); + canons.append(&mut temp); + Self::add_to_sections( + false, + &mut sections, + &new_sections, + &mut num_sections, + l as u32, + ); + } + Payload::ModuleSection { + parser, + unchecked_range, + } => { + // Indicating the start of a new module + parent_stack.push(Encoding::Module); + stack.push(Encoding::Module); + let m = Module::parse_internal( + &wasm[unchecked_range.start - start..unchecked_range.end - start], + enable_multi_memory, + with_offsets, + parser, + )?; + store_handle.borrow_mut().assign_assumed_id( + &space_id, + &m.index_space_of(), + &ComponentSection::Module, + modules.len(), + ); + modules.push(m); + Self::add_to_sections( + false, + &mut sections, + &[ComponentSection::Module], + &mut num_sections, + 1, + ); + } + Payload::ComponentSection { + parser, + unchecked_range, + } => { + let sub_space_id = store_handle.borrow_mut().new_scope(); + let sect = ComponentSection::Component; + + parent_stack.push(Encoding::Component); + stack.push(Encoding::Component); + let cmp = Component::parse_comp( + &wasm[unchecked_range.start - start..unchecked_range.end - start], + enable_multi_memory, + with_offsets, + parser, + unchecked_range.start, + &mut stack, + sub_space_id, + Rc::clone(®istry_handle), + Rc::clone(&store_handle), + next_comp_id, + )?; + store_handle.borrow_mut().assign_assumed_id( + &space_id, + &cmp.index_space_of(), + §, + components.len(), + ); + components.push(cmp); + + Self::add_to_sections(true, &mut sections, &[sect], &mut num_sections, 1); + } + Payload::ComponentStartSection { start, range: _ } => { + start_section.push(start); + Self::add_to_sections( + false, + &mut sections, + &[ComponentSection::ComponentStartSection], + &mut num_sections, + 1, + ); + } + Payload::CustomSection(custom_section_reader) => { + match custom_section_reader.as_known() { + wasmparser::KnownCustom::ComponentName(name_section_reader) => { + for subsection in name_section_reader { + #[allow(clippy::single_match)] + match subsection? { + wasmparser::ComponentName::Component { name, .. } => { + component_name = Some(name.parse().unwrap()) + } + wasmparser::ComponentName::CoreFuncs(names) => { + core_func_names.add_all(names); + } + wasmparser::ComponentName::CoreGlobals(names) => { + global_names.add_all(names); + } + wasmparser::ComponentName::CoreTables(names) => { + table_names.add_all(names); + } + wasmparser::ComponentName::CoreModules(names) => { + module_names.add_all(names); + } + wasmparser::ComponentName::CoreInstances(names) => { + core_instances_names.add_all(names); + } + wasmparser::ComponentName::CoreTypes(names) => { + core_type_names.add_all(names); + } + wasmparser::ComponentName::Types(names) => { + type_names.add_all(names); + } + wasmparser::ComponentName::Instances(names) => { + instance_names.add_all(names); + } + wasmparser::ComponentName::Components(names) => { + components_names.add_all(names); + } + wasmparser::ComponentName::Funcs(names) => { + func_names.add_all(names); + } + wasmparser::ComponentName::Values(names) => { + value_names.add_all(names); + } + wasmparser::ComponentName::CoreMemories(names) => { + memory_names.add_all(names); + } + wasmparser::ComponentName::CoreTags(names) => { + tag_names.add_all(names); + } + wasmparser::ComponentName::Unknown { .. } => {} + } + } + } + _ => { + custom_sections + .push((custom_section_reader.name(), custom_section_reader.data())); + Self::add_to_sections( + false, + &mut sections, + &[ComponentSection::CustomSection], + &mut num_sections, + 1, + ); + } + } + } + Payload::UnknownSection { + id, + contents: _, + range: _, + } => return Err(Error::UnknownSection { section_id: id }), + Payload::Version { .. } | Payload::End { .. } => {} // nothing to do + other => println!("TODO: Not sure what to do for: {:?}", other), + } + } + + // Scope discovery + for comp in components.iter() { + let comp_id = comp.id; + let sub_space_id = comp.space_id; + registry_handle + .borrow_mut() + .register_comp(comp_id, sub_space_id); + } + for ty in core_types.iter() { + populate_space_for_core_ty(ty, registry_handle.clone(), store_handle.clone()); + } + for ty in component_types.iter() { + populate_space_for_comp_ty(ty, registry_handle.clone(), store_handle.clone()); + } + + let comp = Component { + id: my_comp_id, + modules, + alias: Aliases::new(alias), + core_types, + component_types: ComponentTypes::new(component_types), + imports, + exports, + instances, + component_instance, + canons: Canons::new(canons), + space_id, + scope_registry: registry_handle, + index_store: store_handle, + custom_sections: CustomSections::new(custom_sections), + sections, + start_section, + num_sections, + component_name, + core_func_names, + global_names, + memory_names, + tag_names, + table_names, + module_names, + core_instances_names, + core_type_names, + type_names, + instance_names, + components_names, + func_names, + components, + value_names, + }; + + comp.scope_registry + .borrow_mut() + .register_comp(my_comp_id, space_id); + + Ok(comp) + } + + /// Encode this component into a WebAssembly binary. + /// + /// This method performs encoding in three high-level phases: + /// + /// 1. **Collect** – Walks the component IR and records all items that will be + /// encoded, along with any index references that need to be rewritten. + /// 2. **Assign** – Resolves all index references after instrumentation so that + /// every reference points to its final concrete index. + /// 3. **Encode** – Emits the final WebAssembly binary using the resolved indices. + /// + /// # Nested Components + /// + /// Components may be arbitrarily nested. During traversal, the encoder maintains + /// a stack of component IDs that tracks which component is currently being + /// visited. A registry maps component IDs to their corresponding components, + /// allowing the encoder to correctly resolve cross-component references. + /// + /// # Scoped Definitions + /// + /// Many IR nodes introduce scopes (such as types, instances, or component-local + /// definitions). To support correct index resolution in the presence of deep + /// nesting and instrumentation, the encoder tracks scopes explicitly: + /// + /// - A scope stack is maintained during traversal. + /// - Each scoped IR node is registered by identity in a scope registry. + /// - Index lookups can recover the correct scope for any IR node in O(1) time. + /// + /// This design allows instrumentation to safely insert, reorder, or modify + /// nodes without breaking encoding invariants. + /// + /// # Panics + /// + /// Panics if encoding encounters an internal inconsistency, such as: + /// + /// - An index reference that cannot be resolved to a registered scope + /// - A malformed or structurally invalid component + /// - Violations of encoding invariants introduced by instrumentation + /// + /// These panics indicate a bug in the encoder or in transformations applied + /// to the component prior to encoding, rather than a recoverable runtime error. + /// + /// # Example + /// + /// ```no_run + /// use wirm::Component; + /// + /// let file = "path/to/file.wasm"; + /// let buff = wat::parse_file(file).expect("couldn't convert the input wat to Wasm"); + /// let mut comp = Component::parse(&buff, false, false).unwrap(); + /// let result = comp.encode(); + /// ``` + pub fn encode(&self) -> crate::ir::types::Result> { + encode(self) + } + + /// Lookup the type for an exported `lift` canonical function. + pub fn get_type_of_exported_lift_func( + &self, + export_id: ComponentExportId, + ) -> Option<&ComponentType<'a>> { + let mut store = self.index_store.borrow_mut(); + if let Some(export) = self.exports.get(*export_id as usize) { + let func_ref = export.get_item_ref(); + let (vec, f_idx, subidx) = + store.index_from_assumed_id_no_cache(&self.space_id, &func_ref.ref_); + debug_assert!(subidx.is_none(), "a lift function shouldn't reference anything with a subvec space (like a recgroup)"); + let ty = match vec { + SpaceSubtype::Export | SpaceSubtype::Import => { + unreachable!() + } + SpaceSubtype::Alias => self.alias.items[f_idx].get_item_ref(), + SpaceSubtype::Main => *self.canons.items[f_idx].get_type_refs().first().unwrap(), + }; + let (ty, t_idx, subidx) = store.index_from_assumed_id(&self.space_id, &ty.ref_); + debug_assert!(subidx.is_none(), "a lift function shouldn't reference anything with a subvec space (like a recgroup)"); + if !matches!(ty, SpaceSubtype::Main) { + panic!("Internal error: expected main space, got {ty:?}"); + } + + let res = self.component_types.items.get(t_idx); + res.map(|v| &**v) + } else { + None + } + } + + /// Print a rudimentary textual representation of a `Component` + pub fn print(&self) { + // Print Alias + if !self.alias.items.is_empty() { + eprintln!("Alias Section:"); + for alias in self.alias.items.iter() { + print_alias(alias); + } + eprintln!(); + } + + // Print CoreType + if !self.core_types.is_empty() { + eprintln!("Core Type Section:"); + for cty in self.core_types.iter() { + print_core_type(cty); + } + eprintln!(); + } + + // Print ComponentType + if !self.component_types.items.is_empty() { + eprintln!("Component Type Section:"); + for cty in self.component_types.items.iter() { + print_component_type(cty); + } + eprintln!(); + } + + // Print Imports + if !self.imports.is_empty() { + eprintln!("Imports Section:"); + for imp in self.imports.iter() { + print_component_import(imp); + } + eprintln!(); + } + + // Print Exports + if !self.imports.is_empty() { + eprintln!("Exports Section:"); + for exp in self.exports.iter() { + print_component_export(exp); + } + eprintln!(); + } + } + + /// Get Local Function ID by name + // Note: returned absolute id here + pub fn get_fid_by_name(&self, name: &str, module_idx: ModuleID) -> Option { + self.modules[*module_idx as usize] + .functions + .get_local_fid_by_name(name) + } +} + +#[derive(Debug, Default)] +pub struct Names { + // Maintains keys in sorted order (need to encode in order of the index) + pub(crate) names: BTreeMap, +} +impl Names { + pub fn get(&self, id: u32) -> Option<&str> { + self.names.get(&id).map(|s| s.as_str()) + } + pub(crate) fn add_all(&mut self, names: NameMap) { + for name in names { + let naming = name.unwrap(); + self.add(naming.index, naming.name); + } + } + fn add(&mut self, id: u32, name: &str) { + self.names.insert(id, name.to_string()); + } +} diff --git a/src/ir/component/refs.rs b/src/ir/component/refs.rs new file mode 100644 index 00000000..58925bb2 --- /dev/null +++ b/src/ir/component/refs.rs @@ -0,0 +1,1551 @@ +use crate::ir::component::idx_spaces::{IndexSpaceOf, Space}; +use crate::ir::types::CustomSection; +use crate::Module; +use wasmparser::{ + CanonicalFunction, CanonicalOption, ComponentAlias, ComponentDefinedType, ComponentExport, + ComponentFuncType, ComponentImport, ComponentInstance, ComponentInstantiationArg, + ComponentStartFunction, ComponentType, ComponentTypeDeclaration, ComponentTypeRef, + ComponentValType, CompositeInnerType, CompositeType, ContType, CoreType, Export, FieldType, + Instance, InstanceTypeDeclaration, InstantiationArg, ModuleTypeDeclaration, RecGroup, RefType, + StorageType, SubType, TagType, TypeBounds, TypeRef, ValType, VariantCase, +}; + +/// A trait for extracting all referenced indices from an IR node. +/// +/// This provides a unified way to retrieve all semantic references +/// contained within a node, regardless of their specific role. +/// +/// Implementations typically delegate to one or more of the +/// `Get*Refs` traits depending on the node's structure. +/// +/// References contained within inner nested scopes are not included. +/// For example, the references inside the nested core type in +/// the following WAT will not be returned: +/// (type (;0;) ;; << outer component type +/// (instance +/// (core type (;0;) ;; << inner core type (nested scope) +/// (module +/// ( . . . ) ;; << nodes within core type that could hold refs +/// ) +/// ) +/// ) +/// ) +/// This design decision simplifies reasoning about reference resolution. +/// If such references are needed, reference lookup must be performed on +/// those inner IR nodes. +pub trait ReferencedIndices { + /// Returns all referenced indices contained within this node. + /// + /// The returned [`RefKind`] values include both: + /// + /// - The referenced [`IndexedRef`] + /// - The semantic role of the reference + fn referenced_indices(&self) -> Vec; +} +/// Extracts references to `components` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetCompRefs { + fn get_comp_refs(&self) -> Vec; +} +/// Extracts references to `modules` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetModuleRefs { + fn get_module_refs(&self) -> Vec; +} +/// Extracts references to component OR core `types` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetTypeRefs { + fn get_type_refs(&self) -> Vec; +} +/// Extracts references to component OR core `functions` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetFuncRefs { + fn get_func_refs(&self) -> Vec; +} +/// Extracts the single reference to a component OR core `function` the node has. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetFuncRef { + fn get_func_ref(&self) -> RefKind; +} +/// Extracts references to `memories` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetMemRefs { + fn get_mem_refs(&self) -> Vec; +} +/// Extracts references to `tables` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetTableRefs { + fn get_tbl_refs(&self) -> Vec; +} +/// Extracts references to any `item` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetItemRefs { + fn get_item_refs(&self) -> Vec; +} +/// Extracts the single reference to an `item` that the node has. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetItemRef { + fn get_item_ref(&self) -> RefKind; +} +/// Extracts references to `parameters` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetParamRefs { + fn get_param_refs(&self) -> Vec; +} +/// Extracts references to `results` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetResultRefs { + fn get_result_refs(&self) -> Vec; +} +/// Extracts references to `arguments` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetArgRefs { + fn get_arg_refs(&self) -> Vec; +} +/// Extracts references to `descriptors` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetDescriptorRefs { + fn get_descriptor_refs(&self) -> Vec; +} +/// Extracts references to `describes` from a node. References within inner +/// scopes are not included, see [`ReferencedIndices`]; +pub trait GetDescribesRefs { + fn get_describes_refs(&self) -> Vec; +} + +/// Describes the semantic role of a referenced index. +/// +/// This distinguishes *how* a referenced item is used within a node. +/// For example, a function index may be referenced as: +/// +/// - A declaration +/// - A parameter +/// - A result +/// - A descriptor +/// +/// The role is orthogonal to the index space itself. +#[derive(Clone, Copy)] +pub enum RefRole { + Comp, + Module, + Inst, + Func, + Type, + Val, + Mem, + Table, + Global, + Tag, + + /// A declaration at the given position. + Decl(usize), + /// A parameter at the given index. + Param(usize), + /// A result at the given index. + Result(usize), + /// An argument at the given index. + Arg(usize), + /// A `descriptor` reference. + Descriptor, + /// A `describes` reference. + Describes, +} +impl RefRole { + pub(crate) fn role_of(space: &Space) -> Self { + match space { + Space::Comp => Self::Comp, + Space::CoreModule => Self::Module, + Space::CompInst | Space::CoreInst => Self::Inst, + Space::CompFunc | Space::CoreFunc => Self::Func, + Space::CompType | Space::CoreType => Self::Type, + Space::CompVal => Self::Val, + Space::CoreMemory => Self::Mem, + Space::CoreTable => Self::Table, + Space::CoreGlobal => Self::Global, + Space::CoreTag => Self::Tag, + Space::NA => unreachable!(), + } + } +} + +/// A single referenced index annotated with its semantic role. +/// +/// This is the fundamental unit returned by `Get*Refs` and +/// `ReferencedIndices`. +/// +/// A `RefKind` combines: +/// +/// - The raw [`IndexedRef`] (depth + space + index) +/// - The semantic [`RefRole`] describing how it is used +#[derive(Clone, Copy)] +pub struct RefKind { + pub role: RefRole, + pub ref_: IndexedRef, +} +impl RefKind { + pub(crate) fn new(ref_: IndexedRef) -> Self { + let role = RefRole::role_of(&ref_.space); + Self { role, ref_ } + } + pub(crate) fn decl(idx: usize, ref_: IndexedRef) -> Self { + Self { + role: RefRole::Decl(idx), + ref_, + } + } + pub(crate) fn param(idx: usize, ref_: IndexedRef) -> Self { + Self { + role: RefRole::Param(idx), + ref_, + } + } + pub(crate) fn result(idx: usize, ref_: IndexedRef) -> Self { + Self { + role: RefRole::Result(idx), + ref_, + } + } + pub(crate) fn descriptor(ref_: IndexedRef) -> Self { + Self { + role: RefRole::Descriptor, + ref_, + } + } + pub(crate) fn describes(ref_: IndexedRef) -> Self { + Self { + role: RefRole::Descriptor, + ref_, + } + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Depth(usize); +impl Depth { + pub fn val(&self) -> usize { + self.0 + } + pub fn is_curr(&self) -> bool { + self.0 == 0 + } + pub fn outer(mut self) -> Self { + self.0 += 1; + self + } + pub fn outer_at(mut self, depth: u32) -> Self { + self.0 += depth as usize; + self + } + pub fn parent() -> Self { + Self(1) + } +} + +/// A raw indexed reference into a specific index space. +/// +/// This represents an unresolved reference that must be interpreted +/// relative to a [`crate::ir::component::visitor::VisitCtx`]. +/// +/// Fields: +/// +/// - `depth` → Which scope the reference should be resolved in +/// - `space` → The index namespace (component, module, type, etc.) +/// - `index` → The numeric index within that namespace +/// +/// Resolution is performed via [`crate::ir::component::visitor::VisitCtx::resolve`]. +#[derive(Copy, Clone, Debug)] +pub struct IndexedRef { + /// The depth of the index space scope to look this up in. + /// + /// - `0` → current scope + /// - Positive → outer scope(s) + /// - Negative → inner scope(s) + pub depth: Depth, + /// The index namespace this reference belongs to. + pub space: Space, + /// The numeric index within the specified namespace. + pub index: u32, +} + +impl ReferencedIndices for Module<'_> { + fn referenced_indices(&self) -> Vec { + vec![] + } +} + +impl ReferencedIndices for ComponentType<'_> { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.extend(self.get_func_refs()); + refs.extend(self.get_param_refs()); + refs.extend(self.get_result_refs()); + + refs + } +} +impl GetTypeRefs for ComponentType<'_> { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + ComponentType::Defined(ty) => refs.extend(ty.get_type_refs()), + ComponentType::Func(_) => {} + ComponentType::Component(tys) => { + for (idx, ty) in tys.iter().enumerate() { + for ty_ref in ty.get_type_refs() { + refs.push(RefKind::decl(idx, ty_ref.ref_)); + } + } + } + ComponentType::Instance(tys) => { + for (idx, ty) in tys.iter().enumerate() { + for ty_ref in ty.get_type_refs() { + refs.push(RefKind::decl(idx, ty_ref.ref_)); + } + } + } + ComponentType::Resource { rep, .. } => refs.extend(rep.get_type_refs()), + } + refs + } +} +impl GetFuncRefs for ComponentType<'_> { + fn get_func_refs(&self) -> Vec { + match self { + Self::Resource { dtor, .. } => { + if let Some(func) = dtor.map(|id| IndexedRef { + depth: Depth::default(), + space: Space::CoreFunc, + index: id, + }) { + return vec![RefKind::new(func)]; + } + } + + ComponentType::Defined(_) + | ComponentType::Func(_) + | ComponentType::Component(_) + | ComponentType::Instance(_) => {} + } + + vec![] + } +} +impl GetParamRefs for ComponentType<'_> { + fn get_param_refs(&self) -> Vec { + match self { + ComponentType::Func(ty) => ty.get_param_refs(), + + ComponentType::Defined(_) + | ComponentType::Component(_) + | ComponentType::Instance(_) + | ComponentType::Resource { .. } => vec![], + } + } +} +impl GetResultRefs for ComponentType<'_> { + fn get_result_refs(&self) -> Vec { + match self { + ComponentType::Func(ty) => ty.get_result_refs(), + ComponentType::Defined(_) + | ComponentType::Component(_) + | ComponentType::Instance(_) + | ComponentType::Resource { .. } => vec![], + } + } +} + +impl ReferencedIndices for ComponentFuncType<'_> { + fn referenced_indices(&self) -> Vec { + let mut all_refs = vec![]; + all_refs.extend(self.get_param_refs()); + all_refs.extend(self.get_result_refs()); + + all_refs + } +} +impl GetParamRefs for ComponentFuncType<'_> { + fn get_param_refs(&self) -> Vec { + let mut all_refs = vec![]; + for (idx, (_, ty)) in self.params.iter().enumerate() { + for r in ty.get_type_refs() { + all_refs.push(RefKind::param(idx, r.ref_)); + } + } + all_refs + } +} +impl GetResultRefs for ComponentFuncType<'_> { + fn get_result_refs(&self) -> Vec { + let mut all_refs = vec![]; + if let Some(ty) = self.result { + for r in ty.get_type_refs() { + all_refs.push(RefKind::result(0, r.ref_)); + } + } + all_refs + } +} + +impl ReferencedIndices for ComponentDefinedType<'_> { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for ComponentDefinedType<'_> { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + ComponentDefinedType::Record(records) => { + for (_, ty) in records.iter() { + refs.extend(ty.get_type_refs()); + } + } + ComponentDefinedType::Variant(variants) => { + // Explanation of variants.refines: + // This case `refines` (is a subtype/specialization of) another case in the same variant. + // So the u32 refers to: the index of another case within the current variant’s case list. + // It is NOT an index into some global index space (hence not handling it here) + for VariantCase { + name: _, + ty, + refines: _, + } in variants.iter() + { + if let Some(t) = ty { + refs.extend(t.get_type_refs()); + } + } + } + ComponentDefinedType::List(ty) + | ComponentDefinedType::FixedSizeList(ty, _) + | ComponentDefinedType::Option(ty) => refs.extend(ty.get_type_refs()), + ComponentDefinedType::Tuple(tys) => { + for ty in tys.iter() { + refs.extend(ty.get_type_refs()); + } + } + ComponentDefinedType::Result { ok, err } => { + ok.map(|ty| refs.extend(ty.get_type_refs())); + err.map(|ty| refs.extend(ty.get_type_refs())); + } + ComponentDefinedType::Own(ty) | ComponentDefinedType::Borrow(ty) => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *ty, + })) + } + ComponentDefinedType::Future(ty) | ComponentDefinedType::Stream(ty) => { + ty.map(|ty| refs.extend(ty.get_type_refs())); + } + ComponentDefinedType::Map(key_ty, val_ty) => { + refs.extend(key_ty.get_type_refs()); + refs.extend(val_ty.get_type_refs()); + } + ComponentDefinedType::Primitive(_) + | ComponentDefinedType::Enum(_) + | ComponentDefinedType::Flags(_) => {} + } + refs + } +} + +impl ReferencedIndices for ComponentTypeDeclaration<'_> { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.extend(self.get_item_refs()); + + refs + } +} +impl GetTypeRefs for ComponentTypeDeclaration<'_> { + fn get_type_refs(&self) -> Vec { + match self { + ComponentTypeDeclaration::Export { ty, .. } => ty.get_type_refs(), + ComponentTypeDeclaration::Import(import) => import.get_type_refs(), + ComponentTypeDeclaration::CoreType(_) // these are inner refs + | ComponentTypeDeclaration::Type(_) // these are inner refs + | ComponentTypeDeclaration::Alias(_) => vec![], + } + } +} +impl GetItemRefs for ComponentTypeDeclaration<'_> { + fn get_item_refs(&self) -> Vec { + match self { + ComponentTypeDeclaration::Alias(ty) => vec![ty.get_item_ref()], + ComponentTypeDeclaration::CoreType(_) // these are inner refs + | ComponentTypeDeclaration::Type(_) // these are inner refs + | ComponentTypeDeclaration::Export { .. } + | ComponentTypeDeclaration::Import(_) => vec![], + } + } +} + +impl ReferencedIndices for InstanceTypeDeclaration<'_> { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.extend(self.get_item_refs()); + + refs + } +} +impl GetTypeRefs for InstanceTypeDeclaration<'_> { + fn get_type_refs(&self) -> Vec { + match self { + InstanceTypeDeclaration::Export { ty, .. } => ty.get_type_refs(), + InstanceTypeDeclaration::CoreType(_) // these are inner refs + | InstanceTypeDeclaration::Type(_) // these are inner refs + | InstanceTypeDeclaration::Alias(_) => vec![], + } + } +} +impl GetItemRefs for InstanceTypeDeclaration<'_> { + fn get_item_refs(&self) -> Vec { + match self { + InstanceTypeDeclaration::Alias(ty) => vec![ty.get_item_ref()], + InstanceTypeDeclaration::CoreType(_) // these are inner refs + | InstanceTypeDeclaration::Type(_) // these are inner refs + | InstanceTypeDeclaration::Export { .. } => vec![], + } + } +} + +impl ReferencedIndices for CoreType<'_> { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for CoreType<'_> { + fn get_type_refs(&self) -> Vec { + match self { + CoreType::Rec(group) => group.get_type_refs(), + CoreType::Module(_) => vec![], // these are inner refs + } + } +} + +impl ReferencedIndices for RecGroup { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for RecGroup { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + self.types().for_each(|subty| { + refs.extend(subty.get_type_refs()); + }); + + refs + } +} + +impl ReferencedIndices for SubType { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for SubType { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + if let Some(packed) = self.supertype_idx { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreType, + index: packed.unpack().as_module_index().unwrap(), + })) + } + + refs.extend(self.composite_type.get_type_refs()); + + refs + } +} + +impl ReferencedIndices for CompositeType { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.extend(self.get_param_refs()); + refs.extend(self.get_result_refs()); + + refs + } +} +impl GetTypeRefs for CompositeType { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.inner.get_type_refs()); + + if let Some(descriptor) = self.descriptor_idx { + refs.push(RefKind::descriptor(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: descriptor.unpack().as_module_index().unwrap(), + })) + } + if let Some(describes) = self.describes_idx { + refs.push(RefKind::describes(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: describes.unpack().as_module_index().unwrap(), + })) + } + + refs + } +} +impl GetParamRefs for CompositeType { + fn get_param_refs(&self) -> Vec { + self.inner.get_param_refs() + } +} +impl GetResultRefs for CompositeType { + fn get_result_refs(&self) -> Vec { + self.inner.get_result_refs() + } +} + +impl ReferencedIndices for CompositeInnerType { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.extend(self.get_param_refs()); + refs.extend(self.get_result_refs()); + + refs + } +} +impl GetTypeRefs for CompositeInnerType { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CompositeInnerType::Array(a) => refs.extend(a.0.get_type_refs()), + CompositeInnerType::Struct(s) => { + for ty in s.fields.iter() { + refs.extend(ty.get_type_refs()); + } + } + CompositeInnerType::Cont(ContType(ty)) => refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: ty.unpack().as_module_index().unwrap(), + })), + CompositeInnerType::Func(_) => {} + } + refs + } +} +impl GetParamRefs for CompositeInnerType { + fn get_param_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CompositeInnerType::Func(f) => { + for (idx, ty) in f.params().iter().enumerate() { + for r in ty.get_type_refs() { + refs.push(RefKind::param(idx, r.ref_)); + } + } + } + CompositeInnerType::Array(_) + | CompositeInnerType::Struct(_) + | CompositeInnerType::Cont(_) => {} + } + refs + } +} +impl GetResultRefs for CompositeInnerType { + fn get_result_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CompositeInnerType::Func(f) => { + for (idx, ty) in f.results().iter().enumerate() { + for r in ty.get_type_refs() { + refs.push(RefKind::result(idx, r.ref_)); + } + } + } + CompositeInnerType::Array(_) + | CompositeInnerType::Struct(_) + | CompositeInnerType::Cont(_) => {} + } + refs + } +} + +impl ReferencedIndices for FieldType { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for FieldType { + fn get_type_refs(&self) -> Vec { + self.element_type.get_type_refs() + } +} + +impl ReferencedIndices for StorageType { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for StorageType { + fn get_type_refs(&self) -> Vec { + match self { + StorageType::I8 | StorageType::I16 => vec![], + StorageType::Val(value) => value.get_type_refs(), + } + } +} + +impl ReferencedIndices for ModuleTypeDeclaration<'_> { + fn referenced_indices(&self) -> Vec { + match self { + ModuleTypeDeclaration::Type(group) => group.referenced_indices(), + ModuleTypeDeclaration::Export { ty, .. } => ty.referenced_indices(), + ModuleTypeDeclaration::Import(i) => i.ty.referenced_indices(), + ModuleTypeDeclaration::OuterAlias { kind, count, index } => { + vec![RefKind::new(IndexedRef { + depth: Depth(*count as usize), + space: kind.index_space_of(), + index: *index, + })] + } + } + } +} +impl GetTypeRefs for ModuleTypeDeclaration<'_> { + fn get_type_refs(&self) -> Vec { + match self { + ModuleTypeDeclaration::Type(group) => group.get_type_refs(), + ModuleTypeDeclaration::Export { ty, .. } => ty.get_type_refs(), + ModuleTypeDeclaration::Import(i) => i.ty.get_type_refs(), + ModuleTypeDeclaration::OuterAlias { kind, count, index } => { + vec![RefKind::new(IndexedRef { + depth: Depth(*count as usize), + space: kind.index_space_of(), + index: *index, + })] + } + } + } +} + +impl ReferencedIndices for VariantCase<'_> { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for VariantCase<'_> { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + if let Some(ty) = self.ty { + refs.extend(ty.get_type_refs()) + } + + if let Some(index) = self.refines { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index, + })) + } + + refs + } +} + +impl ReferencedIndices for ValType { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for ValType { + fn get_type_refs(&self) -> Vec { + match self { + ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => vec![], + ValType::Ref(r) => r.get_type_refs(), + } + } +} + +impl ReferencedIndices for RefType { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for RefType { + fn get_type_refs(&self) -> Vec { + let index = if self.is_concrete_type_ref() { + self.type_index() + .unwrap() + .unpack() + .as_module_index() + .unwrap() + } else if self.is_exact_type_ref() { + todo!("Need to support this still, we don't have a test case that we can check implementation with yet!") + } else { + // This doesn't actually reference anything + return vec![]; + }; + + vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreType, + index, + })] + } +} + +impl ReferencedIndices for CanonicalFunction { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_func_refs()); + refs.extend(self.get_type_refs()); + refs.extend(self.get_mem_refs()); + refs.extend(self.get_tbl_refs()); + + refs + } +} +impl GetFuncRefs for CanonicalFunction { + fn get_func_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CanonicalFunction::Lift { + core_func_index, + options, + .. + } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreFunc, + index: *core_func_index, + })); + + for opt in options.iter() { + refs.extend(opt.get_func_refs()); + } + } + + CanonicalFunction::Lower { + func_index, + options, + } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompFunc, + index: *func_index, + })); + for opt in options.iter() { + refs.extend(opt.get_func_refs()); + } + } + + CanonicalFunction::ThreadSpawnRef { func_ty_index } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompFunc, + index: *func_ty_index, + })) + } + CanonicalFunction::TaskReturn { options, .. } + | CanonicalFunction::StreamRead { options, .. } + | CanonicalFunction::StreamWrite { options, .. } + | CanonicalFunction::FutureRead { options, .. } + | CanonicalFunction::FutureWrite { options, .. } + | CanonicalFunction::ErrorContextNew { options, .. } + | CanonicalFunction::ErrorContextDebugMessage { options, .. } => { + for opt in options.iter() { + refs.extend(opt.get_func_refs()); + } + } + CanonicalFunction::ResourceNew { .. } + | CanonicalFunction::ResourceDrop { .. } + | CanonicalFunction::ResourceDropAsync { .. } + | CanonicalFunction::ResourceRep { .. } + | CanonicalFunction::ThreadSpawnIndirect { .. } + | CanonicalFunction::ThreadAvailableParallelism + | CanonicalFunction::BackpressureInc + | CanonicalFunction::BackpressureDec + | CanonicalFunction::TaskCancel + | CanonicalFunction::ContextGet(_) + | CanonicalFunction::ContextSet(_) + | CanonicalFunction::ThreadYield { .. } + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::SubtaskCancel { .. } + | CanonicalFunction::StreamNew { .. } + | CanonicalFunction::StreamCancelRead { .. } + | CanonicalFunction::StreamCancelWrite { .. } + | CanonicalFunction::StreamDropReadable { .. } + | CanonicalFunction::StreamDropWritable { .. } + | CanonicalFunction::FutureNew { .. } + | CanonicalFunction::FutureCancelRead { .. } + | CanonicalFunction::FutureCancelWrite { .. } + | CanonicalFunction::FutureDropReadable { .. } + | CanonicalFunction::FutureDropWritable { .. } + | CanonicalFunction::ErrorContextDrop + | CanonicalFunction::WaitableSetNew + | CanonicalFunction::WaitableSetWait { .. } + | CanonicalFunction::WaitableSetPoll { .. } + | CanonicalFunction::WaitableSetDrop + | CanonicalFunction::WaitableJoin + | CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadNewIndirect { .. } + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater + | CanonicalFunction::ThreadYieldTo { .. } => {} + } + refs + } +} +impl GetTypeRefs for CanonicalFunction { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CanonicalFunction::Lift { + type_index, + options, + .. + } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *type_index, + })); + + for opt in options.iter() { + refs.extend(opt.get_type_refs()); + } + } + CanonicalFunction::Lower { options, .. } => { + for opt in options.iter() { + refs.extend(opt.get_type_refs()); + } + } + CanonicalFunction::ResourceNew { resource } + | CanonicalFunction::ResourceDrop { resource } + | CanonicalFunction::ResourceDropAsync { resource } + | CanonicalFunction::ResourceRep { resource } => refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *resource, + })), + CanonicalFunction::ThreadSpawnIndirect { func_ty_index, .. } + | CanonicalFunction::ThreadNewIndirect { func_ty_index, .. } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreType, + index: *func_ty_index, + })) + } + CanonicalFunction::TaskReturn { result, options } => { + result.map(|ty| { + refs.extend(ty.get_type_refs()); + }); + + for opt in options.iter() { + refs.extend(opt.get_type_refs()); + } + } + CanonicalFunction::StreamNew { ty } + | CanonicalFunction::StreamDropReadable { ty } + | CanonicalFunction::StreamDropWritable { ty } + | CanonicalFunction::StreamCancelRead { ty, .. } + | CanonicalFunction::StreamCancelWrite { ty, .. } + | CanonicalFunction::FutureNew { ty } + | CanonicalFunction::FutureDropReadable { ty } + | CanonicalFunction::FutureDropWritable { ty } + | CanonicalFunction::FutureCancelRead { ty, .. } + | CanonicalFunction::FutureCancelWrite { ty, .. } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *ty, + })) + } + CanonicalFunction::StreamRead { ty, options } + | CanonicalFunction::StreamWrite { ty, options } + | CanonicalFunction::FutureRead { ty, options } + | CanonicalFunction::FutureWrite { ty, options } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *ty, + })); + + for opt in options.iter() { + refs.extend(opt.get_type_refs()); + } + } + CanonicalFunction::ErrorContextNew { options } + | CanonicalFunction::ErrorContextDebugMessage { options } => { + for opt in options.iter() { + refs.extend(opt.get_type_refs()); + } + } + CanonicalFunction::ThreadSpawnRef { .. } + | CanonicalFunction::ThreadAvailableParallelism + | CanonicalFunction::BackpressureInc + | CanonicalFunction::BackpressureDec + | CanonicalFunction::TaskCancel + | CanonicalFunction::ContextGet(_) + | CanonicalFunction::ContextSet(_) + | CanonicalFunction::ThreadYield { .. } + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::SubtaskCancel { .. } + | CanonicalFunction::ErrorContextDrop + | CanonicalFunction::WaitableSetNew + | CanonicalFunction::WaitableSetWait { .. } + | CanonicalFunction::WaitableSetPoll { .. } + | CanonicalFunction::WaitableSetDrop + | CanonicalFunction::WaitableJoin + | CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater + | CanonicalFunction::ThreadYieldTo { .. } => {} + } + + refs + } +} +impl GetMemRefs for CanonicalFunction { + fn get_mem_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CanonicalFunction::Lift { options, .. } + | CanonicalFunction::Lower { options, .. } + | CanonicalFunction::TaskReturn { options, .. } + | CanonicalFunction::StreamRead { options, .. } + | CanonicalFunction::StreamWrite { options, .. } + | CanonicalFunction::FutureRead { options, .. } + | CanonicalFunction::FutureWrite { options, .. } + | CanonicalFunction::ErrorContextNew { options } + | CanonicalFunction::ErrorContextDebugMessage { options } => { + for opt in options.iter() { + refs.extend(opt.get_mem_refs()); + } + } + CanonicalFunction::WaitableSetWait { memory, .. } + | CanonicalFunction::WaitableSetPoll { memory, .. } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreMemory, + index: *memory, + })) + } + CanonicalFunction::ResourceNew { .. } + | CanonicalFunction::ResourceDrop { .. } + | CanonicalFunction::ResourceDropAsync { .. } + | CanonicalFunction::ResourceRep { .. } + | CanonicalFunction::ThreadSpawnRef { .. } + | CanonicalFunction::ThreadSpawnIndirect { .. } + | CanonicalFunction::ThreadAvailableParallelism + | CanonicalFunction::BackpressureInc + | CanonicalFunction::BackpressureDec + | CanonicalFunction::TaskCancel + | CanonicalFunction::ContextGet(_) + | CanonicalFunction::ContextSet(_) + | CanonicalFunction::ThreadYield { .. } + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::SubtaskCancel { .. } + | CanonicalFunction::StreamNew { .. } + | CanonicalFunction::StreamCancelRead { .. } + | CanonicalFunction::StreamCancelWrite { .. } + | CanonicalFunction::StreamDropReadable { .. } + | CanonicalFunction::StreamDropWritable { .. } + | CanonicalFunction::FutureNew { .. } + | CanonicalFunction::FutureCancelRead { .. } + | CanonicalFunction::FutureCancelWrite { .. } + | CanonicalFunction::FutureDropReadable { .. } + | CanonicalFunction::FutureDropWritable { .. } + | CanonicalFunction::ErrorContextDrop + | CanonicalFunction::WaitableSetNew + | CanonicalFunction::WaitableSetDrop + | CanonicalFunction::WaitableJoin + | CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadNewIndirect { .. } + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater + | CanonicalFunction::ThreadYieldTo { .. } => {} + } + refs + } +} +impl GetTableRefs for CanonicalFunction { + fn get_tbl_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + CanonicalFunction::ThreadSpawnIndirect { table_index, .. } + | CanonicalFunction::ThreadNewIndirect { table_index, .. } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreTable, + index: *table_index, + })) + } + CanonicalFunction::Lift { .. } + | CanonicalFunction::Lower { .. } + | CanonicalFunction::ResourceNew { .. } + | CanonicalFunction::ResourceDrop { .. } + | CanonicalFunction::ResourceDropAsync { .. } + | CanonicalFunction::ResourceRep { .. } + | CanonicalFunction::ThreadSpawnRef { .. } + | CanonicalFunction::ThreadAvailableParallelism + | CanonicalFunction::BackpressureInc + | CanonicalFunction::BackpressureDec + | CanonicalFunction::TaskReturn { .. } + | CanonicalFunction::TaskCancel + | CanonicalFunction::ContextGet(_) + | CanonicalFunction::ContextSet(_) + | CanonicalFunction::ThreadYield { .. } + | CanonicalFunction::SubtaskDrop + | CanonicalFunction::SubtaskCancel { .. } + | CanonicalFunction::StreamNew { .. } + | CanonicalFunction::StreamRead { .. } + | CanonicalFunction::StreamWrite { .. } + | CanonicalFunction::StreamCancelRead { .. } + | CanonicalFunction::StreamCancelWrite { .. } + | CanonicalFunction::StreamDropReadable { .. } + | CanonicalFunction::StreamDropWritable { .. } + | CanonicalFunction::FutureNew { .. } + | CanonicalFunction::FutureRead { .. } + | CanonicalFunction::FutureWrite { .. } + | CanonicalFunction::FutureCancelRead { .. } + | CanonicalFunction::FutureCancelWrite { .. } + | CanonicalFunction::FutureDropReadable { .. } + | CanonicalFunction::FutureDropWritable { .. } + | CanonicalFunction::ErrorContextNew { .. } + | CanonicalFunction::ErrorContextDebugMessage { .. } + | CanonicalFunction::ErrorContextDrop + | CanonicalFunction::WaitableSetNew + | CanonicalFunction::WaitableSetWait { .. } + | CanonicalFunction::WaitableSetPoll { .. } + | CanonicalFunction::WaitableSetDrop + | CanonicalFunction::WaitableJoin + | CanonicalFunction::ThreadIndex + | CanonicalFunction::ThreadSwitchTo { .. } + | CanonicalFunction::ThreadSuspend { .. } + | CanonicalFunction::ThreadResumeLater + | CanonicalFunction::ThreadYieldTo { .. } => {} + } + refs + } +} + +impl ReferencedIndices for CanonicalOption { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.extend(self.get_func_refs()); + refs.extend(self.get_mem_refs()); + + refs + } +} +impl GetTypeRefs for CanonicalOption { + fn get_type_refs(&self) -> Vec { + match self { + CanonicalOption::CoreType(id) => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreType, + index: *id, + })], + CanonicalOption::UTF8 + | CanonicalOption::UTF16 + | CanonicalOption::CompactUTF16 + | CanonicalOption::Memory(_) + | CanonicalOption::Realloc(_) + | CanonicalOption::PostReturn(_) + | CanonicalOption::Async + | CanonicalOption::Callback(_) + | CanonicalOption::Gc => vec![], + } + } +} +impl GetFuncRefs for CanonicalOption { + fn get_func_refs(&self) -> Vec { + match self { + CanonicalOption::Realloc(id) + | CanonicalOption::PostReturn(id) + | CanonicalOption::Callback(id) => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreFunc, + index: *id, + })], + CanonicalOption::CoreType(_) + | CanonicalOption::UTF8 + | CanonicalOption::UTF16 + | CanonicalOption::CompactUTF16 + | CanonicalOption::Memory(_) + | CanonicalOption::Async + | CanonicalOption::Gc => vec![], + } + } +} +impl GetMemRefs for CanonicalOption { + fn get_mem_refs(&self) -> Vec { + match self { + CanonicalOption::Memory(id) => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreMemory, + index: *id, + })], + CanonicalOption::CoreType(_) + | CanonicalOption::UTF8 + | CanonicalOption::UTF16 + | CanonicalOption::CompactUTF16 + | CanonicalOption::Realloc(_) + | CanonicalOption::PostReturn(_) + | CanonicalOption::Async + | CanonicalOption::Callback(_) + | CanonicalOption::Gc => vec![], + } + } +} + +impl ReferencedIndices for ComponentImport<'_> { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for ComponentImport<'_> { + fn get_type_refs(&self) -> Vec { + self.ty.get_type_refs() + } +} + +impl ReferencedIndices for ComponentTypeRef { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for ComponentTypeRef { + fn get_type_refs(&self) -> Vec { + let depth = Depth::default(); + match &self { + // The reference is to a core module type. + // The index is expected to be core type index to a core module type. + ComponentTypeRef::Module(id) => vec![RefKind::new(IndexedRef { + depth, + space: Space::CoreType, + index: *id, + })], + ComponentTypeRef::Func(id) => vec![RefKind::new(IndexedRef { + depth, + space: Space::CompType, + index: *id, + })], + ComponentTypeRef::Instance(id) => vec![RefKind::new(IndexedRef { + depth, + space: Space::CompType, + index: *id, + })], + ComponentTypeRef::Component(id) => vec![RefKind::new(IndexedRef { + depth, + space: Space::CompType, // verified in wat (instantiate.wast) + index: *id, + })], + ComponentTypeRef::Value(ty) => ty.get_type_refs(), + ComponentTypeRef::Type(ty_bounds) => ty_bounds.get_type_refs(), + } + } +} + +impl ReferencedIndices for TypeBounds { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for TypeBounds { + fn get_type_refs(&self) -> Vec { + match self { + TypeBounds::Eq(id) => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *id, + })], + TypeBounds::SubResource => vec![], + } + } +} + +impl ReferencedIndices for ComponentValType { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for ComponentValType { + fn get_type_refs(&self) -> Vec { + match self { + ComponentValType::Primitive(_) => vec![], + ComponentValType::Type(id) => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompType, + index: *id, + })], + } + } +} + +impl ReferencedIndices for ComponentInstantiationArg<'_> { + fn referenced_indices(&self) -> Vec { + vec![self.get_item_ref()] + } +} +impl GetItemRef for ComponentInstantiationArg<'_> { + fn get_item_ref(&self) -> RefKind { + RefKind::new(IndexedRef { + depth: Depth::default(), + space: self.kind.index_space_of(), + index: self.index, + }) + } +} + +impl ReferencedIndices for ComponentExport<'_> { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_type_refs()); + refs.push(self.get_item_ref()); + + refs + } +} +impl GetTypeRefs for ComponentExport<'_> { + fn get_type_refs(&self) -> Vec { + let mut refs = vec![]; + if let Some(ty) = self.ty { + refs.extend(ty.get_type_refs()) + } + + refs + } +} +impl GetItemRef for ComponentExport<'_> { + fn get_item_ref(&self) -> RefKind { + RefKind::new(IndexedRef { + depth: Depth::default(), + space: self.kind.index_space_of(), + index: self.index, + }) + } +} + +impl ReferencedIndices for Export<'_> { + fn referenced_indices(&self) -> Vec { + vec![self.get_item_ref()] + } +} +impl GetItemRef for Export<'_> { + fn get_item_ref(&self) -> RefKind { + RefKind::new(IndexedRef { + depth: Depth::default(), + space: self.kind.index_space_of(), + index: self.index, + }) + } +} + +impl ReferencedIndices for InstantiationArg<'_> { + fn referenced_indices(&self) -> Vec { + vec![self.get_item_ref()] + } +} +impl GetItemRef for InstantiationArg<'_> { + fn get_item_ref(&self) -> RefKind { + RefKind::new(IndexedRef { + depth: Depth::default(), + space: self.kind.index_space_of(), + index: self.index, + }) + } +} + +impl ReferencedIndices for Instance<'_> { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_module_refs()); + refs.extend(self.get_item_refs()); + + refs + } +} +impl GetModuleRefs for Instance<'_> { + fn get_module_refs(&self) -> Vec { + match self { + Instance::Instantiate { module_index, .. } => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreModule, + index: *module_index, + })], + Instance::FromExports(_) => vec![], + } + } +} +impl GetItemRefs for Instance<'_> { + fn get_item_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + Instance::Instantiate { args, .. } => { + // Recursively include indices from options + for arg in args.iter() { + refs.push(arg.get_item_ref()); + } + } + Instance::FromExports(exports) => { + // Recursively include indices from options + for exp in exports.iter() { + refs.push(exp.get_item_ref()); + } + } + } + refs + } +} + +impl ReferencedIndices for TypeRef { + fn referenced_indices(&self) -> Vec { + self.get_type_refs() + } +} +impl GetTypeRefs for TypeRef { + fn get_type_refs(&self) -> Vec { + match self { + TypeRef::Func(ty) + | TypeRef::Tag(TagType { + kind: _, + func_type_idx: ty, + }) => vec![RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreType, + index: *ty, + })], + TypeRef::Table(_) | TypeRef::Memory(_) | TypeRef::Global(_) | TypeRef::FuncExact(_) => { + vec![] + } + } + } +} + +impl ReferencedIndices for ComponentAlias<'_> { + fn referenced_indices(&self) -> Vec { + vec![self.get_item_ref()] + } +} +impl GetItemRef for ComponentAlias<'_> { + fn get_item_ref(&self) -> RefKind { + match self { + ComponentAlias::InstanceExport { instance_index, .. } => RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompInst, + index: *instance_index, + }), + ComponentAlias::CoreInstanceExport { instance_index, .. } => RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CoreInst, + index: *instance_index, + }), + ComponentAlias::Outer { count, index, kind } => RefKind::new(IndexedRef { + depth: Depth(*count as usize), + space: kind.index_space_of(), + index: *index, + }), + } + } +} + +impl ReferencedIndices for ComponentInstance<'_> { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.extend(self.get_comp_refs()); + refs.extend(self.get_item_refs()); + + refs + } +} +impl GetCompRefs for ComponentInstance<'_> { + fn get_comp_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + ComponentInstance::Instantiate { + component_index, .. + } => { + refs.push(RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::Comp, // verified in alias.wast + index: *component_index, + })); + } + ComponentInstance::FromExports(_) => {} + } + refs + } +} +impl GetItemRefs for ComponentInstance<'_> { + fn get_item_refs(&self) -> Vec { + let mut refs = vec![]; + match self { + ComponentInstance::Instantiate { args, .. } => { + // Recursively include indices from args + for arg in args.iter() { + refs.push(arg.get_item_ref()); + } + } + ComponentInstance::FromExports(export) => { + // Recursively include indices from args + for exp in export.iter() { + refs.push(exp.get_item_ref()); + } + } + } + refs + } +} + +impl ReferencedIndices for CustomSection<'_> { + fn referenced_indices(&self) -> Vec { + vec![] + } +} + +impl ReferencedIndices for ComponentStartFunction { + fn referenced_indices(&self) -> Vec { + let mut refs = vec![]; + refs.push(self.get_func_ref()); + refs.extend(self.get_arg_refs()); + + refs + } +} +impl GetFuncRef for ComponentStartFunction { + fn get_func_ref(&self) -> RefKind { + RefKind::new(IndexedRef { + depth: Depth::default(), + space: Space::CompFunc, + index: self.func_index, + }) + } +} +impl GetArgRefs for ComponentStartFunction { + fn get_arg_refs(&self) -> Vec { + let mut refs = vec![]; + for (idx, v) in self.arguments.iter().enumerate() { + refs.push(RefKind::result( + idx, + IndexedRef { + depth: Depth::default(), + space: Space::CompVal, + index: *v, + }, + )); + } + refs + } +} diff --git a/src/ir/component/scopes.rs b/src/ir/component/scopes.rs new file mode 100644 index 00000000..1ae023b1 --- /dev/null +++ b/src/ir/component/scopes.rs @@ -0,0 +1,427 @@ +//! ## Scope Tracking and Stable Identity +//! +//! This module defines the infrastructure used to safely track **nested index +//! spaces** across parsing, instrumentation, and encoding phases of a +//! WebAssembly *component*. +//! +//! WebAssembly components introduce hierarchical index scopes: components may +//! contain subcomponents, instances, types, and other constructs that form +//! their own index spaces. Additionally, `(outer ...)` references allow inner +//! scopes to refer to indices defined in enclosing scopes. Correctly resolving +//! these relationships at encode time therefore requires an explicit model of +//! scope nesting rather than a single flat index map. +//! +//! At the same time, this crate supports **component instrumentation**, meaning +//! the IR may be visited, transformed, and encoded in an order that does *not* +//! correspond to the original parse order. As a result, index resolution cannot +//! rely on traversal order alone. +//! +//! To address these constraints, this module separates **identity** from +//! **ownership** using a central registry and a small set of carefully enforced +//! invariants. +//! +//! --- +//! +//! ### `ScopeRegistry` +//! +//! `ScopeRegistry` is a shared registry that maps *IR node identity* to the index +//! scope (`SpaceId`) that the node owns or inhabits. This mapping is established +//! during parsing and maintained throughout the lifetime of the IR. +//! +//! The registry supports **two identity mechanisms**, depending on the kind of +//! node being tracked: +//! +//! #### Component scopes (special-cased) +//! +//! Components are identified by a stable `ComponentId`, assigned when the +//! component is parsed or created. Component scopes are registered and looked +//! up **by `ComponentId`**, rather than by pointer. +//! +//! This reflects the fact that components: +//! - May be stored in a central registry +//! - Are visited via an explicit *component ID stack* during traversal +//! - Do not rely on memory address stability for identity +//! +//! By using `ComponentId` as the identity key, component scope lookup remains +//! robust even as components are nested, traversed out of order, or referenced +//! indirectly. +//! +//! #### All other scoped IR nodes +//! +//! All non-component nodes that introduce or inhabit scopes (e.g. component types, +//! core types, etc.) are tracked using **raw pointers** +//! (`*const T`) as identity keys. +//! +//! These nodes are stored in append-only, stable allocations (`Box` +//! inside append-only vectors), ensuring that their addresses remain +//! valid for the lifetime of the component graph. +//! +//! Raw pointers are used **only for identity comparison**; they are never +//! dereferenced. +//! +//! --- +//! +//! ### Scope Resolution During Encoding +//! +//! During encoding, scopes are resolved dynamically using two stacks: +//! +//! - A **component ID stack**, tracking which component is currently being +//! traversed +//! - A **scope stack**, tracking nested index spaces within that component +//! +//! When an IR node needs to resolve its associated scope: +//! +//! - If the node is a component, the current `ComponentId` is used to query the +//! registry +//! - Otherwise, the node’s pointer identity is used to retrieve its `SpaceId` +//! +//! This design allows correct resolution of arbitrarily nested constructs such +//! as deeply nested components, instances, and `(outer ...)` references without +//! encoding traversal order into the registry itself. +//! +//! --- +//! +//! ### Safety and Invariants +//! +//! This design relies on the following invariants: +//! +//! - Each component is assigned a unique `ComponentId` that remains stable for +//! its lifetime. +//! - All non-component IR nodes that participate in scoping are allocated in +//! stable memory (e.g. boxed and stored in append-only vectors). +//! - IR nodes are never moved or removed after registration with the +//! `ScopeRegistry`. +//! - `ScopeRegistry` entries are created during parsing and may be extended +//! during instrumentation, but are never removed. +//! - Raw pointer usage is confined strictly to identity comparison; no pointer +//! is ever dereferenced. +//! +//! These constraints allow the system to use low-level identity mechanisms in a +//! controlled, domain-specific way while preserving correctness and debuggability. +//! +//! --- +//! +//! ### Design Tradeoffs +//! +//! This approach deliberately favors: +//! +//! - Explicit scope modeling over implicit traversal order +//! - Stable identity over borrow-driven lifetimes +//! - Append-only IR construction over in-place mutation +//! +//! While this introduces some bookkeeping and indirection, it ensures that index +//! correctness is enforced structurally and remains robust in the presence of +//! instrumentation, reordering, and future extensions to the component model. +//! +//! In short: **index correctness is enforced structurally, not procedurally**. +//! +//! ## Why `ScopeOwnerKind` Exists +//! +//! In the IR, multiple wrapper structs may reference the same underlying +//! scoped node. For example, a user-facing struct might contain a field +//! pointing to a `CoreType` that is also stored directly in a component's +//! internal vectors. Without additional tracking, the scope resolution logic +//! would see two references to the same pointer and mistakenly treat them as +//! separate scopes. +//! +//! `ScopeOwnerKind` is used to **disambiguate these cases**. Each node in the +//! scope registry records whether it is: +//! - An **original owner** of the scope (the canonical IR node), or +//! - A **derived/alias** that references an existing scope +//! +//! This ensures that the same scope is **never entered twice**, preventing +//! double-counting or incorrect index resolution during encoding. + +use crate::ir::component::idx_spaces::ScopeId; +use crate::ir::id::ComponentId; +use crate::ir::types::CustomSection; +use crate::{Component, Module}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::ptr::NonNull; +use std::rc::Rc; +use wasmparser::{ + CanonicalFunction, CanonicalOption, ComponentAlias, ComponentDefinedType, ComponentExport, + ComponentFuncType, ComponentImport, ComponentInstance, ComponentInstantiationArg, + ComponentStartFunction, ComponentType, ComponentTypeDeclaration, ComponentTypeRef, + ComponentValType, CompositeInnerType, CompositeType, CoreType, Export, FieldType, FuncType, + Import, Instance, InstanceTypeDeclaration, InstantiationArg, ModuleTypeDeclaration, + PrimitiveValType, RecGroup, RefType, StorageType, StructType, SubType, TypeRef, ValType, + VariantCase, +}; + +/// ## Scope Tracking and Index Resolution +/// +/// WebAssembly components introduce **nested index spaces**: components may +/// contain subcomponents, instances, types, and other constructs that define +/// their own indices. Inner scopes may also reference indices defined in +/// enclosing scopes via `(outer ...)`. +/// +/// Because this crate supports **instrumentation and transformation** of +/// components, the order in which the IR is visited and encoded may differ from +/// the original parse order. As a result, index resolution cannot rely on +/// traversal order alone. +/// +/// This module provides the infrastructure that ensures **correct and stable +/// index resolution** across parsing, instrumentation, and encoding. +/// +/// --- +/// +/// ### The Core Idea +/// +/// Each IR node that participates in indexing is associated with a logical +/// **scope**. These associations are recorded once and later queried during +/// encoding. +/// +/// The system guarantees that: +/// +/// - Index scopes are assigned explicitly, not inferred from traversal order +/// - Nested scopes are resolved correctly, even under reordering or +/// instrumentation +/// - Encoding always uses the correct index space for the node being emitted +/// +/// --- +/// +/// ### Component Scopes +/// +/// Components are identified by a stable **component ID** assigned when the +/// component is created or parsed. +/// +/// Component scopes are registered and resolved using this ID rather than by +/// memory identity. During traversal, encoding maintains a **stack of component +/// IDs** representing the current nesting of components. +/// +/// This makes component scope resolution: +/// +/// - Independent of ownership or storage layout +/// - Robust to reordering and nested traversal +/// - Explicit and easy to reason about +/// +/// --- +/// +/// ### Scopes Within Components +/// +/// All other scoped IR nodes—such as instances, type declarations, aliases, and +/// similar constructs—are associated with scopes relative to their enclosing +/// component. +/// +/// During encoding, a **scope stack** tracks the currently active index spaces +/// as traversal enters and exits nested constructs. When an IR node needs to +/// resolve an index, its associated scope is retrieved and interpreted relative +/// to the current stack. +/// +/// This allows deeply nested structures and `(outer ...)` references to be +/// encoded correctly without baking traversal assumptions into the IR. +/// +/// --- +/// +/// ### What This Enables +/// +/// This design ensures that: +/// +/// - Instrumentation can reorder or inject IR nodes without breaking index +/// correctness +/// - Encoding logic remains simple and declarative +/// - Index resolution remains correct for arbitrarily nested components +/// +/// Users of the library do not need to manage scopes manually—scope tracking is +/// handled transparently as part of parsing and encoding. +/// +/// --- +/// +/// ### Design Philosophy +/// +/// The scope system is intentionally explicit and conservative. Rather than +/// inferring meaning from traversal order, it records the structure of index +/// spaces directly and resolves them mechanically at encode time. +/// +/// In short: **index correctness is enforced structurally, not procedurally**. +/// ``` +#[derive(Default, Debug)] +pub(crate) struct IndexScopeRegistry { + pub(crate) node_scopes: HashMap, ScopeEntry>, + pub(crate) comp_scopes: HashMap, +} +impl IndexScopeRegistry { + pub fn register(&mut self, node: &T, space: ScopeId) { + let ptr = NonNull::from(node).cast::<()>(); + let kind = node.scope_kind(); + debug_assert_ne!( + kind, + ScopeOwnerKind::Unregistered, + "attempted to register an unscoped node" + ); + + let old = self.node_scopes.insert(ptr, ScopeEntry { space, kind }); + + debug_assert!(old.is_none(), "node registered twice: {:p}", node); + } + + pub fn scope_entry(&self, node: &T) -> Option { + let ptr = NonNull::from(node).cast::<()>(); + + if let Some(entry) = self.node_scopes.get(&ptr) { + if entry.kind == node.scope_kind() { + return Some(*entry); + } + } + None + } + pub fn register_comp(&mut self, comp_id: ComponentId, space: ScopeId) { + self.comp_scopes.insert(comp_id, space); + } + pub fn scope_of_comp(&self, comp_id: ComponentId) -> Option { + self.comp_scopes.get(&comp_id).copied() + } +} + +/// Every IR node can have a reference to this to allow for instrumentation +/// to have access to the index scope mappings and perform manipulations! +pub(crate) type RegistryHandle = Rc>; + +#[derive(Debug, Clone, Copy)] +pub struct ScopeEntry { + pub space: ScopeId, + pub kind: ScopeOwnerKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScopeOwnerKind { + /// A `(component ...)` + Component, + + /// A core `(core type (module ...))` + CoreTypeModule, + + /// A `(component type (component ...))` + ComponentTypeComponent, + /// A `(component type (instance ...))` + ComponentTypeInstance, + + // Extend as needed + Unregistered, +} + +pub trait GetScopeKind { + fn scope_kind(&self) -> ScopeOwnerKind { + ScopeOwnerKind::Unregistered + } +} +impl GetScopeKind for Component<'_> { + fn scope_kind(&self) -> ScopeOwnerKind { + ScopeOwnerKind::Component + } +} +impl GetScopeKind for CoreType<'_> { + fn scope_kind(&self) -> ScopeOwnerKind { + match self { + CoreType::Module(_) => ScopeOwnerKind::CoreTypeModule, + // other variants that do NOT introduce scopes should never be registered + _ => ScopeOwnerKind::Unregistered, + } + } +} +impl GetScopeKind for ComponentType<'_> { + fn scope_kind(&self) -> ScopeOwnerKind { + match self { + ComponentType::Component(_) => ScopeOwnerKind::ComponentTypeComponent, + ComponentType::Instance(_) => ScopeOwnerKind::ComponentTypeInstance, + ComponentType::Defined(_) | ComponentType::Func(_) | ComponentType::Resource { .. } => { + ScopeOwnerKind::Unregistered + } + } + } +} +impl GetScopeKind for Module<'_> {} +impl GetScopeKind for ComponentTypeRef {} +impl GetScopeKind for ComponentDefinedType<'_> {} +impl GetScopeKind for ComponentFuncType<'_> {} +impl GetScopeKind for ComponentTypeDeclaration<'_> {} +impl GetScopeKind for InstanceTypeDeclaration<'_> {} +impl GetScopeKind for ComponentInstance<'_> {} +impl GetScopeKind for CanonicalFunction {} +impl GetScopeKind for ComponentAlias<'_> {} +impl GetScopeKind for ComponentImport<'_> {} +impl GetScopeKind for ComponentExport<'_> {} +impl GetScopeKind for Instance<'_> {} +impl GetScopeKind for ComponentStartFunction {} +impl GetScopeKind for CustomSection<'_> {} +impl GetScopeKind for ValType {} +impl GetScopeKind for ComponentInstantiationArg<'_> {} +impl GetScopeKind for CanonicalOption {} +impl GetScopeKind for ComponentValType {} +impl GetScopeKind for InstantiationArg<'_> {} +impl GetScopeKind for Export<'_> {} +impl GetScopeKind for PrimitiveValType {} +impl GetScopeKind for VariantCase<'_> {} +impl GetScopeKind for CompositeInnerType {} +impl GetScopeKind for FuncType {} +impl GetScopeKind for FieldType {} +impl GetScopeKind for StructType {} +impl GetScopeKind for CompositeType {} +impl GetScopeKind for StorageType {} +impl GetScopeKind for RefType {} +impl GetScopeKind for RecGroup {} +impl GetScopeKind for ModuleTypeDeclaration<'_> {} +impl GetScopeKind for Import<'_> {} +impl GetScopeKind for TypeRef {} +impl GetScopeKind for SubType {} + +/// Assert that a node is registered in the `ScopeRegistry` at this point. +/// Panics if the node is not found. +/// This helps with debugging issues where a node may have been moved and +/// no longer upholds the invariants required by the scope lookup mechanism. +/// These checks will not be present in a release build, only debug builds, since +/// the check is encapsulated inside a `debug_assert_eq`. +#[macro_export] +macro_rules! assert_registered { + ($registry:expr, $node:expr) => {{ + debug_assert!( + $registry.borrow().scope_entry($node).is_some(), + // concat!( + "Debug assertion failed: node is not registered in ScopeRegistry: {:?}", + $node // ) + ); + }}; +} +#[macro_export] +macro_rules! assert_registered_with_id { + ($registry:expr, $node:expr, $scope_id:expr) => {{ + debug_assert_eq!( + $scope_id, + $registry + .borrow() + .scope_entry($node) + .expect(concat!( + "Debug assertion failed: node is not registered in ScopeRegistry: ", + stringify!($node) + )) + .space + ); + }}; +} + +#[derive(Clone, Debug)] +pub struct ComponentStore<'a> { + components: HashMap>, +} +impl<'a> ComponentStore<'a> { + pub fn get(&self, id: &ComponentId) -> &'a Component<'a> { + self.components.get(id).unwrap() + } +} + +pub fn build_component_store<'a>(root: &'a Component<'a>) -> ComponentStore<'a> { + let mut map = HashMap::new(); + + fn walk<'a>(comp: &'a Component<'a>, map: &mut HashMap>) { + map.insert(comp.id, comp); + for child in comp.components.iter() { + walk(child, map); + } + } + + walk(root, &mut map); + + ComponentStore { components: map } +} diff --git a/src/ir/component/section.rs b/src/ir/component/section.rs new file mode 100644 index 00000000..892823dc --- /dev/null +++ b/src/ir/component/section.rs @@ -0,0 +1,208 @@ +//! Enums the represent a section of a Module or a Component + +use crate::assert_registered_with_id; +use crate::ir::component::idx_spaces::{IndexSpaceOf, ScopeId, StoreHandle}; +use crate::ir::component::scopes::RegistryHandle; +use wasmparser::{ + ComponentType, ComponentTypeDeclaration, CoreType, InstanceTypeDeclaration, + ModuleTypeDeclaration, RecGroup, +}; + +#[derive(Debug, Clone, Eq, PartialEq)] +/// Represents a Section in a Component +pub enum ComponentSection { + Module, + Alias, + CoreType, + ComponentType, + ComponentImport, + ComponentExport, + CoreInstance, + ComponentInstance, + Canon, + CustomSection, + Component, + ComponentStartSection, +} + +pub(crate) fn get_sections_for_comp_ty(ty: &ComponentType) -> (ComponentSection, bool) { + let section = ComponentSection::ComponentType; + match ty { + ComponentType::Component(_) | ComponentType::Instance(_) => (section, true), + ComponentType::Defined(_) | ComponentType::Func(_) | ComponentType::Resource { .. } => { + (section, false) + } + } +} + +pub(crate) fn get_sections_for_core_ty_and_assign_top_level_ids( + ty: &CoreType, + curr_idx: usize, + space_id: &ScopeId, + store: StoreHandle, +) -> (ComponentSection, bool) { + let section = ComponentSection::CoreType; + match ty { + CoreType::Module(_) => { + store.borrow_mut().assign_assumed_id( + space_id, + &ty.index_space_of(), + §ion, + curr_idx, + ); + + (section, true) + } + CoreType::Rec(recgroup) => { + assign_top_level_ids_recgroup(recgroup, curr_idx, space_id, store); + (section, false) + } + } +} + +pub(crate) fn assign_top_level_ids_recgroup( + recgroup: &RecGroup, + curr_idx: usize, + space_id: &ScopeId, + store: StoreHandle, +) { + let section = ComponentSection::CoreType; + let tys = recgroup.types(); + for _ in tys { + store.borrow_mut().assign_assumed_id( + space_id, + &recgroup.index_space_of(), + §ion, + curr_idx, + ); + } +} + +// ============================================================= +// ==== Helper Functions for Section Index Space Population ==== +// ============================================================= + +pub(crate) fn populate_space_for_comp_ty( + ty: &ComponentType, + registry: RegistryHandle, + store: StoreHandle, +) { + match ty { + ComponentType::Component(decls) => { + let space_id = store.borrow_mut().new_scope(); + let section = ComponentSection::ComponentType; + registry.borrow_mut().register(ty, space_id); + assert_registered_with_id!(registry, ty, space_id); + + for (idx, decl) in decls.iter().enumerate() { + populate_space_for_comp_ty_comp_decl( + idx, + &space_id, + decl, + §ion, + registry.clone(), + store.clone(), + ); + } + } + ComponentType::Instance(decls) => { + let space_id = store.borrow_mut().new_scope(); + let section = ComponentSection::ComponentType; + registry.borrow_mut().register(ty, space_id); + assert_registered_with_id!(registry, ty, space_id); + + debug_assert_eq!(space_id, registry.borrow().scope_entry(ty).unwrap().space); + for (idx, decl) in decls.iter().enumerate() { + populate_space_for_comp_ty_inst_decl( + idx, + &space_id, + decl, + §ion, + registry.clone(), + store.clone(), + ); + } + } + _ => {} + } +} + +fn populate_space_for_comp_ty_comp_decl( + idx: usize, + space_id: &ScopeId, + decl: &ComponentTypeDeclaration, + section: &ComponentSection, + registry: RegistryHandle, + handle: StoreHandle, +) { + let space = decl.index_space_of(); + handle + .borrow_mut() + .assign_assumed_id(space_id, &space, section, idx); + + match decl { + ComponentTypeDeclaration::CoreType(ty) => { + populate_space_for_core_ty(ty, registry, handle); + } + ComponentTypeDeclaration::Type(ty) => { + populate_space_for_comp_ty(ty, registry, handle); + } + ComponentTypeDeclaration::Alias(_) + | ComponentTypeDeclaration::Export { .. } + | ComponentTypeDeclaration::Import(_) => {} + } +} + +fn populate_space_for_comp_ty_inst_decl( + idx: usize, + space_id: &ScopeId, + decl: &InstanceTypeDeclaration, + section: &ComponentSection, + registry: RegistryHandle, + handle: StoreHandle, +) { + let space = decl.index_space_of(); + handle + .borrow_mut() + .assign_assumed_id(space_id, &space, section, idx); + + match decl { + InstanceTypeDeclaration::CoreType(ty) => { + populate_space_for_core_ty(ty, registry, handle); + } + InstanceTypeDeclaration::Type(ty) => { + populate_space_for_comp_ty(ty, registry, handle); + } + InstanceTypeDeclaration::Alias(_) | InstanceTypeDeclaration::Export { .. } => {} + } +} + +pub(crate) fn populate_space_for_core_ty( + ty: &CoreType, + registry: RegistryHandle, + handle: StoreHandle, +) { + if let CoreType::Module(decls) = ty { + let space_id = handle.borrow_mut().new_scope(); + let section = ComponentSection::CoreType; + registry.borrow_mut().register(ty, space_id); + assert_registered_with_id!(registry, ty, space_id); + + for (idx, decl) in decls.iter().enumerate() { + populate_space_for_core_module_decl(idx, &space_id, decl, §ion, handle.clone()); + } + } +} + +fn populate_space_for_core_module_decl( + idx: usize, + space_id: &ScopeId, + decl: &ModuleTypeDeclaration, + section: &ComponentSection, + handle: StoreHandle, +) { + let space = decl.index_space_of(); + handle + .borrow_mut() + .assign_assumed_id(space_id, &space, section, idx); +} diff --git a/src/ir/component/types.rs b/src/ir/component/types.rs new file mode 100644 index 00000000..e14a9b61 --- /dev/null +++ b/src/ir/component/types.rs @@ -0,0 +1,90 @@ +use crate::ir::id::ComponentTypeId; +use crate::ir::AppendOnlyVec; +use wasmparser::ComponentType; + +#[derive(Debug, Default)] +pub struct ComponentTypes<'a> { + pub items: AppendOnlyVec>>, + + num_funcs: usize, + num_funcs_added: usize, + num_instances: usize, + num_instances_added: usize, + num_defined: usize, + num_defined_added: usize, + num_components: usize, + num_components_added: usize, + num_resources: usize, + num_resources_added: usize, +} +impl<'a> ComponentTypes<'a> { + pub fn new(items: AppendOnlyVec>>) -> Self { + let ( + mut num_funcs, + mut num_instances, + mut num_defined, + mut num_components, + mut num_resources, + ) = (0, 0, 0, 0, 0); + for i in items.iter() { + match i.as_ref() { + ComponentType::Func(_) => num_funcs += 1, + ComponentType::Instance(_) => num_instances += 1, + ComponentType::Defined(_) => num_defined += 1, + ComponentType::Component(_) => num_components += 1, + ComponentType::Resource { .. } => num_resources += 1, + } + } + + Self { + items, + num_funcs, + num_instances, + num_defined, + num_components, + num_resources, + ..Self::default() + } + } + + /// Add a new component type to the component. + /// This assumes that scope registration is done by the caller! + pub(crate) fn add<'b>(&'b mut self, ty: ComponentType<'a>) -> (u32, ComponentTypeId) { + let ty_id = self.items.len(); + let ty_inner_id = match ty { + ComponentType::Defined(_) => { + self.num_defined += 1; + self.num_defined_added += 1; + + self.num_defined - 1 + } + ComponentType::Func(_) => { + self.num_funcs += 1; + self.num_funcs_added += 1; + + self.num_funcs - 1 + } + ComponentType::Component(_) => { + self.num_components += 1; + self.num_components_added += 1; + + self.num_components - 1 + } + ComponentType::Instance(_) => { + self.num_instances += 1; + self.num_instances_added += 1; + + self.num_instances - 1 + } + ComponentType::Resource { .. } => { + self.num_resources += 1; + self.num_resources_added += 1; + + self.num_resources - 1 + } + }; + + self.items.push(Box::new(ty)); + (ty_inner_id as u32, ComponentTypeId(ty_id as u32)) + } +} diff --git a/src/ir/component/visitor/driver.rs b/src/ir/component/visitor/driver.rs new file mode 100644 index 00000000..cc209ac6 --- /dev/null +++ b/src/ir/component/visitor/driver.rs @@ -0,0 +1,404 @@ +use crate::ir::component::idx_spaces::{IndexSpaceOf, Space}; +use crate::ir::component::section::ComponentSection; +use crate::ir::component::visitor::{ComponentVisitor, ItemKind, VisitCtx}; +use crate::ir::types::CustomSection; +use crate::{Component, Module}; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentImport, ComponentInstance, + ComponentStartFunction, ComponentType, ComponentTypeDeclaration, CoreType, Instance, + InstanceTypeDeclaration, ModuleTypeDeclaration, SubType, +}; + +pub fn drive_event<'ir, V: ComponentVisitor<'ir>>( + event: &VisitEvent<'ir>, + visitor: &mut V, + ctx: &mut VisitCtx<'ir>, +) { + match event { + VisitEvent::EnterRootComp { component } => { + ctx.inner.push_component(component); + visitor.enter_root_component(ctx, component); + } + + VisitEvent::ExitRootComp { component } => { + visitor.exit_root_component(ctx, component); + ctx.inner.pop_component(); + } + VisitEvent::EnterComp { component, idx, .. } => { + ctx.inner.push_component(component); + let id = ctx + .inner + .lookup_id_for(&Space::Comp, &ComponentSection::Component, *idx); + visitor.enter_component(ctx, id, component); + } + + VisitEvent::ExitComp { component, idx } => { + let id = ctx + .inner + .lookup_id_for(&Space::Comp, &ComponentSection::Component, *idx); + visitor.exit_component(ctx, id, component); + ctx.inner.pop_component(); + } + + VisitEvent::Module { idx, module } => { + ctx.inner.maybe_enter_scope(*module); + let id = ctx + .inner + .lookup_id_for(&Space::CoreModule, &ComponentSection::Module, *idx); + visitor.visit_module(ctx, id, module); + ctx.inner.maybe_exit_scope(*module); + } + + VisitEvent::CompInst { idx, inst } => { + ctx.inner.maybe_enter_scope(*inst); + let id = ctx.inner.lookup_id_for( + &Space::CompInst, + &ComponentSection::ComponentInstance, + *idx, + ); + visitor.visit_comp_instance(ctx, id, inst); + ctx.inner.maybe_exit_scope(*inst); + } + + VisitEvent::EnterCompType { idx, ty } => { + ctx.inner.maybe_enter_scope(*ty); + let id = + ctx.inner + .lookup_id_for(&Space::CompType, &ComponentSection::ComponentType, *idx); + visitor.enter_comp_type(ctx, id, ty); + } + + VisitEvent::CompTypeDecl { idx, parent, decl } => { + ctx.inner.maybe_enter_scope(*decl); + let id = ctx.inner.lookup_id_for( + &decl.index_space_of(), + &ComponentSection::ComponentType, + *idx, + ); + visitor.visit_comp_type_decl(ctx, *idx, id, parent, decl); + ctx.inner.maybe_exit_scope(*decl); + } + + VisitEvent::InstTypeDecl { idx, parent, decl } => { + ctx.inner.maybe_enter_scope(*decl); + let id = ctx.inner.lookup_id_for( + &decl.index_space_of(), + &ComponentSection::ComponentType, + *idx, + ); + visitor.visit_inst_type_decl(ctx, *idx, id, parent, decl); + ctx.inner.maybe_exit_scope(*decl); + } + + VisitEvent::ExitCompType { idx, ty } => { + let id = + ctx.inner + .lookup_id_for(&Space::CompType, &ComponentSection::ComponentType, *idx); + visitor.exit_comp_type(ctx, id, ty); + ctx.inner.maybe_exit_scope(*ty); + } + + VisitEvent::Canon { kind, idx, canon } => { + ctx.inner.maybe_enter_scope(*canon); + let space = canon.index_space_of(); + let id = ctx + .inner + .lookup_id_for(&space, &ComponentSection::Canon, *idx); + visitor.visit_canon(ctx, *kind, id, canon); + ctx.inner.maybe_exit_scope(*canon); + } + VisitEvent::Alias { kind, idx, alias } => { + ctx.inner.maybe_enter_scope(*alias); + let space = alias.index_space_of(); + let id = ctx + .inner + .lookup_id_for(&space, &ComponentSection::Alias, *idx); + visitor.visit_alias(ctx, *kind, id, alias); + ctx.inner.maybe_exit_scope(*alias); + } + VisitEvent::Import { kind, idx, imp } => { + ctx.inner.maybe_enter_scope(*imp); + let space = imp.index_space_of(); + let id = ctx + .inner + .lookup_id_for(&space, &ComponentSection::ComponentImport, *idx); + visitor.visit_comp_import(ctx, *kind, id, imp); + ctx.inner.maybe_exit_scope(*imp); + } + VisitEvent::Export { kind, idx, exp } => { + ctx.inner.maybe_enter_scope(*exp); + let space = exp.index_space_of(); + let id = ctx + .inner + .lookup_id_for(&space, &ComponentSection::ComponentExport, *idx); + visitor.visit_comp_export(ctx, *kind, id, exp); + ctx.inner.maybe_exit_scope(*exp); + } + VisitEvent::EnterCoreRecGroup { count, ty } => { + visitor.enter_core_rec_group(ctx, *count, ty); + } + VisitEvent::CoreSubtype { + parent_idx, + subvec_idx, + subtype, + } => { + ctx.inner.maybe_enter_scope(*subtype); + let id = ctx.inner.lookup_id_with_subvec_for( + &Space::CoreType, + &ComponentSection::CoreType, + *parent_idx, + *subvec_idx, + ); + visitor.visit_core_subtype(ctx, id, subtype); + ctx.inner.maybe_exit_scope(*subtype); + } + VisitEvent::ExitCoreRecGroup {} => { + visitor.exit_core_rec_group(ctx); + } + VisitEvent::EnterCoreType { idx, ty } => { + ctx.inner.maybe_enter_scope(*ty); + let id = ctx + .inner + .lookup_id_for(&Space::CoreType, &ComponentSection::CoreType, *idx); + visitor.enter_core_type(ctx, id, ty); + } + VisitEvent::ModuleTypeDecl { idx, parent, decl } => { + ctx.inner.maybe_enter_scope(*decl); + let id = + ctx.inner + .lookup_id_for(&decl.index_space_of(), &ComponentSection::CoreType, *idx); + visitor.visit_module_type_decl(ctx, *idx, id, parent, decl); + ctx.inner.maybe_exit_scope(*decl); + } + VisitEvent::ExitCoreType { idx, ty } => { + let id = ctx + .inner + .lookup_id_for(&Space::CoreType, &ComponentSection::CoreType, *idx); + visitor.exit_core_type(ctx, id, ty); + ctx.inner.maybe_exit_scope(*ty); + } + VisitEvent::CoreInst { idx, inst } => { + ctx.inner.maybe_enter_scope(*inst); + let id = + ctx.inner + .lookup_id_for(&Space::CoreInst, &ComponentSection::CoreInstance, *idx); + visitor.visit_core_instance(ctx, id, inst); + ctx.inner.maybe_exit_scope(*inst); + } + VisitEvent::CustomSection { sect } => { + ctx.inner.maybe_enter_scope(*sect); + visitor.visit_custom_section(ctx, sect); + ctx.inner.maybe_exit_scope(*sect); + } + VisitEvent::StartFunc { func } => { + ctx.inner.maybe_enter_scope(*func); + visitor.visit_start_section(ctx, func); + ctx.inner.maybe_exit_scope(*func); + } + } +} + +#[derive(Debug)] +pub(crate) enum VisitEvent<'ir> { + EnterRootComp { + component: &'ir Component<'ir>, + }, + ExitRootComp { + component: &'ir Component<'ir>, + }, + EnterComp { + idx: usize, + component: &'ir Component<'ir>, + }, + ExitComp { + idx: usize, + component: &'ir Component<'ir>, + }, + Module { + idx: usize, + module: &'ir Module<'ir>, + }, + + // ------------------------ + // Component-level items + // ------------------------ + EnterCompType { + idx: usize, + ty: &'ir ComponentType<'ir>, + }, + ExitCompType { + idx: usize, + ty: &'ir ComponentType<'ir>, + }, + // subitems of a component type + CompTypeDecl { + parent: &'ir ComponentType<'ir>, + /// index in the decl vector + idx: usize, + decl: &'ir ComponentTypeDeclaration<'ir>, + }, + InstTypeDecl { + parent: &'ir ComponentType<'ir>, + /// index in the decl vector + idx: usize, + decl: &'ir InstanceTypeDeclaration<'ir>, + }, + + CompInst { + idx: usize, + inst: &'ir ComponentInstance<'ir>, + }, + + // ------------------------------------------------ + // Items with multiple possible resolved namespaces + // ------------------------------------------------ + Canon { + kind: ItemKind, + idx: usize, + canon: &'ir CanonicalFunction, + }, + Alias { + kind: ItemKind, + idx: usize, + alias: &'ir ComponentAlias<'ir>, + }, + Import { + kind: ItemKind, + idx: usize, + imp: &'ir ComponentImport<'ir>, + }, + Export { + kind: ItemKind, + idx: usize, + exp: &'ir ComponentExport<'ir>, + }, + + // ------------------------ + // Core WebAssembly items + // ------------------------ + EnterCoreRecGroup { + ty: &'ir CoreType<'ir>, + count: usize, + }, + CoreSubtype { + parent_idx: usize, + subvec_idx: usize, + subtype: &'ir SubType, + }, + ExitCoreRecGroup {}, + EnterCoreType { + idx: usize, + ty: &'ir CoreType<'ir>, + }, + ModuleTypeDecl { + parent: &'ir CoreType<'ir>, + /// index in the decl vector + idx: usize, + decl: &'ir ModuleTypeDeclaration<'ir>, + }, + ExitCoreType { + idx: usize, + ty: &'ir CoreType<'ir>, + }, + CoreInst { + idx: usize, + inst: &'ir Instance<'ir>, + }, + + // ------------------------ + // Sections + // ------------------------ + CustomSection { + sect: &'ir CustomSection<'ir>, + }, + StartFunc { + func: &'ir ComponentStartFunction, + }, +} +impl<'ir> VisitEvent<'ir> { + pub fn enter_root_comp(component: &'ir Component<'ir>) -> Self { + Self::EnterRootComp { component } + } + pub fn exit_root_comp(component: &'ir Component<'ir>) -> Self { + Self::ExitRootComp { component } + } + pub fn enter_comp(idx: usize, component: &'ir Component<'ir>) -> Self { + Self::EnterComp { idx, component } + } + pub fn exit_comp(idx: usize, component: &'ir Component<'ir>) -> Self { + Self::ExitComp { idx, component } + } + pub fn module(_: ItemKind, idx: usize, module: &'ir Module<'ir>) -> Self { + Self::Module { idx, module } + } + pub fn enter_comp_type(_: ItemKind, idx: usize, ty: &'ir ComponentType<'ir>) -> Self { + Self::EnterCompType { idx, ty } + } + pub fn comp_type_decl( + parent: &'ir ComponentType<'ir>, + idx: usize, + decl: &'ir ComponentTypeDeclaration<'ir>, + ) -> Self { + Self::CompTypeDecl { parent, idx, decl } + } + pub fn inst_type_decl( + parent: &'ir ComponentType<'ir>, + idx: usize, + decl: &'ir InstanceTypeDeclaration<'ir>, + ) -> Self { + Self::InstTypeDecl { parent, idx, decl } + } + pub fn exit_comp_type(_: ItemKind, idx: usize, ty: &'ir ComponentType<'ir>) -> Self { + Self::ExitCompType { idx, ty } + } + pub fn comp_inst(_: ItemKind, idx: usize, inst: &'ir ComponentInstance<'ir>) -> Self { + Self::CompInst { idx, inst } + } + pub fn canon(kind: ItemKind, idx: usize, canon: &'ir CanonicalFunction) -> Self { + Self::Canon { kind, idx, canon } + } + pub fn alias(kind: ItemKind, idx: usize, alias: &'ir ComponentAlias<'ir>) -> Self { + Self::Alias { kind, idx, alias } + } + pub fn import(kind: ItemKind, idx: usize, imp: &'ir ComponentImport<'ir>) -> Self { + Self::Import { kind, idx, imp } + } + pub fn export(kind: ItemKind, idx: usize, exp: &'ir ComponentExport<'ir>) -> Self { + Self::Export { kind, idx, exp } + } + pub fn enter_rec_group(count: usize, ty: &'ir CoreType<'ir>) -> Self { + Self::EnterCoreRecGroup { count, ty } + } + pub fn core_subtype(parent_idx: usize, subvec_idx: usize, subtype: &'ir SubType) -> Self { + Self::CoreSubtype { + parent_idx, + subvec_idx, + subtype, + } + } + pub fn exit_rec_group() -> Self { + Self::ExitCoreRecGroup {} + } + pub fn enter_core_type(_: ItemKind, idx: usize, ty: &'ir CoreType<'ir>) -> Self { + Self::EnterCoreType { idx, ty } + } + pub fn mod_type_decl( + parent: &'ir CoreType<'ir>, + idx: usize, + decl: &'ir ModuleTypeDeclaration<'ir>, + ) -> Self { + Self::ModuleTypeDecl { parent, idx, decl } + } + pub fn exit_core_type(_: ItemKind, idx: usize, ty: &'ir CoreType<'ir>) -> Self { + Self::ExitCoreType { idx, ty } + } + pub fn core_inst(_: ItemKind, idx: usize, inst: &'ir Instance<'ir>) -> Self { + Self::CoreInst { idx, inst } + } + pub fn custom_sect(_: ItemKind, _: usize, sect: &'ir CustomSection<'ir>) -> Self { + Self::CustomSection { sect } + } + pub fn start_func(_: ItemKind, _: usize, func: &'ir ComponentStartFunction) -> Self { + Self::StartFunc { func } + } +} diff --git a/src/ir/component/visitor/events_structural.rs b/src/ir/component/visitor/events_structural.rs new file mode 100644 index 00000000..d10e9519 --- /dev/null +++ b/src/ir/component/visitor/events_structural.rs @@ -0,0 +1,218 @@ +use crate::ir::component::idx_spaces::IndexSpaceOf; +use crate::ir::component::section::ComponentSection; +use crate::ir::component::visitor::driver::VisitEvent; +use crate::ir::component::visitor::utils::{emit_indexed, for_each_indexed}; +use crate::ir::component::visitor::VisitCtx; +use crate::Component; +use wasmparser::{ + ComponentType, ComponentTypeDeclaration, CoreType, InstanceTypeDeclaration, + ModuleTypeDeclaration, +}; + +pub(crate) fn get_structural_events<'ir>( + component: &'ir Component<'ir>, + ctx: &mut VisitCtx<'ir>, + out: &mut Vec>, +) { + ctx.inner.push_comp_section_tracker(); + out.push(VisitEvent::enter_root_comp(component)); + + visit_comp(component, ctx, out); + + out.push(VisitEvent::exit_root_comp(component)); + ctx.inner.pop_comp_section_tracker(); +} +fn visit_comp<'ir>( + component: &'ir Component<'ir>, + ctx: &mut VisitCtx<'ir>, + out: &mut Vec>, +) { + for (num, section) in component.sections.iter() { + let count = *num as usize; + let start_idx = ctx.inner.visit_section(section, count); + + match section { + ComponentSection::Component => { + for_each_indexed(&component.components, start_idx, count, |idx, sub| { + ctx.inner.push_comp_section_tracker(); + out.push(VisitEvent::enter_comp(idx, sub)); + visit_comp(sub, ctx, out); + ctx.inner.pop_comp_section_tracker(); + out.push(VisitEvent::exit_comp(idx, sub)); + }); + } + + ComponentSection::Module => { + for_each_indexed(&component.modules.vec, start_idx, count, |idx, module| { + emit_indexed(out, idx, module, VisitEvent::module) + }); + } + + ComponentSection::ComponentType => { + for_each_indexed( + &component.component_types.items, + start_idx, + count, + |idx, ty| visit_comp_type(idx, ty, out), + ); + } + + ComponentSection::ComponentInstance => { + for_each_indexed( + &component.component_instance, + start_idx, + count, + |idx, inst| emit_indexed(out, idx, inst, VisitEvent::comp_inst), + ); + } + + ComponentSection::CoreInstance => { + for_each_indexed(&component.instances, start_idx, count, |idx, inst| { + emit_indexed(out, idx, inst, VisitEvent::core_inst) + }); + } + + ComponentSection::CoreType => { + for_each_indexed(&component.core_types, start_idx, count, |idx, ty| { + visit_core_type(idx, ty, out) + }); + } + + ComponentSection::Canon => { + for_each_indexed(&component.canons.items, start_idx, count, |idx, canon| { + emit_indexed(out, idx, canon, VisitEvent::canon) + }); + } + + ComponentSection::ComponentExport => { + for_each_indexed(&component.exports, start_idx, count, |idx, export| { + emit_indexed(out, idx, export, VisitEvent::export) + }); + } + + ComponentSection::ComponentImport => { + for_each_indexed(&component.imports, start_idx, count, |idx, import| { + emit_indexed(out, idx, import, VisitEvent::import) + }); + } + + ComponentSection::Alias => { + for_each_indexed(&component.alias.items, start_idx, count, |idx, alias| { + emit_indexed(out, idx, alias, VisitEvent::alias) + }); + } + + ComponentSection::CustomSection => { + for_each_indexed( + &component.custom_sections.custom_sections, + start_idx, + count, + |idx, sect| emit_indexed(out, idx, sect, VisitEvent::custom_sect), + ); + } + + ComponentSection::ComponentStartSection => { + for_each_indexed(&component.start_section, start_idx, count, |idx, func| { + emit_indexed(out, idx, func, VisitEvent::start_func) + }); + } + } + } +} +fn visit_comp_type<'ir>(idx: usize, ty: &'ir ComponentType<'ir>, out: &mut Vec>) { + out.push(VisitEvent::enter_comp_type( + ty.index_space_of().into(), + idx, + ty, + )); + + match ty { + ComponentType::Component(decls) => { + for (i, decl) in decls.iter().enumerate() { + visit_component_type_decl(ty, decl, i, out); + } + } + + ComponentType::Instance(decls) => { + for (i, decl) in decls.iter().enumerate() { + visit_instance_type_decl(ty, decl, i, out); + } + } + + // no sub-scoping for the below variants + ComponentType::Defined(_) | ComponentType::Func(_) | ComponentType::Resource { .. } => {} + } + + out.push(VisitEvent::exit_comp_type( + ty.index_space_of().into(), + idx, + ty, + )); +} +fn visit_component_type_decl<'ir>( + parent: &'ir ComponentType<'ir>, + decl: &'ir ComponentTypeDeclaration<'ir>, + idx: usize, + out: &mut Vec>, +) { + out.push(VisitEvent::comp_type_decl(parent, idx, decl)); + + match decl { + ComponentTypeDeclaration::Type(ty) => visit_comp_type(idx, ty, out), + ComponentTypeDeclaration::CoreType(ty) => visit_core_type(idx, ty, out), + ComponentTypeDeclaration::Alias(_) + | ComponentTypeDeclaration::Export { .. } + | ComponentTypeDeclaration::Import(_) => {} + } +} +fn visit_instance_type_decl<'ir>( + parent: &'ir ComponentType<'ir>, + decl: &'ir InstanceTypeDeclaration<'ir>, + idx: usize, + out: &mut Vec>, +) { + out.push(VisitEvent::inst_type_decl(parent, idx, decl)); + + match decl { + InstanceTypeDeclaration::Type(ty) => visit_comp_type(idx, ty, out), + InstanceTypeDeclaration::CoreType(ty) => visit_core_type(idx, ty, out), + InstanceTypeDeclaration::Alias(_) | InstanceTypeDeclaration::Export { .. } => {} + } +} +fn visit_core_type<'ir>(idx: usize, ty: &'ir CoreType<'ir>, out: &mut Vec>) { + match ty { + CoreType::Module(decls) => { + out.push(VisitEvent::enter_core_type( + ty.index_space_of().into(), + idx, + ty, + )); + for (i, decl) in decls.iter().enumerate() { + visit_module_type_decl(ty, decl, i, out); + } + out.push(VisitEvent::exit_core_type( + ty.index_space_of().into(), + idx, + ty, + )); + } + + // no sub-scoping for the below variant + CoreType::Rec(group) => { + out.push(VisitEvent::enter_rec_group(group.types().len(), ty)); + for (subvec_idx, item) in group.types().enumerate() { + out.push(VisitEvent::core_subtype(idx, subvec_idx, item)); + } + out.push(VisitEvent::exit_rec_group()); + } + } +} + +fn visit_module_type_decl<'ir>( + parent: &'ir CoreType<'ir>, + decl: &'ir ModuleTypeDeclaration<'ir>, + idx: usize, + out: &mut Vec>, +) { + out.push(VisitEvent::mod_type_decl(parent, idx, decl)); +} diff --git a/src/ir/component/visitor/events_topological.rs b/src/ir/component/visitor/events_topological.rs new file mode 100644 index 00000000..2b738f30 --- /dev/null +++ b/src/ir/component/visitor/events_topological.rs @@ -0,0 +1,580 @@ +use crate::ir::component::idx_spaces::{IndexSpaceOf, Space, SpaceSubtype}; +use crate::ir::component::refs::{RefKind, ReferencedIndices}; +use crate::ir::component::scopes::GetScopeKind; +use crate::ir::component::section::ComponentSection; +use crate::ir::component::visitor::driver::VisitEvent; +use crate::ir::component::visitor::VisitCtx; +use crate::ir::types::CustomSection; +use crate::{Component, Module}; +use std::collections::HashSet; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentImport, ComponentInstance, + ComponentStartFunction, ComponentType, ComponentTypeDeclaration, CoreType, Instance, + InstanceTypeDeclaration, ModuleTypeDeclaration, +}; + +pub(crate) fn get_topological_events<'ir>( + component: &'ir Component<'ir>, + ctx: &mut VisitCtx<'ir>, + out: &mut Vec>, +) { + let mut topo = TopoCtx::default(); + + ctx.inner.push_component(component); + out.push(VisitEvent::enter_root_comp(component)); + + topo.collect_component(component, None, ctx); + out.extend(topo.events); + + out.push(VisitEvent::exit_root_comp(component)); + ctx.inner.pop_component(); +} + +#[derive(Default)] +struct TopoCtx<'ir> { + seen: HashSet, + events: Vec>, +} +impl<'ir> TopoCtx<'ir> { + fn collect_component( + &mut self, + comp: &'ir Component<'ir>, + idx: Option, + ctx: &mut VisitCtx<'ir>, + ) { + let key = NodeKey::Component(id(comp)); + if !self.visit_once(key) { + return; + } + + if let Some(idx) = idx { + ctx.inner.push_component(comp); + self.events.push(VisitEvent::enter_comp(idx, comp)); + } + + for (count, section) in comp.sections.iter() { + let start_idx = ctx.inner.visit_section(section, *count as usize); + self.collect_section_items(comp, section, start_idx, *count as usize, ctx); + } + + if let Some(idx) = idx { + ctx.inner.pop_component(); + self.events.push(VisitEvent::exit_comp(idx, comp)); + } + } + fn collect_module(&mut self, module: &'ir Module<'ir>, idx: usize, ctx: &mut VisitCtx<'ir>) { + self.collect_node( + module, + NodeKey::Module(id(module)), + ctx, + None, + VisitEvent::module(module.index_space_of().into(), idx, module), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_component_type( + &mut self, + node: &'ir ComponentType<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + let key = NodeKey::ComponentType(id(node)); + + self.collect_node( + node, + key, + ctx, + Some(VisitEvent::enter_comp_type( + node.index_space_of().into(), + idx, + node, + )), + VisitEvent::exit_comp_type(node.index_space_of().into(), idx, node), + |this, node, ctx| { + match node { + ComponentType::Component(decls) => { + for (i, item) in decls.iter().enumerate() { + this.collect_subitem( + decls, + item, + i, + NodeKey::component_type_decl, + |inner_this, item, i, cx| { + inner_this.collect_component_type_decl(node, item, i, cx); + }, + ctx, + ); + } + } + + ComponentType::Instance(decls) => { + for (i, item) in decls.iter().enumerate() { + this.collect_subitem( + decls, + item, + i, + NodeKey::inst_type_decl, + |inner_this, item, i, cx| { + inner_this.collect_instance_type_decl(node, item, i, cx); + }, + ctx, + ); + } + } + + // no sub-scoping for the below variants + ComponentType::Defined(_) + | ComponentType::Func(_) + | ComponentType::Resource { .. } => {} + } + }, + ); + } + fn collect_component_type_decl( + &mut self, + parent: &'ir ComponentType<'ir>, + decl: &'ir ComponentTypeDeclaration<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.events + .push(VisitEvent::comp_type_decl(parent, idx, decl)); + match decl { + ComponentTypeDeclaration::Type(ty) => self.collect_component_type(ty, idx, ctx), + ComponentTypeDeclaration::CoreType(ty) => self.collect_core_type(ty, idx, ctx), + ComponentTypeDeclaration::Alias(_) + | ComponentTypeDeclaration::Export { .. } + | ComponentTypeDeclaration::Import(_) => {} + } + } + fn collect_instance_type_decl( + &mut self, + parent: &'ir ComponentType<'ir>, + decl: &'ir InstanceTypeDeclaration<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.events + .push(VisitEvent::inst_type_decl(parent, idx, decl)); + match decl { + InstanceTypeDeclaration::Type(ty) => self.collect_component_type(ty, idx, ctx), + InstanceTypeDeclaration::CoreType(ty) => self.collect_core_type(ty, idx, ctx), + InstanceTypeDeclaration::Alias(_) | InstanceTypeDeclaration::Export { .. } => {} + } + } + fn collect_comp_inst( + &mut self, + inst: &'ir ComponentInstance<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + inst, + NodeKey::ComponentInstance(id(inst)), + ctx, + None, + VisitEvent::comp_inst(inst.index_space_of().into(), idx, inst), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_core_inst(&mut self, inst: &'ir Instance<'ir>, idx: usize, ctx: &mut VisitCtx<'ir>) { + self.collect_node( + inst, + NodeKey::CoreInst(id(inst)), + ctx, + None, + VisitEvent::core_inst(inst.index_space_of().into(), idx, inst), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + + fn collect_core_type(&mut self, node: &'ir CoreType<'ir>, idx: usize, ctx: &mut VisitCtx<'ir>) { + let key = NodeKey::CoreType(id(node)); + + let (enter_evt, exit_evt) = if let CoreType::Rec(group) = node { + ( + VisitEvent::enter_rec_group(group.types().len(), node), + VisitEvent::exit_rec_group(), + ) + } else { + ( + VisitEvent::enter_core_type(node.index_space_of().into(), idx, node), + VisitEvent::exit_core_type(node.index_space_of().into(), idx, node), + ) + }; + + self.collect_node( + node, + key, + ctx, + Some(enter_evt), + exit_evt, + |this, node, ctx| { + match node { + CoreType::Module(decls) => { + for (i, item) in decls.iter().enumerate() { + this.collect_subitem( + decls, + item, + i, + NodeKey::module_type_decl, + |inner_this, item, i, cx| { + inner_this.collect_module_type_decl(node, item, i, cx); + }, + ctx, + ); + } + } + + // no sub-scoping for the below variant + CoreType::Rec(group) => { + for (subvec_idx, item) in group.types().enumerate() { + this.events + .push(VisitEvent::core_subtype(idx, subvec_idx, item)); + } + } + } + }, + ); + } + fn collect_module_type_decl( + &mut self, + parent: &'ir CoreType<'ir>, + decl: &'ir ModuleTypeDeclaration<'ir>, + idx: usize, + _: &mut VisitCtx<'ir>, + ) { + self.events + .push(VisitEvent::mod_type_decl(parent, idx, decl)) + } + fn collect_canon( + &mut self, + canon: &'ir CanonicalFunction, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + canon, + NodeKey::Canon(id(canon)), + ctx, + None, + VisitEvent::canon(canon.index_space_of().into(), idx, canon), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_export( + &mut self, + export: &'ir ComponentExport<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + export, + NodeKey::Export(id(export)), + ctx, + None, + VisitEvent::export(export.index_space_of().into(), idx, export), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_import( + &mut self, + import: &'ir ComponentImport<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + import, + NodeKey::Import(id(import)), + ctx, + None, + VisitEvent::import(import.index_space_of().into(), idx, import), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_alias( + &mut self, + alias: &'ir ComponentAlias<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + alias, + NodeKey::Alias(id(alias)), + ctx, + None, + VisitEvent::alias(alias.index_space_of().into(), idx, alias), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_custom_section( + &mut self, + sect: &'ir CustomSection<'ir>, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + sect, + NodeKey::Custom(id(sect)), + ctx, + None, + VisitEvent::custom_sect(sect.index_space_of().into(), idx, sect), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + fn collect_start_section( + &mut self, + func: &'ir ComponentStartFunction, + idx: usize, + ctx: &mut VisitCtx<'ir>, + ) { + self.collect_node( + func, + NodeKey::Start(id(func)), + ctx, + None, + VisitEvent::start_func(func.index_space_of().into(), idx, func), + |this, node, cx| { + this.collect_deps(node, cx); + }, + ); + } + + fn collect_section_items( + &mut self, + comp: &'ir Component<'ir>, + section: &ComponentSection, + start_idx: usize, + count: usize, + ctx: &mut VisitCtx<'ir>, + ) { + for i in 0..count { + let idx = start_idx + i; + + match section { + ComponentSection::Component => { + self.collect_component(&comp.components[idx], Some(idx), ctx) + } + + ComponentSection::Module => self.collect_module(&comp.modules[idx], idx, ctx), + + ComponentSection::ComponentType => { + self.collect_component_type(&comp.component_types.items[idx], idx, ctx) + } + + ComponentSection::ComponentInstance => { + self.collect_comp_inst(&comp.component_instance[idx], idx, ctx) + } + + ComponentSection::Canon => self.collect_canon(&comp.canons.items[idx], idx, ctx), + + ComponentSection::Alias => self.collect_alias(&comp.alias.items[idx], idx, ctx), + + ComponentSection::ComponentImport => { + self.collect_import(&comp.imports[idx], idx, ctx) + } + + ComponentSection::ComponentExport => { + self.collect_export(&comp.exports[idx], idx, ctx) + } + + ComponentSection::CoreType => { + self.collect_core_type(&comp.core_types[idx], idx, ctx) + } + + ComponentSection::CoreInstance => { + self.collect_core_inst(&comp.instances[idx], idx, ctx) + } + + ComponentSection::CustomSection => self.collect_custom_section( + &comp.custom_sections.custom_sections[idx], + idx, + ctx, + ), + + ComponentSection::ComponentStartSection => { + self.collect_start_section(&comp.start_section[idx], idx, ctx) + } + } + } + } + + fn collect_node( + &mut self, + node: &'ir T, + key: NodeKey, + ctx: &mut VisitCtx<'ir>, + enter_event: Option>, + exit_event: VisitEvent<'ir>, + walk: impl FnOnce(&mut Self, &'ir T, &mut VisitCtx<'ir>), + ) where + T: GetScopeKind + ReferencedIndices + 'ir, + { + if !self.visit_once(key) { + return; + } + + if let Some(evt) = enter_event { + self.events.push(evt) + } + + // walk inner declarations + ctx.inner.maybe_enter_scope(node); + walk(self, node, ctx); + ctx.inner.maybe_exit_scope(node); + + self.events.push(exit_event); + } + fn collect_deps(&mut self, item: &'ir T, ctx: &mut VisitCtx<'ir>) { + let refs = item.referenced_indices(); + for RefKind { ref_, .. } in refs.iter() { + let (vec, idx, subidx) = ctx.inner.index_from_assumed_id(ref_); + if ref_.space != Space::CoreType { + assert!( + subidx.is_none(), + "only core types (with rec groups) should ever have subvec indices!" + ); + } + + let comp_id = ctx.inner.comp_at(ref_.depth); + let referenced_comp = ctx.inner.comp_store.get(comp_id); + + let space = ref_.space; + match vec { + SpaceSubtype::Main => match space { + Space::Comp => { + self.collect_component(&referenced_comp.components[idx], Some(idx), ctx) + } + Space::CompType => self.collect_component_type( + &referenced_comp.component_types.items[idx], + idx, + ctx, + ), + Space::CompInst => { + self.collect_comp_inst(&referenced_comp.component_instance[idx], idx, ctx) + } + Space::CoreInst => { + self.collect_core_inst(&referenced_comp.instances[idx], idx, ctx) + } + Space::CoreModule => { + self.collect_module(&referenced_comp.modules[idx], idx, ctx) + } + Space::CoreType => { + self.collect_core_type(&referenced_comp.core_types[idx], idx, ctx) + } + Space::CompFunc | Space::CoreFunc => { + self.collect_canon(&referenced_comp.canons.items[idx], idx, ctx) + } + Space::CompVal + | Space::CoreMemory + | Space::CoreTable + | Space::CoreGlobal + | Space::CoreTag + | Space::NA => unreachable!( + "This spaces don't exist in a main vector on the component IR: {vec:?}" + ), + }, + SpaceSubtype::Export => { + self.collect_export(&referenced_comp.exports[idx], idx, ctx) + } + SpaceSubtype::Import => { + self.collect_import(&referenced_comp.imports[idx], idx, ctx) + } + SpaceSubtype::Alias => { + self.collect_alias(&referenced_comp.alias.items[idx], idx, ctx) + } + } + } + } + + fn collect_subitem( + &mut self, + all: &'ir [T], + item: &'ir T, + item_idx: usize, + gen_key: fn(&T, usize) -> NodeKey, + mut emit_item: impl FnMut(&mut Self, &'ir T, usize, &mut VisitCtx<'ir>), + ctx: &mut VisitCtx<'ir>, + ) { + if !self.visit_once(gen_key(item, item_idx)) { + return; + } + + // collect the dependencies of this guy + ctx.inner.maybe_enter_scope(item); + let refs = item.referenced_indices(); + for RefKind { ref_, .. } in refs.iter() { + if !ref_.depth.is_curr() { + continue; + } + let (vec, idx, ..) = ctx.inner.index_from_assumed_id(ref_); + assert_eq!(vec, SpaceSubtype::Main); + let dep_item = &all[idx]; + + if !self.visit_once(gen_key(dep_item, idx)) { + continue; + } + + // collect subitem + emit_item(self, dep_item, idx, ctx); + } + + ctx.inner.maybe_exit_scope(item); + + // collect item + emit_item(self, item, item_idx, ctx); + } + fn visit_once(&mut self, key: NodeKey) -> bool { + self.seen.insert(key) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +enum NodeKey { + Component(*const ()), + Module(*const ()), + ComponentType(*const ()), + ComponentTypeDecl(*const (), usize), // decl ptr + index + InstanceTypeDecl(*const (), usize), // decl ptr + index + CoreType(*const ()), + ModuleTypeDecl(*const (), usize), // decl ptr + index + ComponentInstance(*const ()), + CoreInst(*const ()), + Alias(*const ()), + Import(*const ()), + Export(*const ()), + Canon(*const ()), + Custom(*const ()), + Start(*const ()), +} +impl NodeKey { + fn inst_type_decl(decl: &InstanceTypeDeclaration, idx: usize) -> Self { + Self::InstanceTypeDecl(id(decl), idx) + } + fn component_type_decl(decl: &ComponentTypeDeclaration, idx: usize) -> Self { + Self::ComponentTypeDecl(id(decl), idx) + } + fn module_type_decl(decl: &ModuleTypeDeclaration, idx: usize) -> Self { + Self::ModuleTypeDecl(id(decl), idx) + } +} + +fn id(ptr: &T) -> *const () { + ptr as *const T as *const () +} diff --git a/src/ir/component/visitor/mod.rs b/src/ir/component/visitor/mod.rs new file mode 100644 index 00000000..1680de32 --- /dev/null +++ b/src/ir/component/visitor/mod.rs @@ -0,0 +1,711 @@ +use crate::ir::component::idx_spaces::Space; +use crate::ir::component::refs::{IndexedRef, RefKind}; +use crate::ir::component::visitor::driver::{drive_event, VisitEvent}; +use crate::ir::component::visitor::events_structural::get_structural_events; +use crate::ir::component::visitor::events_topological::get_topological_events; +use crate::ir::component::visitor::utils::VisitCtxInner; +use crate::ir::types::CustomSection; +use crate::{Component, Module}; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentImport, ComponentInstance, + ComponentStartFunction, ComponentType, ComponentTypeDeclaration, CoreType, Instance, + InstanceTypeDeclaration, ModuleTypeDeclaration, SubType, +}; + +pub(crate) mod driver; +mod events_structural; +pub(crate) mod events_topological; +#[cfg(test)] +mod tests; +pub(crate) mod utils; + +/// Walk a [`Component`] using its *structural* (in-file) order. +/// +/// This traversal visits items in the same order they appear in the +/// component’s section layout. Nested components are entered and exited +/// according to their lexical structure, and all visitor callbacks are +/// invoked in a manner consistent with the original encoding order. +/// +/// # Semantics +/// +/// - Preserves section order exactly as defined in the component. +/// - Nested components are visited depth-first. +/// - Scope management and ID resolution are handled internally. +/// - No reordering is performed to satisfy reference dependencies. +/// +/// This is the most appropriate traversal for: +/// +/// - Analysis passes +/// - Pretty-printing +/// - Validation-like inspections +/// - Any logic that expects lexical ordering +/// +/// # Guarantees +/// +/// - No forward-reference elimination is attempted. +/// - The visitor observes the same structural hierarchy as encoded. +/// - `enter_component` / `exit_component` callbacks are properly paired. +/// +/// See also [`walk_topological`] for a dependency-ordered traversal. +pub fn walk_structural<'ir, V: ComponentVisitor<'ir>>(root: &'ir Component<'ir>, visitor: &mut V) { + walk(get_structural_events, root, visitor); +} + +/// Walk a [`Component`] in *topological* (dependency) order. +/// +/// This traversal reorders items such that definitions are visited +/// before any items that reference them. The resulting visitation +/// order contains no forward references, making it suitable for +/// encoding or transforming components that require dependency-safe +/// emission. +/// +/// # Semantics +/// +/// - Items are visited in a dependency-respecting order. +/// - Nested components are still entered/exited with correct scope +/// management. +/// - All visitor callbacks observe valid, already-declared references. +/// - Structural layout order is **not** preserved. +/// +/// # When to Use +/// +/// This traversal is intended for: +/// +/// - Component encoding +/// - Instrumentation passes +/// - Lowering or rewriting IR where forward references are illegal +/// - Any pass that requires reference-safe emission order +/// +/// # Guarantees +/// +/// - No visitor callback observes an unresolved forward reference. +/// - Scope handling and ID resolution remain logically consistent. +/// - `enter_component` / `exit_component` callbacks are properly paired. +/// +/// See also [`walk_structural`] for lexical-order traversal. +pub fn walk_topological<'ir, V: ComponentVisitor<'ir>>(root: &'ir Component<'ir>, visitor: &mut V) { + walk(get_topological_events, root, visitor); +} + +fn walk<'ir, V: ComponentVisitor<'ir>>( + get_evts: fn(&'ir Component<'ir>, &mut VisitCtx<'ir>, &mut Vec>), + root: &'ir Component<'ir>, + visitor: &mut V, +) { + let mut ctx = VisitCtx::new(root); + let mut events = Vec::new(); + get_evts(root, &mut ctx, &mut events); + + for event in events.iter() { + drive_event(event, visitor, &mut ctx); + } +} + +/// A structured, read-only visitor over a [`Component`] tree. +/// +/// All methods have default no-op implementations. Override only the +/// callbacks relevant to your use case. +/// +/// # Guarantees +/// +/// - `enter_component` and `exit_component` are always properly paired. +/// - Nested components are visited in a well-structured manner. +/// - IDs are resolved and stable within a single traversal. +/// +/// # ID Semantics +/// +/// - `id: None` is used only for the root component. +/// - All other items receive a resolved `u32` ID corresponding to their +/// index within the appropriate namespace at that depth. +/// - For items that may belong to multiple namespaces (e.g. imports, +/// exports, aliases, canonical functions), the `ItemKind` parameter +/// indicates the resolved kind of the item. +/// +/// # Mutation +/// +/// This visitor is strictly read-only. Implementations must not mutate +/// the underlying component structure. +pub trait ComponentVisitor<'a> { + /// Invoked when entering the outermost, root component to enable special handling. + /// + /// This is the earliest hook available for a component. + fn enter_root_component(&mut self, _cx: &VisitCtx<'a>, _component: &Component<'a>) {} + /// Invoked after all items within the root component have been visited. + /// + /// Always paired with a prior `enter_root_component` call. + fn exit_root_component(&mut self, _cx: &VisitCtx<'a>, _component: &Component<'a>) {} + /// Invoked when entering a subcomponent within the root. + /// + /// The `id` corresponds to the resolved component index within the + /// current namespace. This callback is paired with `exit_component` + /// once traversal of the component’s body has completed. + fn enter_component(&mut self, _cx: &VisitCtx<'a>, _id: u32, _component: &Component<'a>) {} + /// Invoked after all items within a subcomponent have been visited. + /// + /// Always paired with a prior `enter_component` call. + fn exit_component(&mut self, _cx: &VisitCtx<'a>, _id: u32, _component: &Component<'a>) {} + /// Invoked for each core WebAssembly module defined in the component. + /// + /// The `id` corresponds to the module’s resolved index within the + /// current core module namespace. + fn visit_module(&mut self, _cx: &VisitCtx<'a>, _id: u32, _module: &Module<'a>) {} + + // ------------------------ + // Component-level items + // ------------------------ + + /// Invoked when entering a component-level type definition. + /// + /// This includes all variants of `ComponentType`, such as defined, + /// function, component, instance, and resource types. + /// + /// The `id` corresponds to the resolved type index within the + /// component type namespace. + /// + /// This callback is paired with `exit_comp_type`, and any nested + /// declarations (e.g. `ComponentTypeDeclaration` or + /// `InstanceTypeDeclaration`) will be reported between the enter/exit + /// calls. + fn enter_comp_type(&mut self, _cx: &VisitCtx<'a>, _id: u32, _comp_type: &ComponentType<'a>) {} + + /// Invoked for each declaration within a `ComponentType::Component`. + /// + /// The `decl_idx` is the index of this declaration within the parent + /// component type’s declaration list. The `parent` is the enclosing + /// `ComponentType`, and `decl` is the specific declaration. + /// + /// These callbacks are emitted between `enter_comp_type` and + /// `exit_comp_type` for the enclosing type. + fn visit_comp_type_decl( + &mut self, + _cx: &VisitCtx<'a>, + _decl_idx: usize, + _id: u32, + _parent: &ComponentType<'a>, + _decl: &ComponentTypeDeclaration<'a>, + ) { + } + + /// Invoked for each declaration within a `ComponentType::Instance`. + /// + /// The `decl_idx` is the index of this declaration within the parent + /// instance type’s declaration list. The `parent` is the enclosing + /// `ComponentType`, and `decl` is the specific instance type + /// declaration. + /// + /// These callbacks are emitted between `enter_comp_type` and + /// `exit_comp_type` for the enclosing type. + fn visit_inst_type_decl( + &mut self, + _cx: &VisitCtx<'a>, + _decl_idx: usize, + _id: u32, + _parent: &ComponentType<'a>, + _decl: &InstanceTypeDeclaration<'a>, + ) { + } + + /// Invoked after all nested declarations within a component-level + /// type have been visited. + /// + /// Always paired with a prior `enter_comp_type` call for the same `id`. + fn exit_comp_type(&mut self, _cx: &VisitCtx<'a>, _id: u32, _comp_type: &ComponentType<'a>) {} + + /// Invoked for each component instance. + /// + /// The `id` corresponds to the resolved instance index within the + /// component instance namespace. + fn visit_comp_instance( + &mut self, + _cx: &VisitCtx<'a>, + _id: u32, + _instance: &ComponentInstance<'a>, + ) { + } + + // ------------------------------------------------ + // Items with multiple possible resolved namespaces + // ------------------------------------------------ + + /// Invoked for canonical functions. + /// + /// The `kind` parameter indicates the resolved namespace of this item + /// (e.g. component function vs. core function). + /// + /// The `id` is the resolved index within the namespace identified + /// by `kind`. + fn visit_canon( + &mut self, + _cx: &VisitCtx<'a>, + _kind: ItemKind, + _id: u32, + _canon: &CanonicalFunction, + ) { + } + + /// Invoked for component aliases. + /// + /// The `kind` parameter indicates the resolved target namespace + /// referenced by the alias. + /// + /// The `id` is the resolved index of the alias within its namespace. + fn visit_alias( + &mut self, + _cx: &VisitCtx<'a>, + _kind: ItemKind, + _id: u32, + _alias: &ComponentAlias<'a>, + ) { + } + + /// Invoked for component imports. + /// + /// The `kind` parameter identifies the imported item category + /// (e.g. type, function, instance). + /// + /// The `id` is the resolved index assigned to the imported item + /// within the corresponding namespace. + fn visit_comp_import( + &mut self, + _cx: &VisitCtx<'a>, + _kind: ItemKind, + _id: u32, + _import: &ComponentImport<'a>, + ) { + } + + /// Invoked for component exports. + /// + /// The `kind` parameter identifies the exported item category. + /// + /// The `id` is the resolved index of the exported item within the + /// corresponding namespace. + fn visit_comp_export( + &mut self, + _cx: &VisitCtx<'a>, + _kind: ItemKind, + _id: u32, + _export: &ComponentExport<'a>, + ) { + } + + // ============================================================ + // Core Recursion Groups (`core rec`) + // ============================================================ + + /// Called when entering a core recursion group (`core rec`). + /// + /// A recursion group defines one or more mutually recursive core + /// subtypes that are allocated as a unit in the core type index + /// space. All subtypes belonging to this group will be reported + /// via subsequent `visit_core_subtype` calls, followed by a single + /// `exit_core_rec_group`. + /// + /// Parameters: + /// - `count`: The total number of subtypes in this recursion group. + /// - `core_type`: The enclosing `CoreType` that owns this group. + /// + /// Ordering guarantees: + /// - Exactly `count` calls to `visit_core_subtype` will occur + /// before `exit_core_rec_group` is invoked. + /// - No other recursion group callbacks will be interleaved. + /// + /// Indexing semantics: + /// - Each subtype reported within this group corresponds to a + /// consecutive allocation in the core type index space. + fn enter_core_rec_group( + &mut self, + _cx: &VisitCtx<'a>, + _count: usize, + _core_type: &CoreType<'a>, + ) { + } + + /// Called for each subtype within the current recursion group. + /// + /// This callback is emitted between `enter_core_rec_group` and + /// `exit_core_rec_group`. + /// + /// Parameters: + /// - `id`: The resolved core type index assigned to this subtype. + /// These indices are contiguous within the enclosing recursion group. + /// - `subtype`: The subtype definition, including finality, + /// supertype information, and its composite type. + /// + /// Invariants: + /// - This is only invoked while a recursion group is active. + /// - The `id` is stable and corresponds to the canonical core + /// type namespace for the enclosing module. + fn visit_core_subtype(&mut self, _cx: &VisitCtx<'a>, _id: u32, _subtype: &SubType) {} + + /// Called after all subtypes in the current recursion group + /// have been reported. + /// + /// Always paired with a prior `enter_core_rec_group`. No additional + /// `visit_core_subtype` calls will occur after this callback. + /// + /// At this point, the full set of types in the group is known and + /// may be finalized or encoded as a unit. + fn exit_core_rec_group(&mut self, _cx: &VisitCtx<'a>) {} + + // ============================================================ + // Core Type Definitions + // ============================================================ + + /// Called when entering a core type definition. + /// + /// This corresponds to a type allocated in the core type namespace + /// (e.g., a module type). The `id` is the resolved index within that + /// namespace. + /// + /// This callback forms a structured pair with `exit_core_type`. + /// Any nested structure associated with this type (such as module + /// type declarations) will be reported between these two calls. + /// + /// Ordering guarantees: + /// `enter_core_type(id, ...)` + /// → zero or more `visit_module_type_decl(...)` + /// → `exit_core_type(id, ...)` + /// + /// The same `id` is passed to both enter and exit. + fn enter_core_type(&mut self, _cx: &VisitCtx<'a>, _id: u32, _core_type: &CoreType<'a>) {} + + /// Called for each declaration inside a core module type. + /// + /// Emitted only while visiting a core type whose underlying + /// definition is a module type. + /// + /// Parameters: + /// - `decl_idx`: The declaration’s ordinal position within the + /// parent module type. + /// - `id`: The resolved core type index of the enclosing type. + /// - `parent`: The enclosing `CoreType`. + /// - `decl`: The specific module type declaration. + /// + /// Ordering guarantees: + /// - These callbacks occur strictly between `enter_core_type` + /// and `exit_core_type` for the same `id`. + /// - Declarations are visited in source order. + /// + /// Indexing semantics: + /// - `decl_idx` is local to the parent type and does not refer + /// to a global index space. + fn visit_module_type_decl( + &mut self, + _cx: &VisitCtx<'a>, + _decl_idx: usize, + _id: u32, + _parent: &CoreType<'a>, + _decl: &ModuleTypeDeclaration<'a>, + ) { + } + + /// Called after all nested declarations for a core type + /// have been visited. + /// + /// Always paired with a prior `enter_core_type` for the same `id`. + /// No additional callbacks related to this type will occur after + /// this point. + /// + /// Implementations may use this as a finalization hook once the + /// full structural contents of the type are known. + fn exit_core_type(&mut self, _cx: &VisitCtx<'a>, _id: u32, _core_type: &CoreType<'a>) {} + + /// Invoked for each core WebAssembly instance. + /// + /// The `id` corresponds to the resolved instance index within the + /// core instance namespace. + fn visit_core_instance(&mut self, _cx: &VisitCtx<'a>, _id: u32, _inst: &Instance<'a>) {} + + // ------------------------ + // Sections + // ------------------------ + + /// Invoked for each custom section encountered during traversal. + /// + /// Custom sections are visited in traversal order and are not + /// associated with structured enter/exit pairing. + fn visit_custom_section(&mut self, _cx: &VisitCtx<'a>, _sect: &CustomSection<'a>) {} + + /// Invoked if the component defines a start function. + /// + /// This callback is emitted at the point in traversal where the + /// start section appears. + fn visit_start_section(&mut self, _cx: &VisitCtx<'a>, _start: &ComponentStartFunction) {} +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ItemKind { + Comp, + CompFunc, + CompVal, + CompType, + CompInst, + CoreInst, + CoreModule, + CoreType, + CoreFunc, + CoreMemory, + CoreTable, + CoreGlobal, + CoreTag, + NA, +} +impl From for ItemKind { + fn from(space: Space) -> Self { + match space { + Space::Comp => Self::Comp, + Space::CompFunc => Self::CompFunc, + Space::CompVal => Self::CompVal, + Space::CompType => Self::CompType, + Space::CompInst => Self::CompInst, + Space::CoreInst => Self::CoreInst, + Space::CoreModule => Self::CoreModule, + Space::CoreType => Self::CoreType, + Space::CoreFunc => Self::CoreFunc, + Space::CoreMemory => Self::CoreMemory, + Space::CoreTable => Self::CoreTable, + Space::CoreGlobal => Self::CoreGlobal, + Space::CoreTag => Self::CoreTag, + Space::NA => Self::NA, + } + } +} + +/// Context provided during component traversal. +/// +/// `VisitCtx` allows resolution of referenced indices (such as type, +/// function, instance, or module indices) relative to the current +/// traversal position. +/// +/// The context: +/// +/// - Tracks nested component boundaries +/// - Tracks nested index scopes +/// - Correctly resolves `(outer ...)` references +/// - Resolves references across component and core index spaces +/// +/// This type is opaque and cannot be constructed by users. It is only +/// available during traversal via [`walk_topological`] or [`walk_structural`]. +/// +/// All resolution operations are read-only and reflect the *semantic* +/// structure of the component, not its internal storage layout. +pub struct VisitCtx<'a> { + pub(crate) inner: VisitCtxInner<'a>, +} +impl<'a> VisitCtx<'a> { + pub(crate) fn new(component: &'a Component<'a>) -> Self { + Self { + inner: VisitCtxInner::new(component), + } + } + /// Resolves a single [`IndexedRef`] into a fully resolved semantic item. + /// + /// This applies: + /// + /// - Depth resolution (`outer` / nested scopes) + /// - Index space resolution + /// - Component vs core namespace resolution + /// + /// The returned [`ResolvedItem`] represents the semantic target + /// referenced by the index. + /// + /// To pull such references from an IR node, use one of the following traits + /// (only the applicable traits have been defined per node): + /// - [`crate::ir::component::refs::ReferencedIndices`]: to pull ALL refs + /// - [`crate::ir::component::refs::GetCompRefs`]: to pull component refs + /// - [`crate::ir::component::refs::GetModuleRefs`]: to pull module refs + /// - [`crate::ir::component::refs::GetTypeRefs`]: to pull type refs + /// - [`crate::ir::component::refs::GetFuncRefs`]: to pull func refs + /// - [`crate::ir::component::refs::GetFuncRef`]: if a node only has a single func ref + /// - [`crate::ir::component::refs::GetMemRefs`]: to pull memory refs + /// - [`crate::ir::component::refs::GetTableRefs`]: to pull table refs + /// - [`crate::ir::component::refs::GetItemRefs`]: to pull refs to items + /// - [`crate::ir::component::refs::GetItemRef`]: if a node only has a single item ref + /// - [`crate::ir::component::refs::GetParamRefs`]: to pull refs of parameters + /// - [`crate::ir::component::refs::GetResultRefs`]: to pull refs of results + /// - [`crate::ir::component::refs::GetArgRefs`]: to pull refs of args + /// - [`crate::ir::component::refs::GetDescriptorRefs`]: to pull refs of descriptors + /// - [`crate::ir::component::refs::GetDescribesRefs`]: to pull refs of describes + pub fn resolve(&self, ref_: &IndexedRef) -> ResolvedItem<'_, '_> { + self.inner.resolve(ref_) + } + /// Resolves a collection of [`RefKind`] values into their semantic targets. + /// + /// This is a convenience helper for bulk resolution when a node exposes + /// multiple referenced indices. + /// + /// Read through [`VisitCtx::resolve`] for how to pull such references from IR nodes. + pub fn resolve_all(&self, refs: &[RefKind]) -> Vec> { + self.inner.resolve_all(refs) + } + /// Looks up the name (if any) of the root component. + /// + /// Returns `None` if the component has no name. + pub fn lookup_root_comp_name(&self) -> Option<&str> { + self.inner.lookup_root_comp_name() + } + /// Looks up the name (if any) of a component by its ID. + /// + /// Returns `None` if: + /// - The component has no name + /// - The ID is not valid in the current context + pub fn lookup_comp_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_comp_name(id) + } + /// Looks up the name (if any) of a component instance by its ID. + /// + /// Returns `None` if: + /// - The instance has no name + /// - The ID is not valid in the current context + pub fn lookup_comp_inst_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_comp_inst_name(id) + } + /// Looks up the name (if any) of a component type by its ID. + /// + /// Returns `None` if: + /// - The type has no name + /// - The ID is not valid in the current context + pub fn lookup_comp_type_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_comp_type_name(id) + } + /// Looks up the name (if any) of a component func by its ID. + /// + /// Returns `None` if: + /// - The func has no name + /// - The ID is not valid in the current context + pub fn lookup_comp_func_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_comp_func_name(id) + } + /// Looks up the name (if any) of a module by its ID. + /// + /// Returns `None` if: + /// - The module has no name + /// - The ID is not valid in the current context + pub fn lookup_module_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_module_name(id) + } + /// Looks up the name (if any) of a core instance by its ID. + /// + /// Returns `None` if: + /// - The instance has no name + /// - The ID is not valid in the current context + pub fn lookup_core_inst_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_core_inst_name(id) + } + /// Looks up the name (if any) of a core type by its ID. + /// + /// Returns `None` if: + /// - The type has no name + /// - The ID is not valid in the current context + pub fn lookup_core_type_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_core_type_name(id) + } + /// Looks up the name (if any) of a core function by its ID. + /// + /// Returns `None` if: + /// - The function has no name + /// - The ID is not valid in the current context + pub fn lookup_core_func_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_core_func_name(id) + } + /// Looks up the name (if any) of a global by its ID. + /// + /// Returns `None` if: + /// - The global has no name + /// - The ID is not valid in the current context + pub fn lookup_global_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_global_name(id) + } + /// Looks up the name (if any) of a memory by its ID. + /// + /// Returns `None` if: + /// - The memory has no name + /// - The ID is not valid in the current context + pub fn lookup_memory_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_memory_name(id) + } + /// Looks up the name (if any) of a tag by its ID. + /// + /// Returns `None` if: + /// - The tag has no name + /// - The ID is not valid in the current context + pub fn lookup_tag_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_tag_name(id) + } + /// Looks up the name (if any) of a table by its ID. + /// + /// Returns `None` if: + /// - The table has no name + /// - The ID is not valid in the current context + pub fn lookup_table_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_table_name(id) + } + /// Looks up the name (if any) of a value by its ID. + /// + /// Returns `None` if: + /// - The value has no name + /// - The ID is not valid in the current context + pub fn lookup_value_name(&self, id: u32) -> Option<&str> { + self.inner.lookup_value_name(id) + } +} + +/// A resolved component item. +/// +/// This represents the semantic target of a reference after index +/// resolution has been performed. +/// +/// Each variant contains: +/// +/// - A `u32` representing the **resolved index of the item within its +/// corresponding namespace**, and +/// - A reference to the underlying IR node. +/// +/// The `u32` is *not* a syntactic index from the binary. Instead, it is +/// the canonical, namespace-specific ID assigned during resolution. For +/// example, a component type's `u32` is its resolved index in the +/// component type namespace, and a core instance's `u32` is its resolved +/// index in the core instance namespace. +/// +/// This enum allows callers to uniformly handle any reference target +/// without needing to separately track both namespace and ID. +/// +/// # Invariant +/// +/// The `u32` stored in each variant **must** correspond to the namespace +/// implied by the variant and must match the ID used during visitor +/// traversal. For example, `ResolvedItem::CompType(idx, _)` must always +/// have `idx` equal to the resolved index of that component type in the +/// component type namespace. +pub enum ResolvedItem<'a, 'b> { + /// A resolved subcomponent. + Component(u32, &'a Component<'b>), + + /// A resolved core WebAssembly module. + Module(u32, &'a Module<'b>), + + /// A resolved canonical function. + Func(u32, &'a CanonicalFunction), + + /// A resolved component-level type. + CompType(u32, &'a ComponentType<'b>), + + /// A resolved component instance. + CompInst(u32, &'a ComponentInstance<'b>), + + /// A resolved core WebAssembly instance. + CoreInst(u32, &'a Instance<'b>), + + /// A resolved core WebAssembly type. + CoreType(u32, &'a CoreType<'b>), + + /// A resolved component alias. + Alias(u32, &'a ComponentAlias<'b>), + + /// A resolved component import. + Import(u32, &'a ComponentImport<'b>), + + /// A resolved component export. + Export(u32, &'a ComponentExport<'b>), +} diff --git a/src/ir/component/visitor/tests.rs b/src/ir/component/visitor/tests.rs new file mode 100644 index 00000000..7307e2b6 --- /dev/null +++ b/src/ir/component/visitor/tests.rs @@ -0,0 +1,603 @@ +use crate::ir::component::visitor::driver::VisitEvent; +use crate::ir::component::visitor::events_structural::get_structural_events; +use crate::ir::component::visitor::events_topological::get_topological_events; +use crate::ir::component::visitor::VisitCtx; +use crate::{Component, Module}; +use serde_json::Value; +use std::fs; +use std::path::Path; +use std::process::Command; +use wasmparser::{ComponentTypeDeclaration, InstanceTypeDeclaration}; + +const WASM_TOOLS_TEST_COMP_INPUTS: &str = "./tests/wasm-tools/component-model"; + +#[test] +fn test_equivalent_visit_events_wast_components() { + let path_str = WASM_TOOLS_TEST_COMP_INPUTS.to_string(); + tests_from_wast(Path::new(&path_str), test_event_generation); +} + +#[test] +fn test_equivalent_visit_events_wast_components_async() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/async"); + tests_from_wast(Path::new(&path_str), test_event_generation); +} + +#[test] +fn test_equivalent_visit_events_wast_components_error_context() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/error-context"); + tests_from_wast(Path::new(&path_str), test_event_generation); +} + +#[test] +fn test_equivalent_visit_events_wast_components_gc() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/gc"); + tests_from_wast(Path::new(&path_str), test_event_generation); +} + +#[test] +fn test_equivalent_visit_events_wast_components_shared() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/shared-everything-threads"); + tests_from_wast(Path::new(&path_str), test_event_generation); +} + +#[test] +fn test_equivalent_visit_events_wast_components_values() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/values"); + tests_from_wast(Path::new(&path_str), test_event_generation); +} + +fn get_events<'ir>( + comp: &'ir Component<'ir>, + get_evts: fn(&'ir Component<'ir>, &mut VisitCtx<'ir>, &mut Vec>), +) -> Vec> { + let mut ctx = VisitCtx::new(comp); + let mut events = Vec::new(); + get_evts(comp, &mut ctx, &mut events); + + events +} + +fn check_event_validity(evts0: &Vec, evts1: &Vec) { + check_validity_of(evts0); + + // Now we know that the events of evts0 is valid, if they are equal to evts1, then we know + // that evts1 is valid! + check_equality(evts0, evts1); +} + +/// Events are VALID if: +/// 1. every enter* is paired with an exit* +/// 2. recgroup subtypes only appear between enter_recgroup and exit_recgroup +/// 3. mod type decls only appear between enter/exit core type +/// 4. comp and inst type decls only appear between enter/exit comp type +/// - if the decl contains a comp type, the next event is enter_comp_type +/// - if the decl contains a core type, the next event is enter_core_type +fn check_validity_of(evts: &Vec) { + let mut stack = vec![]; + let mut next_is_enter_comp_type = false; + let mut next_is_enter_core_type = false; + + for evt in evts.iter() { + if next_is_enter_comp_type { + assert!(is_comp_ty_enter(evt), + "Had a declaration with an inner component type, but the next event was not an enter component type event." + ); + next_is_enter_comp_type = false; + } + if next_is_enter_core_type { + assert!(is_core_ty_enter(evt), + "Had a declaration with an inner core type, but the next event was not an enter core type event." + ); + next_is_enter_core_type = false; + } + + if is_enter_evt(evt) { + stack.push(evt); + } + // 1. every enter is paired with an exit + if is_exit_evt(evt) { + let enter = stack.last().unwrap(); + assert!( + enter_exit_match(stack.last().unwrap(), evt), + "Received mismatched enter/exit events:\n- enter: {enter:?}\n- exit: {evt:?}" + ); + stack.pop(); + } + + // 2. recgroup subtypes only appear between enter_recgroup and exit_recgroup + if is_subtype(evt) { + assert!( + is_recgroup_enter(stack.last().unwrap()), + "Received a recgroup subtype event without a recgroup enter event!" + ); + } + + // 3. mod type decls only appear between enter/exit core type + if is_mod_decl(evt) { + assert!( + is_core_ty_enter(stack.last().unwrap()), + "Received a module type decl without a core type enter event!" + ); + } + + // 4. comp and inst type decls only appear between enter/exit comp type + if is_comp_ty_decl(evt) || is_inst_ty_decl(evt) { + assert!( + is_comp_ty_enter(stack.last().unwrap()), + "Received a component or instance type decl without a comp type enter event!" + ); + // - if the decl contains a comp type, the next event is enter_comp_type + if decl_contains_inner_comp_ty(evt) { + next_is_enter_comp_type = true; + } else if decl_contains_inner_core_ty(evt) { + // - if the decl contains a core type, the next event is enter_core_type + next_is_enter_core_type = true; + } + } + } +} +fn is_enter_evt(evt: &VisitEvent) -> bool { + matches!( + evt, + VisitEvent::EnterRootComp { .. } + | VisitEvent::EnterComp { .. } + | VisitEvent::EnterCompType { .. } + | VisitEvent::EnterCoreType { .. } + | VisitEvent::EnterCoreRecGroup { .. } + ) +} +fn is_exit_evt(evt: &VisitEvent) -> bool { + matches!( + evt, + VisitEvent::ExitRootComp { .. } + | VisitEvent::ExitComp { .. } + | VisitEvent::ExitCompType { .. } + | VisitEvent::ExitCoreType { .. } + | VisitEvent::ExitCoreRecGroup { .. } + ) +} +fn is_subtype(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::CoreSubtype { .. }) +} +fn is_recgroup_enter(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::EnterCoreRecGroup { .. }) +} +fn is_mod_decl(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::ModuleTypeDecl { .. }) +} +fn is_core_ty_enter(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::EnterCoreType { .. }) +} +fn is_comp_ty_decl(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::CompTypeDecl { .. }) +} +fn is_inst_ty_decl(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::InstTypeDecl { .. }) +} +fn is_comp_ty_enter(evt: &VisitEvent) -> bool { + matches!(evt, VisitEvent::EnterCompType { .. }) +} +fn decl_contains_inner_comp_ty(evt: &VisitEvent) -> bool { + match evt { + VisitEvent::CompTypeDecl { decl, .. } => matches!(decl, ComponentTypeDeclaration::Type(_)), + VisitEvent::InstTypeDecl { decl, .. } => matches!(decl, InstanceTypeDeclaration::Type(_)), + _ => false, + } +} +fn decl_contains_inner_core_ty(evt: &VisitEvent) -> bool { + match evt { + VisitEvent::CompTypeDecl { decl, .. } => { + matches!(decl, ComponentTypeDeclaration::CoreType(_)) + } + VisitEvent::InstTypeDecl { decl, .. } => { + matches!(decl, InstanceTypeDeclaration::CoreType(_)) + } + _ => false, + } +} + +fn enter_exit_match(enter: &VisitEvent, exit: &VisitEvent) -> bool { + matches!( + (enter, exit), + ( + VisitEvent::EnterRootComp { .. }, + VisitEvent::ExitRootComp { .. } + ) | (VisitEvent::EnterComp { .. }, VisitEvent::ExitComp { .. }) + | ( + VisitEvent::EnterCompType { .. }, + VisitEvent::ExitCompType { .. } + ) + | ( + VisitEvent::EnterCoreRecGroup { .. }, + VisitEvent::ExitCoreRecGroup { .. } + ) + | ( + VisitEvent::EnterCoreType { .. }, + VisitEvent::ExitCoreType { .. } + ) + ) +} + +fn check_equality(evts0: &Vec, evts1: &Vec) { + for (a, b) in evts0.iter().zip(evts1.iter()) { + match (a, b) { + ( + VisitEvent::EnterRootComp { component: a_comp }, + VisitEvent::EnterRootComp { component: b_comp }, + ) => { + assert_eq!(a_comp.id, b_comp.id); + // check pointing to same memory region + assert_eq!(*a_comp as *const Component, *b_comp as *const Component); + } + ( + VisitEvent::ExitRootComp { component: a_comp }, + VisitEvent::ExitRootComp { component: b_comp }, + ) => { + assert_eq!(a_comp.id, b_comp.id); + } + ( + VisitEvent::EnterComp { + idx: a_idx, + component: a_comp, + }, + VisitEvent::EnterComp { + idx: b_idx, + component: b_comp, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_comp.id, b_comp.id); + } + ( + VisitEvent::ExitComp { + idx: a_idx, + component: a_comp, + }, + VisitEvent::ExitComp { + idx: b_idx, + component: b_comp, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_comp.id, b_comp.id); + } + ( + VisitEvent::Module { + idx: a_idx, + module: a_mod, + }, + VisitEvent::Module { + idx: b_idx, + module: b_mod, + }, + ) => { + assert_eq!(a_idx, b_idx); + // check pointing to same memory region + assert_eq!(*a_mod as *const Module, *b_mod as *const Module); + } + ( + VisitEvent::EnterCompType { + idx: a_idx, + ty: a_ty, + }, + VisitEvent::EnterCompType { + idx: b_idx, + ty: b_ty, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_ty, b_ty); + } + ( + VisitEvent::ExitCompType { + idx: a_idx, + ty: a_ty, + }, + VisitEvent::ExitCompType { + idx: b_idx, + ty: b_ty, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_ty, b_ty); + } + ( + VisitEvent::CompTypeDecl { + parent: a_parent, + idx: a_idx, + decl: a_decl, + }, + VisitEvent::CompTypeDecl { + parent: b_parent, + idx: b_idx, + decl: b_decl, + }, + ) => { + assert_eq!(a_parent, b_parent); + assert_eq!(a_idx, b_idx); + assert_eq!(a_decl, b_decl); + } + ( + VisitEvent::InstTypeDecl { + parent: a_parent, + idx: a_idx, + decl: a_decl, + }, + VisitEvent::InstTypeDecl { + parent: b_parent, + idx: b_idx, + decl: b_decl, + }, + ) => { + assert_eq!(a_parent, b_parent); + assert_eq!(a_idx, b_idx); + assert_eq!(a_decl, b_decl); + } + ( + VisitEvent::CompInst { + idx: a_idx, + inst: a_inst, + }, + VisitEvent::CompInst { + idx: b_idx, + inst: b_inst, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_inst, b_inst); + } + ( + VisitEvent::Canon { + kind: a_kind, + idx: a_idx, + canon: a_canon, + }, + VisitEvent::Canon { + kind: b_kind, + idx: b_idx, + canon: b_canon, + }, + ) => { + assert_eq!(a_kind, b_kind); + assert_eq!(a_idx, b_idx); + assert_eq!(a_canon, b_canon); + } + ( + VisitEvent::Alias { + kind: a_kind, + idx: a_idx, + alias: a_alias, + }, + VisitEvent::Alias { + kind: b_kind, + idx: b_idx, + alias: b_alias, + }, + ) => { + assert_eq!(a_kind, b_kind); + assert_eq!(a_idx, b_idx); + assert_eq!(a_alias, b_alias); + } + ( + VisitEvent::Import { + kind: a_kind, + idx: a_idx, + imp: a_imp, + }, + VisitEvent::Import { + kind: b_kind, + idx: b_idx, + imp: b_imp, + }, + ) => { + assert_eq!(a_kind, b_kind); + assert_eq!(a_idx, b_idx); + assert_eq!(a_imp, b_imp); + } + ( + VisitEvent::Export { + kind: a_kind, + idx: a_idx, + exp: a_exp, + }, + VisitEvent::Export { + kind: b_kind, + idx: b_idx, + exp: b_exp, + }, + ) => { + assert_eq!(a_kind, b_kind); + assert_eq!(a_idx, b_idx); + assert_eq!(a_exp, b_exp); + } + ( + VisitEvent::EnterCoreRecGroup { + ty: a_ty, + count: a_count, + }, + VisitEvent::EnterCoreRecGroup { + ty: b_ty, + count: b_count, + }, + ) => { + assert_eq!(a_ty, b_ty); + assert_eq!(a_count, b_count); + } + ( + VisitEvent::CoreSubtype { + parent_idx: a_pidx, + subvec_idx: a_sidx, + subtype: a_ty, + }, + VisitEvent::CoreSubtype { + parent_idx: b_pidx, + subvec_idx: b_sidx, + subtype: b_ty, + }, + ) => { + assert_eq!(a_pidx, b_pidx); + assert_eq!(a_sidx, b_sidx); + assert_eq!(a_ty, b_ty); + } + (VisitEvent::ExitCoreRecGroup {}, VisitEvent::ExitCoreRecGroup {}) => {} // just variant equivalence is enough + ( + VisitEvent::EnterCoreType { + idx: a_idx, + ty: a_ty, + }, + VisitEvent::EnterCoreType { + idx: b_idx, + ty: b_ty, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_ty, b_ty); + } + ( + VisitEvent::ModuleTypeDecl { + parent: a_parent, + idx: a_idx, + decl: a_decl, + }, + VisitEvent::ModuleTypeDecl { + parent: b_parent, + idx: b_idx, + decl: b_decl, + }, + ) => { + assert_eq!(a_parent, b_parent); + assert_eq!(a_idx, b_idx); + assert_eq!(a_decl, b_decl); + } + ( + VisitEvent::ExitCoreType { + idx: a_idx, + ty: a_ty, + }, + VisitEvent::ExitCoreType { + idx: b_idx, + ty: b_ty, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_ty, b_ty); + } + ( + VisitEvent::CoreInst { + idx: a_idx, + inst: a_inst, + }, + VisitEvent::CoreInst { + idx: b_idx, + inst: b_inst, + }, + ) => { + assert_eq!(a_idx, b_idx); + assert_eq!(a_inst, b_inst); + } + ( + VisitEvent::CustomSection { sect: a_sect }, + VisitEvent::CustomSection { sect: b_sect }, + ) => { + // best effort check here + assert_eq!(a_sect.name, b_sect.name); + } + (VisitEvent::StartFunc { func: a_func }, VisitEvent::StartFunc { func: b_func }) => { + assert_eq!(a_func.func_index, b_func.func_index); + assert_eq!(a_func.arguments, b_func.arguments); + assert_eq!(a_func.results, b_func.results); + } + _ => panic!("events are not the same discriminant: {a:?} != {b:?}"), + } + } +} + +fn wasm_tools() -> Command { + Command::new("wasm-tools") +} + +fn test_event_generation(filename: &str) { + println!("\nfilename: {:?}", filename); + let buff = wat::parse_file(filename).expect("couldn't convert the input wat to Wasm"); + let original = wasmprinter::print_bytes(&buff).expect("couldn't convert original Wasm to wat"); + println!("original: {:?}", original); + + let comp = Component::parse(&buff, false, false).expect("Unable to parse"); + let evts_struct = get_events(&comp, get_structural_events); + let evts_topo = get_events(&comp, get_topological_events); + check_event_validity(&evts_struct, &evts_topo); +} + +pub fn tests_from_wast(path: &Path, run_test: fn(&str)) { + let path = path.to_str().unwrap().replace("\\", "/"); + for entry in fs::read_dir(path).unwrap() { + let file = entry.unwrap(); + match file.path().extension() { + None => continue, + Some(ext) => { + if ext.to_str() != Some("wast") { + continue; + } + } + } + let mut cmd = wasm_tools(); + let td = tempfile::TempDir::new().unwrap(); + cmd.arg("json-from-wast") + .arg(file.path()) + .arg("--pretty") + .arg("--wasm-dir") + .arg(td.path()) + .arg("-o") + .arg(td.path().join(format!( + "{:?}.json", + Path::new(&file.path()) + .file_stem() + .unwrap() + .to_str() + .unwrap() + ))); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("failed to run {cmd:?}\nstdout: {stdout}\nstderr: {stderr}"); + } + // For every file that is not invalid in the output, do round-trip + for entry in fs::read_dir(td.path()).unwrap() { + let file_json = entry.unwrap(); + match file_json.path().extension() { + None => continue, + Some(ext) => { + if ext.to_str() != Some("json") { + continue; + } + } + } + let json: Value = serde_json::from_str( + &fs::read_to_string(file_json.path()).expect("Unable to open file"), + ) + .unwrap(); + if let Value::Object(map) = json { + if let Value::Array(vals) = map.get_key_value("commands").unwrap().1 { + for value in vals { + if let Value::Object(testcase) = value { + // If assert is not in the string, that means it is a valid test case + if let Value::String(ty) = testcase.get_key_value("type").unwrap().1 { + if !ty.contains("assert") && testcase.contains_key("filename") { + if let Value::String(test_file) = + testcase.get_key_value("filename").unwrap().1 + { + run_test( + Path::new(td.path()).join(test_file).to_str().unwrap(), + ); + } + } + } + } + } + } + } + } + } +} diff --git a/src/ir/component/visitor/utils.rs b/src/ir/component/visitor/utils.rs new file mode 100644 index 00000000..89c85f90 --- /dev/null +++ b/src/ir/component/visitor/utils.rs @@ -0,0 +1,432 @@ +//! Internal traversal and resolution machinery. +//! +//! This module mirrors the encode traversal logic but operates in a +//! read-only mode. It maintains: +//! +//! - A stack of component identities +//! - A stack of active index scopes +//! - A reference to the scope registry +//! +//! It is intentionally not exposed publicly to avoid leaking implementation +//! details such as pointer identity or scope IDs. +//! +//! # Safety and Invariants +//! +//! This traversal logic relies on the following invariants: +//! +//! - Component IDs are stable for the lifetime of the IR. +//! - Scoped IR nodes are stored in stable allocations. +//! - The scope registry is fully populated before traversal begins. +//! - No mutation of the component occurs during traversal. +//! +//! These guarantees allow resolution to rely on structural identity +//! without exposing internal identity mechanisms publicly. + +use crate::ir::component::idx_spaces::{IndexSpaceOf, ScopeId, Space, SpaceSubtype, StoreHandle}; +use crate::ir::component::refs::{Depth, IndexedRef, RefKind}; +use crate::ir::component::scopes::{ + build_component_store, ComponentStore, GetScopeKind, RegistryHandle, +}; +use crate::ir::component::section::ComponentSection; +use crate::ir::component::visitor::driver::VisitEvent; +use crate::ir::component::visitor::{ItemKind, ResolvedItem}; +use crate::ir::id::ComponentId; +use crate::Component; + +pub struct VisitCtxInner<'a> { + pub(crate) registry: RegistryHandle, + pub(crate) component_stack: Vec, // may not need + pub(crate) scope_stack: ScopeStack, + pub(crate) node_has_nested_scope: Vec, + pub(crate) store: StoreHandle, + pub(crate) comp_store: ComponentStore<'a>, + section_tracker_stack: Vec, +} + +// ======================================= +// =========== SCOPE INTERNALS =========== +// ======================================= + +impl<'a> VisitCtxInner<'a> { + pub fn new(root: &'a Component<'a>) -> Self { + let comp_store = build_component_store(root); + Self { + registry: root.scope_registry.clone(), + component_stack: Vec::new(), + section_tracker_stack: Vec::new(), + scope_stack: ScopeStack::new(), + node_has_nested_scope: Vec::new(), + store: root.index_store.clone(), + comp_store, + } + } + + pub fn visit_section(&mut self, section: &ComponentSection, num: usize) -> usize { + self.section_tracker_stack + .last_mut() + .unwrap() + .visit_section(section, num) + } + + pub fn push_comp_section_tracker(&mut self) { + self.section_tracker_stack.push(SectionTracker::default()); + } + pub fn pop_comp_section_tracker(&mut self) { + self.section_tracker_stack.pop(); + } + + pub fn push_component(&mut self, component: &Component) { + let id = component.id; + self.component_stack.push(id); + self.push_comp_section_tracker(); + self.enter_comp_scope(id); + } + + pub fn pop_component(&mut self) { + let id = self.component_stack.pop().unwrap(); + self.pop_comp_section_tracker(); + self.exit_comp_scope(id); + } + pub fn curr_component(&self) -> &Component<'_> { + let id = self.comp_at(Depth::default()); + self.comp_store.get(id) + } + + pub fn maybe_enter_scope(&mut self, node: &T) { + let mut nested = false; + if let Some(scope_entry) = self.registry.borrow().scope_entry(node) { + nested = true; + self.scope_stack.enter_scope(scope_entry.space); + } + self.node_has_nested_scope.push(nested); + } + + pub fn maybe_exit_scope(&mut self, node: &T) { + let nested = self.node_has_nested_scope.pop().unwrap(); + if let Some(scope_entry) = self.registry.borrow().scope_entry(node) { + // Exit the nested index space...should be equivalent to the ID + // of the scope that was entered by this node + let exited_from = self.scope_stack.exit_scope(); + debug_assert!(nested); + debug_assert_eq!(scope_entry.space, exited_from); + } else { + debug_assert!(!nested); + } + } + + pub(crate) fn enter_comp_scope(&mut self, comp_id: ComponentId) { + let scope_id = self + .registry + .borrow() + .scope_of_comp(comp_id) + .expect("Internal error: no scope found for component"); + self.node_has_nested_scope + .push(!self.scope_stack.stack.is_empty()); + self.scope_stack.enter_scope(scope_id); + } + + pub(crate) fn exit_comp_scope(&mut self, comp_id: ComponentId) { + let scope_id = self + .registry + .borrow() + .scope_of_comp(comp_id) + .unwrap_or_else(|| panic!("Internal error: no scope found for component {comp_id:?}")); + let exited_from = self.scope_stack.exit_scope(); + debug_assert_eq!(scope_id, exited_from); + } + + pub(crate) fn comp_at(&self, depth: Depth) -> &ComponentId { + let idx = self.component_stack.len() - depth.val() - 1; + self.component_stack.get(idx).unwrap_or_else(|| { + panic!( + "Internal error: couldn't find component at depth {}; stack: {:?}", + depth.val(), + self.component_stack + ) + }) + } +} + +// =============================================== +// =========== ID RESOLUTION INTERNALS =========== +// =============================================== + +impl VisitCtxInner<'_> { + /// When looking up the ID of some node, we MUST consider whether the node we're assigning an ID for + /// has a nested scope! If it does, this node's ID lives in its parent index space. + pub(crate) fn lookup_id_for( + &self, + space: &Space, + section: &ComponentSection, + vec_idx: usize, + ) -> u32 { + let nested = self.node_has_nested_scope.last().unwrap_or(&false); + let scope_id = if *nested { + self.scope_stack.scope_at_depth(&Depth::parent()) + } else { + self.scope_stack.curr_scope_id() + }; + self.store + .borrow() + .scopes + .get(&scope_id) + .unwrap() + .lookup_assumed_id(space, section, vec_idx) as u32 + } + /// When looking up the ID of some node, we MUST consider whether the node we're assigning an ID for + /// has a nested scope! If it does, this node's ID lives in its parent index space. + pub(crate) fn lookup_id_with_subvec_for( + &self, + space: &Space, + section: &ComponentSection, + vec_idx: usize, + subvec_idx: usize, + ) -> u32 { + let nested = self.node_has_nested_scope.last().unwrap_or(&false); + let scope_id = if *nested { + self.scope_stack.scope_at_depth(&Depth::parent()) + } else { + self.scope_stack.curr_scope_id() + }; + self.store + .borrow() + .scopes + .get(&scope_id) + .unwrap() + .lookup_assumed_id_with_subvec(space, section, vec_idx, subvec_idx) as u32 + } + + /// Looking up a reference should always be relative to the scope of the node that + /// contained the reference! No need to think about whether the node has a nested scope. + pub(crate) fn index_from_assumed_id_no_cache( + &self, + r: &IndexedRef, + ) -> (SpaceSubtype, usize, Option) { + let scope_id = self.scope_stack.scope_at_depth(&r.depth); + self.store + .borrow() + .scopes + .get(&scope_id) + .unwrap() + .index_from_assumed_id_no_cache(r) + } + + /// Looking up a reference should always be relative to the scope of the node that + /// contained the reference! No need to think about whether the node has a nested scope. + pub(crate) fn index_from_assumed_id( + &mut self, + r: &IndexedRef, + ) -> (SpaceSubtype, usize, Option) { + let scope_id = self.scope_stack.scope_at_depth(&r.depth); + self.store + .borrow_mut() + .scopes + .get_mut(&scope_id) + .unwrap() + .index_from_assumed_id(r) + } +} + +// ================================================= +// =========== NODE RESOLUTION INTERNALS =========== +// ================================================= + +impl VisitCtxInner<'_> { + pub fn lookup_root_comp_name(&self) -> Option<&str> { + self.curr_component().component_name.as_deref() + } + pub fn lookup_comp_name(&self, id: u32) -> Option<&str> { + self.curr_component().components_names.get(id) + } + pub fn lookup_comp_inst_name(&self, id: u32) -> Option<&str> { + self.curr_component().instance_names.get(id) + } + pub fn lookup_comp_type_name(&self, id: u32) -> Option<&str> { + self.curr_component().type_names.get(id) + } + pub fn lookup_comp_func_name(&self, id: u32) -> Option<&str> { + self.curr_component().func_names.get(id) + } + pub fn lookup_module_name(&self, id: u32) -> Option<&str> { + self.curr_component().module_names.get(id) + } + pub fn lookup_core_inst_name(&self, id: u32) -> Option<&str> { + self.curr_component().core_instances_names.get(id) + } + pub fn lookup_core_type_name(&self, id: u32) -> Option<&str> { + self.curr_component().core_type_names.get(id) + } + pub fn lookup_core_func_name(&self, id: u32) -> Option<&str> { + self.curr_component().core_func_names.get(id) + } + pub fn lookup_global_name(&self, id: u32) -> Option<&str> { + self.curr_component().global_names.get(id) + } + pub fn lookup_memory_name(&self, id: u32) -> Option<&str> { + self.curr_component().memory_names.get(id) + } + pub fn lookup_tag_name(&self, id: u32) -> Option<&str> { + self.curr_component().tag_names.get(id) + } + pub fn lookup_table_name(&self, id: u32) -> Option<&str> { + self.curr_component().table_names.get(id) + } + pub fn lookup_value_name(&self, id: u32) -> Option<&str> { + self.curr_component().value_names.get(id) + } + + pub fn resolve_all(&self, refs: &[RefKind]) -> Vec> { + let mut items = vec![]; + for r in refs.iter() { + items.push(self.resolve(&r.ref_)); + } + + items + } + + pub fn resolve(&self, r: &IndexedRef) -> ResolvedItem<'_, '_> { + let (vec, idx, subidx) = self.index_from_assumed_id_no_cache(r); + if r.space != Space::CoreType { + assert!( + subidx.is_none(), + "only core types (with rec groups) should ever have subvec indices!" + ); + } + + let comp_id = self.comp_at(r.depth); + let referenced_comp = self.comp_store.get(comp_id); + + let space = r.space; + match vec { + SpaceSubtype::Main => match space { + Space::Comp => ResolvedItem::Component(r.index, &referenced_comp.components[idx]), + Space::CompType => { + ResolvedItem::CompType(r.index, &referenced_comp.component_types.items[idx]) + } + Space::CompInst => { + ResolvedItem::CompInst(r.index, &referenced_comp.component_instance[idx]) + } + Space::CoreInst => ResolvedItem::CoreInst(r.index, &referenced_comp.instances[idx]), + Space::CoreModule => ResolvedItem::Module(r.index, &referenced_comp.modules[idx]), + Space::CoreType => { + ResolvedItem::CoreType(r.index, &referenced_comp.core_types[idx]) + } + Space::CompFunc | Space::CoreFunc => { + ResolvedItem::Func(r.index, &referenced_comp.canons.items[idx]) + } + Space::CompVal + | Space::CoreMemory + | Space::CoreTable + | Space::CoreGlobal + | Space::CoreTag + | Space::NA => unreachable!( + "This spaces don't exist in a main vector on the component IR: {vec:?}" + ), + }, + SpaceSubtype::Export => ResolvedItem::Export(r.index, &referenced_comp.exports[idx]), + SpaceSubtype::Import => ResolvedItem::Import(r.index, &referenced_comp.imports[idx]), + SpaceSubtype::Alias => ResolvedItem::Alias(r.index, &referenced_comp.alias.items[idx]), + } + } +} + +#[derive(Clone)] +pub(crate) struct ScopeStack { + pub(crate) stack: Vec, +} +impl ScopeStack { + fn new() -> Self { + Self { stack: vec![] } + } + pub(crate) fn curr_scope_id(&self) -> ScopeId { + self.stack.last().cloned().unwrap() + } + pub(crate) fn scope_at_depth(&self, depth: &Depth) -> ScopeId { + *self + .stack + .get(self.stack.len() - depth.val() - 1) + .unwrap_or_else(|| { + panic!( + "couldn't find scope at depth {}; this is the current scope stack: {:?}", + depth.val(), + self.stack + ) + }) + } + pub fn enter_scope(&mut self, id: ScopeId) { + self.stack.push(id) + } + pub fn exit_scope(&mut self) -> ScopeId { + self.stack.pop().unwrap() + } +} + +// General trackers for indices of item vectors (used to track where i've been during visitation) +#[derive(Default)] +struct SectionTracker { + last_processed_module: usize, + last_processed_alias: usize, + last_processed_core_ty: usize, + last_processed_comp_ty: usize, + last_processed_imp: usize, + last_processed_exp: usize, + last_processed_core_inst: usize, + last_processed_comp_inst: usize, + last_processed_canon: usize, + last_processed_component: usize, + last_processed_start: usize, + last_processed_custom: usize, +} +impl SectionTracker { + /// This function is used while traversing the component. This means that we + /// should already know the space ID associated with the component section + /// (if in visiting this next session we enter some inner index space). + /// + /// So, we use the associated space ID to return the inner index space. The + /// calling function should use this return value to then context switch into + /// this new index space. When we've finished visiting the section, swap back + /// to the returned index space's `parent` (a field on the space). + pub fn visit_section(&mut self, section: &ComponentSection, num: usize) -> usize { + let tracker = match section { + ComponentSection::Component => &mut self.last_processed_component, + ComponentSection::Module => &mut self.last_processed_module, + ComponentSection::Alias => &mut self.last_processed_alias, + ComponentSection::CoreType => &mut self.last_processed_core_ty, + ComponentSection::ComponentType => &mut self.last_processed_comp_ty, + ComponentSection::ComponentImport => &mut self.last_processed_imp, + ComponentSection::ComponentExport => &mut self.last_processed_exp, + ComponentSection::CoreInstance => &mut self.last_processed_core_inst, + ComponentSection::ComponentInstance => &mut self.last_processed_comp_inst, + ComponentSection::Canon => &mut self.last_processed_canon, + ComponentSection::CustomSection => &mut self.last_processed_custom, + ComponentSection::ComponentStartSection => &mut self.last_processed_start, + }; + + let curr = *tracker; + *tracker += num; + curr + } +} + +pub fn for_each_indexed<'ir, T>( + slice: &'ir [T], + start: usize, + count: usize, + mut f: impl FnMut(usize, &'ir T), +) { + debug_assert!(start + count <= slice.len()); + + for i in 0..count { + let idx = start + i; + f(idx, &slice[idx]); + } +} + +pub fn emit_indexed<'ir, T: IndexSpaceOf>( + out: &mut Vec>, + idx: usize, + item: &'ir T, + make: fn(ItemKind, usize, &'ir T) -> VisitEvent<'ir>, +) { + out.push(make(item.index_space_of().into(), idx, item)); +} diff --git a/src/ir/function.rs b/src/ir/function.rs index 9c488f44..5bed88a6 100644 --- a/src/ir/function.rs +++ b/src/ir/function.rs @@ -1,8 +1,10 @@ //! Function Builder +use crate::error::Error::InvalidOperation; use crate::ir::id::{FunctionID, ImportsID, LocalID, ModuleID, TypeID}; use crate::ir::module::module_functions::{add_local, add_locals, LocalFunction}; use crate::ir::module::{AsVec, Module}; +use crate::ir::types; use crate::ir::types::{Body, FuncInstrFlag, FuncInstrMode, Tag}; use crate::ir::types::{DataType, InjectedInstrs}; use crate::ir::types::{HasInjectTag, InstrumentationMode}; @@ -54,7 +56,7 @@ impl<'a> FunctionBuilder<'a> { tag, ); - assert_eq!( + debug_assert_eq!( module.functions.as_vec().len() as u32, module.num_local_functions + module.imports.num_funcs ); @@ -65,7 +67,11 @@ impl<'a> FunctionBuilder<'a> { /// Use this built function to replace an import in the module (turns into /// a local function). This action will redirect all calls to that import /// to call this new function. - pub fn replace_import_in_module(self, module: &mut Module<'a>, import_id: ImportsID) { + pub fn replace_import_in_module( + self, + module: &mut Module<'a>, + import_id: ImportsID, + ) -> types::Result<()> { self.replace_import_in_module_with_tag(module, import_id, Tag::default()) } @@ -77,35 +83,38 @@ impl<'a> FunctionBuilder<'a> { module: &mut Module<'a>, import_id: ImportsID, tag: Tag, - ) { + ) -> types::Result<()> { // add End as last instruction self.end(); let err_msg = "Could not replace the specified import with this function,"; let imp = module.imports.get(import_id); + if let TypeRef::Func(imp_ty_id) = imp.ty { - if let Some(ty) = module.types.get(TypeID(imp_ty_id)) { - if *ty.params() == self.params && *ty.results() == self.results { - let mut local_func = LocalFunction::new( - TypeID(imp_ty_id), - FunctionID(*import_id), - self.body.clone(), - self.params.len(), - Some(tag), - ); - local_func.body.name = Some(imp.name.to_string()); - module.convert_import_fn_to_local(import_id, local_func); - } else { - panic!("{err_msg} types are not equivalent.") - } - } else { + let ty = module.types.get(TypeID(imp_ty_id)).unwrap_or_else(|| { panic!( - "{} could not find an associated type for the specified import ID: {:?}.", + "Internal error: {} could not find an associated type for the specified import ID: {:?}.", err_msg, import_id ) - } + }); + debug_assert!( + *ty.params()? == self.params && *ty.results()? == self.results, + "{err_msg} types are not equivalent." + ); + let mut local_func = LocalFunction::new( + TypeID(imp_ty_id), + FunctionID(*import_id), + self.body.clone(), + self.params.len(), + Some(tag), + ); + local_func.body.name = Some(imp.name.to_string()); + module.convert_import_fn_to_local(import_id, local_func); + Ok(()) } else { - panic!("{err_msg} the specified import ID does not point to a function!") + Err(InvalidOperation(format!( + "{err_msg} the specified import ID does not point to a function!" + ))) } } @@ -125,8 +134,9 @@ impl<'a> FunctionBuilder<'a> { ) -> FunctionID { // add End as last instruction self.end(); + let module = &mut comp.modules[*mod_idx as usize]; - let id = comp.modules[*mod_idx as usize].add_local_func_with_tag( + let id = module.add_local_func_with_tag( self.name, &self.params, &self.results, @@ -134,11 +144,9 @@ impl<'a> FunctionBuilder<'a> { tag, ); - assert_eq!( - comp.modules[*mod_idx as usize].functions.as_vec().len() as u32, - comp.modules[*mod_idx as usize].num_local_functions - + comp.modules[*mod_idx as usize].imports.num_funcs - + comp.modules[*mod_idx as usize].imports.num_funcs_added + debug_assert_eq!( + module.functions.as_vec().len() as u32, + module.num_local_functions + module.imports.num_funcs + module.imports.num_funcs_added ); id } @@ -226,7 +234,6 @@ impl AddLocal for FunctionModifier<'_, '_> { } impl<'b> Inject<'b> for FunctionModifier<'_, 'b> { - // TODO: refactor the inject the function to return a Result rather than panicking? fn inject(&mut self, instr: Operator<'b>) { if self.instr_flag.current_mode.is_some() { // inject at the function level @@ -262,19 +269,19 @@ impl<'b> Instrumenter<'b> for FunctionModifier<'_, 'b> { self.instr_flag.finish_instr(); } fn curr_instrument_mode(&self) -> Option { - if let Some(idx) = self.instr_idx { - self.body.instructions.current_mode(idx) - } else { - panic!("Instruction index not set"); - } + let idx = self + .instr_idx + .expect("Internal error: Instruction index not set"); + self.body.instructions.current_mode(idx) } fn set_instrument_mode_at(&mut self, mode: InstrumentationMode, loc: Location) { - if let Location::Module { instr_idx, .. } = loc { - self.instr_idx = Some(instr_idx); - self.body.instructions.set_current_mode(instr_idx, mode); - } else { - panic!("Should have gotten module location"); + match loc { + Location::Module { instr_idx, .. } => { + self.instr_idx = Some(instr_idx); + self.body.instructions.set_current_mode(instr_idx, mode); + } + other => panic!("Internal error: Should have gotten module location, got: {other:?}"), } } @@ -293,52 +300,58 @@ impl<'b> Instrumenter<'b> for FunctionModifier<'_, 'b> { self.instr_flag.instr_len() } else { // get at instruction level - if let Some(idx) = self.instr_idx { - self.body.instructions.instr_len(idx) - } else { - panic!("Instruction index not set"); - } + let idx = self + .instr_idx + .expect("Internal error: Instruction index not set"); + self.body.instructions.instr_len(idx) } } fn clear_instr_at(&mut self, loc: Location, mode: InstrumentationMode) { - if let Location::Module { instr_idx, .. } = loc { - self.body.instructions.clear_instr(instr_idx, mode); - } else { - panic!("Should have gotten module location"); + match loc { + Location::Module { instr_idx, .. } => { + self.body.instructions.clear_instr(instr_idx, mode) + } + other => { + panic!("Internal error: clear_instr_at called with non-module location: {other:?}") + } } } fn add_instr_at(&mut self, loc: Location, instr: Operator<'b>) { - if let Location::Module { instr_idx, .. } = loc { - self.body.instructions.add_instr(instr_idx, instr); - } else { - panic!("Should have gotten module location"); - } + match loc { + Location::Module { instr_idx, .. } => { + self.body.instructions.add_instr(instr_idx, instr) + } + other => { + panic!("Internal error: add_instr_at called with non-module location: {other:?}") + } + }; } fn empty_alternate_at(&mut self, loc: Location) -> &mut Self { - if let Location::Module { instr_idx, .. } = loc { - self.body + match loc { + Location::Module { instr_idx, .. } => self + .body .instructions - .set_alternate(instr_idx, InjectedInstrs::default()); - } else { - panic!("Should have gotten Component Location and not Module Location!") - } - + .set_alternate(instr_idx, InjectedInstrs::default()), + other => panic!( + "Internal error: empty_alternate_at called with non-module location: {other:?}" + ), + }; self } fn empty_block_alt_at(&mut self, loc: Location) -> &mut Self { - if let Location::Module { instr_idx, .. } = loc { - self.body + match loc { + Location::Module { instr_idx, .. } => self + .body .instructions - .set_block_alt(instr_idx, InjectedInstrs::default()); - self.instr_flag.has_special_instr |= true; - } else { - panic!("Should have gotten Component Location and not Module Location!") - } - + .set_block_alt(instr_idx, InjectedInstrs::default()), + other => panic!( + "Internal error: empty_block_alt_at called with non-module location: {other:?}" + ), + }; self } @@ -354,7 +367,6 @@ impl<'b> Instrumenter<'b> for FunctionModifier<'_, 'b> { let (Location::Component { instr_idx, .. } | Location::Module { instr_idx, .. }) = loc; self.body.instructions.append_to_tag(instr_idx, data); } - self } } diff --git a/src/ir/helpers.rs b/src/ir/helpers.rs index e45780fe..49bf8f27 100644 --- a/src/ir/helpers.rs +++ b/src/ir/helpers.rs @@ -88,6 +88,7 @@ pub fn print_component_defined_type(ty: &ComponentDefinedType) { ComponentDefinedType::Future(_) => eprintln!("Future"), ComponentDefinedType::Stream(_) => eprintln!("Stream"), ComponentDefinedType::FixedSizeList(_, _) => eprintln!("FixedSizeList"), + ComponentDefinedType::Map(_, _) => eprintln!("Map"), } } diff --git a/src/ir/id.rs b/src/ir/id.rs index a3d7054c..7d2b54a9 100644 --- a/src/ir/id.rs +++ b/src/ir/id.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +use std::fmt::Display; /// LocalID in a function #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -71,6 +72,11 @@ impl From for FunctionID { Self(id) } } +impl Display for FunctionID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} /// DataSegmentID in a module #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -211,3 +217,133 @@ impl std::ops::DerefMut for ElementID { &mut self.0 } } + +/// ComponentId of a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ComponentId(pub u32); +impl std::ops::Deref for ComponentId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// ComponentTypeId in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ComponentTypeId(pub u32); +impl std::ops::Deref for ComponentTypeId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for ComponentTypeId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// ComponentTypeInstanceId in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ComponentTypeInstanceId(pub u32); +impl std::ops::Deref for ComponentTypeInstanceId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for ComponentTypeInstanceId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// ComponentTypeFuncId in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ComponentTypeFuncId(pub u32); +impl std::ops::Deref for ComponentTypeFuncId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for ComponentTypeFuncId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// CanonicalFuncId in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct CanonicalFuncId(pub u32); +impl std::ops::Deref for CanonicalFuncId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for CanonicalFuncId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// ComponentExportId in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ComponentExportId(pub u32); +impl std::ops::Deref for ComponentExportId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for ComponentExportId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// AliasId in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct AliasId(pub u32); +impl std::ops::Deref for AliasId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for AliasId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// The ID of an aliased function in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct AliasFuncId(pub u32); +impl std::ops::Deref for AliasFuncId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for AliasFuncId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// The ID of an aliased function in a Component +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct CoreInstanceId(pub u32); +impl std::ops::Deref for CoreInstanceId { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for CoreInstanceId { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 527fc8ee..483ea823 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -1,5 +1,8 @@ //! The Intermediate Representation for components and modules. +use std::ops::{Deref, Index, IndexMut}; +use std::slice::SliceIndex; + pub mod component; pub mod function; mod helpers; @@ -7,6 +10,183 @@ pub mod id; #[cfg(test)] pub mod instr_tests; pub mod module; -pub mod section; pub mod types; pub(crate) mod wrappers; + +/// An append-only vector with stable element addresses. +/// +/// `AppendOnlyVec` is a wrapper around `Vec` that enforces a single +/// structural invariant: **elements may be appended, but never removed, +/// reordered, or replaced**. +/// +/// This type exists to support IR instrumentation and encoding workflows +/// that rely on *stable identity* of nodes over time, typically via raw +/// pointers or addresses registered during parsing. +/// +/// # Motivation +/// +/// Many parts of the encoding pipeline associate metadata (such as scope +/// IDs or index spaces) with IR nodes using their memory address. If nodes +/// were allowed to be removed, swapped, or compacted, these addresses would +/// become invalid and lead to subtle, hard-to-debug encoding failures. +/// +/// `AppendOnlyVec` provides a constrained mutation model that preserves: +/// +/// - **Pointer stability** for all elements +/// - **Index stability** for previously appended elements +/// - **Monotonic growth**, which matches how Wasm sections are built +/// +/// This allows instrumentation passes to safely mutate nodes *in place* +/// without invalidating previously registered scope or index information. +/// +/// # Allowed Operations +/// +/// - Appending new elements to the end of the vector +/// - Iterating over elements (immutably or mutably) +/// - Indexing into the vector +/// +/// # Disallowed Operations +/// +/// `AppendOnlyVec` intentionally does **not** expose APIs for: +/// +/// - Removing elements +/// - Inserting elements at arbitrary positions +/// - Reordering or swapping elements +/// - Clearing the vector +/// +/// These operations would invalidate assumptions made by the encoder. +/// +/// # Relationship to Scopes +/// +/// Nodes stored in an `AppendOnlyVec` may be registered in the scope registry +/// using their address. Because elements are never moved or removed, these +/// registrations remain valid for the entire lifetime of the component. +/// +/// For nodes that *may* own scopes, this wrapper is commonly used together +/// with `Box` (e.g. `AppendOnlyVec>`) to ensure heap allocation +/// and stable pointers. +/// +/// # Examples +/// +/// ```rust +/// use wirm::ir::AppendOnlyVec; +/// +/// let mut vec = AppendOnlyVec::from(vec![42, 100]); +/// for v in vec.iter_mut() { +/// *v += 1; +/// } +/// +/// assert_eq!(vec[0], 43); +/// assert_eq!(vec[1], 101); +/// ``` +/// +/// # Design Notes +/// +/// `AppendOnlyVec` is a *semantic* restriction, not a performance abstraction. +/// Internally it may still use a `Vec`, but its API enforces invariants +/// required by the encoder. +/// +/// If you need more flexibility (e.g. temporary collections during parsing), +/// use a plain `Vec` instead and transfer elements into an `AppendOnlyVec` +/// once they become part of the component’s stable IR. +/// +/// # Panics +/// +/// This type does not panic on its own, but misuse of raw pointers or +/// assumptions about append-only behavior elsewhere in the system may +/// result in panics during encoding. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AppendOnlyVec { + vec: Vec, +} +impl Default for AppendOnlyVec { + fn default() -> Self { + Self { vec: Vec::new() } + } +} +impl AppendOnlyVec { + // INSERTs (only accessible in the crate) + /// To push an item into the vector. Note that this is not exposed beyond the crate. + /// This is to protect users from appending IR nodes without them going through + /// correct preprocessing (scoping, registration, ID assignment). + /// If there are specific IR nodes that a user needs to be able to add and the library + /// does not support doing so yet, they should put up a PR on the library's GH repo. + pub(crate) fn push(&mut self, value: T) { + self.vec.push(value); + } + /// To append a list of items into the vector. Note that this is not exposed beyond the crate. + /// This is to protect users from appending IR nodes without them going through + /// correct preprocessing (scoping, registration, ID assignment). + /// If there are specific IR nodes that a user needs to be able to add and the library + /// does not support doing so yet, they should put up a PR on the library's GH repo. + pub(crate) fn append(&mut self, other: &mut Vec) { + self.vec.append(other); + } + + /// Must provide our own implementation for `iter_mut` in order to + /// avoid implementing DerefMut trait (we don't want to expose general + /// mutation control to users). + pub fn iter_mut(&'_ mut self) -> core::slice::IterMut<'_, T> { + self.vec.iter_mut() + } + + /// We will only ever expose a non-mutable vec here! + /// Any mutation can only be appending or edit-in-place. + /// Exposing a mutable vec would allow illegal operations. + pub fn as_vec(&self) -> &Vec { + &self.vec + } + + // no remove, no replace +} +impl Deref for AppendOnlyVec { + type Target = [T]; + fn deref(&self) -> &Self::Target { + &self.vec + } +} +impl Index for AppendOnlyVec +where + I: SliceIndex<[T]>, +{ + type Output = I::Output; + fn index(&self, index: I) -> &Self::Output { + &self.vec[index] + } +} +impl IndexMut for AppendOnlyVec +where + I: SliceIndex<[T]>, +{ + fn index_mut(&mut self, index: I) -> &mut Self::Output { + &mut self.vec[index] + } +} +/// General iterator support. +impl IntoIterator for AppendOnlyVec { + type Item = T; + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.vec.into_iter() + } +} +/// Iterator support for references to the vector. +impl<'a, T> IntoIterator for &'a AppendOnlyVec { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.vec.iter() + } +} +impl From> for AppendOnlyVec { + fn from(vec: Vec) -> Self { + Self { vec } + } +} +impl FromIterator for AppendOnlyVec { + fn from_iter>(iter: I) -> Self { + Self { + vec: Vec::from_iter(iter), + } + } +} diff --git a/src/ir/module/mod.rs b/src/ir/module/mod.rs index 345af578..0389a28b 100644 --- a/src/ir/module/mod.rs +++ b/src/ir/module/mod.rs @@ -1,9 +1,14 @@ //! Intermediate Representation of a wasm module. -use super::types::{DataType, InitExpr, InjectedInstrs, InstrumentationMode, Tag, TagUtils}; +use super::types::{ + CustomSection, DataType, InitExpr, InjectedInstrs, InstrumentationMode, Tag, TagUtils, +}; use crate::error::Error; +use crate::error::Error::{InstrumentationError, IO}; use crate::ir::function::FunctionModifier; -use crate::ir::id::{DataSegmentID, FunctionID, GlobalID, ImportsID, LocalID, MemoryID, TypeID}; +use crate::ir::id::{ + CustomSectionID, DataSegmentID, FunctionID, GlobalID, ImportsID, LocalID, MemoryID, TypeID, +}; use crate::ir::module::module_exports::{Export, ModuleExports}; use crate::ir::module::module_functions::{ add_local, FuncKind, Function, Functions, ImportedFunction, LocalFunction, @@ -11,11 +16,12 @@ use crate::ir::module::module_functions::{ use crate::ir::module::module_globals::{ Global, GlobalKind, ImportedGlobal, LocalGlobal, ModuleGlobals, }; -use crate::ir::module::module_imports::{Import, ModuleImports}; +use crate::ir::module::module_imports::{expand_imports, Import, ModuleImports}; use crate::ir::module::module_memories::{ImportedMemory, LocalMemory, MemKind, Memories, Memory}; use crate::ir::module::module_tables::{Element, ModuleTables, Table}; use crate::ir::module::module_types::{ModuleTypes, RecGroup, Types}; use crate::ir::module::side_effects::{InjectType, Injection}; +use crate::ir::types; use crate::ir::types::InstrumentationMode::{BlockAlt, BlockEntry, BlockExit, SemanticAfter}; use crate::ir::types::{ BlockType, Body, CustomSections, DataSegment, DataSegmentKind, ElementItems, ElementKind, @@ -33,8 +39,8 @@ use std::collections::HashMap; use wasm_encoder::reencode::{Reencode, RoundtripReencoder}; use wasm_encoder::TagSection; use wasmparser::{ - CompositeInnerType, ExternalKind, GlobalType, MemoryType, Operator, Parser, Payload, TagType, - TypeRef, + CompositeInnerType, ExternalKind, GlobalType, MemoryType, Operator, PackedIndex, Parser, + Payload, TagType, TypeRef, }; pub mod module_exports; @@ -48,7 +54,7 @@ pub mod side_effects; #[cfg(test)] mod test; -#[derive(Debug, Default, Clone)] +#[derive(Clone, Debug, Default)] /// Intermediate Representation of a wasm module. See the [WASM Spec] for different sections. /// /// [WASM Spec]: https://webassembly.github.io/spec/core/binary/modules.html @@ -245,13 +251,8 @@ impl<'a> Module<'a> { let payload = payload?; match payload { Payload::ImportSection(import_section_reader) => { - let mut temp = vec![]; - // count number of imported functions - for import in import_section_reader.into_iter() { - let imp = Import::from(import?); - temp.push(imp); - } - imports = ModuleImports::new(temp); + let import_vec = expand_imports(import_section_reader.into_iter())?; + imports = ModuleImports::new(import_vec); } Payload::TypeSection(type_section_reader) => { for ty in type_section_reader.into_iter() { @@ -279,6 +280,8 @@ impl<'a> Module<'a> { super_type: subtype.supertype_idx, is_final: subtype.is_final, shared: subtype.composite_type.shared, + descriptor: unpack(subtype.composite_type.descriptor_idx), + describes: unpack(subtype.composite_type.describes_idx), tag: None, }); } @@ -289,6 +292,8 @@ impl<'a> Module<'a> { super_type: subtype.supertype_idx, is_final: subtype.is_final, shared: subtype.composite_type.shared, + descriptor: unpack(subtype.composite_type.descriptor_idx), + describes: unpack(subtype.composite_type.describes_idx), tag: None, }); } @@ -307,6 +312,8 @@ impl<'a> Module<'a> { super_type: subtype.supertype_idx, is_final: subtype.is_final, shared: subtype.composite_type.shared, + descriptor: unpack(subtype.composite_type.descriptor_idx), + describes: unpack(subtype.composite_type.describes_idx), tag: None, }); } @@ -316,11 +323,24 @@ impl<'a> Module<'a> { super_type: subtype.supertype_idx, is_final: subtype.is_final, shared: subtype.composite_type.shared, + descriptor: unpack(subtype.composite_type.descriptor_idx), + describes: unpack(subtype.composite_type.describes_idx), tag: None, }); } } } + fn unpack(packed: Option) -> Option { + if let Some(i) = packed { + let idx = i.unpack().as_module_index(); + if idx.is_none() { + panic!("Internal error: I just made an assumption on how to unpack this. If I'm wrong, create a GH issue.") + } + idx + } else { + None + } + } let mut ids = vec![]; for ty in group_types.iter() { let ty_id = TypeID(types.len() as u32); @@ -404,10 +424,7 @@ impl<'a> Module<'a> { } Payload::TagSection(tag_section_reader) => { for tag in tag_section_reader.into_iter() { - match tag { - Ok(t) => tags.push(t), - Err(e) => panic!("Error encored in tag section!: {}", e), - } + tags.push(tag?); } } Payload::CustomSection(custom_section_reader) => { @@ -599,7 +616,7 @@ impl<'a> Module<'a> { functions[index], FunctionID(imports.num_funcs + index as u32), code_sec, - types[&functions[index]].params().len(), + types[&functions[index]].params()?.len(), None, ))), name, @@ -685,10 +702,10 @@ impl<'a> Module<'a> { } /// Emit the module into a wasm binary file. - pub fn emit_wasm(&mut self, file_name: &str) -> Result<(), std::io::Error> { - let (module, _) = self.encode_internal(false); + pub fn emit_wasm(&mut self, file_name: &str) -> types::Result<()> { + let (module, _) = self.encode_internal(false)?; let wasm = module.finish(); - std::fs::write(file_name, wasm)?; + std::fs::write(file_name, wasm).map_err(IO)?; Ok(()) } @@ -704,8 +721,8 @@ impl<'a> Module<'a> { /// let mut module = Module::parse(&buff, false, false).unwrap(); /// let result = module.encode(); /// ``` - pub fn encode(&mut self) -> Vec { - self.encode_internal(false).0.finish() + pub fn encode(&self) -> types::Result> { + Ok(self.encode_internal(false)?.0.finish()) } /// Visits the Wirm Module and resolves the special instrumentation by @@ -717,7 +734,7 @@ impl<'a> Module<'a> { memory_mapping: &HashMap, pull_side_effects: bool, side_effects: &mut HashMap>>, - ) { + ) -> types::Result<()> { if !self.num_local_functions > 0 { for rel_func_idx in (self.imports.num_funcs - self.imports.num_funcs_added) as usize ..self.functions.as_vec().len() @@ -756,7 +773,7 @@ impl<'a> Module<'a> { global_mapping, memory_mapping, side_effects, - ); + )?; } func.instr_flag.exit.instrs.clear(); @@ -775,25 +792,18 @@ impl<'a> Module<'a> { > = HashMap::new(); if let Some(on_exit) = &mut instr_func_on_exit { if !on_exit.instrs.is_empty() { - let on_entry = if let Some(on_entry) = &mut instr_func_on_entry { - on_entry - } else { + let on_entry = instr_func_on_entry.get_or_insert_with(|| { // NOTE: This retains the tag information for function exit just in case // that's necessary for the library user. This may need to be handled on // the user side. - instr_func_on_entry = Some(InjectedInstrs { + InjectedInstrs { tag: on_exit.tag.clone(), ..Default::default() - }); - if let Some(ref mut on_entry) = instr_func_on_entry { - on_entry - } else { - panic!() } - }; + }); let func_ty = self.functions.get_type_id(func_idx); - let func_results = self.types.get(func_ty).unwrap().results(); + let func_results = self.types.get(func_ty).unwrap().results()?; // NOTE: This retains the tag information for function exit just in case // that's necessary for the library user. This may need to be handled on @@ -806,7 +816,7 @@ impl<'a> Module<'a> { resolve_function_exit_with_block_wrapper(&mut on_entry.instrs, block_ty); } } - let mut builder = self.functions.get_fn_modifier(func_idx).unwrap(); + let mut builder = self.functions.get_fn_modifier(func_idx)?; let flags = if let Some(flags) = builder.body.instructions.get_flags() { #[allow(clippy::unnecessary_to_owned)] @@ -1033,7 +1043,7 @@ impl<'a> Module<'a> { &mut resolve_on_end, &op, idx, - ); + )?; builder.clear_instr_at( Location::Module { func_idx: FunctionID(0), // not used @@ -1046,6 +1056,7 @@ impl<'a> Module<'a> { } } } + Ok(()) } /// Reorganises items (both local and imports) in the correct ordering after any potential modifications @@ -1083,7 +1094,7 @@ impl<'a> Module<'a> { ) -> HashMap { Self::reorganize(items.as_vec_mut()); let id_mapping = Self::get_mapping_generic(items.as_vec().iter()); - assert_eq!(items.as_vec().len(), id_mapping.len()); + debug_assert_eq!(items.as_vec().len(), id_mapping.len()); id_mapping } @@ -1095,6 +1106,8 @@ impl<'a> Module<'a> { super_type, is_final, shared, + descriptor, + describes, .. } => { let params = params @@ -1115,6 +1128,8 @@ impl<'a> Module<'a> { composite_type: wasm_encoder::CompositeType { inner: wasm_encoder::CompositeInnerType::Func(fty), shared: *shared, + descriptor: *descriptor, + describes: *describes, }, } } @@ -1124,6 +1139,8 @@ impl<'a> Module<'a> { super_type, is_final, shared, + descriptor, + describes, .. } => wasm_encoder::SubType { is_final: *is_final, @@ -1139,6 +1156,8 @@ impl<'a> Module<'a> { }, )), shared: *shared, + descriptor: *descriptor, + describes: *describes, }, }, Types::StructType { @@ -1147,6 +1166,8 @@ impl<'a> Module<'a> { super_type, is_final, shared, + descriptor, + describes, .. } => { let mut encoded_fields: Vec = vec![]; @@ -1167,6 +1188,8 @@ impl<'a> Module<'a> { fields: Box::from(encoded_fields), }), shared: *shared, + descriptor: *descriptor, + describes: *describes, }, } } @@ -1181,7 +1204,7 @@ impl<'a> Module<'a> { func_mapping: &HashMap, global_mapping: &HashMap, memory_mapping: &HashMap, - ) -> wasm_encoder::Function { + ) -> types::Result { let mut reencode = RoundtripReencoder; let Body { instructions, @@ -1196,7 +1219,7 @@ impl<'a> Module<'a> { let instr_len = instructions.len() - 1; let (ops, mut flags) = instructions.get_ops_flags_mut(); for (idx, op) in ops.iter_mut().enumerate() { - fix_op_id_mapping(op, func_mapping, global_mapping, memory_mapping); + fix_op_id_mapping(op, func_mapping, global_mapping, memory_mapping)?; if flags.is_none() { encode(&op.clone(), &mut function, &mut reencode); continue; @@ -1228,7 +1251,7 @@ impl<'a> Module<'a> { memory_mapping, &mut function, &mut reencode, - ); + )?; // If there are any alternate, encode the alternate if !at_end && !alternate.is_none() { @@ -1240,7 +1263,7 @@ impl<'a> Module<'a> { memory_mapping, &mut function, &mut reencode, - ); + )?; } } else { encode(&op.clone(), &mut function, &mut reencode); @@ -1255,7 +1278,7 @@ impl<'a> Module<'a> { memory_mapping, &mut function, &mut reencode, - ); + )?; } } @@ -1266,11 +1289,12 @@ impl<'a> Module<'a> { memory_mapping: &HashMap, function: &mut wasm_encoder::Function, reencode: &mut RoundtripReencoder, - ) { + ) -> types::Result<()> { for instr in instrs { - fix_op_id_mapping(instr, func_mapping, global_mapping, memory_mapping); + fix_op_id_mapping(instr, func_mapping, global_mapping, memory_mapping)?; encode(instr, function, reencode); } + Ok(()) } fn encode( instr: &Operator, @@ -1285,54 +1309,56 @@ impl<'a> Module<'a> { } } - function + Ok(function) } /// Encodes an Wirm Module to a wasm_encoder Module. /// This requires a mutable reference to self due to the special instrumentation resolution step. pub(crate) fn encode_internal( - &mut self, + &self, pull_side_effects: bool, - ) -> ( + ) -> types::Result<( wasm_encoder::Module, HashMap>>, - ) { + )> { #[cfg(feature = "parallel")] use rayon::prelude::*; + let mut tmp = self.clone(); + // First fix the ID mappings throughout the module - let func_mapping = if self.functions.recalculate_ids { - Self::recalculate_ids(&mut self.functions) + let func_mapping = if tmp.functions.recalculate_ids { + Self::recalculate_ids(&mut tmp.functions) } else { - Self::get_mapping_generic(self.functions.as_vec().iter()) + Self::get_mapping_generic(tmp.functions.as_vec().iter()) }; - let global_mapping = if self.globals.recalculate_ids { - Self::recalculate_ids(&mut self.globals) + let global_mapping = if tmp.globals.recalculate_ids { + Self::recalculate_ids(&mut tmp.globals) } else { - Self::get_mapping_generic(self.globals.iter()) + Self::get_mapping_generic(tmp.globals.iter()) }; - let memory_mapping = if self.memories.recalculate_ids { - Self::recalculate_ids(&mut self.memories) + let memory_mapping = if tmp.memories.recalculate_ids { + Self::recalculate_ids(&mut tmp.memories) } else { - Self::get_mapping_generic(self.memories.iter()) + Self::get_mapping_generic(tmp.memories.iter()) }; // Collect side effects second to make sure you get the right IDs in the injections let mut side_effects = HashMap::new(); // Then resolve any instrumentation that needs to be translated to before/after/alt - self.resolve_special_instrumentation( + tmp.resolve_special_instrumentation( &func_mapping, &global_mapping, &memory_mapping, pull_side_effects, &mut side_effects, - ); + )?; let mut module = wasm_encoder::Module::new(); let mut reencode = RoundtripReencoder; - let new_start = if let Some(start_fn) = self.start { + let new_start = if let Some(start_fn) = tmp.start { // fix the start function mapping match func_mapping.get(&*start_fn) { Some(new_index) => Some(FunctionID(*new_index)), @@ -1344,15 +1370,15 @@ impl<'a> Module<'a> { } else { None }; - self.start = new_start; + tmp.start = new_start; // handle recursion groups - if !self.types.groups.is_empty() { + if !tmp.types.groups.is_empty() { let mut type_sect = wasm_encoder::TypeSection::new(); - for RecGroup { types, is_explicit } in self.types.groups.iter() { + for RecGroup { types, is_explicit } in tmp.types.groups.iter() { let mut subtypes = vec![]; for ty_id in types.iter() { - let ty = self.types.types.get(ty_id).unwrap(); + let ty = tmp.types.types.get(ty_id).unwrap(); if pull_side_effects { if let Some(tag) = ty.get_tag() { add_injection( @@ -1366,7 +1392,7 @@ impl<'a> Module<'a> { } } - let subtype = self.encode_type(ty); + let subtype = tmp.encode_type(ty); if *is_explicit { subtypes.push(subtype); } else { @@ -1383,10 +1409,10 @@ impl<'a> Module<'a> { // initialize function name section let mut function_names = wasm_encoder::NameMap::new(); - if !self.imports.is_empty() { + if !tmp.imports.is_empty() { let mut imports = wasm_encoder::ImportSection::new(); let mut import_func_idx = 0; - for import in self.imports.iter() { + for import in tmp.imports.iter() { if !import.deleted { if import.is_function() { if let Some(import_name) = &import.custom_name { @@ -1418,15 +1444,15 @@ impl<'a> Module<'a> { module.section(&imports); } - if !self.functions.is_empty() { + if !tmp.functions.is_empty() { let mut functions = wasm_encoder::FunctionSection::new(); - for func in self.functions.iter() { + for func in tmp.functions.iter() { if !func.deleted { if let FuncKind::Local(l) = func.kind() { functions.function(*l.ty_id); if pull_side_effects { if let Some(tag) = l.get_tag() { - let sig = self.types.get(l.ty_id).unwrap_or_else(|| { + let sig = tmp.types.get(l.ty_id).unwrap_or_else(|| { panic!("Could not find type for type ID: {}", *l.ty_id) }); add_injection( @@ -1435,7 +1461,7 @@ impl<'a> Module<'a> { Injection::Func { id: *l.func_id, fname: l.body.name.clone(), - sig: (sig.params(), sig.results()), + sig: (sig.params()?, sig.results()?), locals: l.body.locals_as_vec(), tag: tag.clone(), body: l.body.instructions.get_ops().to_vec(), @@ -1449,9 +1475,9 @@ impl<'a> Module<'a> { module.section(&functions); } - if !self.tables.is_empty() { + if !tmp.tables.is_empty() { let mut tables = wasm_encoder::TableSection::new(); - for table in self.tables.iter() { + for table in tmp.tables.iter() { let table_ty = wasm_encoder::TableType { element_type: wasm_encoder::RefType { nullable: table.ty.element_type.is_nullable(), @@ -1487,9 +1513,9 @@ impl<'a> Module<'a> { module.section(&tables); } - if !self.memories.is_empty() { + if !tmp.memories.is_empty() { let mut memories = wasm_encoder::MemorySection::new(); - for memory in self.memories.iter() { + for memory in tmp.memories.iter() { if memory.is_local() { memories.memory(wasm_encoder::MemoryType::from(memory.ty)); @@ -1512,9 +1538,9 @@ impl<'a> Module<'a> { module.section(&memories); } - if !self.tags.is_empty() { + if !tmp.tags.is_empty() { let mut tags = TagSection::new(); - for tag in self.tags.iter() { + for tag in tmp.tags.iter() { tags.tag(wasm_encoder::TagType { kind: wasm_encoder::TagKind::from(tag.kind), func_type_idx: tag.func_type_idx, @@ -1523,16 +1549,16 @@ impl<'a> Module<'a> { module.section(&tags); } - if !self.globals.is_empty() { + if !tmp.globals.is_empty() { let mut globals = wasm_encoder::GlobalSection::new(); - for global in self.globals.iter_mut() { + for global in tmp.globals.iter_mut() { if !global.deleted { // save these off for the side effect processing before matching on global.kind (due to rust borrow issues) let id = global.get_id(); let tag = global.get_tag().clone(); if let GlobalKind::Local(LocalGlobal { ty, init_expr, .. }) = &mut global.kind { for expr in init_expr.exprs.iter_mut() { - expr.fix_id_mapping(&func_mapping, &global_mapping); + expr.fix_id_mapping(&func_mapping, &global_mapping)?; } globals.global( wasm_encoder::GlobalType { @@ -1566,9 +1592,9 @@ impl<'a> Module<'a> { module.section(&globals); } - if !self.exports.is_empty() { + if !tmp.exports.is_empty() { let mut exports = wasm_encoder::ExportSection::new(); - for export in self.exports.iter() { + for export in tmp.exports.iter() { if !export.deleted { match export.kind { ExternalKind::Func => { @@ -1614,17 +1640,17 @@ impl<'a> Module<'a> { module.section(&exports); } - if let Some(function_index) = self.start { + if let Some(function_index) = tmp.start { module.section(&wasm_encoder::StartSection { function_index: *function_index, }); } - if !self.elements.is_empty() { + if !tmp.elements.is_empty() { let mut elements = wasm_encoder::ElementSection::new(); let mut temp_const_exprs = vec![]; let mut element_items = vec![]; - for element in self.elements.iter_mut() { + for element in tmp.elements.iter_mut() { temp_const_exprs.clear(); element_items.clear(); let (items, kind) = (&mut element.items, &mut element.kind); @@ -1641,7 +1667,7 @@ impl<'a> Module<'a> { temp_const_exprs.reserve(exprs.len()); for e in exprs.iter_mut() { for i in e.exprs.iter_mut() { - i.fix_id_mapping(&func_mapping, &global_mapping); + i.fix_id_mapping(&func_mapping, &global_mapping)?; } temp_const_exprs.push(e.to_wasmencoder_type()); } @@ -1664,7 +1690,7 @@ impl<'a> Module<'a> { offset_expr, } => { for e in offset_expr.exprs.iter_mut() { - e.fix_id_mapping(&func_mapping, &global_mapping); + e.fix_id_mapping(&func_mapping, &global_mapping)?; } elements.active( *table_index, @@ -1690,19 +1716,19 @@ impl<'a> Module<'a> { module.section(&elements); } - if self.data_count_section_exists { + if tmp.data_count_section_exists { let data_count = wasm_encoder::DataCountSection { - count: self.data.len() as u32, + count: tmp.data.len() as u32, }; module.section(&data_count); } - if !self.num_local_functions > 0 { + if !tmp.num_local_functions > 0 { let mut code = wasm_encoder::CodeSection::new(); #[cfg(feature = "parallel")] let functions = { - let functions_mut = self.functions.iter_mut().collect::>(); + let functions_mut = tmp.functions.iter_mut().collect::>(); functions_mut .into_par_iter() .enumerate() @@ -1710,7 +1736,9 @@ impl<'a> Module<'a> { if f.is_deleted() || f.is_import() { return None; } - let f = f.unwrap_local_mut(); + let f = f.unwrap_local_mut().expect( + "Internal error: Should have found the local function successfully!", + ); let encoded_func = Self::encode_function( f, &func_mapping, @@ -1723,7 +1751,7 @@ impl<'a> Module<'a> { }; #[cfg(not(feature = "parallel"))] - let functions = self + let functions = tmp .functions .iter_mut() .enumerate() @@ -1731,7 +1759,9 @@ impl<'a> Module<'a> { if f.is_deleted() || f.is_import() { return None; } - let f = f.unwrap_local_mut(); + let f = f.unwrap_local_mut().expect( + "Internal error: Should have found the local function successfully!", + ); let encoded_func = Self::encode_function(f, &func_mapping, &global_mapping, &memory_mapping); Some((idx, f, encoded_func)) @@ -1747,14 +1777,14 @@ impl<'a> Module<'a> { if let Some(name) = &original_func.body.name { function_names.append(idx as u32, name.as_str()); } - code.function(&function); + code.function(&function?); } module.section(&code); } - if !self.data.is_empty() { + if !tmp.data.is_empty() { let mut data = wasm_encoder::DataSection::new(); - for segment in self.data.iter_mut() { + for segment in tmp.data.iter_mut() { // save this off for the side effect processing before matching on segment.kind (due to rust borrow issues) let tag = segment.get_tag().clone(); let segment_data = segment.data.iter().copied(); @@ -1779,14 +1809,16 @@ impl<'a> Module<'a> { offset_expr, } => { for expr in offset_expr.exprs.iter_mut() { - expr.fix_id_mapping(&func_mapping, &global_mapping); + expr.fix_id_mapping(&func_mapping, &global_mapping)?; } let new_idx = match memory_mapping.get(memory_index) { Some(new_index) => *new_index, - None => panic!( - "Attempting to reference a deleted memory, ID: {}", - memory_index - ), + None => { + return Err(InstrumentationError(format!( + "Attempting to reference a deleted memory, ID: {}", + memory_index + ))) + } }; if pull_side_effects { if let Some(tag) = tag { @@ -1810,35 +1842,35 @@ impl<'a> Module<'a> { module.section(&data); } - // the name section is not stored in self.custom_sections anymore + // the name section is not stored in tmp.custom_sections anymore let mut names = wasm_encoder::NameSection::new(); - if let Some(module_name) = &self.module_name { + if let Some(module_name) = &tmp.module_name { names.module(module_name); } names.functions(&function_names); - names.locals(&self.local_names); - names.labels(&self.label_names); - names.types(&self.type_names); - names.tables(&self.table_names); - names.memories(&self.memory_names); - names.globals(&self.global_names); - names.elements(&self.elem_names); - names.data(&self.data_names); - names.fields(&self.field_names); - names.tag(&self.tag_names); + names.locals(&tmp.local_names); + names.labels(&tmp.label_names); + names.types(&tmp.type_names); + names.tables(&tmp.table_names); + names.memories(&tmp.memory_names); + names.globals(&tmp.global_names); + names.elements(&tmp.elem_names); + names.data(&tmp.data_names); + names.fields(&tmp.field_names); + names.tag(&tmp.tag_names); module.section(&names); // encode the rest of custom sections - for section in self.custom_sections.iter() { + for section in tmp.custom_sections.iter() { module.section(&wasm_encoder::CustomSection { name: std::borrow::Cow::Borrowed(section.name), data: section.data.clone(), }); } - (module, side_effects) + Ok((module, side_effects)) } // ============================== @@ -1856,7 +1888,7 @@ impl<'a> Module<'a> { /// Get the memory ID of a module. Does not support multiple memories pub fn get_memory_id(&self) -> Option { if self.memories.as_vec().len() > 1 { - panic!("multiple memories unsupported") + unimplemented!("multiple memories unsupported") } if !self.memories.is_empty() { @@ -1885,6 +1917,11 @@ impl<'a> Module<'a> { self.imports.num_memories, self.memories.as_vec().len() as u32, ), + TypeRef::FuncExact(..) => ( + self.num_local_functions, + self.imports.num_funcs, + self.functions.as_vec().len() as u32, + ), }; let id = if num_local > 0 { @@ -1895,6 +1932,10 @@ impl<'a> Module<'a> { (id, self.imports.add(import)) } + pub fn add_custom_section(&mut self, section: CustomSection<'a>) -> CustomSectionID { + self.custom_sections.add(section) + } + // =========================== // ==== Memory Management ==== // =========================== @@ -2100,7 +2141,7 @@ impl<'a> Module<'a> { import_fn_id: function_id, ty_id, })); - assert!(self.functions.set_imported_fn_name(function_id, name)); + debug_assert!(self.functions.set_imported_fn_name(function_id, name)); true } @@ -2109,10 +2150,10 @@ impl<'a> Module<'a> { if *id < self.imports.num_funcs { // the function is an import self.imports.set_fn_name(name.clone(), id); - assert!(self.functions.set_imported_fn_name(id, name)); + debug_assert!(self.functions.set_imported_fn_name(id, name)); } else { // the function is local - assert!(self.functions.set_local_fn_name(id, name)); + debug_assert!(self.functions.set_local_fn_name(id, name)); } } @@ -2228,8 +2269,12 @@ impl<'a> Module<'a> { } /// Change a locally-defined global's init expression. - pub fn mod_global_init_expr(&mut self, global_id: GlobalID, new_expr: InitExpr) { - self.globals.mod_global_init_expr(*global_id, new_expr); + pub fn mod_global_init_expr( + &mut self, + global_id: GlobalID, + new_expr: InitExpr, + ) -> types::Result<()> { + self.globals.mod_global_init_expr(*global_id, new_expr) } } @@ -2272,16 +2317,17 @@ pub(crate) fn fix_op_id_mapping( func_mapping: &HashMap, global_mapping: &HashMap, memory_mapping: &HashMap, -) { +) -> types::Result<()> { if refers_to_func(op) { - update_fn_instr(op, func_mapping); + update_fn_instr(op, func_mapping)?; } if refers_to_global(op) { - update_global_instr(op, global_mapping); + update_global_instr(op, global_mapping)?; } if refers_to_memory(op) { - update_memory_instr(op, memory_mapping); + update_memory_instr(op, memory_mapping)?; } + Ok(()) } fn resolve_function_entry<'a, 'b, 'c>( @@ -2500,7 +2546,8 @@ fn plan_resolution_semantic_after<'a, 'b, 'c>( resolve_on_end: &mut HashMap>>, op: &Operator, idx: usize, -) where +) -> types::Result<()> +where 'c: 'b, { // save instrumentation to be converted to simple before/after/alt @@ -2519,7 +2566,7 @@ fn plan_resolution_semantic_after<'a, 'b, 'c>( ); } Operator::BrTable { targets } => { - let bool_flag_id = create_bool_flag(builder, idx, op, semantic_after); + let bool_flag_id = create_bool_flag(builder, idx, op, semantic_after)?; targets.targets().for_each(|target| { if let Ok(relative_depth) = target { save_flagged_body_to_resolve( @@ -2548,7 +2595,7 @@ fn plan_resolution_semantic_after<'a, 'b, 'c>( | Operator::BrOnCastFail { relative_depth, .. } | Operator::BrOnNonNull { relative_depth } | Operator::BrOnNull { relative_depth } => { - let bool_flag_id = create_bool_flag(builder, idx, op, semantic_after); + let bool_flag_id = create_bool_flag(builder, idx, op, semantic_after)?; save_flagged_body_to_resolve( resolve_on_end, InstrumentationMode::After, @@ -2560,6 +2607,7 @@ fn plan_resolution_semantic_after<'a, 'b, 'c>( } _ => {} // skip all other opcodes } + Ok(()) } fn create_bool_flag<'a, 'b, 'c>( @@ -2567,7 +2615,7 @@ fn create_bool_flag<'a, 'b, 'c>( idx: usize, op: &Operator, semantic_after: &Vec>, -) -> LocalID +) -> types::Result where 'c: 'b, { @@ -2612,7 +2660,7 @@ where } _ => {} } - bool_flag_id + Ok(bool_flag_id) } fn save_not_flagged_body_to_resolve<'a>( diff --git a/src/ir/module/module_functions.rs b/src/ir/module/module_functions.rs index 7b0bddd5..f731aafe 100644 --- a/src/ir/module/module_functions.rs +++ b/src/ir/module/module_functions.rs @@ -1,9 +1,11 @@ //! Intermediate Representation of a Function +use crate::error::Error::{InvalidOperation, UnknownId}; use crate::ir::function::FunctionModifier; use crate::ir::id::{FunctionID, ImportsID, LocalID, TypeID}; use crate::ir::module::side_effects::{InjectType, Injection}; use crate::ir::module::{AsVec, GetID, LocalOrImport}; +use crate::ir::types; use crate::ir::types::{ Body, FuncInstrFlag, HasInjectTag, InjectTag, InstrumentationMode, Tag, TagUtils, }; @@ -75,12 +77,12 @@ impl<'a> Function<'a> { } /// Unwrap a local function. If it is an imported function, it panics. - pub fn unwrap_local(&self) -> &LocalFunction<'a> { + pub fn unwrap_local(&self) -> types::Result<&LocalFunction<'a>> { self.kind.unwrap_local() } /// Unwrap a local function as mutable. If it is an imported function, it panics. - pub fn unwrap_local_mut(&mut self) -> &mut LocalFunction<'a> { + pub fn unwrap_local_mut(&mut self) -> types::Result<&mut LocalFunction<'a>> { self.kind.unwrap_local_mut() } @@ -97,18 +99,22 @@ pub enum FuncKind<'a> { } impl<'a> FuncKind<'a> { - /// Unwrap a local function as a read-only reference. If it is an imported function, it panics. - pub fn unwrap_local(&self) -> &LocalFunction<'a> { + /// Unwrap a local function as a read-only reference. If it is an imported function, it errors. + pub fn unwrap_local(&self) -> types::Result<&LocalFunction<'a>> { match &self { - FuncKind::Local(l) => l, - FuncKind::Import(_) => panic!("Attempting to unwrap an imported function as a local!!"), + FuncKind::Local(l) => Ok(l), + FuncKind::Import(_) => Err(InvalidOperation( + "Attempting to unwrap an imported function as a local!!".to_string(), + )), } } - /// Unwrap a local function as a mutable reference. If it is an imported function, it panics. - pub fn unwrap_local_mut(&mut self) -> &mut LocalFunction<'a> { + /// Unwrap a local function as a mutable reference. If it is an imported function, it errors. + pub fn unwrap_local_mut(&mut self) -> types::Result<&mut LocalFunction<'a>> { match self { - FuncKind::Local(l) => l, - FuncKind::Import(_) => panic!("Attempting to unwrap an imported function as a local!!"), + FuncKind::Local(l) => Ok(l), + FuncKind::Import(_) => Err(InvalidOperation( + "Attempting to unwrap an imported function as a local!!".to_string(), + )), } } @@ -225,14 +231,14 @@ impl<'a> LocalFunction<'a> { global_mapping: &HashMap, memory_mapping: &HashMap, side_effects: &mut HashMap>>, - ) { + ) -> types::Result<()> { self.instr_flag.add_injections( rel_fid, func_mapping, global_mapping, memory_mapping, side_effects, - ); + ) } pub(crate) fn add_opcode_injections( @@ -419,12 +425,15 @@ impl<'a> Functions<'a> { } /// Unwrap local function. - pub fn unwrap_local(&self, function_id: FunctionID) -> &LocalFunction<'a> { + pub fn unwrap_local(&self, function_id: FunctionID) -> types::Result<&LocalFunction<'a>> { self.functions[*function_id as usize].unwrap_local() } /// Unwrap local function. - pub fn unwrap_local_mut(&mut self, function_id: FunctionID) -> &mut LocalFunction<'a> { + pub fn unwrap_local_mut( + &mut self, + function_id: FunctionID, + ) -> types::Result<&mut LocalFunction<'a>> { self.functions[*function_id as usize].unwrap_local_mut() } @@ -450,19 +459,28 @@ impl<'a> Functions<'a> { pub fn get_fn_modifier<'b>( &'b mut self, func_id: FunctionID, - ) -> Option> { + ) -> types::Result> { // grab type and section and code section - match &mut self.functions.get_mut(*func_id as usize)?.kind { + let func = self.functions.get_mut(*func_id as usize); + if func.is_none() { + return Err(UnknownId(format!( + "Could not find function with ID: {func_id:?}" + ))); + } + + match func.unwrap().kind { FuncKind::Local(ref mut l) => { // the instrflag should be reset! l.instr_flag.finish_instr(); - Some(FunctionModifier::init( + Ok(FunctionModifier::init( &mut l.instr_flag, &mut l.body, &mut l.args, )) } - _ => None, + _ => Err(InvalidOperation( + "Cannot modify a non-local function".to_string(), + )), } } @@ -507,16 +525,21 @@ impl<'a> Functions<'a> { imp_fn_id: u32, ) { self.recalculate_ids = true; - assert_eq!(*self.next_id(), imp_fn_id); + debug_assert_eq!(*self.next_id(), imp_fn_id); self.functions.push(Function::new( FuncKind::Import(ImportedFunction::new(imp_id, ty_id, FunctionID(imp_fn_id))), name, )); } - pub(crate) fn add_local(&mut self, func_idx: FunctionID, ty: DataType) -> LocalID { - let local_func = self.functions[*func_idx as usize].unwrap_local_mut(); - local_func.add_local(ty) + /// Add a local to a function + pub(crate) fn add_local( + &mut self, + func_idx: FunctionID, + ty: DataType, + ) -> types::Result { + let local_func = self.functions[*func_idx as usize].unwrap_local_mut()?; + Ok(local_func.add_local(ty)) } /// Set the name for a local function. Returns false if it is an imported function. diff --git a/src/ir/module/module_globals.rs b/src/ir/module/module_globals.rs index d9d1fd19..1664c885 100644 --- a/src/ir/module/module_globals.rs +++ b/src/ir/module/module_globals.rs @@ -1,9 +1,11 @@ //! Intermediate representation of the globals. use crate::error::Error; +use crate::error::Error::UnknownId; use crate::ir::id::{GlobalID, ImportsID}; use crate::ir::module::module_imports::ModuleImports; use crate::ir::module::{AsVec, GetID, LocalOrImport}; +use crate::ir::types; use crate::ir::types::{InitExpr, InjectTag, Tag, TagUtils}; use wasmparser::{GlobalType, TypeRef}; @@ -227,7 +229,11 @@ impl ModuleGlobals { self.globals.push(global); id } - pub(crate) fn mod_global_init_expr(&mut self, global_id: u32, new_expr: InitExpr) { + pub(crate) fn mod_global_init_expr( + &mut self, + global_id: u32, + new_expr: InitExpr, + ) -> types::Result<()> { if let Some(Global { kind: GlobalKind::Local(LocalGlobal { init_expr, .. }), .. @@ -235,7 +241,10 @@ impl ModuleGlobals { { *init_expr = new_expr; } else { - panic!("Cannot update requested global's init_expr, id: {global_id}") + return Err(UnknownId(format!( + "Cannot update requested global's init_expr, id: {global_id}" + ))); } + Ok(()) } } diff --git a/src/ir/module/module_imports.rs b/src/ir/module/module_imports.rs index 55dd21f1..17e79ebe 100644 --- a/src/ir/module/module_imports.rs +++ b/src/ir/module/module_imports.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use crate::ir::id::{FunctionID, ImportsID}; use crate::ir::types::{InjectTag, Tag, TagUtils}; -use wasmparser::TypeRef; +use wasmparser::{Imports, Result, TypeRef}; // TODO: Need to handle the relationship between Functions and Imports /// Represents an import in a WebAssembly module. @@ -30,6 +30,44 @@ impl TagUtils for Import<'_> { &self.tag } } + +pub fn expand_imports<'a>( + imports: impl IntoIterator>>, +) -> Result>> { + let mut out: Vec = Vec::new(); + + for group in imports { + match group? { + Imports::Single(_, import) => { + out.push(import.into()); + } + + Imports::Compact1 { module, items } => { + for item in items { + let item = item?; + out.push( + wasmparser::Import { + module, + name: item.name, + ty: item.ty, + } + .into(), + ); + } + } + + Imports::Compact2 { module, ty, names } => { + for name in names { + let name = name?; + out.push(wasmparser::Import { module, name, ty }.into()); + } + } + } + } + + Ok(out) +} + impl<'a> From> for Import<'a> { fn from(import: wasmparser::Import<'a>) -> Self { Import { @@ -125,6 +163,13 @@ impl<'a> ModuleImports<'a> { self.imports[*imports_id as usize].custom_name = Some(name) } + /// Set the binding of a given import using the ImportsID. + pub fn set_import_name(&mut self, module: String, name: String, imports_id: ImportsID) { + let import = &mut self.imports[*imports_id as usize]; + import.module = module.into(); + import.name = name.into(); + } + /// Set the name of an imported function, using the FunctionID rather /// than the ImportsID. Note that these are not necessarily equal if /// the module has non-function imports! (It is more efficient to @@ -148,7 +193,7 @@ impl<'a> ModuleImports<'a> { // using a match instead of import.is_*() to make sure that we're // exhaustive due to the compiler guarantees. match import.ty { - TypeRef::Func(..) => { + TypeRef::Func(..) | TypeRef::FuncExact(..) => { self.num_funcs += 1; self.num_funcs_added += 1; } diff --git a/src/ir/module/module_memories.rs b/src/ir/module/module_memories.rs index 70128688..cd9be8e5 100644 --- a/src/ir/module/module_memories.rs +++ b/src/ir/module/module_memories.rs @@ -1,5 +1,7 @@ +use crate::error::Error::InvalidOperation; use crate::ir::id::{ImportsID, MemoryID}; use crate::ir::module::{AsVec, GetID, LocalOrImport}; +use crate::ir::types; use crate::ir::types::{InjectTag, Tag, TagUtils}; use wasmparser::MemoryType; @@ -133,7 +135,7 @@ impl Memories { tag: InjectTag, ) { self.recalculate_ids = true; - assert_eq!(*self.next_id(), imp_mem_id); + debug_assert_eq!(*self.next_id(), imp_mem_id); self.memories.push(Memory { ty, kind: MemKind::Import(ImportedMemory { @@ -212,12 +214,12 @@ impl Memory { } /// Unwrap a local memory. If it is an imported memory, it panics. - pub fn unwrap_local(&self) -> &LocalMemory { + pub fn unwrap_local(&self) -> types::Result<&LocalMemory> { self.kind.unwrap_local() } /// Unwrap a local memory as mutable. If it is an imported memory, it panics. - pub fn unwrap_local_mut(&mut self) -> &mut LocalMemory { + pub fn unwrap_local_mut(&mut self) -> types::Result<&mut LocalMemory> { self.kind.unwrap_local_mut() } @@ -234,18 +236,22 @@ pub enum MemKind { } impl MemKind { - /// Unwrap a local memory as a read-only reference. If it is an imported memory, it panics. - pub fn unwrap_local(&self) -> &LocalMemory { + /// Unwrap a local memory as a read-only reference. If it is an imported memory, it errors. + pub fn unwrap_local(&self) -> types::Result<&LocalMemory> { match &self { - MemKind::Local(l) => l, - MemKind::Import(_) => panic!("Attempting to unwrap an imported memory as a local!!"), + MemKind::Local(l) => Ok(l), + MemKind::Import(_) => Err(InvalidOperation( + "Attempting to unwrap an imported memory as a local!!".to_string(), + )), } } - /// Unwrap a local memory as a mutable reference. If it is an imported memory, it panics. - pub fn unwrap_local_mut(&mut self) -> &mut LocalMemory { + /// Unwrap a local memory as a mutable reference. If it is an imported memory, it errors. + pub fn unwrap_local_mut(&mut self) -> types::Result<&mut LocalMemory> { match self { - MemKind::Local(l) => l, - MemKind::Import(_) => panic!("Attempting to unwrap an imported memory as a local!!"), + MemKind::Local(l) => Ok(l), + MemKind::Import(_) => Err(InvalidOperation( + "Attempting to unwrap an imported memory as a local!!".to_string(), + )), } } } diff --git a/src/ir/module/module_tables.rs b/src/ir/module/module_tables.rs index 14d149dd..685350ec 100644 --- a/src/ir/module/module_tables.rs +++ b/src/ir/module/module_tables.rs @@ -1,6 +1,8 @@ //! Intermediate representation of the Tables in a Module +use crate::error::Error::{InvalidOperation, UnknownId}; use crate::ir::id::TableID; +use crate::ir::types; use crate::ir::types::{ElementItems, ElementKind, InjectTag, Tag, TagUtils}; use wasmparser::{RefType, TableType}; @@ -40,20 +42,19 @@ impl<'a> ModuleTables<'a> { /// Inspired from [walrus' implementation] /// /// [walrus' implementation]: https://docs.rs/walrus/latest/walrus/struct.ModuleTables.html#method.main_function_table - pub fn main_function(&self) -> Option { + pub fn main_function(&self) -> types::Result> { let mut tables = self .tables .iter() .enumerate() .filter(|(_, t)| t.ty.element_type == RefType::FUNCREF); - let id = match tables.next() { - Some((index, _)) => Some(TableID(index as u32)), - None => return None, - }; + let id = tables.next().map(|(index, _)| TableID(index as u32)); if tables.next().is_some() { - panic!("module contains more than one function table"); + return Err(InvalidOperation( + "module contains more than one function table".to_string(), + )); } - id + Ok(id) } /// Get a table @@ -65,11 +66,11 @@ impl<'a> ModuleTables<'a> { } /// Get a mutable reference to a table - pub fn get_mut(&mut self, table_id: TableID) -> &mut TableType { + pub fn get_mut(&mut self, table_id: TableID) -> types::Result<&mut TableType> { if *table_id < self.tables.len() as u32 { - return &mut self.tables[*table_id as usize].ty; + return Ok(&mut self.tables[*table_id as usize].ty); } - panic!("Invalid Table ID") + Err(UnknownId("Invalid Table ID".to_string())) } } diff --git a/src/ir/module/module_types.rs b/src/ir/module/module_types.rs index 803b1341..f9b9a1f1 100644 --- a/src/ir/module/module_types.rs +++ b/src/ir/module/module_types.rs @@ -1,6 +1,9 @@ +#![allow(clippy::too_many_arguments)] //! Intermediate representation of Module Types +use crate::error::Error::InvalidOperation; use crate::ir::id::TypeID; +use crate::ir::types; use crate::ir::types::{InjectTag, Tag, TagUtils}; use crate::DataType; use std::collections::HashMap; @@ -29,6 +32,10 @@ pub enum Types { super_type: Option, is_final: bool, shared: bool, + /// Optional descriptor attribute. + descriptor: Option, + /// Optional describes attribute. + describes: Option, tag: InjectTag, }, ArrayType { @@ -37,6 +44,10 @@ pub enum Types { super_type: Option, is_final: bool, shared: bool, + /// Optional descriptor attribute. + descriptor: Option, + /// Optional describes attribute. + describes: Option, tag: InjectTag, }, StructType { @@ -45,6 +56,10 @@ pub enum Types { super_type: Option, is_final: bool, shared: bool, + /// Optional descriptor attribute. + descriptor: Option, + /// Optional describes attribute. + describes: Option, tag: InjectTag, }, ContType { @@ -52,6 +67,10 @@ pub enum Types { super_type: Option, is_final: bool, shared: bool, + /// Optional descriptor attribute. + descriptor: Option, + /// Optional describes attribute. + describes: Option, tag: InjectTag, }, } @@ -239,18 +258,18 @@ impl TagUtils for Types { } impl Types { /// Return the params of a Function Type - pub fn params(&self) -> Vec { + pub fn params(&self) -> types::Result> { match &self { - Types::FuncType { params, .. } => params.to_vec(), - _ => panic!("Not a function!"), + Types::FuncType { params, .. } => Ok(params.to_vec()), + _ => Err(InvalidOperation("Not a function!".to_string())), } } /// Return the params of a Function Type - pub fn results(&self) -> Vec { + pub fn results(&self) -> types::Result> { match &self { - Types::FuncType { results, .. } => results.to_vec(), - _ => panic!("Not a function!"), + Types::FuncType { results, .. } => Ok(results.to_vec()), + _ => Err(InvalidOperation("Not a function!".to_string())), } } @@ -332,6 +351,8 @@ impl ModuleTypes { super_type: None, is_final: true, shared: false, + descriptor: None, + describes: None, tag, }; @@ -346,6 +367,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, ) -> TypeID { self.add_func_type_with_params_with_tag( param, @@ -353,6 +376,8 @@ impl ModuleTypes { super_type, is_final, shared, + descriptor, + describes, Tag::default(), ) } @@ -363,9 +388,20 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, tag: Tag, ) -> TypeID { - self.add_func_type_with_params_internal(param, ret, super_type, is_final, shared, Some(tag)) + self.add_func_type_with_params_internal( + param, + ret, + super_type, + is_final, + shared, + descriptor, + describes, + Some(tag), + ) } pub(crate) fn add_func_type_with_params_internal( &mut self, @@ -374,6 +410,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, tag: InjectTag, ) -> TypeID { let ty = Types::FuncType { @@ -383,6 +421,8 @@ impl ModuleTypes { None => None, Some(id) => PackedIndex::from_module_index(*id), }, + descriptor, + describes, is_final, shared, tag, @@ -392,21 +432,31 @@ impl ModuleTypes { } /// Add a new array type to the module. Assumes no `super_type` and `is_final` is `true` - pub fn add_array_type(&mut self, field_type: DataType, mutable: bool) -> TypeID { - self.add_array_type_with_tag(field_type, mutable, Tag::default()) + pub fn add_array_type( + &mut self, + field_type: DataType, + mutable: bool, + descriptor: Option, + describes: Option, + ) -> TypeID { + self.add_array_type_with_tag(field_type, mutable, descriptor, describes, Tag::default()) } pub fn add_array_type_with_tag( &mut self, field_type: DataType, mutable: bool, + descriptor: Option, + describes: Option, tag: Tag, ) -> TypeID { - self.add_array_type_internal(field_type, mutable, Some(tag)) + self.add_array_type_internal(field_type, mutable, descriptor, describes, Some(tag)) } pub(crate) fn add_array_type_internal( &mut self, field_type: DataType, mutable: bool, + descriptor: Option, + describes: Option, tag: InjectTag, ) -> TypeID { let ty = Types::ArrayType { @@ -415,6 +465,8 @@ impl ModuleTypes { super_type: None, is_final: true, shared: false, + descriptor, + describes, tag, }; @@ -429,6 +481,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, ) -> TypeID { self.add_array_type_with_params_with_tag( field_type, @@ -436,6 +490,8 @@ impl ModuleTypes { super_type, is_final, shared, + descriptor, + describes, Tag::default(), ) } @@ -446,6 +502,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, tag: Tag, ) -> TypeID { self.add_array_type_with_params_internal( @@ -454,6 +512,8 @@ impl ModuleTypes { super_type, is_final, shared, + descriptor, + describes, Some(tag), ) } @@ -464,6 +524,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, tag: InjectTag, ) -> TypeID { let ty = Types::ArrayType { @@ -475,6 +537,8 @@ impl ModuleTypes { }, is_final, shared, + descriptor, + describes, tag, }; @@ -482,21 +546,31 @@ impl ModuleTypes { } /// Add a new struct type to the module. Assumes no `super_type` and `is_final` is `true` - pub fn add_struct_type(&mut self, field_type: Vec, mutable: Vec) -> TypeID { - self.add_struct_type_with_tag(field_type, mutable, Tag::default()) + pub fn add_struct_type( + &mut self, + field_type: Vec, + mutable: Vec, + descriptor: Option, + describes: Option, + ) -> TypeID { + self.add_struct_type_with_tag(field_type, mutable, descriptor, describes, Tag::default()) } pub fn add_struct_type_with_tag( &mut self, field_type: Vec, mutable: Vec, + descriptor: Option, + describes: Option, tag: Tag, ) -> TypeID { - self.add_struct_type_internal(field_type, mutable, Some(tag)) + self.add_struct_type_internal(field_type, mutable, descriptor, describes, Some(tag)) } pub(crate) fn add_struct_type_internal( &mut self, field_type: Vec, mutable: Vec, + descriptor: Option, + describes: Option, tag: InjectTag, ) -> TypeID { let ty = Types::StructType { @@ -505,6 +579,8 @@ impl ModuleTypes { super_type: None, is_final: true, shared: false, + descriptor, + describes, tag, }; @@ -519,6 +595,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, ) -> TypeID { self.add_struct_type_with_params_with_tag( field_type, @@ -526,6 +604,8 @@ impl ModuleTypes { super_type, is_final, shared, + descriptor, + describes, Tag::default(), ) } @@ -536,6 +616,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, tag: Tag, ) -> TypeID { self.add_struct_type_with_params_internal( @@ -544,6 +626,8 @@ impl ModuleTypes { super_type, is_final, shared, + descriptor, + describes, Some(tag), ) } @@ -554,6 +638,8 @@ impl ModuleTypes { super_type: Option, is_final: bool, shared: bool, + descriptor: Option, + describes: Option, tag: InjectTag, ) -> TypeID { let ty = Types::StructType { @@ -565,6 +651,8 @@ impl ModuleTypes { }, is_final, shared, + descriptor, + describes, tag, }; @@ -592,6 +680,7 @@ pub enum HeapType { Abstract { shared: bool, ty: AbstractHeapType }, // TODO: See to replace UnpackedIndex with `wirm` specific implementation Concrete(UnpackedIndex), + Exact(UnpackedIndex), } impl From for HeapType { @@ -602,6 +691,7 @@ impl From for HeapType { ty: AbstractHeapType::from(ty), }, wasmparser::HeapType::Concrete(idx) => HeapType::Concrete(idx), + wasmparser::HeapType::Exact(idx) => HeapType::Exact(idx), } } } @@ -614,6 +704,7 @@ impl From for wasmparser::HeapType { ty: wasmparser::AbstractHeapType::from(ty), }, HeapType::Concrete(idx) => wasmparser::HeapType::Concrete(idx), + HeapType::Exact(idx) => wasmparser::HeapType::Exact(idx), } } } diff --git a/src/ir/module/side_effects.rs b/src/ir/module/side_effects.rs index 53821c91..2d74f94c 100644 --- a/src/ir/module/side_effects.rs +++ b/src/ir/module/side_effects.rs @@ -1,4 +1,5 @@ use crate::ir::module::module_types::Types; +use crate::ir::types; use crate::ir::types::{FuncInstrMode, InitExpr, InstrumentationMode, Tag}; use crate::{DataType, Module}; use std::collections::HashMap; @@ -6,8 +7,8 @@ use std::fmt::{Display, Formatter}; use wasmparser::{ExternalKind, Operator, TypeRef}; impl<'a> Module<'a> { - pub fn pull_side_effects(&mut self) -> HashMap>> { - self.encode_internal(true).1 + pub fn pull_side_effects(&mut self) -> types::Result>>> { + Ok(self.encode_internal(true)?.1) } } diff --git a/src/ir/module/test.rs b/src/ir/module/test.rs index 35645820..bcdee0f0 100644 --- a/src/ir/module/test.rs +++ b/src/ir/module/test.rs @@ -242,7 +242,9 @@ fn test_convert_import_fn_to_local() { let mut builder = FunctionBuilder::new(&[DataType::I32], &[DataType::I32]); builder.i32_const(1); builder.drop(); - builder.replace_import_in_module(&mut module, ImportsID(0)); + builder + .replace_import_in_module(&mut module, ImportsID(0)) + .expect("error"); // add local function using the translated function let mut builder = FunctionBuilder::new(&[], &[DataType::I32]); @@ -822,7 +824,7 @@ fn test_custom_sections_cow_behavior() { let id = sections.get_id("test".to_string()).unwrap(); // First, verify the section starts as borrowed - let section = sections.get_by_id(id); + let section = sections.get_by_id(id).expect("Should be present"); assert_eq!(section.data.as_ref(), original_data); // Now trigger copy-on-write @@ -830,7 +832,7 @@ fn test_custom_sections_cow_behavior() { data_mut.push(b'!'); // The data should now be owned - let section = sections.get_by_id(id); + let section = sections.get_by_id(id).expect("Should be present"); assert_eq!(section.data.as_ref(), b"original!"); } @@ -899,7 +901,7 @@ fn test_custom_sections_integration_with_existing_api() { assert!(!sections.is_empty()); let id1 = sections.get_id("original1".to_string()).unwrap(); - let section1 = sections.get_by_id(id1); + let section1 = sections.get_by_id(id1).expect("Should be present"); assert_eq!(section1.name, "original1"); assert_eq!(section1.data.as_ref(), b"data1"); @@ -909,7 +911,7 @@ fn test_custom_sections_integration_with_existing_api() { data_mut.extend_from_slice(b"modified_data1"); // Verify change via existing API - let section1_after = sections.get_by_id(id1); + let section1_after = sections.get_by_id(id1).expect("Should be present"); assert_eq!(section1_after.data.as_ref(), b"modified_data1"); // Test iteration diff --git a/src/ir/section.rs b/src/ir/section.rs deleted file mode 100644 index 40f01b11..00000000 --- a/src/ir/section.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Enums the represent a section of a Module or a Component - -#[derive(Debug, Clone, Eq, PartialEq)] -/// Represents a Section in a Component -pub enum ComponentSection { - Module, - Alias, - CoreType, - ComponentType, - ComponentImport, - ComponentExport, - CoreInstance, - ComponentInstance, - Canon, - CustomSection, - Component, - ComponentStartSection, -} diff --git a/src/ir/types.rs b/src/ir/types.rs index ef34f636..0fc25415 100644 --- a/src/ir/types.rs +++ b/src/ir/types.rs @@ -12,14 +12,16 @@ use wasm_encoder::reencode::Reencode; use wasm_encoder::{AbstractHeapType, Encode, Ieee32, Ieee64}; use wasmparser::types::TypeIdentifier; -use wasmparser::{ConstExpr, HeapType, Operator, RefType, ValType}; +use wasmparser::{ConstExpr, HeapType, Operator, RefType, UnpackedIndex, ValType}; use crate::error::Error; +use crate::error::Error::{InstrumentationError, UnknownId}; use crate::ir::id::{CustomSectionID, FunctionID, GlobalID, ModuleID, TypeID}; use crate::ir::module::side_effects::{InjectType, Injection}; use crate::ir::module::{add_injection, fix_op_id_mapping}; +use crate::ir::types; -type Result = std::result::Result; +pub type Result = std::result::Result; /// An optional tag that flags items that have been added to the module. /// It can also carry some bytes of information the explain why it was added. @@ -245,13 +247,13 @@ impl From for DataType { wasmparser::AbstractHeapType::Cont => DataType::Cont, wasmparser::AbstractHeapType::NoCont => DataType::NoCont, }, - HeapType::Concrete(u) => match u { - wasmparser::UnpackedIndex::Module(idx) => DataType::Module { + HeapType::Concrete(u) | HeapType::Exact(u) => match u { + UnpackedIndex::Module(idx) => DataType::Module { ty_id: *ModuleID(idx), nullable: ref_type.is_nullable(), }, - wasmparser::UnpackedIndex::RecGroup(idx) => DataType::RecGroup(idx), - wasmparser::UnpackedIndex::Id(_id) => panic!("Not supported yet!"), + UnpackedIndex::RecGroup(idx) => DataType::RecGroup(idx), + UnpackedIndex::Id(_id) => unimplemented!("Not supported yet!"), }, }, } @@ -611,7 +613,7 @@ impl From<&DataType> for ValType { ) .unwrap(), ), - DataType::CoreTypeId(_idx) => panic!("Not Supported Yet!"), + DataType::CoreTypeId(_idx) => unimplemented!("Not Supported Yet!"), DataType::Cont => ValType::Ref( RefType::new( false, @@ -854,7 +856,7 @@ impl TagUtils for FuncInstrFlag<'_> { fn get_or_create_tag(&mut self) -> &mut Tag { match self.current_mode { None => { - panic!("Current mode is not set...cannot append to the tag!") + panic!("Internal error: Current mode is not set...cannot append to the tag!") } Some(FuncInstrMode::Entry) => self.entry.get_or_create_tag(), Some(FuncInstrMode::Exit) => self.exit.get_or_create_tag(), @@ -864,7 +866,7 @@ impl TagUtils for FuncInstrFlag<'_> { fn get_tag(&self) -> &Option { match self.current_mode { None => { - panic!("Current mode is not set...cannot append to the tag!") + panic!("Internal error: Current mode is not set...cannot append to the tag!") } Some(FuncInstrMode::Entry) => self.entry.get_tag(), Some(FuncInstrMode::Exit) => self.exit.get_tag(), @@ -936,7 +938,7 @@ impl<'a> FuncInstrFlag<'a> { self.has_special_instr = true; match self.current_mode { None => { - panic!("Current mode is not set...cannot inject instructions!") + panic!("Internal error: Current mode is not set...cannot inject instructions!") } Some(FuncInstrMode::Entry) => self.entry.instrs.push(val), Some(FuncInstrMode::Exit) => self.exit.instrs.push(val), @@ -947,17 +949,17 @@ impl<'a> FuncInstrFlag<'a> { pub fn get_instr(&self, idx: usize) -> &Operator<'_> { match self.current_mode { None => { - panic!("Current mode is not set...cannot grab instruction without context!") + panic!("Internal error: Current mode is not set...cannot grab instruction without context!") } - Some(FuncInstrMode::Entry) => self.entry.instrs.get(idx).unwrap(), - Some(FuncInstrMode::Exit) => self.exit.instrs.get(idx).unwrap(), + Some(FuncInstrMode::Entry) => &self.entry.instrs[idx], + Some(FuncInstrMode::Exit) => &self.exit.instrs[idx], } } pub fn instr_len(&self) -> usize { match self.current_mode { None => { - panic!("Current mode is not set...cannot grab instruction without context!") + panic!("Internal error: Current mode is not set...cannot grab instruction without context!") } Some(FuncInstrMode::Entry) => self.entry.instrs.len(), Some(FuncInstrMode::Exit) => self.exit.instrs.len(), @@ -971,32 +973,35 @@ impl<'a> FuncInstrFlag<'a> { global_mapping: &HashMap, memory_mapping: &HashMap, side_effects: &mut HashMap>>, - ) { + ) -> types::Result<()> { let Self { entry, exit, .. } = self; - let mut add_inj = |mode: FuncInstrMode, instrs: &mut InjectedInstrs<'a>| { - // Fix the ID mapping in each of the injected opcodes. - for op in instrs.instrs.iter_mut() { - fix_op_id_mapping(op, func_mapping, global_mapping, memory_mapping); - } + let mut add_inj = + |mode: FuncInstrMode, instrs: &mut InjectedInstrs<'a>| -> types::Result<()> { + // Fix the ID mapping in each of the injected opcodes. + for op in instrs.instrs.iter_mut() { + fix_op_id_mapping(op, func_mapping, global_mapping, memory_mapping)?; + } - if instrs.instrs.is_empty() { - return; - } + if instrs.instrs.is_empty() { + return Ok(()); + } - add_injection( - side_effects, - InjectType::Probe, - Injection::FuncProbe { - target_fid: fid, - mode, - body: instrs.instrs.clone(), - tag: instrs.tag.clone().unwrap_or_default(), - }, - ); - }; + add_injection( + side_effects, + InjectType::Probe, + Injection::FuncProbe { + target_fid: fid, + mode, + body: instrs.instrs.clone(), + tag: instrs.tag.clone().unwrap_or_default(), + }, + ); + Ok(()) + }; - add_inj(FuncInstrMode::Entry, entry); - add_inj(FuncInstrMode::Exit, exit); + add_inj(FuncInstrMode::Entry, entry)?; + add_inj(FuncInstrMode::Exit, exit)?; + Ok(()) } /// Can be called after finishing some instrumentation to reset the mode. @@ -1043,7 +1048,7 @@ impl TagUtils for InstrumentationFlag<'_> { fn get_or_create_tag(&mut self) -> &mut Tag { match self.current_mode { None => { - panic!("Current mode is not set...cannot get the tag!") + panic!("Internal error: Current mode is not set...cannot get the tag!") } Some(InstrumentationMode::Before) => self.before.get_or_create_tag(), Some(InstrumentationMode::After) => self.after.get_or_create_tag(), @@ -1062,7 +1067,7 @@ impl TagUtils for InstrumentationFlag<'_> { fn get_tag(&self) -> &Option { match self.current_mode { None => { - panic!("Current mode is not set...cannot get the tag!") + panic!("Internal error: Current mode is not set...cannot get the tag!") } Some(InstrumentationMode::Before) => self.before.get_tag(), Some(InstrumentationMode::After) => self.after.get_tag(), @@ -1241,7 +1246,7 @@ impl<'a> InstrumentationFlag<'a> { pub fn add_instr(&mut self, op: &Operator, val: Operator<'a>) -> bool { match self.current_mode { None => { - panic!("Current mode is not set...cannot inject instructions!") + panic!("Internal error: Current mode is not set...cannot inject instructions!") } Some(InstrumentationMode::Before) => { self.before.instrs.push(val); @@ -1385,25 +1390,29 @@ impl<'a> InstrumentationFlag<'a> { } /// Get an instruction to the current InstrumentationMode's list - pub fn get_instr(&self, idx: usize) -> &Operator<'_> { + pub fn get_instr(&self, idx: usize) -> Result<&Operator<'_>> { match self.current_mode { - None => { - panic!("Current mode is not set...cannot grab instruction without context!") - } - Some(InstrumentationMode::Before) => self.before.instrs.get(idx).unwrap(), - Some(InstrumentationMode::After) => self.after.instrs.get(idx).unwrap(), + None => Err(InstrumentationError( + "Current mode is not set...cannot grab instruction without context!".to_string(), + )), + Some(InstrumentationMode::Before) => Ok(&self.before.instrs[idx]), + Some(InstrumentationMode::After) => Ok(&self.after.instrs[idx]), Some(InstrumentationMode::Alternate) => match &self.alternate { - None => panic!("No alternate instructions to pull idx '{}' from", idx), - Some(alternate) => alternate.instrs.get(idx).unwrap(), + None => Err(InstrumentationError(format!( + "No alternate instructions to pull idx '{}' from", + idx + ))), + Some(alternate) => Ok(&alternate.instrs[idx]), }, - Some(InstrumentationMode::SemanticAfter) => { - self.semantic_after.instrs.get(idx).unwrap() - } - Some(InstrumentationMode::BlockEntry) => self.block_entry.instrs.get(idx).unwrap(), - Some(InstrumentationMode::BlockExit) => self.block_exit.instrs.get(idx).unwrap(), + Some(InstrumentationMode::SemanticAfter) => Ok(&self.semantic_after.instrs[idx]), + Some(InstrumentationMode::BlockEntry) => Ok(&self.block_entry.instrs[idx]), + Some(InstrumentationMode::BlockExit) => Ok(&self.block_exit.instrs[idx]), Some(InstrumentationMode::BlockAlt) => match &self.block_alt { - None => panic!("No block alt instructions to pull idx '{}' from", idx), - Some(block_alt) => block_alt.instrs.get(idx).unwrap(), + None => Err(InstrumentationError(format!( + "No block alt instructions to pull idx '{}' from", + idx + ))), + Some(block_alt) => Ok(&block_alt.instrs[idx]), }, } } @@ -1501,20 +1510,18 @@ impl<'a> Instructions<'a> { } } - /// # Panics - /// /// Directly mutating the instructions will likely invalidate the - /// instruction flags, so this will panic if the instructions have been + /// instruction flags, so this will Error if the instructions have been /// instrumented. - /// - pub fn get_ops_mut(&mut self) -> &mut Vec> { + pub fn get_ops_mut(&mut self) -> Result<&mut Vec>> { if self.flags.is_some() { - panic!( + return Err(InstrumentationError( "Cannot get mutable instructions if flags are set. \ Mutating instructions will invalidate instrumentation flags." - ); + .to_string(), + )); } - &mut self.instructions + Ok(&mut self.instructions) } pub fn get_instr_flag(&self, idx: usize) -> Option<&InstrumentationFlag<'a>> { @@ -1677,22 +1684,23 @@ impl InitInstr { &mut self, func_mapping: &HashMap, global_mapping: &HashMap, - ) { + ) -> types::Result<()> { match self { InitInstr::Global(id) => match global_mapping.get(&(*id)) { Some(new_index) => { **id = *new_index; } - None => panic!("Deleted global!"), + None => return Err(InstrumentationError("Deleted global!".to_string())), }, InitInstr::RefFunc(id) => match func_mapping.get(&(*id)) { Some(new_index) => { **id = *new_index; } - None => panic!("Deleted function!"), + None => return Err(InstrumentationError("Deleted function!".to_string())), }, _ => {} } + Ok(()) } } @@ -1758,9 +1766,10 @@ impl InitExpr { }; instrs.push(val); } - if !reader.eof() { - panic!("There was more data after the function end!"); - } + assert!( + reader.eof(), + "Internal error: There was more data after the function end!" + ); InitExpr { exprs: instrs } } @@ -1872,7 +1881,18 @@ impl InitExpr { } else if let Some(core) = id.as_core_type_id() { wasm_encoder::HeapType::Concrete(core.index() as u32) } else { - panic!("Did not unpack concrete type!") + unimplemented!("Did not unpack concrete type!") + } + } + HeapType::Exact(id) => { + if let Some(mod_id) = id.as_module_index() { + wasm_encoder::HeapType::Exact(mod_id) + } else if let Some(rg_id) = id.as_rec_group_index() { + wasm_encoder::HeapType::Exact(rg_id) + } else if let Some(core) = id.as_core_type_id() { + wasm_encoder::HeapType::Exact(core.index() as u32) + } else { + unimplemented!("Did not unpack exact type!") } } }) @@ -1991,7 +2011,8 @@ impl From for wasmparser::BlockType { /// Intermediate Representation of Custom Sections #[derive(Clone, Debug, Default)] pub struct CustomSections<'a> { - custom_sections: Vec>, + // Custom sections are special, they don't have to be append only! + pub(crate) custom_sections: Vec>, } impl<'a> CustomSections<'a> { @@ -2015,11 +2036,12 @@ impl<'a> CustomSections<'a> { } /// Get a custom section by its ID - pub fn get_by_id(&self, custom_section_id: CustomSectionID) -> &CustomSection<'_> { - if *custom_section_id < self.custom_sections.len() as u32 { - return &self.custom_sections[*custom_section_id as usize]; + pub fn get_by_id(&self, custom_section_id: CustomSectionID) -> Result<&CustomSection<'_>> { + if *custom_section_id >= self.custom_sections.len() as u32 { + Err(UnknownId("Invalid custom section ID!".into())) + } else { + Ok(&self.custom_sections[*custom_section_id as usize]) } - panic!("Invalid custom section ID"); } /// Number of custom sections @@ -2058,7 +2080,7 @@ impl<'a> CustomSections<'a> { #[derive(Clone, Debug)] pub struct CustomSection<'a> { pub name: &'a str, - pub data: std::borrow::Cow<'a, [u8]>, + pub data: Cow<'a, [u8]>, } impl<'a> CustomSection<'a> { diff --git a/src/ir/wrappers.rs b/src/ir/wrappers.rs index ceef36b9..41a57657 100644 --- a/src/ir/wrappers.rs +++ b/src/ir/wrappers.rs @@ -1,339 +1,9 @@ //! Wrapper functions +use crate::error::Error::InstrumentationError; +use crate::ir::types; use std::collections::HashMap; - -use wasm_encoder::reencode::{Reencode, ReencodeComponent, RoundtripReencoder}; -use wasm_encoder::{ - Alias, ComponentCoreTypeEncoder, ComponentFuncTypeEncoder, ComponentTypeEncoder, - CoreTypeEncoder, InstanceType, -}; -use wasmparser::{ - ComponentAlias, ComponentType, ComponentTypeDeclaration, ComponentValType, CoreType, - InstanceTypeDeclaration, Operator, SubType, -}; - -// Not added to wasm-tools -/// Convert ModuleTypeDeclaration to ModuleType -pub fn convert_module_type_declaration( - module: &[wasmparser::ModuleTypeDeclaration], - enc: ComponentCoreTypeEncoder, - reencode: &mut RoundtripReencoder, -) { - let mut mty = wasm_encoder::ModuleType::new(); - for m in module.iter() { - match m { - wasmparser::ModuleTypeDeclaration::Type(recgroup) => { - let types = recgroup - .types() - .map(|ty| { - reencode.sub_type(ty.to_owned()).unwrap_or_else(|_| { - panic!("Could not encode type as subtype: {:?}", ty) - }) - }) - .collect::>(); - - if recgroup.is_explicit_rec_group() { - mty.ty().rec(types); - } else { - // it's implicit! - for subty in types { - mty.ty().subtype(&subty); - } - } - } - wasmparser::ModuleTypeDeclaration::Export { name, ty } => { - mty.export(name, reencode.entity_type(*ty).unwrap()); - } - wasmparser::ModuleTypeDeclaration::OuterAlias { - kind: _kind, - count, - index, - } => { - mty.alias_outer_core_type(*count, *index); - } - wasmparser::ModuleTypeDeclaration::Import(import) => { - mty.import( - import.module, - import.name, - reencode.entity_type(import.ty).unwrap(), - ); - } - } - } - enc.module(&mty); -} - -// Not added to wasm-tools -/// Convert Instance Types -pub fn convert_instance_type( - instance: &[InstanceTypeDeclaration], - reencode: &mut RoundtripReencoder, -) -> InstanceType { - let mut ity = InstanceType::new(); - for value in instance.iter() { - match value { - InstanceTypeDeclaration::CoreType(core_type) => match core_type { - CoreType::Rec(recgroup) => { - for sub in recgroup.types() { - let enc = ity.core_type().core(); - encode_core_type_subtype(enc, sub, reencode); - } - } - CoreType::Module(module) => { - let enc = ity.core_type(); - convert_module_type_declaration(module, enc, reencode); - } - }, - InstanceTypeDeclaration::Type(ty) => { - let enc = ity.ty(); - convert_component_type(ty, enc, reencode); - } - InstanceTypeDeclaration::Alias(alias) => match alias { - ComponentAlias::InstanceExport { - kind, - instance_index, - name, - } => { - ity.alias(Alias::InstanceExport { - instance: *instance_index, - kind: reencode.component_export_kind(*kind), - name, - }); - } - ComponentAlias::CoreInstanceExport { - kind, - instance_index, - name, - } => { - ity.alias(Alias::CoreInstanceExport { - instance: *instance_index, - kind: do_reencode( - *kind, - RoundtripReencoder::export_kind, - reencode, - "export kind", - ), - name, - }); - } - ComponentAlias::Outer { kind, count, index } => { - ity.alias(Alias::Outer { - kind: reencode.component_outer_alias_kind(*kind), - count: *count, - index: *index, - }); - } - }, - InstanceTypeDeclaration::Export { name, ty } => { - ity.export( - name.0, - do_reencode( - *ty, - RoundtripReencoder::component_type_ref, - reencode, - "component type", - ), - ); - } - } - } - ity -} - -// Not added to wasm-tools -/// Convert Func Results -pub fn convert_results( - result: Option, - mut enc: ComponentFuncTypeEncoder, - reencode: &mut RoundtripReencoder, -) { - enc.result(result.map(|v| reencode.component_val_type(v))); -} - -// Not added to wasm-tools -/// CoreTypeEncoding -pub fn encode_core_type_subtype( - enc: CoreTypeEncoder, - subtype: &SubType, - reencode: &mut RoundtripReencoder, -) { - let subty = reencode - .sub_type(subtype.to_owned()) - .unwrap_or_else(|_| panic!("Could not encode type as subtype: {:?}", subtype)); - enc.subtype(&subty); -} - -/// Process Alias -pub fn process_alias<'a>( - a: &'a ComponentAlias<'a>, - reencode: &mut RoundtripReencoder, -) -> Alias<'a> { - match a { - ComponentAlias::InstanceExport { - kind, - instance_index, - name, - } => Alias::InstanceExport { - instance: *instance_index, - kind: reencode.component_export_kind(*kind), - name, - }, - ComponentAlias::CoreInstanceExport { - kind, - instance_index, - name, - } => Alias::CoreInstanceExport { - instance: *instance_index, - kind: do_reencode( - *kind, - RoundtripReencoder::export_kind, - reencode, - "export kind", - ), - name, - }, - ComponentAlias::Outer { kind, count, index } => Alias::Outer { - kind: reencode.component_outer_alias_kind(*kind), - count: *count, - index: *index, - }, - } -} - -/// Convert Component Type -pub fn convert_component_type( - ty: &ComponentType, - enc: ComponentTypeEncoder, - reencode: &mut RoundtripReencoder, -) { - match ty { - ComponentType::Defined(comp_ty) => { - let def_enc = enc.defined_type(); - match comp_ty { - wasmparser::ComponentDefinedType::Primitive(p) => { - def_enc.primitive(wasm_encoder::PrimitiveValType::from(*p)) - } - wasmparser::ComponentDefinedType::Record(record) => { - def_enc.record( - record - .iter() - .map(|record| (record.0, reencode.component_val_type(record.1))), - ); - } - wasmparser::ComponentDefinedType::Variant(variant) => { - def_enc.variant(variant.iter().map(|variant| { - ( - variant.name, - variant.ty.map(|ty| reencode.component_val_type(ty)), - variant.refines, - ) - })) - } - wasmparser::ComponentDefinedType::List(l) => { - def_enc.list(reencode.component_val_type(*l)) - } - wasmparser::ComponentDefinedType::Tuple(tup) => def_enc.tuple( - tup.iter() - .map(|val_type| reencode.component_val_type(*val_type)), - ), - wasmparser::ComponentDefinedType::Flags(flags) => { - def_enc.flags((*flags).clone().into_vec()) - } - wasmparser::ComponentDefinedType::Enum(en) => { - def_enc.enum_type((*en).clone().into_vec()) - } - wasmparser::ComponentDefinedType::Option(opt) => { - def_enc.option(reencode.component_val_type(*opt)) - } - wasmparser::ComponentDefinedType::Result { ok, err } => def_enc.result( - ok.map(|val_type| reencode.component_val_type(val_type)), - err.map(|val_type| reencode.component_val_type(val_type)), - ), - wasmparser::ComponentDefinedType::Own(u) => def_enc.own(*u), - wasmparser::ComponentDefinedType::Borrow(u) => def_enc.borrow(*u), - wasmparser::ComponentDefinedType::Future(opt) => match opt { - Some(u) => def_enc.future(Some(reencode.component_val_type(*u))), - None => def_enc.future(None), - }, - wasmparser::ComponentDefinedType::Stream(opt) => match opt { - Some(u) => def_enc.stream(Some(reencode.component_val_type(*u))), - None => def_enc.future(None), - }, - wasmparser::ComponentDefinedType::FixedSizeList(ty, len) => { - def_enc.fixed_size_list(reencode.component_val_type(*ty), *len) - } - } - } - ComponentType::Func(func_ty) => { - let mut new_enc = enc.function(); - new_enc.params( - func_ty - .clone() - .params - .into_vec() - .into_iter() - .map(|p| (p.0, reencode.component_val_type(p.1))), - ); - convert_results(func_ty.clone().result, new_enc, reencode); - } - ComponentType::Component(comp) => { - let mut new_comp = wasm_encoder::ComponentType::new(); - for c in comp.iter() { - match c { - ComponentTypeDeclaration::CoreType(core) => match core { - CoreType::Rec(recgroup) => { - for sub in recgroup.types() { - let enc = new_comp.core_type().core(); - encode_core_type_subtype(enc, sub, reencode); - } - } - CoreType::Module(module) => { - let enc = new_comp.core_type(); - convert_module_type_declaration(module, enc, reencode); - } - }, - ComponentTypeDeclaration::Type(typ) => { - let enc = new_comp.ty(); - convert_component_type(typ, enc, reencode); - } - ComponentTypeDeclaration::Alias(a) => { - new_comp.alias(process_alias(a, reencode)); - } - ComponentTypeDeclaration::Export { name, ty } => { - new_comp.export( - name.0, - do_reencode( - *ty, - RoundtripReencoder::component_type_ref, - reencode, - "component type", - ), - ); - } - ComponentTypeDeclaration::Import(imp) => { - new_comp.import( - imp.name.0, - do_reencode( - imp.ty, - RoundtripReencoder::component_type_ref, - reencode, - "component type", - ), - ); - } - } - } - enc.component(&new_comp); - } - ComponentType::Instance(inst) => { - let ity = convert_instance_type(inst, reencode); - enc.instance(&ity); - } - ComponentType::Resource { rep, dtor } => { - enc.resource(reencode.val_type(*rep).unwrap(), *dtor); - } - } -} +use wasmparser::Operator; pub fn indirect_namemap_parser2encoder( namemap: wasmparser::IndirectNameMap, @@ -355,13 +25,6 @@ pub fn namemap_parser2encoder(namemap: wasmparser::NameMap) -> wasm_encoder::Nam names } -pub fn add_to_namemap(namemap: &mut wasm_encoder::NameMap, names: wasmparser::NameMap) { - for name in names { - let naming = name.unwrap(); - namemap.append(naming.index, naming.name); - } -} - pub(crate) fn refers_to_func(op: &Operator) -> bool { matches!( op, @@ -463,7 +126,7 @@ pub(crate) fn refers_to_memory(op: &Operator) -> bool { ) } -pub(crate) fn update_fn_instr(op: &mut Operator, mapping: &HashMap) { +pub(crate) fn update_fn_instr(op: &mut Operator, mapping: &HashMap) -> types::Result<()> { match op { Operator::Call { function_index } | Operator::RefFunc { function_index } @@ -471,13 +134,21 @@ pub(crate) fn update_fn_instr(op: &mut Operator, mapping: &HashMap) { Some(new_index) => { *function_index = *new_index; } - None => panic!("Deleted function!"), + None => { + return Err(InstrumentationError( + "Called a deleted function!".to_string(), + )) + } }, - _ => panic!("Operation doesn't need to be checked for function IDs!"), + _ => panic!("Internal error: Operation doesn't need to be checked for function IDs!"), } + Ok(()) } -pub(crate) fn update_global_instr(op: &mut Operator, mapping: &HashMap) { +pub(crate) fn update_global_instr( + op: &mut Operator, + mapping: &HashMap, +) -> types::Result<()> { match op { Operator::GlobalGet { global_index } | Operator::GlobalSet { global_index } @@ -494,14 +165,22 @@ pub(crate) fn update_global_instr(op: &mut Operator, mapping: &HashMap Some(new_index) => { *global_index = *new_index; } - None => panic!("Deleted global!"), + None => { + return Err(InstrumentationError( + "Operation on a deleted global!".to_string(), + )) + } } } - _ => panic!("Operation doesn't need to be checked for global IDs!"), + _ => panic!("Internal error: Operation doesn't need to be checked for global IDs!"), } + Ok(()) } -pub(crate) fn update_memory_instr(op: &mut Operator, mapping: &HashMap) { +pub(crate) fn update_memory_instr( + op: &mut Operator, + mapping: &HashMap, +) -> types::Result<()> { match op { // loads Operator::I32Load { memarg } | @@ -573,7 +252,7 @@ pub(crate) fn update_memory_instr(op: &mut Operator, mapping: &HashMap Some(new_index) => { memarg.memory = *new_index; } - None => panic!("Attempting to reference a deleted memory, ID: {}", memarg.memory), + None => return Err(InstrumentationError(format!("Attempting to reference a deleted memory, ID: {}", memarg.memory))), } } Operator::MemoryGrow {mem} | @@ -585,7 +264,7 @@ pub(crate) fn update_memory_instr(op: &mut Operator, mapping: &HashMap Some(new_index) => { *mem = *new_index; } - None => panic!("Attempting to reference a deleted memory, ID: {}", mem), + None => return Err(InstrumentationError(format!("Attempting to reference a deleted memory, ID: {}", mem))), } } Operator::MemoryCopy {src_mem, dst_mem} => { @@ -593,27 +272,16 @@ pub(crate) fn update_memory_instr(op: &mut Operator, mapping: &HashMap Some(new_index) => { *src_mem = *new_index; } - None => panic!("Attempting to reference a deleted memory, ID: {}", src_mem), + None => return Err(InstrumentationError(format!("Attempting to reference a deleted memory, ID: {}", src_mem))), } match mapping.get(dst_mem) { Some(new_index) => { *dst_mem = *new_index; } - None => panic!("Attempting to reference a deleted memory, ID: {}", dst_mem), + None => return Err(InstrumentationError(format!("Attempting to reference a deleted memory, ID: {}", dst_mem))), } } - _ => panic!("Operation doesn't need to be checked for memory IDs!"), - } -} - -pub(crate) fn do_reencode( - i: I, - reencode: fn(&mut RoundtripReencoder, I) -> Result, - inst: &mut RoundtripReencoder, - msg: &str, -) -> O { - match reencode(inst, i) { - Ok(o) => o, - Err(e) => panic!("Couldn't encode {} due to error: {}", msg, e), + _ => panic!("Internal error: Operation doesn't need to be checked for memory IDs!"), } + Ok(()) } diff --git a/src/iterator/component_iterator.rs b/src/iterator/component_iterator.rs index e1bc5be2..1ae3eed0 100644 --- a/src/iterator/component_iterator.rs +++ b/src/iterator/component_iterator.rs @@ -1,6 +1,5 @@ //! Iterator to traverse a Component -use crate::ir::component::Component; use crate::ir::id::{FunctionID, GlobalID, LocalID, ModuleID}; use crate::ir::module::module_functions::FuncKind; use crate::ir::module::module_globals::Global; @@ -9,6 +8,7 @@ use crate::iterator::iterator_trait::{IteratingInstrumenter, Iterator}; use crate::module_builder::AddLocal; use crate::opcode::{Inject, InjectAt, Instrumenter, MacroOpcode, Opcode}; use crate::subiterator::component_subiterator::ComponentSubIterator; +use crate::Component; use std::collections::HashMap; use std::iter::Iterator as StdIter; use wasmparser::Operator; @@ -43,7 +43,7 @@ impl<'a, 'b> ComponentIterator<'a, 'b> { metadata.insert(ModuleID(mod_idx as u32), m.get_func_metadata()); } print_metadata(&metadata); - let num_modules = comp.num_modules; + let num_modules = comp.modules.len(); ComponentIterator { comp, comp_iterator: ComponentSubIterator::new( @@ -57,45 +57,48 @@ impl<'a, 'b> ComponentIterator<'a, 'b> { /// Returns the current module the component iterator is in pub fn curr_module(&self) -> ModuleID { - if let ( - Location::Component { - mod_idx, - func_idx: _func_idx, - instr_idx: _instr_idx, - .. - }, - .., - ) = self.curr_loc() - { - mod_idx - } else { - panic!("Should have gotten component location"); + match self.curr_loc() { + ( + Location::Component { + mod_idx, + func_idx: _func_idx, + instr_idx: _instr_idx, + .. + }, + .., + ) => mod_idx, + other => { + panic!("Internal error: Should have gotten component location, got: {other:?}") + } } } pub fn curr_op_owned(&self) -> Option> { if self.comp_iterator.end() { - None - } else if let ( - Location::Component { - mod_idx, - func_idx, - instr_idx, - .. - }, - .., - ) = self.comp_iterator.curr_loc() - { - match &self.comp.modules[*mod_idx as usize] - .functions - .get(func_idx) - .kind - { - FuncKind::Import(_) => None, - FuncKind::Local(l) => Some(l.body.instructions.get_ops()[instr_idx].clone()), + return None; + } + + match self.comp_iterator.curr_loc() { + ( + Location::Component { + mod_idx, + func_idx, + instr_idx, + }, + .., + ) => { + match &self.comp.modules[*mod_idx as usize] + .functions + .get(func_idx) + .kind + { + FuncKind::Import(_) => None, + FuncKind::Local(l) => Some(l.body.instructions.get_ops()[instr_idx].clone()), + } + } + other => { + panic!("Internal error: Should have gotten component location, got: {other:?}") } - } else { - panic!("Should have gotten Component Location!") } } } @@ -145,47 +148,36 @@ impl<'b> Inject<'b> for ComponentIterator<'_, 'b> { /// } /// ``` fn inject(&mut self, instr: Operator<'b>) { - if let ( - Location::Component { - mod_idx, - func_idx, - instr_idx, - .. - }, - .., - ) = self.curr_loc() - { - match self.comp.modules[*mod_idx as usize] - .functions - .get_mut(func_idx) - .kind - { - FuncKind::Import(_) => panic!("Can't inject into an imported function!"), - FuncKind::Local(ref mut l) => l.add_instr(instr, instr_idx), - } - } else { - panic!("Should have gotten component location!") + let (mod_idx, func_idx, instr_idx) = self.comp_iterator.curr_loc_indices(); + + match self.comp.modules[mod_idx].functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), + FuncKind::Local(ref mut l) => l.add_instr(instr, instr_idx), } } } impl<'b> InjectAt<'b> for ComponentIterator<'_, 'b> { fn inject_at(&mut self, idx: usize, mode: InstrumentationMode, instr: Operator<'b>) { - if let ( - Location::Component { - mod_idx, func_idx, .. - }, - .., - ) = self.curr_loc() - { - let loc = Location::Component { - mod_idx, - func_idx, - instr_idx: idx, - }; - self.set_instrument_mode_at(mode, loc); - self.add_instr_at(loc, instr); - } else { - panic!("Should have gotten Component Location!") + match self.curr_loc() { + ( + Location::Component { + mod_idx, func_idx, .. + }, + .., + ) => { + let loc = Location::Component { + mod_idx, + func_idx, + instr_idx: idx, + }; + self.set_instrument_mode_at(mode, loc); + self.add_instr_at(loc, instr); + } + other => { + panic!("Internal error: Should have gotten component location, got: {other:?}") + } } } } @@ -194,29 +186,16 @@ impl<'b> MacroOpcode<'b> for ComponentIterator<'_, 'b> {} impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { ///Can be called after finishing some instrumentation to reset the mode. fn finish_instr(&mut self) { - if let ( - Location::Component { - mod_idx, - func_idx, - instr_idx, - .. - }, - .., - ) = self.comp_iterator.curr_loc() - { - match &mut self.comp.modules[*mod_idx as usize] - .functions - .get_mut(func_idx) - .kind - { - FuncKind::Import(_) => panic!("Can't inject into an imported function!"), - FuncKind::Local(l) => { - l.instr_flag.finish_instr(); - l.body.instructions.finish_instr(instr_idx); - } + let (mod_idx, func_idx, instr_idx) = self.comp_iterator.curr_loc_indices(); + + match self.comp.modules[mod_idx].functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), + FuncKind::Local(ref mut l) => { + l.instr_flag.finish_instr(); + l.body.instructions.finish_instr(instr_idx); } - } else { - panic!("Should have gotten Component Location and not Module Location!") } } @@ -237,13 +216,13 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get(func_idx) .kind { - FuncKind::Import(_) => { - panic!("Can't get instrumentation from an imported function!") - } + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => l.body.instructions.current_mode(instr_idx), } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location!") } } @@ -260,13 +239,15 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get_mut(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't instrument into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.body.instructions.set_current_mode(instr_idx, mode); } } } else { - panic!("Should have gotten component location!") + panic!("Internal error: Should have gotten component location!") } } @@ -283,34 +264,24 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get(func_idx) .kind { - FuncKind::Import(_) => { - panic!("Can't get instrumentation from an imported function!") - } + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => &l.instr_flag.current_mode, } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } } fn set_func_instrument_mode(&mut self, mode: FuncInstrMode) { - if let ( - Location::Component { - mod_idx, func_idx, .. - }, - .., - ) = self.curr_loc() - { - match self.comp.modules[*mod_idx as usize] - .functions - .get_mut(func_idx) - .kind - { - FuncKind::Import(_) => panic!("Can't instrument into an imported function!"), - FuncKind::Local(ref mut l) => l.instr_flag.current_mode = Some(mode), - } - } else { - panic!("Should have gotten component location!") + let (mod_idx, func_idx, _) = self.comp_iterator.curr_loc_indices(); + + match self.comp.modules[mod_idx].functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), + FuncKind::Local(ref mut l) => l.instr_flag.current_mode = Some(mode), } } @@ -330,13 +301,13 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get(func_idx) .kind { - FuncKind::Import(_) => { - panic!("Can't get instrumentation from an imported function!") - } + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => l.instr_len_at(instr_idx), } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } } @@ -353,11 +324,13 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get_mut(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't instrument into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => l.clear_instr_at(instr_idx, mode), } } else { - panic!("Should have gotten component location!") + panic!("Internal error: Should have gotten component location!") } } @@ -374,13 +347,15 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get_mut(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't instrument into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.add_instr(instr, instr_idx); } } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } } @@ -397,7 +372,9 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get_mut(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't instrument into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.body .instructions @@ -405,9 +382,8 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { } } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } - self } @@ -424,7 +400,9 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get_mut(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't instrument into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.body .instructions @@ -433,9 +411,8 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { } } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } - self } @@ -452,11 +429,13 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { .get_mut(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't inject into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => l.append_instr_tag_at(data, instr_idx), } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } self } @@ -465,6 +444,7 @@ impl<'b> Instrumenter<'b> for ComponentIterator<'_, 'b> { impl<'b> IteratingInstrumenter<'b> for ComponentIterator<'_, 'b> { fn add_global(&mut self, global: Global) -> GlobalID { let curr_mod = *self.curr_module() as usize; + self.comp.modules[curr_mod].globals.add(global) } } @@ -493,8 +473,10 @@ impl Iterator for ComponentIterator<'_, '_> { /// Returns the instruction at the current location fn curr_op(&self) -> Option<&Operator<'_>> { if self.comp_iterator.end() { - None - } else if let ( + return None; + } + + if let ( Location::Component { mod_idx, func_idx, @@ -509,29 +491,24 @@ impl Iterator for ComponentIterator<'_, '_> { .get(func_idx) .kind { - FuncKind::Import(_) => panic!("Can't inject into an imported function!"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => Some(&l.body.instructions.get_ops()[instr_idx]), } } else { - panic!("Should have gotten Component Location and not Module Location!") + panic!("Internal error: Should have gotten Component Location and not Module Location!") } } } impl AddLocal for ComponentIterator<'_, '_> { fn add_local(&mut self, val_type: DataType) -> LocalID { - let curr_loc = self.curr_loc().0; - if let Location::Component { - mod_idx, func_idx, .. - } = curr_loc - { - { - self.comp.modules[*mod_idx as usize] - .functions - .add_local(func_idx, val_type) - } - } else { - panic!("Should have gotten Component Location and not Module Location!") - } + let (mod_idx, func_idx, _) = self.comp_iterator.curr_loc_indices(); + + self.comp.modules[mod_idx] + .functions + .add_local(func_idx, val_type) + .expect("Internal error: Should have found the local function successfully!") } } diff --git a/src/iterator/iterator_trait.rs b/src/iterator/iterator_trait.rs index 96cff806..bb118a43 100644 --- a/src/iterator/iterator_trait.rs +++ b/src/iterator/iterator_trait.rs @@ -34,7 +34,7 @@ pub trait IteratingInstrumenter<'a>: Instrumenter<'a> + Iterator { /// Sets the type of Instrumentation Type of the current location fn set_instrument_mode(&mut self, mode: InstrumentationMode) { - self.set_instrument_mode_at(mode, self.curr_loc().0); + self.set_instrument_mode_at(mode, self.curr_loc().0) } /// Appends the passed data to the tag of the current location. diff --git a/src/iterator/module_iterator.rs b/src/iterator/module_iterator.rs index 1e77afb5..3c3922db 100644 --- a/src/iterator/module_iterator.rs +++ b/src/iterator/module_iterator.rs @@ -42,11 +42,13 @@ impl<'a, 'b> ModuleIterator<'a, 'b> { ) = self.mod_iterator.curr_loc() { match &self.module.functions.get(func_idx).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => Some(l.body.instructions.get_ops()[instr_idx].clone()), } } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } } @@ -103,12 +105,14 @@ impl<'b> Inject<'b> for ModuleIterator<'_, 'b> { .., ) = self.curr_loc() { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => l.add_instr(instr, instr_idx), } } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } } @@ -122,7 +126,7 @@ impl<'a> InjectAt<'a> for ModuleIterator<'_, 'a> { self.set_instrument_mode_at(mode, loc); self.add_instr_at(loc, instr); } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } } @@ -140,15 +144,17 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .., ) = self.mod_iterator.curr_loc() { - match &mut self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + match &mut self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => { l.instr_flag.finish_instr(); l.body.instructions.finish_instr(instr_idx); } } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } /// Returns the Instrumentation at the current Location @@ -162,12 +168,14 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .., ) = self.mod_iterator.curr_loc() { - match &self.module.functions.get(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + match &self.module.functions.get(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => l.body.instructions.current_mode(instr_idx), } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } @@ -178,36 +186,42 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .. } = loc { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot add an instruction to an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.body.instructions.set_current_mode(instr_idx, mode); } } } else { - panic!("Should have gotten module location!") + panic!("Internal error: Should have gotten Module Location!") } } fn curr_func_instrument_mode(&self) -> &Option { if let (Location::Module { func_idx, .. }, ..) = self.mod_iterator.curr_loc() { - match &self.module.functions.get(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + match &self.module.functions.get(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => &l.instr_flag.current_mode, } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } fn set_func_instrument_mode(&mut self, mode: FuncInstrMode) { if let (Location::Module { func_idx, .. }, ..) = self.mod_iterator.curr_loc() { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => l.instr_flag.current_mode = Some(mode), } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } @@ -221,12 +235,14 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .., ) = self.mod_iterator.curr_loc() { - match &self.module.functions.get(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + match &self.module.functions.get(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => l.instr_len_at(instr_idx), } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } @@ -237,15 +253,16 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .. } = loc { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot add an instruction to an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.clear_instr_at(instr_idx, mode); } } - // Only injects if it is an instrumented location } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } @@ -256,15 +273,16 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .. } = loc { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot add an instruction to an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.add_instr(instr, instr_idx); } } - // Only injects if it is an instrumented location } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } @@ -276,7 +294,9 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { } = loc { match self.module.functions.get_mut(func_idx).kind { - FuncKind::Import(_) => panic!("Cannot instrument an imported function"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.body .instructions @@ -284,7 +304,7 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { } } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } self } @@ -296,8 +316,10 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .. } = loc { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot instrument an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.body .instructions @@ -306,7 +328,7 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { } } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } self } @@ -318,14 +340,16 @@ impl<'a> Instrumenter<'a> for ModuleIterator<'_, 'a> { .. } = loc { - match self.module.functions.get_mut(func_idx as FunctionID).kind { - FuncKind::Import(_) => panic!("Cannot instrument an imported function"), + match self.module.functions.get_mut(func_idx).kind { + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(ref mut l) => { l.append_instr_tag_at(data, instr_idx); } } } else { - panic!("Should have gotten Module Location and not Module Location!") + panic!("Internal error: Should have gotten Module Location!") } self } @@ -341,9 +365,12 @@ impl AddLocal for ModuleIterator<'_, '_> { fn add_local(&mut self, val_type: DataType) -> LocalID { let curr_loc = self.curr_loc(); if let (Location::Module { func_idx, .. }, ..) = curr_loc { - self.module.functions.add_local(func_idx, val_type) + self.module + .functions + .add_local(func_idx, val_type) + .expect("Internal error: Should have found the local function successfully!") } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } } @@ -370,7 +397,7 @@ impl<'a> Iterator for ModuleIterator<'_, 'a> { } /// Returns the current instruction - fn curr_op(&self) -> Option<&Operator<'a>> { + fn curr_op(&self) -> Option<&Operator<'_>> { if let ( Location::Module { func_idx, @@ -381,11 +408,13 @@ impl<'a> Iterator for ModuleIterator<'_, 'a> { ) = self.mod_iterator.curr_loc() { match &self.module.functions.get(func_idx).kind { - FuncKind::Import(_) => panic!("Cannot get an instruction to an imported function"), + FuncKind::Import(_) => panic!( + "Internal error: Shouldn't have gotten the location of an imported function!" + ), FuncKind::Local(l) => Some(&l.body.instructions.get_ops()[instr_idx]), } } else { - panic!("Should have gotten Module Location!") + panic!("Internal error: Should have gotten Module Location!") } } } diff --git a/src/lib.rs b/src/lib.rs index 85012ff1..47e0ce08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ //! [Dfinity's IC]: https://github.com/dfinity/ic/tree/master/rs/wasm_transform //! [Walrus]: https://github.com/rustwasm/walrus/tree/main -mod error; +mod encode; +pub mod error; pub mod ir; pub mod iterator; pub mod module_builder; diff --git a/src/subiterator/component_subiterator.rs b/src/subiterator/component_subiterator.rs index 74f3503d..c7c5c1ad 100644 --- a/src/subiterator/component_subiterator.rs +++ b/src/subiterator/component_subiterator.rs @@ -110,7 +110,22 @@ impl ComponentSubIterator { is_end, ) } else { - panic!("Should have gotten Module Location from Module Iterator and not Component Location!"); + panic!("Internal error: Should have gotten Module Location!") + } + } + + pub(crate) fn curr_loc_indices(&self) -> (usize, FunctionID, usize) { + match self.curr_loc() { + ( + Location::Component { + mod_idx, + func_idx, + instr_idx, + .. + }, + .., + ) => (*mod_idx as usize, func_idx, instr_idx), + _ => panic!("Internal error: Should have gotten component location!"), } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 8000d440..6f0f83cd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,11 +1,12 @@ #![allow(dead_code)] +use log::{error, trace}; +use serde_json::Value; use std::fs; use std::fs::File; use std::io::Write; use std::io::{BufRead, BufReader}; use std::path::Path; - -use log::{error, trace}; +use std::process::Command; pub const WASM_OUTPUT_DIR: &str = "output/wasm"; pub const WAT_OUTPUT_DIR: &str = "output/wat"; @@ -95,3 +96,88 @@ fn get_wat_with_inline_instrumentation( .unwrap(), ) } + +fn wasm_tools() -> Command { + Command::new("wasm-tools") +} + +pub fn tests_from_wast(path: &Path) -> Vec { + let mut tests = vec![]; + let path = path.to_str().unwrap().replace("\\", "/"); + for entry in fs::read_dir(path).unwrap() { + let file = entry.unwrap(); + match file.path().extension() { + None => continue, + Some(ext) => { + if ext.to_str() != Some("wast") { + continue; + } + } + } + let mut cmd = wasm_tools(); + let td = tempfile::TempDir::new().unwrap(); + cmd.arg("json-from-wast") + .arg(file.path()) + .arg("--pretty") + .arg("--wasm-dir") + .arg(td.path()) + .arg("-o") + .arg(td.path().join(format!( + "{:?}.json", + Path::new(&file.path()) + .file_stem() + .unwrap() + .to_str() + .unwrap() + ))); + let output = cmd.output().unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("failed to run {cmd:?}\nstdout: {stdout}\nstderr: {stderr}"); + } + // For every file that is not invalid in the output, do round-trip + for entry in fs::read_dir(td.path()).unwrap() { + let file_json = entry.unwrap(); + match file_json.path().extension() { + None => continue, + Some(ext) => { + if ext.to_str() != Some("json") { + continue; + } + } + } + let json: Value = serde_json::from_str( + &fs::read_to_string(file_json.path()).expect("Unable to open file"), + ) + .unwrap(); + if let Value::Object(map) = json { + if let Value::Array(vals) = map.get_key_value("commands").unwrap().1 { + for value in vals { + if let Value::Object(testcase) = value { + // If assert is not in the string, that means it is a valid test case + if let Value::String(ty) = testcase.get_key_value("type").unwrap().1 { + if !ty.contains("assert") && testcase.contains_key("filename") { + if let Value::String(test_file) = + testcase.get_key_value("filename").unwrap().1 + { + tests.push( + Path::new(td.path()) + .join(test_file) + .to_str() + .unwrap() + .parse() + .unwrap(), + ); + } + } + } + } + } + } + } + } + } + + tests +} diff --git a/tests/func_builder.rs b/tests/func_builder.rs index e239b671..4f649db1 100644 --- a/tests/func_builder.rs +++ b/tests/func_builder.rs @@ -45,7 +45,7 @@ fn run_start_wirm() { }) .i32_const(1); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result.clone()).expect("couldn't translate Wasm to wat"); println!("{}", out); } @@ -63,7 +63,7 @@ fn run_start_wirm_default() { function_builder.i32_const(1); - let result = module.encode(); + let result = module.encode().expect("error!"); let out = wasmprinter::print_bytes(result.clone()).expect("couldn't translate Wasm to wat"); println!("{}", out); } @@ -93,7 +93,7 @@ fn add_import_and_local_fn_then_iterate() { }; } - let result = module.encode(); + let result = module.encode().expect("error!"); let out = wasmprinter::print_bytes(result.clone()).expect("couldn't translate Wasm to wat"); println!("{}", out); } diff --git a/tests/instrumentation/component_level.rs b/tests/instrumentation/component_level.rs new file mode 100644 index 00000000..cee4fb2b --- /dev/null +++ b/tests/instrumentation/component_level.rs @@ -0,0 +1,150 @@ +use crate::instrumentation::test_module::{try_path, validate_wasm}; +use wasmparser::{ + CanonicalFunction, ComponentAlias, ComponentExport, ComponentExternalKind, ComponentImport, + ComponentImportName, ComponentType, ComponentTypeRef, Export, ExternalKind, Instance, + InstanceTypeDeclaration, InstantiationArg, InstantiationArgKind, +}; +use wirm::ir::id::ComponentExportId; +use wirm::Component; + +pub const WHAMM_CORE_LIB_NAME: &str = "whamm_core"; +const TEST_DEBUG_DIR: &str = "output/tests/debug_me/instrumentation/"; +#[test] +fn whamm_side_effects() { + let file = "tests/test_inputs/spin/hello_world.wat"; + let output_wasm_path = format!("{TEST_DEBUG_DIR}/whamm_side_effects.wasm"); + let buff = wat::parse_file(file).expect("couldn't convert the input wat to Wasm"); + let mut component = Component::parse(&buff, false, false).expect("Unable to parse"); + + let lib_path = "tests/test_inputs/whamm/whamm_core.wasm"; + let lib_buff = wat::parse_file(lib_path).expect("couldn't convert the input wat to Wasm"); + + configure_component_libraries(0, &mut component, lib_buff.as_slice()); + + try_path(&output_wasm_path); + if let Err(e) = component.emit_wasm(&output_wasm_path) { + panic!( + "Failed to dump wasm to {output_wasm_path} due to error: {}", + e + ); + } + validate_wasm(&output_wasm_path); +} + +pub fn configure_component_libraries<'a>( + target_module_id: u32, + component: &mut Component<'a>, + core_lib: &'a [u8], +) { + // find "wasi_snapshot_preview1" instance + let mut wasi_instance = None; + let wasi_exports = ["fd_write", "environ_get", "environ_sizes_get", "proc_exit"]; + for (i, inst) in component.instances.iter().enumerate() { + if let Instance::FromExports(exports) = inst { + let mut found_count = 0; + for export in exports.iter() { + if wasi_exports.contains(&export.name) { + found_count += 1; + } + } + + if found_count == wasi_exports.len() { + wasi_instance = Some(i); + break; + } + } + } + if wasi_instance.is_some() { + configure_lib(target_module_id, component, WHAMM_CORE_LIB_NAME, core_lib); + } else { + panic!( + "Target component does not already import wasi_snapshot_preview1, not supported yet." + ) + } + + fn configure_lib<'a>( + target_module_id: u32, + wasm: &mut Component<'a>, + lib_name: &'a str, + lib_bytes: &'a [u8], + ) { + let wasi_name = "wasi_snapshot_preview1"; + let lib_wasm = Component::parse(lib_bytes, false, true).unwrap(); + + // Create an instance type that defines the library + let mut decls = vec![]; + // let mut num_exported_fns = 0; + let mut curr_ty_id = 0; + for (i, export) in lib_wasm.exports.iter().enumerate() { + if !matches!(export.kind, ComponentExternalKind::Func) { + continue; + } + let comp_ty = lib_wasm.get_type_of_exported_lift_func(ComponentExportId(i as u32)); + if let Some(ty) = comp_ty.as_ref() { + if matches!(ty, ComponentType::Func(_)) { + decls.push(InstanceTypeDeclaration::Type((*ty).clone())); + decls.push(InstanceTypeDeclaration::Export { + name: export.name, + ty: ComponentTypeRef::Func(curr_ty_id), + }); + curr_ty_id += 1; + } + } + } + let (inst_ty_id, ..) = wasm.add_type_instance(decls); + + // Import the library from an external provider + let inst_id = wasm.add_import(ComponentImport { + name: ComponentImportName("whamm-core"), + ty: ComponentTypeRef::Instance(*inst_ty_id), + }); + + // Lower the exported functions using aliases + let mut exports = vec![]; + for ComponentExport { name, kind, .. } in lib_wasm.exports.iter() { + let (alias_func_id, ..) = wasm.add_alias_func(ComponentAlias::InstanceExport { + name: name.0, + kind: *kind, + instance_index: inst_id, + }); + let canon_id = wasm.add_canon_func(CanonicalFunction::Lower { + func_index: *alias_func_id, + options: vec![].into_boxed_slice(), + }); + + exports.push(Export { + name: name.0, + kind: ExternalKind::Func, + index: *canon_id, + }); + } + + // Create a core instance from the library + let lib_inst_id = wasm.add_core_instance(Instance::FromExports(exports.into_boxed_slice())); + + // Edit the instantiation of the instrumented module to include the added library + for inst in wasm.instances.iter_mut() { + if let Instance::Instantiate { module_index, args } = inst { + if target_module_id == *module_index { + let mut uses_wasi = false; + let mut new_args = vec![]; + for arg in args.iter() { + if arg.name == wasi_name { + uses_wasi = true; + } + new_args.push(arg.clone()); + } + assert!(uses_wasi, "Target module does not already import wasi_snapshot_preview1, not supported yet."); + + new_args.push(InstantiationArg { + name: lib_name, + kind: InstantiationArgKind::Instance, + index: *lib_inst_id, + }); + + *args = new_args.into_boxed_slice(); + } + } + } + } +} diff --git a/tests/instrumentation_test.rs b/tests/instrumentation/instrumentation_test.rs similarity index 96% rename from tests/instrumentation_test.rs rename to tests/instrumentation/instrumentation_test.rs index 0669a3d7..7f407dfb 100644 --- a/tests/instrumentation_test.rs +++ b/tests/instrumentation/instrumentation_test.rs @@ -13,7 +13,6 @@ use wirm::module_builder::AddLocal; use wirm::opcode::{Inject, Instrumenter}; use wirm::{Component, Location, Module, Opcode}; -mod common; use crate::common::check_instrumentation_encoding; #[test] @@ -126,7 +125,7 @@ fn iterator_inject_i32_before() { } comp_it.reset(); - let result = component.encode(); + let result = component.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -184,7 +183,7 @@ fn iterator_inject_all_variations() { panic!("Should've gotten Component Location!"); } } - let result = component.encode(); + let result = component.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -227,7 +226,7 @@ fn test_inject_locals() { is_first = false; } - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -254,7 +253,7 @@ fn test_block_alt_one_func_nested_block() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -279,7 +278,7 @@ fn test_block_alt_one_func_remove_else() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -304,7 +303,7 @@ fn test_block_alt_one_func_replace_else() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -329,7 +328,7 @@ fn test_block_alt_one_func_two_blocks() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -354,7 +353,7 @@ fn test_block_alt_remove_else_nested_if() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -388,7 +387,7 @@ fn test_block_alt_remove_else_with_instrumented_after_if() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -422,7 +421,7 @@ fn test_block_alt_remove_else_with_instrumented_exit_if() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -447,7 +446,7 @@ fn test_block_alt_remove_entire_block() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -472,7 +471,7 @@ fn test_block_alt_remove_if_with_else() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -497,7 +496,7 @@ fn test_block_alt_remove_nested_block() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -522,7 +521,7 @@ fn test_block_alt_replace_else_nested_if() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -551,7 +550,7 @@ fn test_block_alt_replace_if_with_else() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -576,7 +575,7 @@ fn test_block_alt_replace_nested_block() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -611,7 +610,7 @@ fn test_block_entry_one_func_nested_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -644,7 +643,7 @@ fn test_block_entry_one_func_one_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -681,7 +680,7 @@ fn test_block_entry_one_func_two_blocks() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -723,7 +722,7 @@ fn test_block_entry_two_funcs_nested_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -765,7 +764,7 @@ fn test_block_entry_two_funcs_one_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -790,7 +789,7 @@ fn test_block_entry_two_funcs_two_blocks() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -824,7 +823,7 @@ fn test_block_exit_one_func_nested_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -856,7 +855,7 @@ fn test_block_exit_one_func_one_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -893,7 +892,7 @@ fn test_block_exit_one_func_two_blocks() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -935,7 +934,7 @@ fn test_block_exit_two_funcs_nested_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -977,7 +976,7 @@ fn test_block_exit_two_funcs_one_block() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1002,7 +1001,7 @@ fn test_block_exit_two_funcs_two_blocks() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1025,7 +1024,7 @@ fn test_fn_entry_one_func() { inject_function_entry(&mut mod_it, fn_entry_body); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1046,7 +1045,7 @@ fn test_fn_entry_two_funcs() { inject_function_entry(&mut mod_it, fn_entry_body); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1069,7 +1068,7 @@ fn test_fn_exit_one_func() { inject_function_exit(&mut mod_it, fn_entry_body); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1090,7 +1089,7 @@ fn test_fn_exit_two_funcs() { inject_function_exit(&mut mod_it, fn_entry_body); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1148,7 +1147,7 @@ fn test_semantic_after_complex_mult_nested_diff_opcodes() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1180,7 +1179,7 @@ fn test_semantic_after_medium_1br() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1212,7 +1211,7 @@ fn test_semantic_after_medium_1br_if() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1244,7 +1243,7 @@ fn test_semantic_after_medium_1br_table() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1276,7 +1275,7 @@ fn test_semantic_after_medium_2br() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1308,7 +1307,7 @@ fn test_semantic_after_medium_2br_if() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1340,7 +1339,7 @@ fn test_semantic_after_medium_2br_table() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1372,7 +1371,7 @@ fn test_semantic_after_medium_blocks() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1419,7 +1418,7 @@ fn test_semantic_after_medium_ifelse() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1461,7 +1460,7 @@ fn test_semantic_after_medium_ifs() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1493,7 +1492,7 @@ fn test_semantic_after_medium_multiple() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1524,7 +1523,7 @@ fn test_semantic_after_simple_1br() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1549,7 +1548,7 @@ fn test_semantic_after_simple_1br_if() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1574,7 +1573,7 @@ fn test_semantic_after_simple_1br_table() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1606,7 +1605,7 @@ fn test_semantic_after_simple_1if() { ]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1631,7 +1630,7 @@ fn test_semantic_after_simple_2br() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1656,7 +1655,7 @@ fn test_semantic_after_simple_2br_if() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1681,7 +1680,7 @@ fn test_semantic_after_simple_2br_table() { )]; run_block_injection(&mut mod_it, &ops_of_interest); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( @@ -1700,7 +1699,7 @@ fn add_imports_when_has_start_func() { module.add_import_func("ima".to_string(), "new_import".to_string(), TypeID(0)); module.add_import_func("ya_dont".to_string(), "say".to_string(), TypeID(0)); - let result = module.encode(); + let result = module.encode().expect("error"); let out = wasmprinter::print_bytes(result).expect("couldn't translate wasm to wat"); if let Err(e) = check_instrumentation_encoding(&out, file) { error!( diff --git a/tests/instrumentation/mod.rs b/tests/instrumentation/mod.rs new file mode 100644 index 00000000..c9772e8e --- /dev/null +++ b/tests/instrumentation/mod.rs @@ -0,0 +1,3 @@ +pub mod component_level; +pub mod instrumentation_test; +pub mod test_module; diff --git a/tests/test_module.rs b/tests/instrumentation/test_module.rs similarity index 93% rename from tests/test_module.rs rename to tests/instrumentation/test_module.rs index 10b9660a..b7cc891f 100644 --- a/tests/test_module.rs +++ b/tests/instrumentation/test_module.rs @@ -8,7 +8,6 @@ use wirm::ir::module::module_functions::{ImportedFunction, LocalFunction}; use wirm::ir::types::{Body, InitExpr, Value}; use wirm::{DataType, InitInstr, Module, Opcode}; -mod common; use crate::common::check_instrumentation_encoding; #[test] @@ -135,7 +134,7 @@ fn test_panic_call_delete() { module.delete_func(FunctionID(1)); // Should panic here as func 2 calls func 1 which has been deleted - module.encode(); + module.encode().expect("error"); } #[test] @@ -166,7 +165,9 @@ fn test_middle_import_to_local() { builder.i32_const(1); builder.drop(); - builder.replace_import_in_module(&mut module, ImportsID(1)); + builder + .replace_import_in_module(&mut module, ImportsID(1)) + .expect("error"); check_validity( file, @@ -187,7 +188,9 @@ fn test_first_import_to_local() { builder.i32_const(1); builder.drop(); - builder.replace_import_in_module(&mut module, ImportsID(0)); + builder + .replace_import_in_module(&mut module, ImportsID(0)) + .expect("error"); check_validity( file, @@ -208,7 +211,9 @@ fn test_last_import_to_local() { builder.i32_const(1); builder.drop(); - builder.replace_import_in_module(&mut module, ImportsID(2)); + builder + .replace_import_in_module(&mut module, ImportsID(2)) + .expect("error"); check_validity( file, @@ -229,17 +234,23 @@ fn test_all_import_to_local() { let mut first_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); first_builder.i32_const(1); first_builder.drop(); - first_builder.replace_import_in_module(&mut module, ImportsID(0)); + first_builder + .replace_import_in_module(&mut module, ImportsID(0)) + .expect("error"); let mut second_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); second_builder.i32_const(2); second_builder.drop(); - second_builder.replace_import_in_module(&mut module, ImportsID(1)); + second_builder + .replace_import_in_module(&mut module, ImportsID(1)) + .expect("error"); let mut third_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); third_builder.i32_const(3); third_builder.drop(); - third_builder.replace_import_in_module(&mut module, ImportsID(2)); + third_builder + .replace_import_in_module(&mut module, ImportsID(2)) + .expect("error"); check_validity( file, @@ -260,12 +271,16 @@ fn test_some_import_to_local() { let mut first_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); first_builder.i32_const(1); first_builder.drop(); - first_builder.replace_import_in_module(&mut module, ImportsID(0)); + first_builder + .replace_import_in_module(&mut module, ImportsID(0)) + .expect("error"); let mut second_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); second_builder.i32_const(2); second_builder.drop(); - second_builder.replace_import_in_module(&mut module, ImportsID(1)); + second_builder + .replace_import_in_module(&mut module, ImportsID(1)) + .expect("error"); check_validity( file, @@ -286,7 +301,9 @@ fn test_middle_import_to_local_import_delete() { builder.i32_const(1); builder.drop(); - builder.replace_import_in_module(&mut module, ImportsID(1)); + builder + .replace_import_in_module(&mut module, ImportsID(1)) + .expect("error"); module.delete_func(FunctionID(2)); @@ -309,7 +326,9 @@ fn test_middle_import_to_local_local_delete() { builder.i32_const(1); builder.drop(); - builder.replace_import_in_module(&mut module, ImportsID(1)); + builder + .replace_import_in_module(&mut module, ImportsID(1)) + .expect("error"); module.delete_func(FunctionID(2)); module.delete_func(FunctionID(3)); @@ -476,17 +495,23 @@ fn test_all_local_to_import_all_import_to_local() { let mut first_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); first_builder.i32_const(4); first_builder.drop(); - first_builder.replace_import_in_module(&mut module, ImportsID(0)); + first_builder + .replace_import_in_module(&mut module, ImportsID(0)) + .expect("error"); let mut second_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); second_builder.i32_const(5); second_builder.drop(); - second_builder.replace_import_in_module(&mut module, ImportsID(1)); + second_builder + .replace_import_in_module(&mut module, ImportsID(1)) + .expect("error"); let mut third_builder = FunctionBuilder::new(&[DataType::I32, DataType::I32], &[]); third_builder.i32_const(6); third_builder.drop(); - third_builder.replace_import_in_module(&mut module, ImportsID(2)); + third_builder + .replace_import_in_module(&mut module, ImportsID(2)) + .expect("error"); module.convert_local_fn_to_import( FunctionID(3), @@ -628,7 +653,7 @@ fn test_elem_reindexing() { // the wrong type unless the element section is reindexed. let ty_id = module.types.add_func_type(&[DataType::I32], &[]); let _ = module.add_import_func("".to_string(), "".to_string(), ty_id); - validate(&module.encode(), &output_path).unwrap(); + validate(&module.encode().expect("error"), &output_path).unwrap(); // Run the check function to assert that entries in the table have the expected types. let engine = wasmtime::Engine::default(); @@ -680,7 +705,7 @@ pub(crate) fn validate_wasm(wasm_path: &str) -> bool { } fn check_validity(file: &str, module: &mut Module, output_wasm_path: &str, check_encoding: bool) { - let result = module.encode(); + let result = module.encode().expect("error"); validate(&result, output_wasm_path).expect("Failed to write out to wasm file."); if check_encoding { diff --git a/tests/iterator_test.rs b/tests/iterator_test.rs index dbbfd2f2..a102f05b 100644 --- a/tests/iterator_test.rs +++ b/tests/iterator_test.rs @@ -1,7 +1,6 @@ use log::{debug, trace}; use std::collections::{HashMap, HashSet}; use wasmparser::Operator; -use wirm::ir::component::Component; use wirm::ir::id::{FunctionID, ModuleID}; use wirm::ir::module::Module; use wirm::ir::types::Location; @@ -10,6 +9,7 @@ use wirm::iterator::iterator_trait::{IteratingInstrumenter, Iterator}; use wirm::iterator::module_iterator::ModuleIterator; use wirm::module_builder::AddLocal; use wirm::opcode::Instrumenter; +use wirm::Component; #[test] fn test_iterator_count() { @@ -81,7 +81,7 @@ fn test_it_instr_at() { }; } - let a = module.encode(); + let a = module.encode().expect("error during encode"); let wat = wasmprinter::print_bytes(&a).unwrap(); debug!("{}", wat); } @@ -105,7 +105,7 @@ fn test_it_dup_instr() { trace!("Func: {:?}, {}: {:?},", func_idx, instr_idx, op); let loc = mod_it.curr_loc().0; - let orig = mod_it.curr_op_owned().unwrap(); + let orig = mod_it.curr_op_owned().expect("error!"); if !matches!(orig, Operator::End) { mod_it.before(); mod_it.add_instr_at(loc, orig); @@ -118,7 +118,7 @@ fn test_it_dup_instr() { }; } - let a = module.encode(); + let a = module.encode().expect("error!"); let wat = wasmprinter::print_bytes(&a).unwrap(); debug!("{}", wat); } @@ -132,7 +132,7 @@ fn test_it_add_local_diff_type() { mod_it.add_local(wirm::ir::types::DataType::I64); mod_it.add_local(wirm::ir::types::DataType::I32); - let a = module.encode(); + let a = module.encode().expect("error!"); let wat = wasmprinter::print_bytes(&a).unwrap(); debug!("{}", wat); } @@ -147,7 +147,7 @@ fn test_imports() { let mut mod_it = ModuleIterator::new(&mut module, &vec![]); iterate_module_and_count(&mut mod_it, 2, 2); - let a = module.encode(); + let a = module.encode().expect("error!"); let wat = wasmprinter::print_bytes(&a).unwrap(); debug!("{}", wat); } diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 00000000..5ef5b04b --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,2 @@ +mod common; +mod instrumentation; diff --git a/tests/round_trip_component.rs b/tests/round_trip_component.rs index f5becf80..4b6bd3fd 100644 --- a/tests/round_trip_component.rs +++ b/tests/round_trip_component.rs @@ -1,7 +1,6 @@ -use wirm::ir::component::Component; - mod common; use common::{write_to_file, WASM_OUTPUT_DIR}; +use wirm::Component; fn round_trip_component(testname: &str, folder: &str) { let filename = format!( @@ -11,9 +10,8 @@ fn round_trip_component(testname: &str, folder: &str) { testname ); let buff = wat::parse_file(filename).expect("couldn't convert the input wat to Wasm"); - let mut component = Component::parse(&buff, false, false).expect("Unable to parse"); - // component.print(); - let result = component.encode(); + let component = Component::parse(&buff, false, false).expect("Unable to parse"); + let result = component.encode().expect("error"); write_to_file( &result, format!("{WASM_OUTPUT_DIR}/component_{testname}.wasm"), diff --git a/tests/round_trip_module.rs b/tests/round_trip_module.rs index c64884cf..94e95c45 100644 --- a/tests/round_trip_module.rs +++ b/tests/round_trip_module.rs @@ -15,8 +15,8 @@ fn round_trip_module(testname: &str, folder: &str) { let buff = wat::parse_file(filename).expect("couldn't convert the input wat to Wasm"); let original = wasmprinter::print_bytes(buff.clone()).expect("couldn't convert original Wasm to wat"); - let mut module = Module::parse(&buff, false, false).unwrap(); - let result = module.encode(); + let module = Module::parse(&buff, false, false).unwrap(); + let result = module.encode().expect("error!"); let out = wasmprinter::print_bytes(result).expect("couldn't translated Wasm to wat"); if out != original { @@ -68,7 +68,7 @@ fn set_name() { let mut module = Module::parse(&buff, false, false).unwrap(); module.set_fn_name(FunctionID(1), "test".to_string()); // println!("{:#?}", module); - let result = module.encode(); + let result = module.encode().expect("error!"); //write result to file write_to_file(&result, format!("{WASM_OUTPUT_DIR}/func1.wasm")); diff --git a/tests/round_trip_wast.rs b/tests/round_trip_wast.rs index 4b681a91..2497595b 100644 --- a/tests/round_trip_wast.rs +++ b/tests/round_trip_wast.rs @@ -10,27 +10,25 @@ fn wasm_tools() -> Command { } fn roundtrip(filename: String, component: bool) { - println!("filename: {:?}", filename); + println!("\nfilename: {:?}", filename); let buff = wat::parse_file(filename).expect("couldn't convert the input wat to Wasm"); + let original = wasmprinter::print_bytes(&buff).expect("couldn't convert original Wasm to wat"); + println!("original: {:?}", original); if component { - let mut parser = Component::parse(&buff, false, false).expect("Unable to parse"); - let result = parser.encode(); + let parser = Component::parse(&buff, false, false).expect("Unable to parse"); + let result = parser.encode().expect("error"); let out = wasmprinter::print_bytes(result.clone()).expect("couldn't translate Wasm to wat"); - let original = - wasmprinter::print_bytes(&buff).expect("couldn't convert original Wasm to wat"); assert_eq!(out, original); } else { - let mut parser = Module::parse(&buff, false, false).expect("Unable to parse"); - let result = parser.encode(); + let parser = Module::parse(&buff, false, false).expect("Unable to parse"); + let result = parser.encode().expect("error during parse"); let out = wasmprinter::print_bytes(result.clone()).expect("couldn't translate Wasm to wat"); - let original = - wasmprinter::print_bytes(&buff).expect("couldn't convert original Wasm to wat"); assert_eq!(out, original); } - // component.print(); } -fn test_wast(path: String, component: bool) { +fn test_wast(path: &Path, component: bool) { + let path = path.to_str().unwrap().replace("\\", "/"); for entry in fs::read_dir(path).unwrap() { let file = entry.unwrap(); match file.path().extension() { @@ -109,20 +107,45 @@ fn test_wast(path: String, component: bool) { } } +const WASM_TOOLS_TEST_COMP_INPUTS: &str = "./tests/wasm-tools/component-model"; + #[test] fn test_wast_components() { - let path = Path::new("./tests/wasm-tools/component-model/"); - // Generate the same output on windows and unix - let path = path.to_str().unwrap().replace("\\", "/"); + let path_str = WASM_TOOLS_TEST_COMP_INPUTS.to_string(); + test_wast(Path::new(&path_str), true); +} - test_wast(path, true); +#[test] +fn test_wast_components_async() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/async"); + test_wast(Path::new(&path_str), true); } #[test] -fn test_wast_gc() { - let path = Path::new("./tests/wasm-tools/gc/"); - // Generate the same output on windows and unix - let path = path.to_str().unwrap().replace("\\", "/"); +fn test_wast_components_error_context() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/error-context"); + test_wast(Path::new(&path_str), true); +} - test_wast(path, false); +#[test] +fn test_wast_components_gc() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/gc"); + test_wast(Path::new(&path_str), true); +} + +#[test] +fn test_wast_components_shared_everything_threads() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/shared-everything-threads"); + test_wast(Path::new(&path_str), true); +} + +#[test] +fn test_wast_components_values() { + let path_str = format!("{WASM_TOOLS_TEST_COMP_INPUTS}/values"); + test_wast(Path::new(&path_str), true); +} + +#[test] +fn test_wast_gc() { + test_wast(Path::new("./tests/wasm-tools/gc/"), false); } diff --git a/tests/test_inputs/whamm/whamm_core.wasm b/tests/test_inputs/whamm/whamm_core.wasm new file mode 100644 index 00000000..9062ab55 Binary files /dev/null and b/tests/test_inputs/whamm/whamm_core.wasm differ diff --git a/tests/wasm-tools/component-model/adapt.wast b/tests/wasm-tools/component-model/adapt.wast index 6f5457ca..8cc76f8f 100644 --- a/tests/wasm-tools/component-model/adapt.wast +++ b/tests/wasm-tools/component-model/adapt.wast @@ -1,6 +1,6 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % -(component +(component definition (import "log" (func $log (param "msg" string))) (core module $libc (memory (export "memory") 1) diff --git a/tests/wasm-tools/component-model/alias.wast b/tests/wasm-tools/component-model/alias.wast index 6691d0a1..dd9d1b37 100644 --- a/tests/wasm-tools/component-model/alias.wast +++ b/tests/wasm-tools/component-model/alias.wast @@ -1,23 +1,27 @@ -;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-values +;; RUN: wast --assert default --snapshot tests/snapshots % (component - (import "i" (instance $i - (export "f1" (func)) - (export "f2" (func (param "p1" string))) - )) - (export "run" (func $i "f1")) + (component + (import "i" (instance $i + (export "f1" (func)) + (export "f2" (func (param "p1" string))) + )) + (export "run" (func $i "f1")) + ) ) (component - (import "i" (component $c - (export "f1" (func)) - (export "f2" (func (param "p1" string))) - )) - (instance $i (instantiate $c)) - (export "run" (func $i "f1")) + (component + (import "i" (component $c + (export "f1" (func)) + (export "f2" (func (param "p1" string))) + )) + (instance $i (instantiate $c)) + (export "run" (func $i "f1")) + ) ) -(component +(component definition (import "i" (core module $m (export "f1" (func $f1)) (export "f2" (func $f2 (param i32))) @@ -29,7 +33,7 @@ (core instance (instantiate $m2 (with "" (instance (export "" (func $i "f1")))))) ) -(component +(component definition (import "a" (core module $libc (export "memory" (memory 1)) (export "table" (table 0 funcref)) @@ -62,17 +66,20 @@ ) (component - (import "a" (instance $i - (export "a" (func)) - (export "b" (core module)) - (export "c" (instance)) - )) - (export "b" (func $i "a")) - (export "c" (core module $i "b")) - (export "d" (instance $i "c")) + (component + (import "a" (instance $i + (export "a" (func)) + (export "b" (core module)) + (export "c" (instance)) + )) + (export "b" (func $i "a")) + (export "c" (core module $i "b")) + (export "d" (instance $i "c")) + ) ) -(component + +(component definition (import "a" (core module $libc (export "memory" (memory 1)) (export "table" (table 0 funcref)) @@ -193,36 +200,35 @@ ) ;; multiple projections in alias sugar -(component $a - (import "a" (instance $a - (export "a" (instance +(component + (component $a + (import "a" (instance $a (export "a" (instance (export "a" (instance - (export "a" (func)) + (export "a" (instance + (export "a" (func)) + )) )) )) )) - )) - (import "b" (component $b (import "a" (func)))) + (import "b" (component $b (import "a" (func)))) - (instance (instantiate $b - (with "a" (func $a "a" "a" "a" "a")) - )) + (instance (instantiate $b + (with "a" (func $a "a" "a" "a" "a")) + )) + ) ) ;; alias some constructs (component - (import "a" (instance $foo (export "v" (value s32)))) - (export "v" (value $foo "v")) -) - -(component - (import "a" (instance $foo (export "v" (component)))) - (export "v" (component $foo "v")) + (component + (import "a" (instance $foo (export "v" (component)))) + (export "v" (component $foo "v")) + ) ) -(component +(component definition (import "a" (instance $foo (export "v" (core module)))) (export "v" (core module $foo "v")) ) @@ -244,10 +250,12 @@ (export "v" (core module $target)) ) -(component $C - (component $m) - (alias outer $C $m (component $target)) - (export "v" (component $target)) +(component + (component $C + (component $m) + (alias outer $C $m (component $target)) + (export "v" (component $target)) + ) ) (assert_invalid @@ -274,7 +282,7 @@ (component (alias outer 0 0 (component))) "index out of bounds") -(component +(component definition (import "a" (instance $i (export "x" (core module)) )) @@ -283,9 +291,11 @@ ) (component - (import "a" (instance $i - (export "x" (component)) - )) - ;; inline alias injection sugar works for component references - (instance (instantiate (component $i "x"))) + (component + (import "a" (instance $i + (export "x" (component)) + )) + ;; inline alias injection sugar works for component references + (instance (instantiate (component $i "x"))) + ) ) diff --git a/tests/wasm-tools/component-model/async/abi.wast b/tests/wasm-tools/component-model/async/abi.wast new file mode 100644 index 00000000..04c94d8d --- /dev/null +++ b/tests/wasm-tools/component-model/async/abi.wast @@ -0,0 +1,111 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async,cm-fixed-size-list + +;; async lower +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + + ;; func() + (import "f1" (func $f1)) + (core func $f1 (canon lower (func $f1) async)) + (core module $m1 (func (import "" "f") (result i32))) + (core instance (instantiate $m1 (with "" (instance (export "f" (func $f1)))))) + + ;; func(x: u32) + (import "f2" (func $f2 (param "x" u32))) + (core func $f2 (canon lower (func $f2) async)) + (core module $m2 (func (import "" "f") (param i32) (result i32))) + (core instance (instantiate $m2 (with "" (instance (export "f" (func $f2)))))) + + ;; func() -> u32 + (import "f3" (func $f3 (result u32))) + (core func $f3 (canon lower (func $f3) async (memory $libc "memory"))) + (core module $m3 (func (import "" "f") (param i32) (result i32))) + (core instance (instantiate $m3 (with "" (instance (export "f" (func $f3)))))) + + ;; func(x: u32, y: f32, z: string) + (import "f4" (func $f4 (param "x" u32) (param "y" f32) (param "z" string))) + (core func $f4 (canon lower (func $f4) async (memory $libc "memory"))) + (core module $m4 (func (import "" "f") (param i32 f32 i32 i32) (result i32))) + (core instance (instantiate $m4 (with "" (instance (export "f" (func $f4)))))) + + ;; func() -> f32 + (import "f5" (func $f5 (result f32))) + (core func $f5 (canon lower (func $f5) async (memory $libc "memory"))) + (core module $m5 (func (import "" "f") (param i32) (result i32))) + (core instance (instantiate $m5 (with "" (instance (export "f" (func $f5)))))) + + ;; func(x: list) -> f32 + (import "f6" (func $f6 (param "x" (list string 6)) (result f32))) + (core func $f6 (canon lower (func $f6) async (memory $libc "memory"))) + (core module $m6 (func (import "" "f") (param i32 i32) (result i32))) + (core instance (instantiate $m6 (with "" (instance (export "f" (func $f6)))))) +) + +;; async lift, callback abi +(component + ;; func() + (core module $m1 + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "f") (result i32) unreachable)) + (core instance $m1 (instantiate $m1)) + (func + (canon lift (core func $m1 "f") async (callback (func $m1 "cb")))) + + ;; func(x: u32) + (core module $m2 + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "f") (param i32) (result i32) unreachable)) + (core instance $m2 (instantiate $m2)) + (func (param "x" u32) + (canon lift (core func $m2 "f") async (callback (func $m2 "cb")))) + + ;; func() -> u32 + (core module $m3 + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "f") (result i32) unreachable)) + (core instance $m3 (instantiate $m3)) + (func (result u32) + (canon lift (core func $m3 "f") async (callback (func $m3 "cb")))) + + ;; func(x: f32) + (core module $m4 + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "f") (param f32) (result i32) unreachable)) + (core instance $m4 (instantiate $m4)) + (func (param "x" f32) + (canon lift (core func $m4 "f") async (callback (func $m4 "cb")))) + + ;; func(x: f32, y: string) + (core module $m5 + (memory (export "memory") 1) + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "f") (param f32 i32 i32) (result i32) unreachable)) + (core instance $m5 (instantiate $m5)) + (func (param "x" f32) (param "y" string) + (canon lift (core func $m5 "f") async (callback (func $m5 "cb")) + (memory $m5 "memory") (realloc (func $m5 "realloc")))) + + ;; func(x: list) + (core module $m6 + (memory (export "memory") 1) + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "f") (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32) unreachable)) + (core instance $m6 (instantiate $m6)) + (func (param "x" (list string 4)) + (canon lift (core func $m6 "f") async (callback (func $m6 "cb")) + (memory $m6 "memory") (realloc (func $m6 "realloc")))) + + ;; func(x: list) + (core module $m7 + (memory (export "memory") 1) + (func (export "cb") (param i32 i32 i32) (result i32) unreachable) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "f") (param i32) (result i32) unreachable)) + (core instance $m7 (instantiate $m7)) + (func (param "x" (list string 10)) + (canon lift (core func $m7 "f") async (callback (func $m7 "cb")) + (memory $m7 "memory") (realloc (func $m7 "realloc")))) +) diff --git a/tests/wasm-tools/component-model/async/async-builtins.wast b/tests/wasm-tools/component-model/async/async-builtins.wast new file mode 100644 index 00000000..b4f2deff --- /dev/null +++ b/tests/wasm-tools/component-model/async/async-builtins.wast @@ -0,0 +1,103 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async,cm-async-builtins + +;; stream.cancel-read +(component + (core module $m + (import "" "stream.cancel-read" (func $stream-cancel-read (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-read (canon stream.cancel-read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read)))))) +) + +;; stream.cancel-read; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-read" (func $stream-cancel-read (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-read (canon stream.cancel-read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read)))))) + ) + "type mismatch for export `stream.cancel-read` of module instantiation argument ``" +) + +;; stream.cancel-write +(component + (core module $m + (import "" "stream.cancel-write" (func $stream-cancel-write (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-write (canon stream.cancel-write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write)))))) +) + +;; stream.cancel-write; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-write" (func $stream-cancel-write (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-write (canon stream.cancel-write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write)))))) + ) + "type mismatch for export `stream.cancel-write` of module instantiation argument ``" +) + +(component + (core func (canon subtask.cancel async)) + (canon subtask.cancel async (core func)) + + (type $r (resource (rep i32))) + (core func (canon resource.drop $r async)) + (canon resource.drop $r async (core func)) + +) + +;; future.cancel-read +(component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) +) + +;; future.cancel-read; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) + ) + "type mismatch for export `future.cancel-read` of module instantiation argument ``" +) + +;; future.cancel-write +(component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) +) + +;; future.cancel-write; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) + ) + "type mismatch for export `future.cancel-write` of module instantiation argument ``" +) diff --git a/tests/wasm-tools/component-model/async/futures.wast b/tests/wasm-tools/component-model/async/futures.wast new file mode 100644 index 00000000..7366f8fc --- /dev/null +++ b/tests/wasm-tools/component-model/async/futures.wast @@ -0,0 +1,258 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async + +;; future.new +(component + (core module $m + (import "" "future.new" (func $future-new (result i64))) + ) + (type $future-type (future u8)) + (core func $future-new (canon future.new $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future-new)))))) +) + +;; future.new; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.new" (func $future-new (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-new (canon future.new $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future-new)))))) + ) + "type mismatch for export `future.new` of module instantiation argument ``" +) + +;; future.read +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) +) + +;; future.read; no payload +(component + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future)) + (core func $future-read (canon future.read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) +) + +;; future.read; with realloc +(component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future string)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) +) + +;; future.read; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "type mismatch for export `future.read` of module instantiation argument ``" +) + +;; future.read; incorrect type argument +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $string-type string) + (core func $future-read (canon future.read $string-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "`future.read` requires a future type" +) + +;; future.read; missing realloc +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.read" (func $future-read (param i32 i32) (result i32))) + ) + (type $future-type (future string)) + (core func $future-read (canon future.read $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.read" (func $future-read)))))) + ) + "canonical option `realloc` is required" +) + +;; future.write +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.write" (func $future-write (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-write (canon future.write $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write)))))) +) + +;; future.write; no payload +(component + (core module $m + (import "" "future.write" (func $future-write (param i32 i32) (result i32))) + ) + (type $future-type (future)) + (core func $future-write (canon future.write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write)))))) +) + +;; future.write; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "future.write" (func $future-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-write (canon future.write $future-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "future.write" (func $future-write)))))) + ) + "type mismatch for export `future.write` of module instantiation argument ``" +) + +;; future.cancel-read +(component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) +) + +(assert_invalid + (component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) + ) + "requires the component model async builtins feature") + +;; future.cancel-read; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.cancel-read" (func $future-cancel-read (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-read (canon future.cancel-read $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-read" (func $future-cancel-read)))))) + ) + "type mismatch for export `future.cancel-read` of module instantiation argument ``" +) + +;; future.cancel-write +(component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) +) + +(assert_invalid + (component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type async)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) + ) + "requires the component model async builtins feature" +) + +;; future.cancel-write; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.cancel-write" (func $future-cancel-write (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-cancel-write (canon future.cancel-write $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.cancel-write" (func $future-cancel-write)))))) + ) + "type mismatch for export `future.cancel-write` of module instantiation argument ``" +) + +;; future.drop-readable +(component + (core module $m + (import "" "future.drop-readable" (func $future-drop-readable (param i32))) + ) + (type $future-type (future u8)) + (core func $future-drop-readable (canon future.drop-readable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.drop-readable" (func $future-drop-readable)))))) +) + +;; future.drop-readable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.drop-readable" (func $future-drop-readable (param i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-drop-readable (canon future.drop-readable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.drop-readable" (func $future-drop-readable)))))) + ) + "type mismatch for export `future.drop-readable` of module instantiation argument ``" +) + +;; future.drop-writable +(component + (core module $m + (import "" "future.drop-writable" (func $future-drop-writable (param i32))) + ) + (type $future-type (future u8)) + (core func $future-drop-writable (canon future.drop-writable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.drop-writable" (func $future-drop-writable)))))) +) + +;; future.drop-writable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "future.drop-writable" (func $future-drop-writable (param i32 i32) (result i32))) + ) + (type $future-type (future u8)) + (core func $future-drop-writable (canon future.drop-writable $future-type)) + (core instance $i (instantiate $m (with "" (instance (export "future.drop-writable" (func $future-drop-writable)))))) + ) + "type mismatch for export `future.drop-writable` of module instantiation argument ``" +) diff --git a/tests/wasm-tools/component-model/async/lift.wast b/tests/wasm-tools/component-model/async/lift.wast new file mode 100644 index 00000000..2f2d80fb --- /dev/null +++ b/tests/wasm-tools/component-model/async/lift.wast @@ -0,0 +1,142 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async + +;; async lift; no callback; with post-return +(assert_invalid + (component + (core module $m + (func (export "foo") (param i32) unreachable) + (func (export "post-return-foo") unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (post-return (func $i "post-return-foo"))) + ) + ) + "cannot specify post-return function in async" +) + +;; async lift; with callback +(component + (core module $m + (func (export "callback") (param i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) +) + +;; async lift; with incorrectly-typed callback +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 f32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "canonical option `callback` uses a core function with an incorrect signature" +) + +;; async lift; with callback and post-return +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + (func (export "post-return-foo") (param i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback")) (post-return (func $i "post-return-foo"))) + ) + ) + "cannot specify post-return function in async" +) + +;; async lift; with incorrectly-typed core function +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32 i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "lowered parameter types `[I32]` do not match parameter types `[I32, I32]` of core function 0" +) + +;; async lift; with missing callback +(assert_invalid + (component + (core module $m + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "core instance 0 has no export named `callback`" +) + +;; sync lift; with redundant callback +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32) (result i32) unreachable) + (func (export "foo") (param i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") (callback (func $i "callback"))) + ) + ) + "cannot specify callback without async" +) + +;; async lift; missing memory (needed for string return value) +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32) (result i32) unreachable) + (func (export "foo") (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (result string) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "canonical option `memory` is required" +) + +;; async lift; missing memory (needed for return value exceeding MAX_FLAT_PARAMS) +(assert_invalid + (component + (core module $m + (func (export "callback") (param i32 i32 i32) (result i32) unreachable) + (func (export "foo") (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (result (tuple u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32 u32)) + (canon lift (core func $i "foo") async (callback (func $i "callback"))) + ) + ) + "canonical option `memory` is required" +) diff --git a/tests/wasm-tools/component-model/async/lower.wast b/tests/wasm-tools/component-model/async/lower.wast new file mode 100644 index 00000000..fd473077 --- /dev/null +++ b/tests/wasm-tools/component-model/async/lower.wast @@ -0,0 +1,41 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async + +;; async lower +(component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core func $foo (canon lower (func $foo) async (memory $libc "memory"))) + (core module $m + (func (import "" "foo") (param i32 i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) +) + +;; async lower; with incorrectly-typed core function +(assert_invalid + (component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core func $foo (canon lower (func $foo) async (memory $libc "memory"))) + (core module $m + (func (import "" "foo") (param i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) + ) + "type mismatch for export `foo` of module instantiation argument ``" +) + +;; async lower; missing memory +(assert_invalid + (component + (import "foo" (func $foo (param "p1" u32) (result u32))) + (core func $foo (canon lower (func $foo) async)) + (core module $m + (func (import "" "foo") (param i32) (result i32)) + ) + (core instance $i (instantiate $m (with "" (instance (export "foo" (func $foo)))))) + ) + "canonical option `memory` is required" +) diff --git a/tests/wasm-tools/component-model/async/names.wast b/tests/wasm-tools/component-model/async/names.wast new file mode 100644 index 00000000..52089205 --- /dev/null +++ b/tests/wasm-tools/component-model/async/names.wast @@ -0,0 +1,29 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async + +;; historically these were part of the component-model-async development but +;; they have since been removed. + +(assert_invalid + (component + (import "[async]f" (func)) + ) + "not in kebab case") + +(assert_invalid + (component + (import "[async method]f" (func)) + ) + "not in kebab case") + +(assert_invalid + (component + (import "[async static]f" (func)) + ) + "not in kebab case") + + +(assert_invalid + (component + (import "a" (type $a (sub resource))) + (import "[constructor]a" (func async (result (own $a))))) + "constructor function cannot be async") diff --git a/tests/wasm-tools/component-model/async/stackful.wast b/tests/wasm-tools/component-model/async/stackful.wast new file mode 100644 index 00000000..e581b80a --- /dev/null +++ b/tests/wasm-tools/component-model/async/stackful.wast @@ -0,0 +1,145 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async,cm-async-stackful,cm-fixed-size-list + +;; waitable-set.wait +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.wait" (func $waitable-set-wait (param i32 i32) (result i32))) + ) + (core func $waitable-set-wait (canon waitable-set.wait cancellable (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) +) + +;; waitable-set.wait; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.wait" (func $waitable-set-wait (param i32) (result i32))) + ) + (core func $waitable-set-wait (canon waitable-set.wait cancellable (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) + ) + "type mismatch for export `waitable-set.wait` of module instantiation argument ``" +) + +;; waitable-set.poll +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.poll" (func $waitable-set-poll (param i32 i32) (result i32))) + ) + (core func $waitable-set-poll (canon waitable-set.poll cancellable (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) +) + +;; waitable-set.poll; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.poll" (func $waitable-set-poll (param i32) (result i32))) + ) + (core func $waitable-set-poll (canon waitable-set.poll cancellable (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) + ) + "type mismatch for export `waitable-set.poll` of module instantiation argument ``" +) + +;; thread.yield +(component + (core module $m + (import "" "thread.yield" (func $thread.yield (result i32))) + ) + (core func $thread.yield (canon thread.yield cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) +) + +;; thread.yield; incorrect type +(assert_invalid + (component + (core module $m + (import "" "thread.yield" (func $thread.yield (param i32) (result i32))) + ) + (core func $thread.yield (canon thread.yield cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) + ) + "type mismatch for export `thread.yield` of module instantiation argument ``" +) + +;; async lift, stackful abi +(component + ;; func() + (core module $m1 + (func (export "f") unreachable)) + (core instance $m1 (instantiate $m1)) + (func + (canon lift (core func $m1 "f") async)) + + ;; func(x: u32) + (core module $m2 + (func (export "f") (param i32) unreachable)) + (core instance $m2 (instantiate $m2)) + (func (param "x" u32) + (canon lift (core func $m2 "f") async)) + + ;; func() -> u32 + (core module $m3 + (func (export "f") unreachable)) + (core instance $m3 (instantiate $m3)) + (func (result u32) + (canon lift (core func $m3 "f") async)) + + ;; func(x: f32) + (core module $m4 + (func (export "f") (param f32) unreachable)) + (core instance $m4 (instantiate $m4)) + (func (param "x" f32) + (canon lift (core func $m4 "f") async)) + + ;; func(x: f32, y: string) + (core module $m5 + (memory (export "memory") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "f") (param f32 i32 i32) unreachable)) + (core instance $m5 (instantiate $m5)) + (func (param "x" f32) (param "y" string) + (canon lift (core func $m5 "f") async + (memory $m5 "memory") (realloc (func $m5 "realloc")))) + + ;; func(x: list) + (core module $m6 + (memory (export "memory") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "f") (param i32 i32 i32 i32 i32 i32 i32 i32) unreachable)) + (core instance $m6 (instantiate $m6)) + (func (param "x" (list string 4)) + (canon lift (core func $m6 "f") async + (memory $m6 "memory") (realloc (func $m6 "realloc")))) + + ;; func(x: list) + (core module $m7 + (memory (export "memory") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "f") (param i32) unreachable)) + (core instance $m7 (instantiate $m7)) + (func (param "x" (list string 10)) + (canon lift (core func $m7 "f") async + (memory $m7 "memory") (realloc (func $m7 "realloc")))) +) + +;; async lift; no callback +(component + (core module $m + (func (export "foo") (param i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "foo") (param "p1" u32) (result u32) + (canon lift (core func $i "foo") async) + ) +) diff --git a/tests/wasm-tools/component-model/async/streams.wast b/tests/wasm-tools/component-model/async/streams.wast new file mode 100644 index 00000000..5ee17871 --- /dev/null +++ b/tests/wasm-tools/component-model/async/streams.wast @@ -0,0 +1,213 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async + +;; stream.new +(component + (core module $m + (import "" "stream.new" (func $stream-new (result i64))) + ) + (type $stream-type (stream u8)) + (core func $stream-new (canon stream.new $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream-new)))))) +) + +;; stream.new; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.new" (func $stream-new (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-new (canon stream.new $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream-new)))))) + ) + "type mismatch for export `stream.new` of module instantiation argument ``" +) + +;; stream.read +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) +) + +;; stream.read; no payload +(component + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream)) + (core func $stream-read (canon stream.read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) +) + +;; stream.read; with realloc +(component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream string)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) +) + +;; stream.read; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "type mismatch for export `stream.read` of module instantiation argument ``" +) + +;; stream.read; incorrect type argument +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $string-type string) + (core func $stream-read (canon stream.read $string-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "`stream.read` requires a stream type" +) + +;; stream.read; missing realloc +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.read" (func $stream-read (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream string)) + (core func $stream-read (canon stream.read $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream-read)))))) + ) + "canonical option `realloc` is required" +) + +;; stream.write +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.write" (func $stream-write (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-write (canon stream.write $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write)))))) +) + +;; stream.write; no payload +(component + (core module $m + (import "" "stream.write" (func $stream-write (param i32 i32 i32) (result i32))) + ) + (type $stream-type (stream)) + (core func $stream-write (canon stream.write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write)))))) +) + +;; stream.write; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "stream.write" (func $stream-write (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-write (canon stream.write $stream-type async (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "stream.write" (func $stream-write)))))) + ) + "type mismatch for export `stream.write` of module instantiation argument ``" +) + +;; stream.cancel-read +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-read" (func $stream-cancel-read (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-read (canon stream.cancel-read $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-read" (func $stream-cancel-read)))))) + ) + "requires the component model async builtins feature") + +;; stream.drop-readable +(component + (core module $m + (import "" "stream.drop-readable" (func $stream-drop-readable (param i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-drop-readable (canon stream.drop-readable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.drop-readable" (func $stream-drop-readable)))))) +) + +;; stream.drop-readable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.drop-readable" (func $stream-drop-readable (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-drop-readable (canon stream.drop-readable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.drop-readable" (func $stream-drop-readable)))))) + ) + "type mismatch for export `stream.drop-readable` of module instantiation argument ``" +) + +;; stream.cancel-write +(assert_invalid + (component + (core module $m + (import "" "stream.cancel-write" (func $stream-cancel-write (param i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-cancel-write (canon stream.cancel-write $stream-type async)) + (core instance $i (instantiate $m (with "" (instance (export "stream.cancel-write" (func $stream-cancel-write)))))) + ) + "requires the component model async builtins feature") + +;; stream.drop-writable +(component + (core module $m + (import "" "stream.drop-writable" (func $stream-drop-writable (param i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-drop-writable (canon stream.drop-writable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.drop-writable" (func $stream-drop-writable)))))) +) + +;; stream.drop-writable; incorrect type +(assert_invalid + (component + (core module $m + (import "" "stream.drop-writable" (func $stream-drop-writable (param i32 i32) (result i32))) + ) + (type $stream-type (stream u8)) + (core func $stream-drop-writable (canon stream.drop-writable $stream-type)) + (core instance $i (instantiate $m (with "" (instance (export "stream.drop-writable" (func $stream-drop-writable)))))) + ) + "type mismatch for export `stream.drop-writable` of module instantiation argument ``" +) diff --git a/tests/wasm-tools/component-model/async/task-builtins.wast b/tests/wasm-tools/component-model/async/task-builtins.wast new file mode 100644 index 00000000..fa929070 --- /dev/null +++ b/tests/wasm-tools/component-model/async/task-builtins.wast @@ -0,0 +1,441 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async + +;; backpressure.set -- removed from the spec +(assert_malformed + (component quote + "(core func (canon backpressure.set))" + ) + "unexpected token") + +;; backpressure.inc +(component + (core module $m + (import "" "backpressure.inc" (func $backpressure.inc)) + ) + (core func $backpressure.inc (canon backpressure.inc)) + (core instance $i (instantiate $m (with "" (instance (export "backpressure.inc" (func $backpressure.inc)))))) +) + +;; backpressure.inc; incorrect type +(assert_invalid + (component + (core module $m + (import "" "backpressure.inc" (func $backpressure.inc (param i32))) + ) + (core func $backpressure.inc (canon backpressure.inc)) + (core instance $i (instantiate $m (with "" (instance (export "backpressure.inc" (func $backpressure.inc)))))) + ) + "type mismatch for export `backpressure.inc` of module instantiation argument ``" +) + +;; backpressure.dec +(component + (core module $m + (import "" "backpressure.dec" (func $backpressure.dec)) + ) + (core func $backpressure.dec (canon backpressure.dec)) + (core instance $i (instantiate $m (with "" (instance (export "backpressure.dec" (func $backpressure.dec)))))) +) + +;; backpressure.dec; decorrect type +(assert_invalid + (component + (core module $m + (import "" "backpressure.dec" (func $backpressure.dec (param i32))) + ) + (core func $backpressure.dec (canon backpressure.dec)) + (core instance $i (instantiate $m (with "" (instance (export "backpressure.dec" (func $backpressure.dec)))))) + ) + "type mismatch for export `backpressure.dec` of module instantiation argument ``" +) + +;; task.return +(component + (core module $m + (import "" "task.return" (func $task-return (param i32))) + ) + (core func $task-return (canon task.return (result u32))) + (core instance $i (instantiate $m (with "" (instance (export "task.return" (func $task-return)))))) +) + +(assert_invalid + (component (core func $task-return (canon task.return (result u32) async))) + "cannot specify `async` option on `task.return`") + +(assert_invalid + (component + (core func $f (canon backpressure.inc)) + (core func $task-return (canon task.return (result u32) (callback $f))) + ) + "cannot specify callback without async") + +(assert_invalid + (component + (core func $f (canon backpressure.inc)) + (core func $task-return (canon task.return (result u32) (post-return $f))) + ) + "cannot specify `post-return` option on `task.return`") + +(assert_invalid + (component + (core module $m + (func (export "r") (param i32 i32 i32 i32) (result i32) unreachable) + ) + (core instance $m (instantiate $m)) + (core func $task-return (canon task.return (result u32) (realloc (func $m "r")))) + ) + "cannot specify `realloc` option on `task.return`") + +(component + (core module $m + (memory (export "m") 1) + ) + (core instance $i (instantiate $m)) + (core func (canon task.return (result u32) string-encoding=utf8)) + (core func (canon task.return (result u32) string-encoding=utf16)) + (core func (canon task.return (result u32) string-encoding=latin1+utf16)) + (core func (canon task.return (result u32) (memory $i "m"))) +) + +;; task.cancel +(component + (core module $m + (import "" "task.cancel" (func $task-cancel)) + ) + (core func $task-cancel (canon task.cancel)) + (core instance $i (instantiate $m (with "" (instance (export "task.cancel" (func $task-cancel)))))) +) + +;; waitable-set.new +(component + (core module $m (import "" "waitable-set.new" (func (result i32)))) + (core func $waitable-set-new (canon waitable-set.new)) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.new" (func $waitable-set-new)))))) +) + +;; waitable-set.new; incorrect type +(assert_invalid + (component + (core module $m (import "" "waitable-set.new" (func (result i64)))) + (core func $waitable-set-new (canon waitable-set.new)) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.new" (func $waitable-set-new)))))) + ) + "type mismatch for export `waitable-set.new` of module instantiation argument ``" +) + +;; waitable-set.wait +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.wait" (func $waitable-set-wait (param i32 i32) (result i32))) + ) + (core func $waitable-set-wait (canon waitable-set.wait (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) +) + +;; waitable-set.wait; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.wait" (func $waitable-set-wait (param i32) (result i32))) + ) + (core func $waitable-set-wait (canon waitable-set.wait (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) + ) + "type mismatch for export `waitable-set.wait` of module instantiation argument ``" +) + +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.wait" (func $waitable-set-wait (param i32 i32) (result i32))) + ) + (core func $waitable-set-wait (canon waitable-set.wait cancellable (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.wait" (func $waitable-set-wait)))))) + ) + "requires the component model async stackful feature") + +;; waitable-set.poll +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.poll" (func $waitable-set-poll (param i32 i32) (result i32))) + ) + (core func $waitable-set-poll (canon waitable-set.poll (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) +) +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.poll" (func $waitable-set-poll (param i32 i32) (result i32))) + ) + (core func $waitable-set-poll (canon waitable-set.poll cancellable (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) + ) + "requires the component model async stackful feature") + +;; waitable-set.poll; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "waitable-set.poll" (func $waitable-set-poll (param i32) (result i32))) + ) + (core func $waitable-set-poll (canon waitable-set.poll (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.poll" (func $waitable-set-poll)))))) + ) + "type mismatch for export `waitable-set.poll` of module instantiation argument ``" +) + +;; waitable-set.drop +(component + (core module $m (import "" "waitable-set.drop" (func (param i32)))) + (core func $waitable-set-drop (canon waitable-set.drop)) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.drop" (func $waitable-set-drop)))))) +) + +;; waitable-set.drop; incorrect type +(assert_invalid + (component + (core module $m (import "" "waitable-set.drop" (func (param i64)))) + (core func $waitable-set-drop (canon waitable-set.drop)) + (core instance $i (instantiate $m (with "" (instance (export "waitable-set.drop" (func $waitable-set-drop)))))) + ) + "type mismatch for export `waitable-set.drop` of module instantiation argument ``" +) + +;; waitable.join +(component + (core module $m (import "" "waitable.join" (func (param i32 i32)))) + (core func $waitable.join (canon waitable.join)) + (core instance $i (instantiate $m (with "" (instance (export "waitable.join" (func $waitable.join)))))) +) + +;; waitable.join; incorrect type +(assert_invalid + (component + (core module $m (import "" "waitable.join" (func (param i64)))) + (core func $waitable.join (canon waitable.join)) + (core instance $i (instantiate $m (with "" (instance (export "waitable.join" (func $waitable.join)))))) + ) + "type mismatch for export `waitable.join` of module instantiation argument ``" +) + +;; subtask.drop +(component + (core module $m + (import "" "subtask.drop" (func $subtask-drop (param i32))) + ) + (core func $subtask-drop (canon subtask.drop)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.drop" (func $subtask-drop)))))) +) + +;; subtask.drop; incorrect type +(assert_invalid + (component + (core module $m + (import "" "subtask.drop" (func $subtask-drop (param i32) (result i32))) + ) + (core func $subtask-drop (canon subtask.drop)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.drop" (func $subtask-drop)))))) + ) + "type mismatch for export `subtask.drop` of module instantiation argument ``" +) + +;; subtask.cancel +(component + (core module $m + (import "" "subtask.cancel" (func $subtask-cancel (param i32) (result i32))) + ) + (core func $subtask-cancel (canon subtask.cancel)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.cancel" (func $subtask-cancel)))))) +) + +;; subtask.cancel; incorrect type +(assert_invalid + (component + (core module $m + (import "" "subtask.cancel" (func $subtask-cancel (param i32 i32) (result i32))) + ) + (core func $subtask-cancel (canon subtask.cancel)) + (core instance $i (instantiate $m (with "" (instance (export "subtask.cancel" (func $subtask-cancel)))))) + ) + "type mismatch for export `subtask.cancel` of module instantiation argument ``" +) + +;; context.{get,set} +(component + (core func $get0 (canon context.get i32 0)) + (core func $set0 (canon context.set i32 0)) + + (core module $m + (import "" "get0" (func (result i32))) + (import "" "set0" (func (param i32))) + ) + (core instance (instantiate $m + (with "" (instance + (export "get0" (func $get0)) + (export "set0" (func $set0)) + )) + )) +) + +;; thread.yield +(component + (core module $m + (import "" "thread.yield" (func $thread.yield (result i32))) + ) + (core func $thread.yield (canon thread.yield)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) +) + +(assert_invalid + (component + (core module $m + (import "" "thread.yield" (func $thread.yield (result i32))) + ) + (core func $thread.yield (canon thread.yield cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) + ) + "requires the component model async stackful feature") + +;; thread.yield; incorrect type +(assert_invalid + (component + (core module $m + (import "" "thread.yield" (func $thread.yield (param i32) (result i32))) + ) + (core func $thread.yield (canon thread.yield)) + (core instance $i (instantiate $m (with "" (instance (export "thread.yield" (func $thread.yield)))))) + ) + "type mismatch for export `thread.yield` of module instantiation argument ``" +) + +(assert_invalid + (component + (core module $m (import "" "" (func (param i32) (result i32)))) + (core func $f (canon context.get i32 0)) + (core instance $i (instantiate $m (with "" (instance (export "" (func $f)))))) + ) + "found: (func (result i32))") +(assert_invalid + (component + (core module $m (import "" "" (func (param i32) (result i32)))) + (core func $f (canon context.set i32 0)) + (core instance $i (instantiate $m (with "" (instance (export "" (func $f)))))) + ) + "found: (func (param i32))") +(assert_invalid + (component + (core func (canon context.get i32 1))) + "immediate must be zero: 1") +(assert_invalid + (component + (core func (canon context.set i32 1))) + "immediate must be zero: 1") +(assert_invalid + (component + (core func (canon context.get i32 100))) + "immediate must be zero: 100") +(assert_invalid + (component + (core func (canon context.set i32 100))) + "immediate must be zero: 100") +(assert_malformed + (component quote + "(core func (canon context.get i64 100))") + "expected keyword `i32`") +(assert_malformed + (component quote + "(core func (canon context.set i64 100))") + "expected keyword `i32`") + +(assert_malformed + (component binary + "\00asm" "\0d\00\01\00" ;; component header + "\08\04" ;; canonicals section, 4 bytes + "\01" ;; 1 count + "\0a\7e\00") ;; context.get i64 0 + "invalid leading byte (0x7e) for context.get") +(assert_malformed + (component binary + "\00asm" "\0d\00\01\00" ;; component header + "\08\04" ;; canonicals section, 4 bytes + "\01" ;; 1 count + "\0b\7e\00") ;; context.set i64 0 + "invalid leading byte (0x7e) for context.set") + +;; different forms of canonical intrinsics + +(component + (core func (canon backpressure.inc)) + (canon backpressure.inc (core func)) + (core func (canon backpressure.dec)) + (canon backpressure.dec (core func)) + (core func (canon task.return)) + (canon task.return (core func)) + (core func (canon task.cancel)) + (canon task.cancel (core func)) + (core func (canon subtask.drop)) + (canon subtask.drop (core func)) + (core func (canon subtask.cancel)) + (canon subtask.cancel (core func)) + + (core module $m + (memory (export "m") 1) + ) + (core instance $i (instantiate $m)) + (alias core export $i "m" (core memory $m)) + + (type $s (stream)) + (type $f (future)) + (core func (canon future.new $f)) + (canon future.new $f (core func)) + (core func (canon stream.new $s)) + (canon stream.new $s (core func)) + (core func (canon future.cancel-read $f)) + (canon future.cancel-read $f (core func)) + (core func (canon stream.cancel-read $s)) + (canon stream.cancel-read $s (core func)) + (core func (canon future.cancel-write $f)) + (canon future.cancel-write $f (core func)) + (core func (canon stream.cancel-write $s)) + (canon stream.cancel-write $s (core func)) + (core func (canon future.drop-readable $f)) + (canon future.drop-readable $f (core func)) + (core func (canon future.drop-writable $f)) + (canon future.drop-writable $f (core func)) + (core func (canon stream.drop-readable $s)) + (canon stream.drop-readable $s (core func)) + (core func (canon stream.drop-writable $s)) + (canon stream.drop-writable $s (core func)) + (core func (canon future.read $f (memory $m))) + (canon future.read $f (memory $m) (core func)) + (core func (canon future.write $f (memory $m))) + (canon future.write $f (memory $m) (core func)) + (core func (canon stream.read $s (memory $m))) + (canon stream.read $s (memory $m) (core func)) + (core func (canon stream.write $s (memory $m))) + (canon stream.write $s (memory $m) (core func)) + + (core func (canon context.get i32 0)) + (canon context.get i32 0 (core func)) + (core func (canon context.set i32 0)) + (canon context.set i32 0 (core func)) + + (core func (canon thread.yield)) + (canon thread.yield (core func)) +) + +(component + (canon task.return (result (stream u8)) (core func)) +) diff --git a/tests/wasm-tools/component-model/async/threading.wast b/tests/wasm-tools/component-model/async/threading.wast new file mode 100644 index 00000000..9cb6e2a6 --- /dev/null +++ b/tests/wasm-tools/component-model/async/threading.wast @@ -0,0 +1,175 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-async,cm-threading + +;; context.{get,set} 1 +(component + (core func $get1 (canon context.get i32 1)) + (core func $set1 (canon context.set i32 1)) + + (core module $m + (import "" "get1" (func (result i32))) + (import "" "set1" (func (param i32))) + ) + (core instance (instantiate $m + (with "" (instance + (export "get1" (func $get1)) + (export "set1" (func $set1)) + )) + )) +) + +(assert_invalid + (component + (core func (canon context.get i32 2))) + "immediate must be zero or one: 2") +(assert_invalid + (component + (core func (canon context.set i32 2))) + "immediate must be zero or one: 2") +(assert_invalid + (component + (core func (canon context.get i32 100))) + "immediate must be zero or one: 100") +(assert_invalid + (component + (core func (canon context.set i32 100))) + "immediate must be zero or one: 100") + +(assert_invalid + (component + (core module $m (import "" "" (func (param i32) (result i32)))) + (core func $f (canon context.get i32 1)) + (core instance $i (instantiate $m (with "" (instance (export "" (func $f)))))) + ) + "found: (func (result i32))") +(assert_invalid + (component + (core module $m (import "" "" (func (param i32) (result i32)))) + (core func $f (canon context.set i32 1)) + (core instance $i (instantiate $m (with "" (instance (export "" (func $f)))))) + ) + "found: (func (param i32))") + +;; thread.new-indirect +(component + (core type $start (func (param $context i32))) + (core module $libc (table (export "start-table") 1 (ref null func))) + (core instance $libc (instantiate $libc)) + (core func $new-indirect (canon thread.new-indirect $start (table $libc "start-table"))) +) + +(component + (core type $start (func (param $context i32))) + (core module $libc (table (export "start-table") 1 (ref null func))) + (core instance $libc (instantiate $libc)) + (core func $new-indirect (canon thread.new-indirect $start (table $libc "start-table"))) + + (core module $m + (type $new-indirect-ty (func (param i32) (param i32) (result i32))) + (import "" "thread.new-indirect" (func (type $new-indirect-ty))) + ) + + (core instance (instantiate $m + (with "" (instance + (export "thread.new-indirect" (func $new-indirect)) + )) + )) +) + +(assert_invalid + (component + (core type $start (func (param i32))) + ;; Refer to a non-existent table type (i.e., 0); validation + ;; for `thread.new-indirect` happens first. + (core func $new-indirect (canon thread.new-indirect $start (table 0))) + ) + "unknown table 0: table index out of bounds" +) + +(assert_invalid + (component + (core type $start (func)) + (core module $libc (table (export "start-table") 1 (ref null func))) + (core instance $libc (instantiate $libc)) + (core func $new-indirect (canon thread.new-indirect $start (table $libc "start-table"))) + ) + "start function must take a single `i32` argument" +) + +;; thead.index +(component + (core module $m + (import "" "thread.index" (func $thread.index (result i32))) + ) + (core func $thread.index (canon thread.index)) + (core instance $i (instantiate $m (with "" (instance (export "thread.index" (func $thread.index)))))) +) + +;; thread.index; incorrect type +(assert_invalid + (component + (core module $m + (import "" "thread.index" (func $thread.index (param i32) (result i32))) + ) + (core func $thread.index (canon thread.index)) + (core instance $i (instantiate $m (with "" (instance (export "thread.index" (func $thread.index)))))) + ) + "type mismatch for export `thread.index` of module instantiation argument ``" +) + +;; thread.switch-to +(component + (core module $m + (import "" "thread.switch-to" (func $thread.switch-to (param i32) (result i32))) + ) + (core func $thread.switch-to (canon thread.switch-to cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.switch-to" (func $thread.switch-to)))))) +) + +;; thread.switch-to; incorrect type +(assert_invalid + (component + (core module $m + (import "" "thread.switch-to" (func $thread.switch-to (param i32))) + ) + (core func $thread.switch-to (canon thread.switch-to cancellable)) + (core instance $i (instantiate $m (with "" (instance (export "thread.switch-to" (func $thread.switch-to)))))) + ) + "type mismatch for export `thread.switch-to` of module instantiation argument ``" +) + +;; different forms of canonical intrinsics + +(component + (core module $m + (table (export "start-table") 1 (ref null func)) + ) + (core instance $i (instantiate $m)) + (alias core export $i "start-table" (core table $start-table)) + + (core func (canon context.get i32 1)) + (canon context.get i32 1 (core func)) + (core func (canon context.set i32 1)) + (canon context.set i32 1 (core func)) + + (core type $start (func (param i32))) + (core func (canon thread.new-indirect $start (table $start-table))) + (canon thread.new-indirect $start (table $start-table) (core func)) + (core func (canon thread.switch-to)) + (canon thread.switch-to (core func)) + (core func (canon thread.switch-to cancellable)) + (canon thread.switch-to cancellable (core func)) + (core func (canon thread.suspend)) + (canon thread.suspend (core func)) + (core func (canon thread.suspend cancellable)) + (canon thread.suspend cancellable (core func)) + (core func (canon thread.resume-later)) + (canon thread.resume-later (core func)) + (core func (canon thread.yield-to)) + (canon thread.yield-to (core func)) + (core func (canon thread.yield-to cancellable)) + (canon thread.yield-to cancellable (core func)) +) + +(component + (canon task.return (result (stream u8)) (core func)) +) diff --git a/tests/wasm-tools/component-model/big.wast b/tests/wasm-tools/component-model/big.wast index 4fffa972..770df0ab 100644 --- a/tests/wasm-tools/component-model/big.wast +++ b/tests/wasm-tools/component-model/big.wast @@ -1,6 +1,6 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % -(component +(component definition (import "wasi-logging" (instance $logging (export "log" (func (param "msg" string))) )) diff --git a/tests/wasm-tools/component-model/error-context/error-context.wast b/tests/wasm-tools/component-model/error-context/error-context.wast new file mode 100644 index 00000000..ead72e4f --- /dev/null +++ b/tests/wasm-tools/component-model/error-context/error-context.wast @@ -0,0 +1,99 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-error-context + +;; error-context.new +(component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.new" (func $error-context-new (param i32 i32) (result i32))) + ) + (core func $error-context-new (canon error-context.new (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.new" (func $error-context-new)))))) +) + +;; error-context.new; incorrect type +(assert_invalid + (component + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.new" (func $error-context-new (param i32) (result i32))) + ) + (core func $error-context-new (canon error-context.new (memory $libc "memory"))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.new" (func $error-context-new)))))) + ) + "type mismatch for export `error-context.new` of module instantiation argument ``" +) + +;; error-context.debug-message +(component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.debug-message" (func $error-context-debug-message (param i32 i32))) + ) + (core func $error-context-debug-message (canon error-context.debug-message (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.debug-message" (func $error-context-debug-message)))))) +) + +;; error-context.debug-message; incorrect type +(assert_invalid + (component + (core module $libc + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (memory (export "memory") 1) + ) + (core instance $libc (instantiate $libc)) + (core module $m + (import "" "error-context.debug-message" (func $error-context-debug-message (param i32) (result i32))) + ) + (core func $error-context-debug-message (canon error-context.debug-message (memory $libc "memory") (realloc (func $libc "realloc")))) + (core instance $i (instantiate $m (with "" (instance (export "error-context.debug-message" (func $error-context-debug-message)))))) + ) + "type mismatch for export `error-context.debug-message` of module instantiation argument ``" +) + +;; error-context.drop +(component + (core module $m + (import "" "error-context.drop" (func $error-context-drop (param i32))) + ) + (core func $error-context-drop (canon error-context.drop)) + (core instance $i (instantiate $m (with "" (instance (export "error-context.drop" (func $error-context-drop)))))) +) + +;; error-context.drop; incorrect type +(assert_invalid + (component + (core module $m + (import "" "error-context.drop" (func $error-context-drop (param i32) (result i32))) + ) + (core func $error-context-drop (canon error-context.drop)) + (core instance $i (instantiate $m (with "" (instance (export "error-context.drop" (func $error-context-drop)))))) + ) + "type mismatch for export `error-context.drop` of module instantiation argument ``" +) + +;; can define the `error-context` type +(component (type error-context)) +(component (type (list error-context))) + +(component + (core module $m + (memory (export "m") 1) + (func (export "r") (param i32 i32 i32 i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + (alias core export $i "m" (core memory $m)) + (alias core export $i "r" (core func $r)) + + (core func (canon error-context.new (memory $m))) + (canon error-context.new (memory $m) (core func)) + (core func (canon error-context.debug-message (memory $m) (realloc $r))) + (canon error-context.debug-message (memory $m) (realloc $r) (core func)) + (core func (canon error-context.drop)) + (canon error-context.drop (core func)) +) diff --git a/tests/wasm-tools/component-model/export-ascription.wast b/tests/wasm-tools/component-model/export-ascription.wast index ca96f8fa..ca70736e 100644 --- a/tests/wasm-tools/component-model/export-ascription.wast +++ b/tests/wasm-tools/component-model/export-ascription.wast @@ -1,14 +1,18 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % (component - (import "f" (func $f)) - (export "f2" (func $f) (func)) + (component + (import "f" (func $f)) + (export "f2" (func $f) (func)) + ) ) ;; subtyping works (component - (import "f" (instance $i (export "f" (func)))) - (export "f2" (instance $i) (instance)) + (component + (import "f" (instance $i (export "f" (func)))) + (export "f2" (instance $i) (instance)) + ) ) ;; make sure subtyping works in the right direction diff --git a/tests/wasm-tools/component-model/export-introduces-alias.wast b/tests/wasm-tools/component-model/export-introduces-alias.wast index ac7d32a9..40d33efa 100644 --- a/tests/wasm-tools/component-model/export-introduces-alias.wast +++ b/tests/wasm-tools/component-model/export-introduces-alias.wast @@ -1,10 +1,12 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % (component - (import "x" (func $f)) + (component + (import "x" (func $f)) - (export $g "g" (func $f)) - (export $g2 "g2" (func $g)) + (export $g "g" (func $f)) + (export $g2 "g2" (func $g)) + ) ) (component diff --git a/tests/wasm-tools/component-model/export.wast b/tests/wasm-tools/component-model/export.wast index 7c8a9276..73d094c8 100644 --- a/tests/wasm-tools/component-model/export.wast +++ b/tests/wasm-tools/component-model/export.wast @@ -1,4 +1,4 @@ -;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-values +;; RUN: wast --assert default --snapshot tests/snapshots % (assert_invalid (component (export "" (instance 0))) @@ -16,42 +16,33 @@ (component (export "" (func 0))) "index out of bounds") -(assert_invalid - (component (export "" (value 0))) - "index out of bounds") - (component - (import "a" (instance $i)) - (import "b" (core module $m)) - (import "c" (component $c)) - (import "d" (value $v string)) - (import "e" (func $f)) - - (export "f" (instance $i)) - (export "g" (core module $m)) - (export "h" (component $c)) - (export "i" (value $v)) - (export "j" (func $f)) -) - -(assert_invalid (component - (import "a" (value $v string)) - (export "b" (value $v)) - (export "c" (value $v)) + (import "a" (instance $i)) + (import "b" (core module $m)) + (import "c" (component $c)) + (import "e" (func $f)) + + (export "f" (instance $i)) + (export "g" (core module $m)) + (export "h" (component $c)) + (export "j" (func $f)) ) - "cannot be used more than once") - +) (component - (import "a" (func)) - (export (interface "wasi:http/types@2.0.0") (func 0)) + (component + (import "a" (func)) + (export (interface "wasi:http/types@2.0.0") (func 0)) + ) ) ;; import/exports can overlap on ids (component - (import (interface "wasi:http/types@2.0.0") (func)) - (export (interface "wasi:http/types@2.0.0") (func 0)) + (component + (import (interface "wasi:http/types@2.0.0") (func)) + (export (interface "wasi:http/types@2.0.0") (func 0)) + ) ) ;; cannot export some types of strings diff --git a/tests/wasm-tools/component-model/func.wast b/tests/wasm-tools/component-model/func.wast index c2009892..4a207f99 100644 --- a/tests/wasm-tools/component-model/func.wast +++ b/tests/wasm-tools/component-model/func.wast @@ -1,12 +1,12 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % -(component +(component definition (import "a" (func (param "foo" string))) (import "b" (func (param "foo" string) (param "bar" s32) (param "baz" u32))) (import "c" (func (result (tuple u8)))) ) -(component +(component definition (import "a" (func)) (import "b" (func (param "p1" string))) (import "c" (func (result u32))) @@ -36,7 +36,7 @@ "canonical option `memory` is required" ) -(component +(component definition (import "a" (func $log (param "msg" string))) (core module $libc (memory (export "memory") 1) diff --git a/tests/wasm-tools/component-model/gc/core-type.wat b/tests/wasm-tools/component-model/gc/core-type.wat new file mode 100644 index 00000000..ddb9e284 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/core-type.wat @@ -0,0 +1,220 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (result u32))) + (core func (canon lower (func $f) gc)) + ) + "cannot specify `gc` without also specifying a `core-type` for lowerings" +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (result u32))) + (core type $ty (func (param i32) (result i32))) + (core func (canon lower (func $f) gc gc (core-type $ty))) + ) + "canonical option `gc` is specified more than once" +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (result u32))) + (core type $ty (func (param i32) (result i32))) + (core func (canon lower (func $f) (core-type $ty))) + ) + "cannot specify `core-type` without `gc`" +) + +(component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + (core type $ty (func (param i32 i32) (result i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (core module $m + (memory (export "memory") 1) + (func (export "f") (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (core type $ty (func (result i32))) + (func (export "f") (result u32) + (canon lift (core func $i "f") gc (core-type $ty)) + ) + ) + "canonical option `core-type` is not allowed in `canon lift`" +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + (core type $ty (func (param i64 i32) (result i32))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u32` type to core `i32` type, found `i64`" +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + (core type $ty (func (param i32 i32) (result i64))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u32` type to core `i32` type, found `i64`" +) + +(component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core rec + (type (func)) + (type $ty (func (param i32 i32) (result i32))) + ) + + (core func $f (canon lower (func $f) gc (core-type $ty))) + + (core module $m + (rec + (type (func)) + (type $ty (func (param i32 i32) (result i32))) + ) + (import "a" "b" (func (type $ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) +) + +;; Satisfying an import with the "same" type but from the wrong rec group should +;; fail. +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $ty (func (param i32 i32) (result i32))) + (core func $f (canon lower (func $f) gc (core-type $ty))) + + (core module $m + (rec + (type (func)) + (type $ty (func (param i32 i32) (result i32))) + ) + (import "a" "b" (func (type $ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) + ) + "type mismatch for export `b` of module instantiation argument `a`" +) + +;; Satisfying an import with an exact subtype should succeed. +(component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $super_ty (sub (func (param i32 i32) (result i32)))) + (core type $sub_ty (sub $super_ty (func (param i32 i32) (result i32)))) + + (core func $f (canon lower (func $f) gc (core-type $sub_ty))) + + (core module $m + (type $super_ty (sub (func (param i32 i32) (result i32)))) + (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32)))) + (import "a" "b" (func (type $sub_ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) +) + +;; Satisfying an import with a subtype should succeed. +(component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $super_ty (sub (func (param i32 i32) (result i32)))) + (core type $sub_ty (sub $super_ty (func (param i32 i32) (result i32)))) + + (core func $f (canon lower (func $f) gc (core-type $sub_ty))) + + (core module $m + (type $super_ty (sub (func (param i32 i32) (result i32)))) + (import "a" "b" (func (type $super_ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) +) + +;; Satisfying an import with the "same" type but from a different subtyping +;; hierarchy should fail. +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $ty (func (param i32 i32) (result i32))) + (core func $f (canon lower (func $f) gc (core-type $ty))) + + (core module $m + (type $super_ty (sub (func (param i32 i32) (result i32)))) + (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32)))) + (import "a" "b" (func (type $sub_ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) + ) + "type mismatch for export `b` of module instantiation argument `a`" +) + +;; Satisfying an import with the "same" type but is a supertype, rather than a +;; subtype, should fail. +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $super_ty (sub (func (param i32 i32) (result i32)))) + (core func $f (canon lower (func $f) gc (core-type $super_ty))) + + (core module $m + (type $super_ty (sub (func (param i32 i32) (result i32)))) + (type $sub_ty (sub $super_ty (func (param i32 i32) (result i32)))) + (import "a" "b" (func (type $sub_ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) + ) + "type mismatch for export `b` of module instantiation argument `a`" +) + +;; Satisfying an import with the "same" type but with the wrong finality should +;; fail. +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $ty (sub final (func (param i32 i32) (result i32)))) + (core func $f (canon lower (func $f) gc (core-type $ty))) + + (core module $m + (type $ty (sub (func (param i32 i32) (result i32)))) + (import "a" "b" (func (type $ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) + ) + "type mismatch for export `b` of module instantiation argument `a`" +) +(assert_invalid + (component + (import "f" (func $f (param "x" u32) (param "y" u32) (result u32))) + + (core type $ty (sub (func (param i32 i32) (result i32)))) + (core func $f (canon lower (func $f) gc (core-type $ty))) + + (core module $m + (type $ty (sub final (func (param i32 i32) (result i32)))) + (import "a" "b" (func (type $ty))) + ) + + (core instance (instantiate $m (with "a" (instance (export "b" (func $f)))))) + ) + "type mismatch for export `b` of module instantiation argument `a`" +) diff --git a/tests/wasm-tools/component-model/gc/enum-types.wast b/tests/wasm-tools/component-model/gc/enum-types.wast new file mode 100644 index 00000000..061c6141 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/enum-types.wast @@ -0,0 +1,26 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (type $enum (enum "a" "b" "c")) + + (core type $ty (func (param i32))) + + (import "i" (instance $i + (export "ty" (type $enum' (eq $enum))) + (export "f" (func (param "x" $enum'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (type $enum (enum "a" "b" "c")) + + (core type $ty (func (param anyref))) + + (import "i" (instance $i + (export "ty" (type $enum' (eq $enum))) + (export "f" (func (param "x" $enum'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `enum` type into core `i32` type, but found `anyref`" +) diff --git a/tests/wasm-tools/component-model/gc/error-context.wast b/tests/wasm-tools/component-model/gc/error-context.wast new file mode 100644 index 00000000..1d2b84c1 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/error-context.wast @@ -0,0 +1,20 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc,cm-error-context + +(component + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "f" (func (param "x" error-context))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +(assert_invalid + (component + (core type $ty (func (param anyref))) + + (import "i" (instance $i + (export "f" (func (param "x" error-context))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `error-context` type into core `(ref null? extern)` type, but found `anyref`" +) diff --git a/tests/wasm-tools/component-model/gc/flag-types.wast b/tests/wasm-tools/component-model/gc/flag-types.wast new file mode 100644 index 00000000..cbe02e16 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/flag-types.wast @@ -0,0 +1,26 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (type $flags (flags "a" "b" "c")) + + (core type $ty (func (param i32))) + + (import "i" (instance $i + (export "ty" (type $flags' (eq $flags))) + (export "f" (func (param "x" $flags'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (type $flags (flags "a" "b" "c")) + + (core type $ty (func (param anyref))) + + (import "i" (instance $i + (export "ty" (type $flags' (eq $flags))) + (export "f" (func (param "x" $flags'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `flags` type into core `i32` type, but found `anyref`" +) diff --git a/tests/wasm-tools/component-model/gc/futures.wast b/tests/wasm-tools/component-model/gc/futures.wast new file mode 100644 index 00000000..6ebfe50c --- /dev/null +++ b/tests/wasm-tools/component-model/gc/futures.wast @@ -0,0 +1,24 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc,cm-async + +(component + (type $future (future bool)) + + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "f" (func (param "x" $future))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (type $future (future bool)) + + (core type $ty (func (param anyref))) + + (import "i" (instance $i + (export "f" (func (param "x" $future))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `future` type into core `(ref null? extern)` type, but found `anyref`" +) diff --git a/tests/wasm-tools/component-model/gc/gc.wast b/tests/wasm-tools/component-model/gc/gc.wast new file mode 100644 index 00000000..b69ce214 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/gc.wast @@ -0,0 +1,165 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % + +(component binary + "\00asm" "\0d\00\01\00" ;; component header + "\03\07" ;; core type section, 7 bytes large + "\01" ;; 1 count + "\00\50" ;; sub type + "\00" ;; no supertypes + "\60" ;; function type + "\00\00" ;; no parameters, no results +) + +;; text equivalent of above +(component (core type (sub (func)))) + +(component binary + "\00asm" "\0d\00\01\00" ;; component header + "\03\06" ;; core type section, 6 bytes large + "\02" ;; 2 count + "\50" ;; module type + "\00" ;; empty + "\60" ;; function type + "\00\00" ;; no parameters, no results +) + +;; text equivalent of above +(component (core type (module)) (core type (func))) + +(component binary + "\00asm" "\0d\00\01\00" ;; component header + "\03\09" ;; core type section, 9 bytes large + "\01" ;; 1 count + "\50" ;; module type + "\01" ;; 1 count + "\01" ;; core type in module + "\50" ;; sub type + "\00" ;; no supertypes + "\60" ;; function type + "\00\00" ;; no parameters, no results +) + +;; text equivalent of above +(component (core type (module (type (sub (func)))))) + +(assert_malformed + (component binary + "\00asm" "\0d\00\01\00" ;; component header + "\03\06" ;; core type section, 6 bytes large + "\01" ;; 1 count + "\50" ;; attempted sub type, but actually a module type + "\00" ;; attempted zero super types, but actually empty + "\60" ;; function type + "\00\00" ;; no parameters, no results + ) +"unexpected data at the end of the section") + +(assert_malformed + (component binary + "\00asm" "\0d\00\01\00" ;; component header + "\03\05" ;; core type section, 5 bytes large + "\01" ;; 1 count + "\00\60" ;; attempted function type with invalid prefix + "\00\00" ;; no parameters, no results + ) +"invalid leading byte (0x60) for non-final sub type") + + +;; test various shapes and properties of GC types within a component +(component $C + (core type $t1 (struct)) + (core type $t2 (array i8)) + (core type $t3 (func)) + (core type (struct + (field $a (ref $t1)) + (field $b (ref $t2)) + (field $c (ref $t3)) + )) + (core type $s1 (sub (struct))) + (core type $s2 (sub final (struct))) + (core type $s3 (sub $s1 (struct))) + (core type $f (func (result (ref $f)))) + (core rec) + (core rec + (type $f1 (func (result (ref $f2)))) + (type $f2 (func (result (ref $f1)))) + ) + + (core type (module + (alias outer $C $t1 (type $t1)) + (import "x" "x" (func (result (ref $t1)))) + + (type $a1 (struct (field $a (ref $t1)))) + (type $a2 (array (ref $a1))) + (type $a3 (func (result (ref $a2)))) + (type $a4 (sub (struct))) + (type $a5 (sub final (struct))) + (rec) + (rec + (type $f1 (func (result (ref $f2)))) + (type $f2 (func (result (ref $f1)))) + ) + (type $f (func (result (ref $f)))) + )) +) + +;; aliases don't work within core types +(assert_malformed + (component quote + "(core type $t1 (struct))" + "(core type (module (type (sub $t1 (struct)))))" + ) + "unknown core type: failed to find name `$t1`") + +(assert_invalid + (component $C + (core type $t1 (struct)) + (core type (sub $t1 (struct))) + ) + "sub type cannot have a final super type") + +(assert_invalid + (component $C + (core type (module + (type $t1 (struct)) + (type (sub $t1 (struct))) + )) + ) + "sub type cannot have a final super type") + +(component + (type $t (resource (rep i32))) + (core func (canon resource.drop $t)) + (core module + (type $t1 (sub (func))) + (type (sub $t1 (func))) + ) +) + +;; test components with non-mvp types +(component definition + ;; all abstract heap types work + (core type (func (param (ref any)))) + (core type (func (param (ref func)))) + (core type (func (param (ref extern)))) + (core type (func (param (ref exn)))) + (core type (func (param (ref noexn)))) + (core type (func (param (ref eq)))) + (core type (func (param (ref struct)))) + (core type (func (param (ref array)))) + (core type (func (param (ref nofunc)))) + (core type (func (param (ref noextern)))) + (core type (func (param (ref none)))) + (core type (func (param (ref i31)))) + + ;; some shorthands work + (core type (func (param anyref))) + (core type (func (param eqref))) + + ;; simd types work + (core type (func (param v128))) + + ;; types-pointing-to-types works + (core type $t (func)) + (core type (func (param (ref $t)))) +) diff --git a/tests/wasm-tools/component-model/gc/lift-func-many-params.wast b/tests/wasm-tools/component-model/gc/lift-func-many-params.wast new file mode 100644 index 00000000..ce6363a3 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/lift-func-many-params.wast @@ -0,0 +1,73 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (core module $m + (type $string (array i8)) + (type $list (array i64)) + (type $record (struct (field i32) (field f32))) + (type $tuple (struct (field i8) (field i16))) + + (func (export "f") (param i32 ;; bool + i32 ;; s8 + i32 ;; u8 + i32 ;; s16 + i32 ;; u16 + i32 ;; s32 + i32 ;; u32 + i64 ;; s64 + i64 ;; u64 + f32 ;; f32 + f64 ;; f64 + (ref $string) ;; string + i32 ;; enum + i32 ;; flags + (ref $list) ;; list + (ref $record) ;; record + (ref extern) ;; (own resource) + (ref extern) ;; (borrow resource) + (ref $tuple) ;; tuple + )) + ) + + (core instance $i (instantiate $m)) + + (type $enum' (enum "a" "b" "c")) + (export $enum "enum" (type $enum')) + + (type $flags' (flags "d" "e" "f")) + (export $flags "flags" (type $flags')) + + (type $list' (list u64)) + (export $list "list" (type $list')) + + (type $record' (record (field "g" u32) (field "h" f32))) + (export $record "record" (type $record')) + + (type $resource' (resource (rep i32))) + (export $resource "resource" (type $resource')) + + (type $tuple' (tuple u8 u16)) + (export $tuple "tuple" (type $tuple')) + + (func (export "f") (param "p0" bool) + (param "p1" s8) + (param "p2" u8) + (param "p3" s16) + (param "p4" u16) + (param "p5" s32) + (param "p6" u32) + (param "p7" s64) + (param "p8" u64) + (param "p9" f32) + (param "p10" f64) + (param "p11" string) + (param "p12" $enum) + (param "p13" $flags) + (param "p14" $list) + (param "p15" $record) + (param "p16" (own $resource)) + (param "p17" (borrow $resource)) + (param "p18" $tuple) + (canon lift (core func $i "f") gc) + ) +) diff --git a/tests/wasm-tools/component-model/gc/lift-func-no-params.wast b/tests/wasm-tools/component-model/gc/lift-func-no-params.wast new file mode 100644 index 00000000..c78bb12f --- /dev/null +++ b/tests/wasm-tools/component-model/gc/lift-func-no-params.wast @@ -0,0 +1,13 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component $A + (core module $m + (func (export "f")) + ) + + (core instance $i (instantiate $m)) + + (func (export "f") + (canon lift (core func $i "f") gc) + ) +) diff --git a/tests/wasm-tools/component-model/gc/list-types.wast b/tests/wasm-tools/component-model/gc/list-types.wast new file mode 100644 index 00000000..f8fa7bf0 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/list-types.wast @@ -0,0 +1,28 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (type $list (list string)) + + (core type $string (array i8)) + (core type $list (array (ref $string))) + (core type $ty (func (param (ref $list)))) + + (import "i" (instance $i + (export "ty" (type $list' (eq $list))) + (export "f" (func (param "x" $list'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (type $list (list string)) + + (core type $ty (func (param i32))) + + (import "i" (instance $i + (export "ty" (type $list' (eq $list))) + (export "f" (func (param "x" $list'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `list` type into `(ref null? (array ...))`, but found `i32`" +) diff --git a/tests/wasm-tools/component-model/gc/option-types.wast b/tests/wasm-tools/component-model/gc/option-types.wast new file mode 100644 index 00000000..9fc3fc60 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/option-types.wast @@ -0,0 +1,86 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +;; Basic. +(component + (type $opt (option u32)) + + (core type $opt (struct)) + (core type $ty (func (param (ref $opt)))) + + (import "i" (instance $i + (export "ty" (type $opt' (eq $opt))) + (export "f" (func (param "x" $opt'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a nullable reference. +(component + (type $opt (option u32)) + + (core type $opt (struct)) + (core type $ty (func (param (ref null $opt)))) + + (import "i" (instance $i + (export "ty" (type $opt' (eq $opt))) + (export "f" (func (param "x" $opt'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a custom rec group. +(component + (type $opt (option u32)) + + (core rec + (type $opt (struct)) + (type $ty (func (param (ref $opt))))) + + (import "i" (instance $i + (export "ty" (type $opt' (eq $opt))) + (export "f" (func (param "x" $opt'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a custom subtype. +(component + (type $opt (option u32)) + + (core type $base (sub (struct))) + (core type $opt (sub $base (struct))) + (core type $ty (func (param (ref $opt)))) + + (import "i" (instance $i + (export "ty" (type $opt' (eq $opt))) + (export "f" (func (param "x" $opt'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; Unexpected field. +(assert_invalid + (component + (type $opt (option u32)) + + (core type $opt (struct (field i32))) + (core type $ty (func (param (ref $opt)))) + + (import "i" (instance $i + (export "ty" (type $opt' (eq $opt))) + (export "f" (func (param "x" $opt'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) + ) + "expected to lower component `option` type to core `(ref null? (struct))`" +) + +;; Lowering into a non-struct. +(assert_invalid + (component + (type $opt (option u32)) + + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "ty" (type $opt' (eq $opt))) + (export "f" (func (param "x" $opt'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) + ) + "expected to lower component `option` type to core `(ref null? (struct))`" +) diff --git a/tests/wasm-tools/component-model/gc/primitive-types.wast b/tests/wasm-tools/component-model/gc/primitive-types.wast new file mode 100644 index 00000000..40bc325f --- /dev/null +++ b/tests/wasm-tools/component-model/gc/primitive-types.wast @@ -0,0 +1,517 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +;; bool + +(component + (import "f" (func $f (param "x" bool))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" bool))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `bool` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list bool)))) + (core type $list (array i8)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list bool)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `bool` type to core `i8` type, found `i32`" +) + +;; u8 + +(component + (import "f" (func $f (param "x" u8))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u8))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u8` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list u8)))) + (core type $list (array i8)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list u8)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u8` type to core `i8` type, found `i32`" +) + +;; s8 + +(component + (import "f" (func $f (param "x" s8))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" s8))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s8` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list s8)))) + (core type $list (array i8)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list s8)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s8` type to core `i8` type, found `i32`" +) + +;; u16 + +(component + (import "f" (func $f (param "x" u16))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u16))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u16` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list u16)))) + (core type $list (array i16)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list u16)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u16` type to core `i16` type, found `i32`" +) + +;; s16 + +(component + (import "f" (func $f (param "x" s16))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" s16))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s16` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list s16)))) + (core type $list (array i16)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list s16)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s16` type to core `i16` type, found `i32`" +) + +;; u32 + +(component + (import "f" (func $f (param "x" u32))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u32))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u32` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list u32)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list u32)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u32` type to core `i32` type, found `anyref`" +) + +;; s32 + +(component + (import "f" (func $f (param "x" s32))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" s32))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s32` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list s32)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list s32)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s32` type to core `i32` type, found `anyref`" +) + +;; u64 + +(component + (import "f" (func $f (param "x" u64))) + (core type $ty (func (param i64))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" u64))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u64` type to core `i64` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list u64)))) + (core type $list (array i64)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list u64)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `u64` type to core `i64` type, found `anyref`" +) + +;; s64 + +(component + (import "f" (func $f (param "x" s64))) + (core type $ty (func (param i64))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" s64))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s64` type to core `i64` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list s64)))) + (core type $list (array i64)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list s64)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `s64` type to core `i64` type, found `anyref`" +) + +;; f32 + +(component + (import "f" (func $f (param "x" f32))) + (core type $ty (func (param f32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" f32))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `f32` type to core `f32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list f32)))) + (core type $list (array f32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list f32)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `f32` type to core `f32` type, found `anyref`" +) + +;; f64 + +(component + (import "f" (func $f (param "x" f64))) + (core type $ty (func (param f64))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" f64))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `f64` type to core `f64` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list f64)))) + (core type $list (array f64)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list f64)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `f64` type to core `f64` type, found `anyref`" +) + +;; char + +(component + (import "f" (func $f (param "x" char))) + (core type $ty (func (param i32))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" char))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `char` type to core `i32` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list char)))) + (core type $list (array i32)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list char)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc (core-type $ty))) + ) + "expected to lower component `char` type to core `i32` type, found `anyref`" +) + +;; utf8 strings + +(component + (import "f" (func $f (param "x" string))) + (core type $s (array i8)) + (core type $ty (func (param (ref $s)))) + (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" string))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list string)))) + (core type $s (array i8)) + (core type $list (array (ref $s))) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list string)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`" +) + +;; utf16 strings + +(component + (import "f" (func $f (param "x" string))) + (core type $s (array i16)) + (core type $ty (func (param (ref $s)))) + (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" string))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty))) + ) + "expected to lower component `string` type to core `(ref null? (array (mut? i16)))` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list string)))) + (core type $s (array i16)) + (core type $list (array (ref $s))) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list string)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc string-encoding=utf16 (core-type $ty))) + ) + "expected to lower component `string` type to core `(ref null? (array (mut? i16)))` type, found `anyref`" +) + +;; compact utf16 strings + +(component + (import "f" (func $f (param "x" string))) + (core type $s (array i8)) + (core type $ty (func (param (ref $s)))) + (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" string))) + (core type $ty (func (param anyref))) + (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty))) + ) + "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`" +) + +(component + (import "f" (func $f (param "x" (list string)))) + (core type $s (array i8)) + (core type $list (array (ref $s))) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty))) +) + +(assert_invalid + (component + (import "f" (func $f (param "x" (list string)))) + (core type $list (array anyref)) + (core type $ty (func (param (ref $list)))) + (core func (canon lower (func $f) gc string-encoding=latin1+utf16 (core-type $ty))) + ) + "expected to lower component `string` type to core `(ref null? (array (mut? i8)))` type, found `anyref`" +) diff --git a/tests/wasm-tools/component-model/gc/record-types.wast b/tests/wasm-tools/component-model/gc/record-types.wast new file mode 100644 index 00000000..9c2dad9a --- /dev/null +++ b/tests/wasm-tools/component-model/gc/record-types.wast @@ -0,0 +1,85 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (type $record (record (field "a" bool) + (field "b" u8) + (field "c" s8) + (field "d" u16) + (field "e" s16) + (field "f" u32) + (field "g" s32) + (field "h" u64) + (field "i" s64) + (field "j" f32) + (field "k" f64) + (field "l" char) + (field "m" string))) + + (core type $string (array i8)) + (core type $record (struct (field i8) + (field i8) + (field i8) + (field i16) + (field i16) + (field i32) + (field i32) + (field i64) + (field i64) + (field f32) + (field f64) + (field i32) + (field (ref $string)))) + (core type $ty (func (param (ref $record)))) + + (import "i" (instance $i + (export "ty" (type $record' (eq $record))) + (export "f" (func (param "x" $record'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (type $record (record (field "a" bool))) + + (core type $record (struct (field i8) + (field i8))) + (core type $ty (func (param (ref $record)))) + + (import "i" (instance $i + (export "ty" (type $record' (eq $record))) + (export "f" (func (param "x" $record'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "core `struct` has 2 fields, but component `record` has 1 fields" +) + +(assert_invalid + (component + (type $record (record (field "a" bool) + (field "b" bool))) + + (core type $record (struct (field i8))) + (core type $ty (func (param (ref $record)))) + + (import "i" (instance $i + (export "ty" (type $record' (eq $record))) + (export "f" (func (param "x" $record'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "core `struct` has 1 fields, but component `record` has 2 fields" +) + +(assert_invalid + (component + (type $record (record (field "a" bool) + (field "b" bool))) + + (core type $ty (func (param i32))) + + (import "i" (instance $i + (export "ty" (type $record' (eq $record))) + (export "f" (func (param "x" $record'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `record` type to core `(ref null? (struct ...))`, but found `i32`" +) diff --git a/tests/wasm-tools/component-model/gc/resource-types.wast b/tests/wasm-tools/component-model/gc/resource-types.wast new file mode 100644 index 00000000..a56b1c4d --- /dev/null +++ b/tests/wasm-tools/component-model/gc/resource-types.wast @@ -0,0 +1,43 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "r" (type $resource (sub resource))) + (export "f" (func (param "x" (own $resource)))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (core type $ty (func (param anyref))) + + (import "i" (instance $i + (export "r" (type $resource (sub resource))) + (export "f" (func (param "x" (own $resource)))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `own` type into core `(ref null? extern)` type, but found `anyref`" +) + +(component + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "r" (type $resource (sub resource))) + (export "f" (func (param "x" (borrow $resource)))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (core type $ty (func (param anyref))) + + (import "i" (instance $i + (export "r" (type $resource (sub resource))) + (export "f" (func (param "x" (borrow $resource)))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `borrow` type into core `(ref null? extern)` type, but found `anyref`" +) diff --git a/tests/wasm-tools/component-model/gc/result-types.wast b/tests/wasm-tools/component-model/gc/result-types.wast new file mode 100644 index 00000000..e8a02ae8 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/result-types.wast @@ -0,0 +1,86 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +;; Basic. +(component + (type $result (result u32 (error u8))) + + (core type $result (struct)) + (core type $ty (func (param (ref $result)))) + + (import "i" (instance $i + (export "ty" (type $result' (eq $result))) + (export "f" (func (param "x" $result'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a nullable reference. +(component + (type $result (result u32 (error u8))) + + (core type $result (struct)) + (core type $ty (func (param (ref null $result)))) + + (import "i" (instance $i + (export "ty" (type $result' (eq $result))) + (export "f" (func (param "x" $result'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a custom rec group. +(component + (type $result (result u32 (error u8))) + + (core rec + (type $result (struct)) + (type $ty (func (param (ref null $result))))) + + (import "i" (instance $i + (export "ty" (type $result' (eq $result))) + (export "f" (func (param "x" $result'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a custom subtype. +(component + (type $result (result u32 (error u8))) + + (core type $base (sub (struct))) + (core type $result (sub $base (struct))) + (core type $ty (func (param (ref null $result)))) + + (import "i" (instance $i + (export "ty" (type $result' (eq $result))) + (export "f" (func (param "x" $result'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; Unexpected fields in struct type. +(assert_invalid + (component + (type $result (result u32 (error u8))) + + (core type $result (struct (field i8) (field i8))) + (core type $ty (func (param (ref $result)))) + + (import "i" (instance $i + (export "ty" (type $result' (eq $result))) + (export "f" (func (param "x" $result'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) + ) + "expected to lower component `result` type to core `(ref null? (struct))`" +) + +;; Lowering into a non-struct. +(assert_invalid + (component + (type $result (result u32 (error u8))) + + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "ty" (type $result' (eq $result))) + (export "f" (func (param "x" $result'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) + ) + "expected to lower component `result` type to core `(ref null? (struct))`" +) diff --git a/tests/wasm-tools/component-model/gc/tuple-types.wast b/tests/wasm-tools/component-model/gc/tuple-types.wast new file mode 100644 index 00000000..4a9456c9 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/tuple-types.wast @@ -0,0 +1,63 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +(component + (type $tup (tuple string bool)) + + (core type $string (array i8)) + (core type $tup (struct (field (ref $string)) + (field i8))) + (core type $ty (func (param (ref $tup)))) + + (import "i" (instance $i + (export "ty" (type $tup' (eq $tup))) + (export "f" (func (param "x" $tup'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) +) + +(assert_invalid + (component + (type $tup (tuple string bool)) + + (core type $string (array i8)) + (core type $tup (struct (field (ref $string)))) + (core type $ty (func (param (ref $tup)))) + + (import "i" (instance $i + (export "ty" (type $tup' (eq $tup))) + (export "f" (func (param "x" $tup'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "core `struct` has 1 fields, but component `tuple` has 2 fields" +) + +(assert_invalid + (component + (type $tup (tuple string bool)) + + (core type $string (array i8)) + (core type $tup (struct (field (ref $string)) + (field i8) + (field i8))) + (core type $ty (func (param (ref $tup)))) + + (import "i" (instance $i + (export "ty" (type $tup' (eq $tup))) + (export "f" (func (param "x" $tup'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "core `struct` has 3 fields, but component `tuple` has 2 fields" +) + +(assert_invalid + (component + (type $tup (tuple string bool)) + + (core type $ty (func (param i32))) + + (import "i" (instance $i + (export "ty" (type $tup' (eq $tup))) + (export "f" (func (param "x" $tup'))))) + (core func (canon lower (func $i "f") gc string-encoding=utf8 (core-type $ty))) + ) + "expected to lower component `tuple` type to core `(ref null? (struct ...))`, but found `i32`" +) diff --git a/tests/wasm-tools/component-model/gc/variant-types.wast b/tests/wasm-tools/component-model/gc/variant-types.wast new file mode 100644 index 00000000..4a66c066 --- /dev/null +++ b/tests/wasm-tools/component-model/gc/variant-types.wast @@ -0,0 +1,98 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f gc,cm-gc + +;; Basic. +(component + (type $variant (variant (case "a" bool) + (case "b" bool) + (case "c" u8))) + + (core type $variant (struct)) + (core type $ty (func (param (ref $variant)))) + + (import "i" (instance $i + (export "ty" (type $variant' (eq $variant))) + (export "f" (func (param "x" $variant'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a nullable reference. +(component + (type $variant (variant (case "a" bool) + (case "b" bool) + (case "c" u8))) + + (core type $variant (struct)) + (core type $ty (func (param (ref null $variant)))) + + (import "i" (instance $i + (export "ty" (type $variant' (eq $variant))) + (export "f" (func (param "x" $variant'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a custom rec group. +(component + (type $variant (variant (case "a" bool) + (case "b" bool) + (case "c" u8))) + + (core rec + (type $variant (struct)) + (type $ty (func (param (ref null $variant))))) + + (import "i" (instance $i + (export "ty" (type $variant' (eq $variant))) + (export "f" (func (param "x" $variant'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; With a custom subtype. +(component + (type $variant (variant (case "a" bool) + (case "b" bool) + (case "c" u8))) + + (core type $base (sub (struct))) + (core type $variant (sub $base (struct))) + (core type $ty (func (param (ref null $variant)))) + + (import "i" (instance $i + (export "ty" (type $variant' (eq $variant))) + (export "f" (func (param "x" $variant'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) +) + +;; Unexpected field in struct. +(assert_invalid + (component + (type $variant (variant (case "a" bool) + (case "b" bool) + (case "c" u8))) + + (core type $variant (struct (field i8))) + (core type $ty (func (param (ref $variant)))) + + (import "i" (instance $i + (export "ty" (type $variant' (eq $variant))) + (export "f" (func (param "x" $variant'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) + ) + "expected to lower component `variant` type to core `(ref null? (struct))`" +) + +;; Lowering into a non-struct. +(assert_invalid + (component + (type $variant (variant (case "a" bool) + (case "b" bool) + (case "c" u8))) + + (core type $ty (func (param externref))) + + (import "i" (instance $i + (export "ty" (type $variant' (eq $variant))) + (export "f" (func (param "x" $variant'))))) + (core func (canon lower (func $i "f") gc (core-type $ty))) + ) + "expected to lower component `variant` type to core `(ref null? (struct))`" +) diff --git a/tests/wasm-tools/component-model/import.wast b/tests/wasm-tools/component-model/import.wast index 5473b009..b226157e 100644 --- a/tests/wasm-tools/component-model/import.wast +++ b/tests/wasm-tools/component-model/import.wast @@ -1,17 +1,19 @@ -;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-values +;; RUN: wast --assert default --snapshot tests/snapshots % (component - (import "a" (func)) - (import "b" (instance)) - (import "c" (instance - (export "a" (func)) - )) - (import "d" (component - (import "a" (core module)) - (export "b" (func)) - )) - (type $t (func)) - (import "e" (type (eq $t))) + (component + (import "a" (func)) + (import "b" (instance)) + (import "c" (instance + (export "a" (func)) + )) + (import "d" (component + (import "a" (core module)) + (export "b" (func)) + )) + (type $t (func)) + (import "e" (type (eq $t))) + ) ) (assert_invalid @@ -99,18 +101,7 @@ ) "type index out of bounds") -(assert_invalid - (component - (import "a" (value string)) - ) - "value index 0 was not used as part of an instantiation, start function, or export") - -(component - (import "a" (value string)) - (export "b" (value 0)) -) - -(component +(component definition (import "wasi:http/types" (func)) (import "wasi:http/types@1.0.0" (func)) (import "wasi:http/types@2.0.0" (func)) @@ -186,11 +177,13 @@ "trailing characters found: `/qux`") (component - (import "a" (func $a)) - (export "a" (func $a)) + (component + (import "a" (func $a)) + (export "a" (func $a)) + ) ) -(component +(component definition (import "unlocked-dep=" (func)) (import "unlocked-dep=" (func)) (import "unlocked-dep==1.2.3}>" (func)) @@ -232,7 +225,7 @@ (component (import "unlocked-dep==2.3.4}>" (func))) "`1.2.3 >=2.3.4` is not a valid semver") -(component +(component definition (import "locked-dep=" (func)) (import "locked-dep=" (func)) (import "locked-dep=,integrity=" (func)) @@ -273,7 +266,7 @@ (component (import "locked-dep=x" (func))) "trailing characters found: `x`") -(component +(component definition (import "url=<>" (func)) (import "url=" (func)) (import "url=,integrity=" (func)) @@ -307,7 +300,7 @@ (component (import "relative-url=<<>" (func))) "not a valid extern name") -(component +(component definition (import "integrity=" (func)) (import "integrity=" (func)) (import "integrity=" (func)) @@ -349,7 +342,7 @@ ;; not valid in the spec but it's accepted for backwards compatibility. This ;; tests is here to ensure such compatibility. In the future this test should ;; be changed to `(assert_invalid ...)` -(component binary +(component definition binary "\00asm" "\0d\00\01\00" ;; component header "\07\05" ;; type section, 5 bytes large diff --git a/tests/wasm-tools/component-model/imports-exports.wast b/tests/wasm-tools/component-model/imports-exports.wast index 4d42925c..e650c8c8 100644 --- a/tests/wasm-tools/component-model/imports-exports.wast +++ b/tests/wasm-tools/component-model/imports-exports.wast @@ -3,22 +3,24 @@ ;; With what's defined so far, we can define a component that imports, links and exports other components: (component - (import "c" (instance $c - (export "f" (func (result string))) - )) - (import "d" (component $D + (component (import "c" (instance $c (export "f" (func (result string))) )) - (export "g" (func (result string))) - )) - (instance $d1 (instantiate $D - (with "c" (instance $c)) - )) - (instance $d2 (instantiate $D - (with "c" (instance - (export "f" (func $d1 "g")) + (import "d" (component $D + (import "c" (instance $c + (export "f" (func (result string))) + )) + (export "g" (func (result string))) )) - )) - (export "d2" (instance $d2)) + (instance $d1 (instantiate $D + (with "c" (instance $c)) + )) + (instance $d2 (instantiate $D + (with "c" (instance + (export "f" (func $d1 "g")) + )) + )) + (export "d2" (instance $d2)) + ) ) diff --git a/tests/wasm-tools/component-model/instantiate.wast b/tests/wasm-tools/component-model/instantiate.wast index 35abcbc6..cb992394 100644 --- a/tests/wasm-tools/component-model/instantiate.wast +++ b/tests/wasm-tools/component-model/instantiate.wast @@ -1,41 +1,43 @@ -;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-values +;; RUN: wast --assert default --snapshot tests/snapshots % -(component +(component definition (import "a" (core module $m)) (core instance $a (instantiate $m)) ) (component - (import "a" (func $i)) - (import "b" (component $c (import "a" (func)))) - (instance (instantiate $c (with "a" (func $i)))) -) - -(component - (import "a" (value $i string)) - (import "b" (component $c (import "a" (value string)))) - (instance (instantiate $c (with "a" (value $i)))) + (component + (import "a" (func $i)) + (import "b" (component $c (import "a" (func)))) + (instance (instantiate $c (with "a" (func $i)))) + ) ) (component - (import "a" (component $i)) - (import "b" (component $c (import "a" (component)))) - (instance (instantiate $c (with "a" (component $i)))) + (component + (import "a" (component $i)) + (import "b" (component $c (import "a" (component)))) + (instance (instantiate $c (with "a" (component $i)))) + ) ) (component - (import "a" (core module $i)) - (import "b" (component $c (import "a" (core module)))) - (instance (instantiate $c (with "a" (core module $i)))) + (component + (import "a" (core module $i)) + (import "b" (component $c (import "a" (core module)))) + (instance (instantiate $c (with "a" (core module $i)))) + ) ) (component - (import "a" (instance $i)) - (import "b" (component $c (import "a" (instance)))) - (instance (instantiate $c (with "a" (instance $i)))) + (component + (import "a" (instance $i)) + (import "b" (component $c (import "a" (instance)))) + (instance (instantiate $c (with "a" (instance $i)))) + ) ) -(component +(component definition (import "a" (core module $m (import "" "a" (func)) (import "" "b" (global i32)) @@ -52,7 +54,7 @@ (core instance (instantiate $m (with "" (instance $x)))) ) -(component +(component definition (import "a" (core module $m (import "" "d" (func)) (import "" "c" (global i32)) @@ -76,62 +78,56 @@ ) (component - (type $t string) - (import "a" (value (type $t))) - (component $c (import "a" (value string)) (export "b" (value 0))) - (instance (instantiate $c (with "a" (value 0)))) -) - -(component - (import "a" (component $m - (import "a" (instance - (export "a" (core module)) + (component + (import "a" (component $m + (import "a" (instance + (export "a" (core module)) + )) )) - )) - (import "b" (component $m2 - (export "b" (core module)) - )) - (instance $x (instantiate $m2)) + (import "b" (component $m2 + (export "b" (core module)) + )) + (instance $x (instantiate $m2)) - (instance (instantiate $m (with "a" (instance - (export "a" (core module $x "b")) - )))) + (instance (instantiate $m (with "a" (instance + (export "a" (core module $x "b")) + )))) + ) ) (component - (import "a" (component $c - (import "a" (core module)) - (import "b" (func)) - (import "c" (component)) - (import "d" (instance)) - (import "e" (value string)) - )) - (core module $m (import "b")) - (func $f (import "c")) - (component $c2 (import "d")) - (instance $i (import "e")) - (import "f" (value $v string)) - - (instance - (instantiate $c - (with "a" (core module $m)) - (with "b" (func $f)) - (with "c" (component $c2)) - (with "d" (instance $i)) - (with "e" (value $v)) + (component + (import "a" (component $c + (import "a" (core module)) + (import "b" (func)) + (import "c" (component)) + (import "d" (instance)) + )) + (core module $m (import "b")) + (func $f (import "c")) + (component $c2 (import "d")) + (instance $i (import "e")) + + (instance + (instantiate $c + (with "a" (core module $m)) + (with "b" (func $f)) + (with "c" (component $c2)) + (with "d" (instance $i)) + ) ) - ) - (core instance $c (instantiate $m)) - (core instance (instantiate $m)) + (core instance $c (instantiate $m)) + (core instance (instantiate $m)) - ;; inline exports/imports - (type $empty (instance)) - (instance $d (import "g") (type $empty)) - (instance (import "h")) - (instance (import "i") - (export "x" (func))) - (instance (export "j") (export "k") (import "x")) + ;; inline exports/imports + (type $empty (instance)) + (instance $d (import "g") (type $empty)) + (instance (import "h")) + (instance (import "i") + (export "x" (func))) + (instance (export "j") (export "k") (import "x")) + ) ) (assert_invalid @@ -152,9 +148,11 @@ "unknown module") (component - (import "a" (func $f)) - (import "b" (component $c)) - (instance (instantiate $c (with "a" (func $f)))) + (component + (import "a" (func $f)) + (import "b" (component $c)) + (instance (instantiate $c (with "a" (func $f)))) + ) ) (assert_invalid (component @@ -236,42 +234,48 @@ ;; it's ok to give a module with fewer imports (component - (import "a" (component $m - (import "a" (core module + (component + (import "a" (component $m + (import "a" (core module + (import "" "" (global i32)) + (import "" "f" (func)) + )) + )) + (import "b" (core module $i (import "" "" (global i32)) - (import "" "f" (func)) )) - )) - (import "b" (core module $i - (import "" "" (global i32)) - )) - (instance $i (instantiate $m (with "a" (core module $i)))) + (instance $i (instantiate $m (with "a" (core module $i)))) + ) ) ;; export subsets (component - (import "a" (component $m - (import "a" (core module + (component + (import "a" (component $m + (import "a" (core module + (export "" (func)) + )) + )) + (import "b" (core module $i (export "" (func)) + (export "a" (func)) )) - )) - (import "b" (core module $i - (export "" (func)) - (export "a" (func)) - )) - (instance $i (instantiate $m (with "a" (core module $i)))) + (instance $i (instantiate $m (with "a" (core module $i)))) + ) ) (component - (import "a" (component $m - (import "a" (instance + (component + (import "a" (component $m + (import "a" (instance + (export "a" (func)) + )) + )) + (import "b" (instance $i (export "a" (func)) + (export "b" (func)) )) - )) - (import "b" (instance $i - (export "a" (func)) - (export "b" (func)) - )) - (instance (instantiate $m (with "a" (instance $i)))) + (instance (instantiate $m (with "a" (instance $i)))) + ) ) @@ -404,10 +408,6 @@ (component (instance $i (export "" (core module 0)))) "index out of bounds") -(assert_invalid - (component (instance $i (export "" (value 0)))) - "index out of bounds") - (assert_invalid (component (core instance (export "" (func 0)))) "index out of bounds") @@ -487,15 +487,6 @@ ) "index out of bounds") -(assert_invalid - (component - (component $c) - (instance (instantiate $c - (with "" (value 0)) - )) - ) - "index out of bounds") - (assert_invalid (component (component $c) @@ -534,17 +525,17 @@ "export name `a` conflicts with previous name `a`") (component - (import "a" (instance $i)) - (import "b" (func $f)) - (import "c" (component $c)) - (import "d" (core module $m)) - (import "e" (value $v string)) - (instance - (export "a" (instance $i)) - (export "b" (func $f)) - (export "c" (component $c)) - (export "d" (core module $m)) - (export "e" (value $v)) + (component + (import "a" (instance $i)) + (import "b" (func $f)) + (import "c" (component $c)) + (import "d" (core module $m)) + (instance + (export "a" (instance $i)) + (export "b" (func $f)) + (export "c" (component $c)) + (export "d" (core module $m)) + ) ) ) diff --git a/tests/wasm-tools/component-model/map.wast b/tests/wasm-tools/component-model/map.wast new file mode 100644 index 00000000..dc3c6207 --- /dev/null +++ b/tests/wasm-tools/component-model/map.wast @@ -0,0 +1,71 @@ +;; RUN: wast % --assert default --snapshot tests/snapshots -f cm-map + +(component + (core module $m + (memory (export "memory") 1) + (func (export "ret-map") (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "ret-map") (result (map string u32)) + (canon lift (core func $i "ret-map") (memory $i "memory")) + ) +) + +(component + (core module $m + (memory (export "memory") 1) + (func (export "param-map") (param i32 i32) unreachable) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "param-map") (param "m" (map string u32)) + (canon lift (core func $i "param-map") (memory $i "memory") (realloc (func $i "realloc"))) + ) +) + +(component + (type $map-type (map u32 string)) + (import "f" (func (param "x" $map-type))) +) + +(component + (type $nested-map (map string (map string u32))) + (import "f" (func (param "x" $nested-map))) +) + +(component + (type $map-with-list (map string (list u32))) + (import "f" (func (param "x" $map-with-list))) +) + +(component + (type $map-with-option (map u32 (option string))) + (import "f" (func (param "x" $map-with-option))) +) + +(assert_invalid + (component + (import "y" (component $c + (type $t (map string u32)) + (import "x" (type (eq $t))) + )) + + (type $x (map u32 string)) + (instance (instantiate $c (with "x" (type $x)))) + ) + "type mismatch for import `x`") + +(assert_invalid + (component + (import "y" (component $c + (type $t (map string u32)) + (import "x" (type (eq $t))) + )) + + (type $x (list u32)) + (instance (instantiate $c (with "x" (type $x)))) + ) + "type mismatch for import `x`") + diff --git a/tests/wasm-tools/component-model/naming.wast b/tests/wasm-tools/component-model/naming.wast index c5e389b5..5e7f1672 100644 --- a/tests/wasm-tools/component-model/naming.wast +++ b/tests/wasm-tools/component-model/naming.wast @@ -1,6 +1,6 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % -(component +(component definition (func (import "a")) (component) (instance (instantiate 0 (with "NotKebab-Case" (func 0)))) @@ -24,9 +24,9 @@ (assert_invalid (component - (type (flags "a-1-c")) + (type (flags "0-a-1-c")) ) - "flag name `a-1-c` is not in kebab case" + "flag name `0-a-1-c` is not in kebab case" ) (assert_invalid @@ -110,7 +110,7 @@ (instance (import "a1:b1/c")) ) -(component +(component definition (import "a" (type $a (sub resource))) (import "[constructor]a" (func (result (own $a)))) ) @@ -128,3 +128,7 @@ (import "[static]a.a" (func)) ) "import name `[static]a.a` conflicts with previous name `a`") + +(component + (type (flags "a-1-c")) +) diff --git a/tests/wasm-tools/component-model/nested-modules.wast b/tests/wasm-tools/component-model/nested-modules.wast index d3a9bec1..5e629ea5 100644 --- a/tests/wasm-tools/component-model/nested-modules.wast +++ b/tests/wasm-tools/component-model/nested-modules.wast @@ -1,6 +1,6 @@ ;; RUN: wast --assert default --snapshot tests/snapshots % -(component +(component definition (import "i1" (core module)) (core module) @@ -20,7 +20,7 @@ ) ;; does the `import` use the type annotation specified later? -(component +(component definition (import "a" (core module)) (core type (module)) ) @@ -37,7 +37,7 @@ ;; interleave module definitions with imports/aliases and ensure that we ;; typecheck the module code section correctly -(component +(component definition (core module (func (export "")) ) diff --git a/tests/wasm-tools/component-model/nested-names.wast b/tests/wasm-tools/component-model/nested-names.wast new file mode 100644 index 00000000..b3db71d5 --- /dev/null +++ b/tests/wasm-tools/component-model/nested-names.wast @@ -0,0 +1,29 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-nested-names + +;; These are the extended import name forms that are currently supported +;; via WasmFeatures::component_model_nested_names. + +(component + (component + (import "a:b:c:d/e" (func)) + (import "a:b-c:d-e:f-g/h-i/j-k/l-m/n/o/p@1.0.0" (func)) + ) +) + +(component + (import "unlocked-dep=" (func)) + (import "unlocked-dep=" (func)) + (import "unlocked-dep==1.2.3}>" (func)) + (import "unlocked-dep==1.2.3-rc}>" (func)) + (import "unlocked-dep=" (func)) + (import "unlocked-dep=" (func)) + (import "unlocked-dep==1.2.3 <1.2.3}>" (func)) + (import "unlocked-dep==1.2.3-rc <1.2.3}>" (func)) +) + +(component + (import "locked-dep=" (func)) + (import "locked-dep=" (func)) + (import "locked-dep=,integrity=" (func)) + (import "locked-dep=,integrity=" (func)) +) diff --git a/tests/wasm-tools/component-model/resources.wast b/tests/wasm-tools/component-model/resources.wast index 2468797e..14509c54 100644 --- a/tests/wasm-tools/component-model/resources.wast +++ b/tests/wasm-tools/component-model/resources.wast @@ -12,7 +12,7 @@ (core func (canon resource.drop $x)) ) -(component +(component definition (import "x" (type $x (sub resource))) (core func (canon resource.drop $x)) @@ -188,7 +188,7 @@ )) ) -(component +(component definition (import "fancy-fs" (instance $fancy-fs (export "fs" (instance $fs (export "file" (type (sub resource))) @@ -217,12 +217,12 @@ )) ) -(component +(component definition (import "T1" (type $T1 (sub resource))) (import "T2" (type $T2 (sub resource))) ) -(component $C +(component definition $C (import "T1" (type $T1 (sub resource))) (import "T2" (type $T2 (sub resource))) (import "T3" (type $T3 (eq $T2))) @@ -231,7 +231,7 @@ (type $ListT3 (list (own $T3))) ) -(component +(component definition (import "T" (type $T (sub resource))) (import "U" (type $U (sub resource))) (type $Own1 (own $T)) @@ -249,15 +249,17 @@ ) (component - (import "C" (component $C - (export "T1" (type (sub resource))) - (export "T2" (type $T2 (sub resource))) - (export "T3" (type (eq $T2))) - )) - (instance $c (instantiate $C)) - (alias export $c "T1" (type $T1)) - (alias export $c "T2" (type $T2)) - (alias export $c "T3" (type $T3)) + (component + (import "C" (component $C + (export "T1" (type (sub resource))) + (export "T2" (type $T2 (sub resource))) + (export "T3" (type (eq $T2))) + )) + (instance $c (instantiate $C)) + (alias export $c "T1" (type $T1)) + (alias export $c "T2" (type $T2)) + (alias export $c "T3" (type $T3)) + ) ) (component @@ -299,42 +301,46 @@ )) ) -(component $P - (import "C1" (component $C1 - (import "T" (type $T (sub resource))) - (export "foo" (func (param "t" (own $T)))) - )) - (import "C2" (component $C2 - (import "T" (type $T (sub resource))) - (import "foo" (func (param "t" (own $T)))) - )) - (type $R (resource (rep i32))) - (instance $c1 (instantiate $C1 (with "T" (type $R)))) - (instance $c2 (instantiate $C2 - (with "T" (type $R)) - (with "foo" (func $c1 "foo")) - )) +(component + (component $P + (import "C1" (component $C1 + (import "T" (type $T (sub resource))) + (export "foo" (func (param "t" (own $T)))) + )) + (import "C2" (component $C2 + (import "T" (type $T (sub resource))) + (import "foo" (func (param "t" (own $T)))) + )) + (type $R (resource (rep i32))) + (instance $c1 (instantiate $C1 (with "T" (type $R)))) + (instance $c2 (instantiate $C2 + (with "T" (type $R)) + (with "foo" (func $c1 "foo")) + )) + ) ) (component - (import "C1" (component $C1 - (import "T1" (type $T1 (sub resource))) - (import "T2" (type $T2 (sub resource))) - (export "foo" (func (param "t" (tuple (own $T1) (own $T2))))) - )) - (import "C2" (component $C2 - (import "T" (type $T (sub resource))) - (export "foo" (func (param "t" (tuple (own $T) (own $T))))) - )) - (type $R (resource (rep i32))) - (instance $c1 (instantiate $C1 - (with "T1" (type $R)) - (with "T2" (type $R)) - )) - (instance $c2 (instantiate $C2 - (with "T" (type $R)) - (with "foo" (func $c1 "foo")) - )) + (component + (import "C1" (component $C1 + (import "T1" (type $T1 (sub resource))) + (import "T2" (type $T2 (sub resource))) + (export "foo" (func (param "t" (tuple (own $T1) (own $T2))))) + )) + (import "C2" (component $C2 + (import "T" (type $T (sub resource))) + (export "foo" (func (param "t" (tuple (own $T) (own $T))))) + )) + (type $R (resource (rep i32))) + (instance $c1 (instantiate $C1 + (with "T1" (type $R)) + (with "T2" (type $R)) + )) + (instance $c2 (instantiate $C2 + (with "T" (type $R)) + (with "foo" (func $c1 "foo")) + )) + ) ) (assert_invalid @@ -668,7 +674,7 @@ (canon lift (core func $f))) ) -(component +(component definition (type $i (instance (export "r" (type $r (sub resource))) (export "f" (func (result (own $r)))) @@ -912,12 +918,33 @@ (import "b" (type $a (sub resource))) (import "[constructor]a" (func (result (own $a))))) "function does not match expected resource name `b`") -(component +(assert_invalid + (component + (import "b" (type $a (sub resource))) + (import "[constructor]a" (func (result (result(own $a)))))) + "function does not match expected resource name `b`") +(component definition (import "a" (type $a (sub resource))) (import "[constructor]a" (func (result (own $a))))) -(component +(component definition + (import "a" (type $a (sub resource))) + (import "[constructor]a" (func (result (result (own $a)))))) +(component definition + (import "a" (type $a (sub resource))) + (import "[constructor]a" (func (result (result (own $a) (error string)))))) +(component definition (import "a" (type $a (sub resource))) (import "[constructor]a" (func (param "x" u32) (result (own $a))))) +(assert_invalid + (component + (import "a" (type $a (sub resource))) + (import "[constructor]a" (func (result string)))) + "function should return `(own $T)` or `(result (own $T))`") +(assert_invalid + (component + (import "a" (type $a (sub resource))) + (import "[constructor]a" (func (result (result string))))) + "function should return `(own $T)` or `(result (own $T))`") ;; validation of `[method]a.b` (assert_invalid @@ -952,7 +979,7 @@ (import "b" (type $T (sub resource))) (import "[method]a.b" (func (param "self" (borrow $T))))) "does not match expected resource name") -(component +(component definition (import "a" (type $T (sub resource))) (import "[method]a.b" (func (param "self" (borrow $T))))) @@ -979,7 +1006,7 @@ (component (import "[static]a.b" (func))) "static resource name is not known in this context") -(component +(component definition (import "a" (type (sub resource))) (import "[static]a.b" (func))) @@ -993,10 +1020,12 @@ "resource used in function does not have a name in this context") (component - (import "b" (type $T (sub resource))) - (import "f" (func $f (param "self" (borrow $T)))) - (export $c "c" (type $T)) - (export "[method]c.foo" (func $f) (func (param "self" (borrow $c)))) + (component + (import "b" (type $T (sub resource))) + (import "f" (func $f (param "self" (borrow $T)))) + (export $c "c" (type $T)) + (export "[method]c.foo" (func $f) (func (param "self" (borrow $c)))) + ) ) ;; imports aren't transitive diff --git a/tests/wasm-tools/component-model/shared-everything-threads/not-accepted.wast b/tests/wasm-tools/component-model/shared-everything-threads/not-accepted.wast new file mode 100644 index 00000000..d86d1307 --- /dev/null +++ b/tests/wasm-tools/component-model/shared-everything-threads/not-accepted.wast @@ -0,0 +1,34 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f shared-everything-threads + +(component + (core module $A + (memory (export "m") 1 2 shared)) + (core instance $A (instantiate $A)) + (alias core export $A "m" (core memory $m)) + + (core module $B (import "" "" (memory 1 2 shared))) + (core instance (instantiate $B (with "" (instance (export "" (memory $m)))))) +) + +(component + (core module $A + (table (export "m") shared 1 2 (ref null (shared func))) + ) + (core instance $A (instantiate $A)) + (alias core export $A "m" (core table $m)) + + (core module $B (import "" "" (table shared 1 2 (ref null (shared func))))) + (core instance (instantiate $B (with "" (instance (export "" (table $m)))))) +) + +(assert_invalid + (component + (import "x" (func $x (param "x" string))) + + (core module $A + (memory (export "m") 1 2 shared)) + (core instance $A (instantiate $A)) + (alias core export $A "m" (core memory $m)) + (core func (canon lower (func $x) (memory $m))) + ) + "canonical ABI memory is not a 32-bit linear memory") diff --git a/tests/wasm-tools/component-model/type-export-restrictions.wast b/tests/wasm-tools/component-model/type-export-restrictions.wast index 4932ce6b..d2971f8d 100644 --- a/tests/wasm-tools/component-model/type-export-restrictions.wast +++ b/tests/wasm-tools/component-model/type-export-restrictions.wast @@ -284,11 +284,13 @@ (export "e-t1" (type $t2)) ) (component - (type $t1 (record (field "f" u32))) - (import "t1" (type $t2 (eq $t1))) - (import "i" (func $f (result $t2))) + (component + (type $t1 (record (field "f" u32))) + (import "t1" (type $t2 (eq $t1))) + (import "i" (func $f (result $t2))) - (export "e-i" (func $f)) + (export "e-i" (func $f)) + ) ) ;; outer aliases don't work for imports/exports @@ -335,8 +337,10 @@ ;; reexport of an import is fine (component - (import "r" (func $r)) - (export "r2" (func $r)) + (component + (import "r" (func $r)) + (export "r2" (func $r)) + ) ) (component (type $t (record (field "f" u32))) @@ -347,7 +351,7 @@ (import "r" (instance $r)) (export "r2" (instance $r)) ) -(component +(component definition (import "r" (type $r (sub resource))) (export "r2" (type $r)) ) @@ -395,7 +399,7 @@ "instance not valid to be used as import") ;; allow for one import to refer to another -(component $C +(component definition $C (import "foo" (instance $i (type $baz' (record (field "f" u32))) (export "baz" (type $baz (eq $baz'))) diff --git a/tests/wasm-tools/component-model/types.wast b/tests/wasm-tools/component-model/types.wast index 1a9ba7af..5df1ec9d 100644 --- a/tests/wasm-tools/component-model/types.wast +++ b/tests/wasm-tools/component-model/types.wast @@ -360,35 +360,6 @@ ) "cannot have more than 32 flags") -;; test components with non-mvp types -(component - ;; all abstract heap types work - (core type (func (param (ref any)))) - (core type (func (param (ref func)))) - (core type (func (param (ref extern)))) - (core type (func (param (ref exn)))) - (core type (func (param (ref noexn)))) - (core type (func (param (ref eq)))) - (core type (func (param (ref struct)))) - (core type (func (param (ref array)))) - (core type (func (param (ref nofunc)))) - (core type (func (param (ref noextern)))) - (core type (func (param (ref none)))) - (core type (func (param (ref i31)))) - - ;; some shorthands work - (core type (func (param anyref))) - (core type (func (param eqref))) - - ;; simd types work - (core type (func (param v128))) - - ;; types-pointing-to-types works - (core type $t (func)) - (core type (func (param (ref $t)))) - -) - (assert_invalid (component (core type $t (module)) diff --git a/tests/wasm-tools/component-model/values/start.wast b/tests/wasm-tools/component-model/values/start.wast new file mode 100644 index 00000000..33c3bd88 --- /dev/null +++ b/tests/wasm-tools/component-model/values/start.wast @@ -0,0 +1,100 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-values + + +(assert_invalid + (component + (import "a" (func $f (param "p1" string))) + (start $f) + ) + "start function requires 1 arguments") + +(assert_invalid + (component + (import "a" (func $f (param "p" string))) + (import "b" (value $v string)) + (start $f (value $v) (value $v)) + ) + "start function requires 1 arguments") + +(assert_invalid + (component + (import "a" (func $f (param "p1" string) (param "p2" string))) + (import "b" (value $v string)) + (start $f (value $v) (value $v)) + ) + "cannot be used more than once") + +(assert_invalid + (component + (import "a" (func $f (param "x" string) (param "y" string))) + (import "b" (value $v string)) + (import "c" (value $v2 u32)) + (start $f (value $v) (value $v2)) + ) + "type mismatch for component start function argument 1") + +(component + (import "a" (func $f (param "z" string) (param "a" string))) + (import "b" (value $v string)) + (import "c" (value $v2 string)) + (start $f (value $v) (value $v2)) +) + +(component + (import "a" (func $f (result string))) + (start $f (result (value $a))) + (export "b" (value $a)) +) + +(assert_malformed + (component quote + "(import \"a\" (func $f)) " + "(start $f) " + "(start $f) " + ) + "component cannot have more than one start function") + +(assert_malformed + (component binary + "\00asm" "\0d\00\01\00" ;; component header + + "\07\05" ;; type section, 5 bytes large + "\01" ;; 1 count + "\40" ;; function + "\00" ;; parameters, 0 count + "\01\00" ;; results, named, 0 count + + "\0a\06" ;; import section, 6 bytes large + "\01" ;; 1 count + "\00\01a" ;; name = "a" + "\01\00" ;; type = func ($type 0) + + "\09\06" ;; start section, 6 bytes large + "\00" ;; function 0 + "\00" ;; no arguments + "\ff\ff\ff\00" ;; tons of results + ) + "start function results size is out of bounds") + +(assert_malformed + (component binary + "\00asm" "\0d\00\01\00" ;; component header + + "\07\05" ;; type section, 5 bytes large + "\01" ;; 1 count + "\40" ;; function + "\00" ;; parameters, 0 count + "\01\00" ;; results, named, 0 count + + "\0a\06" ;; import section, 6 bytes large + "\01" ;; 1 count + "\00\01a" ;; name = "a" + "\01\00" ;; type = func ($type 0) + + "\09\04" ;; start section, 4 bytes large + "\00" ;; function 0 + "\00" ;; no arguments + "\00" ;; no results + "\ff" ;; trailing garbage byte + ) + "unexpected content in the component start section") diff --git a/tests/wasm-tools/component-model/values/values.wast b/tests/wasm-tools/component-model/values/values.wast new file mode 100644 index 00000000..7e320a40 --- /dev/null +++ b/tests/wasm-tools/component-model/values/values.wast @@ -0,0 +1,139 @@ +;; RUN: wast --assert default --snapshot tests/snapshots % -f cm-values + +(component + (import "one" (func)) + + (import "two" (value $v string)) + (import "three" (instance + (export "four" (instance + (export "five" (core module + (import "six" "a" (func)) + (import "six" "b" (func)) + )) + )) + )) + + (export "four" (value $v)) + ;; ... +) + +(component + (component + (import "a" (instance $foo (export "v" (value s32)))) + (export "v" (value $foo "v")) + ) +) + +(assert_invalid + (component (export "" (value 0))) + "index out of bounds") + +(component + (component + (import "d" (value $v string)) + (export "i" (value $v)) + ) +) + +(assert_invalid + (component + (import "a" (value $v string)) + (export "b" (value $v)) + (export "c" (value $v)) + ) + "cannot be used more than once") + +(assert_invalid + (component + (import "a" (value string)) + ) + "value index 0 was not used as part of an instantiation, start function, or export") + +(component + (component + (import "a" (value string)) + (export "b" (value 0)) + ) +) + +(component + (component + (import "a" (value $i string)) + (import "b" (component $c (import "a" (value string)))) + (instance (instantiate $c (with "a" (value $i)))) + ) +) + +(component definition + (type $t string) + (import "a" (value (type $t))) + (component $c (import "a" (value string)) (export "b" (value 0))) + (instance (instantiate $c (with "a" (value 0)))) +) + +(component + (component + (import "a" (component $c + (import "e" (value string)) + )) + (import "f" (value $v string)) + + (instance + (instantiate $c + (with "e" (value $v)) + ) + ) + ) +) + +(assert_invalid + (component (instance $i (export "" (value 0)))) + "index out of bounds") + +(assert_invalid + (component + (component $c) + (instance (instantiate $c + (with "" (value 0)) + )) + ) + "index out of bounds") + +(component + (component + (import "e" (value $v string)) + (instance + (export "e" (value $v)) + ) + ) +) + +(component + (import "name" (value $name string)) + (import "libc" (core module $Libc + (export "memory" (memory 1)) + (export "realloc" (func (param i32 i32 i32 i32) (result i32))) + (export "free" (func (param i32 i32 i32))) + (export "canonical_abi_realloc" (func (param i32 i32 i32 i32) (result i32))) + )) + (core instance $libc (instantiate $Libc)) + (core module $Main + (import "libc" "memory" (memory 1)) + (func (export "start") (param i32 i32) (result i32) + ;;... general-purpose compute + unreachable + ) + (func (export "start-post-return") (param i32)) + ) + (core instance $main (instantiate $Main (with "libc" (instance $libc)))) + (alias core export $main "start" (core func $main_func)) + (func $start (param "p1" string) (result string) + (canon lift (core func $main_func) + (memory $libc "memory") + (realloc (func $libc "canonical_abi_realloc")) + (post-return (func $main "start-post-return")) + ) + ) + (start $start (value $name) (result (value $greeting))) + (export "greeting" (value $greeting)) +)