diff --git a/.cargo/config.toml b/.cargo/config.toml index 6614b654..b6a7b103 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,8 +2,15 @@ bindeps = true [target.x86_64-unknown-linux-gnu] -rustflags = ["-Cforce-frame-pointers=on"] +rustflags = [ + "-C", "force-frame-pointers=on" +] [target.x86_64-unknown-none] -rustflags = ["-Cforce-frame-pointers=on"] +rustflags = [ + "-C", "force-frame-pointers=on", + "-C", "code-model=large", + "-C", "relocation-model=static", + # "-C", "target-feature=+crt-static", +] runner = "cargo run -p vmm --" diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index d35eadf2..fed52e51 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -47,7 +47,6 @@ jobs: - name: Setup run: | sudo apt update && sudo apt install gcc-multilib - sudo modprobe vhost_vsock echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm @@ -61,3 +60,6 @@ jobs: - name: Test Kernel run: | sudo -su $USER /home/runner/.cargo/bin/cargo test -p kernel --target=x86_64-unknown-none --verbose + - name: Test Fix + run: | + sudo -su $USER /home/runner/.cargo/bin/cargo build -p fix --target=x86_64-unknown-none diff --git a/.gitignore b/.gitignore index 43f45691..e7aa5ba8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .build +.fix diff --git a/Cargo.lock b/Cargo.lock index 6ff0779b..60b7d94b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -34,15 +34,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -53,7 +53,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -64,20 +64,20 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ar_archive_writer" -version = "0.2.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" dependencies = [ "object", ] @@ -99,6 +99,24 @@ dependencies = [ "derive_more", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assume" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6f9ca11400f14ef046700eb6401c706c587871303453a5e7586efb82340c3d" + [[package]] name = "async-channel" version = "2.5.0" @@ -113,9 +131,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -151,7 +169,7 @@ dependencies = [ "polling", "rustix", "slab", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -196,9 +214,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -209,7 +227,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -254,7 +272,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools", @@ -279,6 +297,17 @@ dependencies = [ "syn", ] +[[package]] +name = "bitfield-struct" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -287,9 +316,48 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitint" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5af51679f7efe6960f19605c73eedc10962cf0b5a33927f8c2ee1d5c92ff75e" +dependencies = [ + "assume", + "bitint-macros", + "num-traits", + "paste", + "seq-macro", +] + +[[package]] +name = "bitint-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90afa834bb07d4648cebdc952942a3c10bf0060de99751ebe60a8cef4322fcb" +dependencies = [ + "litrs", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] [[package]] name = "block2" @@ -313,6 +381,12 @@ dependencies = [ "piper", ] +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -321,15 +395,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "shlex", @@ -358,9 +432,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "num-traits", "serde", @@ -393,9 +467,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -403,9 +477,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -415,9 +489,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -427,15 +501,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -451,9 +525,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "common" @@ -468,8 +542,9 @@ dependencies = [ "libc", "log", "macros", - "nix", + "nix 0.30.1", "rand", + "serde", "snafu", ] @@ -482,6 +557,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -502,6 +583,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -516,20 +606,20 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "ctrlc" -version = "3.5.1" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix", - "windows-sys 0.61.2", + "nix 0.31.3", + "windows-sys", ] [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -559,11 +649,11 @@ dependencies = [ [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -571,9 +661,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elevate" @@ -605,9 +695,9 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -615,9 +705,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -639,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -665,15 +755,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fix" @@ -684,15 +774,24 @@ dependencies = [ "arcane", "async-lock", "autotools", + "bindgen", + "bitfield-struct 0.11.0", + "bitint", + "blake3", + "bytemuck", "cc", "chrono", "cmake", "common", "derive_more", + "fixhandle", + "fixshell", "futures", + "hex", "include_directory", "kernel", "log", + "macros", "postcard", "serde", "serde_bytes", @@ -700,6 +799,28 @@ dependencies = [ "user", ] +[[package]] +name = "fixhandle" +version = "0.1.0" +dependencies = [ + "bitint", + "common", + "derive_more", +] + +[[package]] +name = "fixshell" +version = "0.1.0" +dependencies = [ + "anyhow", + "arca", + "arcane", + "bindgen", + "cc", + "fixhandle", + "user", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -708,9 +829,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -723,9 +844,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -733,15 +854,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -750,9 +871,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -769,9 +890,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -780,21 +901,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -804,7 +925,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -850,9 +970,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heapless" @@ -880,6 +1000,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "include_directory" version = "0.1.1" @@ -904,12 +1030,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] @@ -929,9 +1055,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -942,9 +1068,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", @@ -959,7 +1085,7 @@ dependencies = [ "arca", "arcane", "async-lock", - "bitfield-struct", + "bitfield-struct 0.7.0", "cc", "cfg-if", "common", @@ -972,6 +1098,7 @@ dependencies = [ "spin 0.10.0", "talc", "time", + "user", ] [[package]] @@ -989,7 +1116,7 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e00243d27a20feb05cf001ae52ddc79831ac70c020f215ba1153ff9270b650a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "kvm-bindings", "libc", "vmm-sys-util", @@ -997,9 +1124,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1013,9 +1140,18 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +dependencies = [ + "proc-macro2", +] [[package]] name = "lock_api" @@ -1044,9 +1180,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -1071,13 +1207,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1096,7 +1232,20 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -1115,9 +1264,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1146,9 +1295,9 @@ checksum = "9a6da8d50bd4f4b2e9788c44714a3fa4e465d33fd6a6ad70991db6eb30807dca" [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -1161,9 +1310,9 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "object" -version = "0.32.2" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1181,22 +1330,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", @@ -1214,20 +1363,20 @@ dependencies = [ "hermit-abi", "pin-project-lite", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -1272,27 +1421,27 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" dependencies = [ "ar_archive_writer", "cc", @@ -1300,9 +1449,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1315,9 +1464,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core", @@ -1344,14 +1493,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.13", - "regex-syntax 0.8.8", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", ] [[package]] @@ -1367,13 +1516,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.8", + "regex-syntax 0.8.10", ] [[package]] @@ -1384,9 +1533,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rlimit" @@ -1405,9 +1554,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-std-workspace-alloc" @@ -1432,15 +1581,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1457,9 +1606,15 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" @@ -1519,9 +1674,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smol" @@ -1563,12 +1718,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -1597,15 +1752,15 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1616,9 +1771,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1636,18 +1791,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1660,7 +1815,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2210811179577da3d54eb69ab0b50490ee40491a25d95b8c6011ba40771cb721" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.1", "cfg-if", "libc", "log", @@ -1670,9 +1825,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "num-conv", @@ -1683,15 +1838,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -1699,7 +1854,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1729,18 +1884,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", "toml_datetime", @@ -1750,9 +1905,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] @@ -1776,15 +1931,15 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -1832,10 +1987,12 @@ dependencies = [ "kvm-ioctls", "libc", "log", - "nix", + "nix 0.30.1", + "postcard", "regex", "rlimit", "rustc-demangle", + "serde", "smol", "thread-priority", "tokio-stream", @@ -1855,12 +2012,12 @@ dependencies = [ [[package]] name = "vsock" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2da6e4ac76cd19635dce0f98985378bb62f8044ee2ff80abd2a7334b920ed63" +checksum = "6ba782755fc073877e567c2253c0be48e4aa9a254c232d36d3985dfae0bd5205" dependencies = [ "libc", - "nix", + "nix 0.31.3", ] [[package]] @@ -1871,9 +2028,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ "wit-bindgen", ] @@ -2008,24 +2165,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2035,39 +2174,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -2077,131 +2183,35 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[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.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[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" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "zerocopy" -version = "0.8.34" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.34" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index f08277f4..604fc715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,12 @@ [workspace] -members = [ "common", "vmm", "kernel", "macros", "user", "arca" , "arcane", "fix"] -default-members = [ "common", "vmm", "macros", "arca"] +members = [ "common", "vmm", "kernel", "macros" , "user", "arca" , "arcane", "fix", "fix/handle", "fix/shell" ] +default-members = [ "common", "vmm", "macros", "arca", "fix/handle" ] resolver = "2" [profile.release] debug = true + +[workspace.package] +license = "LGPL-2.1-or-later" +license-file = "LICENSE" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8000a6fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/README.md b/README.md index fd234e90..d47f72a3 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,24 @@ ## Installation +### Cloning + +This repository contains submodules, you can clone it using: +``` +git clone --recurse-submodules git@github.com:fix-project/arca +``` + ### Runtime Arca is currently paravirtualized within a custom hypervisor; it requires a -Linux AMD64 machine with KVM enabled. +Linux AMD64 machine with KVM enabled. If you're using `stagecast.org`, make +sure your user is in the `kvm` group. ### Toolchain Arca is written in nightly Rust. You should install Rust and Cargo via `rustup`. Arca requires the `x86_64-unknown-none` target. Arca expects Rust -version 1.85+. +version 1.98+. Instructions for Debian- and RHEL-based distributions: ```sh @@ -26,6 +34,10 @@ You can update Rust and Cargo using: rustup update ``` +Be aware that nightly releases often break compatibility, so you may have to +patch code to run on newer versions. In general we try to follow the latest +nightly release. + ## Fix Compilation Building Fix-on-Arca additionally requires installing [the GCC multilib package @@ -35,24 +47,28 @@ Building Fix-on-Arca additionally requires installing [the GCC multilib package ## Running -Arca's general test suite can be run using the standard Cargo build commands. +We use the [just command runner](https://github.com/casey/just) to help +simplify the commands needed to build and run arca. +To run the test suite: ```sh -cargo build -cargo test +just test ``` -Kernel-specific tests can be run using: +To run an example kernel (from `kernel/examples`): ```sh -cargo test -p kernel --target=x86_64-unknown-none +just run hello +just run threads +just run webserver ``` -Arca-powered kernels (such as Fix) can be run using: +To run Fix-on-Arca, run: ```sh -cargo run -p kernel --bin hello --target=x86_64-unknown-none +just fix eval addblob.fix ``` -for small demo kernels (located in `kernel/src/bin`), or using: -```sh -cargo run -p fix --target=x86_64-unknown-none -``` -for standalone kernels. + +# License + +This codebase is licensed under the GNU Lesser General Public License v2.1 or +later (LGPL-2.1-or-later). See [LICENSE](LICENSE) +for more information. diff --git a/addblob.fix b/addblob.fix new file mode 100644 index 00000000..ff6e50b9 --- /dev/null +++ b/addblob.fix @@ -0,0 +1,8 @@ +x = create_blob(Int(2)); +y = create_blob(Int(3)); +add = create_blob(Path("./target/x86_64-unknown-none/addblob")); +add_x_y = create_application_thunk(create_tree(add, x, y)); +z = create_blob(Int(1)); +sum_x_y = create_strict_encode(add_x_y) +add_xy_z = create_application_thunk(create_tree(add, sum_x_y, z)); +eval(create_strict_encode(add_xy_z)); diff --git a/arca/src/serde.rs b/arca/src/serde.rs index 23257df5..a1ef5d35 100644 --- a/arca/src/serde.rs +++ b/arca/src/serde.rs @@ -333,7 +333,7 @@ impl<'de, R: Runtime> Visitor<'de> for TableVisitor { where A: serde::de::MapAccess<'de>, { - let (first_key, first_value): (alloc::string::String, usize) = + let (first_key, first_value): (&str, usize) = map.next_entry()?.expect("at least one element needed"); assert_eq!(first_key, "len"); let mut table = Table::new(first_value); diff --git a/common/Cargo.toml b/common/Cargo.toml index 8275348a..bad944a9 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -8,14 +8,14 @@ default = ["std"] std = ["alloc", "snafu/std", "libc", "nix"] alloc = [] thread_local_cache = ["cache"] -core_local_cache = ["macros", "cache"] +core_local_cache = ["cache"] cache = [] nix = ["dep:nix"] [dependencies] log = "0.4.22" snafu = { version="0.8.5", default-features=false } -macros = { path = "../macros", optional=true } +macros = { path = "../macros" } arca = { path = "../arca" } libc = { version="0.2.164", optional=true } elf = { version = "0.7.4", default-features = false } @@ -24,6 +24,8 @@ derive_more = { version = "2.0.1", default-features = false, features = ["full"] nix = { version = "0.30.1", features = ["mman"], optional = true } async-channel = { version = "2.5.0", default-features = false } async-lock = { version = "3.4.1", default-features = false } +serde = { version = "1.0.228", default-features = false, features = ["alloc", "derive"] } +embedded-io = { version = "0.6", default-features = false } [target.'cfg(target_os="linux")'.dev-dependencies] rand = "0.9.2" diff --git a/common/src/bitpack.rs b/common/src/bitpack.rs new file mode 100644 index 00000000..0f0e630c --- /dev/null +++ b/common/src/bitpack.rs @@ -0,0 +1,7 @@ +pub use macros::BitPack; + +pub trait BitPack { + const TAGBITS: u32; + fn pack(&self) -> [u8; 32]; + fn unpack(content: [u8; 32]) -> Self; +} diff --git a/common/src/buddy.rs b/common/src/buddy.rs index 0acd5957..fd31d6c0 100644 --- a/common/src/buddy.rs +++ b/common/src/buddy.rs @@ -413,6 +413,13 @@ impl AllocatorInner { size: 1 << size_log2, }); } + // Check if index is within valid range for this level + if index >= self.size_of_level_bits(size_log2) { + return Err(AllocationError::InvalidReservation { + index, + size: 1 << size_log2, + }); + } self.with_level(base, size_log2, |level: &mut AllocatorLevel<'_>| { if level.reserve(index) { Ok(index) @@ -553,12 +560,15 @@ impl BuddyAllocatorImpl { // prevent physical zero page from being allocated assert_eq!(temp.to_offset(temp.reserve_raw(0, 4096)), 0); - // reserve kernel pages + // reserve kernel pages (only if within range) let mut pages = alloc::vec![]; for i in 0..8 { - let p = temp.reserve_raw(0x100000 * (i + 1), 0x100000); - assert!(!p.is_null()); - pages.push(p); + let addr = 0x100000 * (i + 1); + if addr + 0x100000 <= size { + let p = temp.reserve_raw(addr, 0x100000); + assert!(!p.is_null()); + pages.push(p); + } } let new_inner = AllocatorInner::new_in(slice, &temp); @@ -612,7 +622,7 @@ impl BuddyAllocatorImpl { pub fn set_caching(&self, enable: bool) -> bool { if cfg!(feature = "cache") { self.caching - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| Some(enable)) + .try_update(Ordering::SeqCst, Ordering::SeqCst, |_| Some(enable)) .unwrap_or_else(|x| x) } else { false @@ -681,6 +691,7 @@ impl BuddyAllocatorImpl { for (i, item) in ptrs.iter_mut().enumerate() { let result = self.allocate_raw_unchecked(size); if result.is_null() { + self.inner.unlock(); return Some(i); } *item = result; @@ -696,6 +707,7 @@ impl BuddyAllocatorImpl { for (i, item) in ptrs.iter_mut().enumerate() { let result = self.allocate_raw_unchecked(size); if result.is_null() { + self.inner.unlock(); return i; } *item = result; @@ -1139,163 +1151,4 @@ unsafe impl Allocator for BuddyAllocator { } #[cfg(test)] -mod tests { - extern crate test; - - use super::*; - use test::Bencher; - - #[test] - fn test_bitref() { - let mut word = 10; - - let mut r0 = BitRef::new(&mut word, 0); - r0.set(); - - let mut r1 = BitRef::new(&mut word, 1); - r1.clear(); - - let mut r2 = BitRef::new(&mut word, 2); - r2.write(false); - - let mut r3 = BitRef::new(&mut word, 3); - r3.write(true); - - assert_eq!(word, 9); - } - - #[test] - fn test_bitslice() { - let mut words = [0; 2]; - let mut slice = BitSlice::new(128, &mut words); - let mut r0 = slice.bit(0); - r0.set(); - - let mut r1 = slice.bit(1); - r1.set(); - - let mut r127 = slice.bit(127); - r127.set(); - - assert_eq!(words[0], 3); - assert_eq!( - words[127 / (core::mem::size_of::() * 8)], - 1 << (127 % (core::mem::size_of::() * 8)) - ); - } - - #[test] - fn test_buddy_allocator() { - let allocator = BuddyAllocatorImpl::new(0x10000000); - - let test = Box::new_in(10, allocator.clone()); - assert_eq!(*test, 10); - - let mut v = Vec::new_in(allocator.clone()); - for i in 0..10000 { - v.push(i); - } - } - - #[bench] - fn bench_allocate_free(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - b.iter(|| { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }); - } - - #[bench] - fn bench_allocate_free_no_cache(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - allocator.set_caching(false); - b.iter(|| { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }); - } - - #[bench] - fn bench_contended_allocate_free(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - let f = || { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }; - use core::sync::atomic::AtomicBool; - use std::sync::Arc; - std::thread::scope(|s| { - let flag = Arc::new(AtomicBool::new(true)); - for _ in 0..16 { - let flag = flag.clone(); - s.spawn(move || { - while flag.load(Ordering::SeqCst) { - f(); - } - }); - } - b.iter(f); - flag.store(false, Ordering::SeqCst); - }); - } - - #[bench] - #[ignore] - fn bench_contended_allocate_free_no_cache(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - allocator.set_caching(false); - let f = || { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }; - use core::sync::atomic::AtomicBool; - use std::sync::Arc; - std::thread::scope(|s| { - let flag = Arc::new(AtomicBool::new(true)); - for _ in 0..16 { - let flag = flag.clone(); - s.spawn(move || { - while flag.load(Ordering::SeqCst) { - f(); - } - }); - } - b.iter(f); - flag.store(false, Ordering::SeqCst); - }); - } - - #[test] - fn stress_test() { - use std::hash::{BuildHasher, Hasher, RandomState}; - let allocator = BuddyAllocatorImpl::new(0x10000000); - allocator.set_caching(false); - let mut v = vec![]; - let random = |limit: usize| { - let x: u64 = RandomState::new().build_hasher().finish(); - x as usize % limit - }; - for _ in 0..100000 { - let used_before = allocator.used_size(); - let remaining = allocator.total_size() - used_before; - let size = random(core::cmp::min(1 << 21, remaining / 2)); - let alloc = - Box::<[u8], BuddyAllocatorImpl>::new_uninit_slice_in(size, allocator.clone()); - let used_after = allocator.used_size(); - assert!(used_after >= used_before + size); - if !v.is_empty() && size % 3 == 0 { - let number = random(v.len()); - for _ in 0..number { - let index = random(v.len()); - v.remove(index); - } - } - v.push(alloc); - } - } -} +mod tests; diff --git a/common/src/buddy/tests.rs b/common/src/buddy/tests.rs new file mode 100644 index 00000000..3b579e9a --- /dev/null +++ b/common/src/buddy/tests.rs @@ -0,0 +1,951 @@ +extern crate test; + +use super::*; +#[test] +// Setting/clearing individual bits in u64 words to check that the bit manipulation works +fn test_bitref() { + let mut word = 10; + + let mut r0 = BitRef::new(&mut word, 0); + r0.set(); + + let mut r1 = BitRef::new(&mut word, 1); + r1.clear(); + + let mut r2 = BitRef::new(&mut word, 2); + r2.write(false); + + let mut r3 = BitRef::new(&mut word, 3); + r3.write(true); + + assert_eq!(word, 9); +} + +#[test] +// Setting/clearing individual bits in a BitSlice to check that the bit manipulation works +fn test_bitslice() { + let mut words = [0; 2]; + let mut slice = BitSlice::new(128, &mut words); + let mut r0 = slice.bit(0); + r0.set(); + + let mut r1 = slice.bit(1); + r1.set(); + + let mut r127 = slice.bit(127); + r127.set(); + + assert_eq!(words[0], 3); + assert_eq!( + words[127 / (core::mem::size_of::() * 8)], + 1 << (127 % (core::mem::size_of::() * 8)) + ); +} + +#[test] +// Basic setup + continuous element pushing to check that the allocator grows and adjusts properly +fn test_buddy_allocator() { + let allocator = BuddyAllocatorImpl::new(0x10000000); + + let test = Box::new_in(10, allocator.clone()); + assert_eq!(*test, 10); + + let mut v = Vec::new_in(allocator.clone()); + for i in 0..10000 { + v.push(i); + } +} + +#[test] +// Verifying that too small allocations of allocator do not panic +// Potential issue: reserve_unchecked does not validate that the requested index is within the number of blocks at that level +fn test_too_small_allocation() { + let allocator = BuddyAllocatorImpl::new(1 << 20); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let _used_before = allocator.used_size(); + let _ptr = allocator.allocate_raw(size); +} + +#[test] +// Verifying allocate_raw adds size to used_size, and free_raw subtracts it back, returning usage to the original amount. +fn test_allocate_raw_and_used_size() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let used_before = allocator.used_size(); + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + assert_eq!(allocator.used_size(), used_before + size); + allocator.free_raw(ptr, size); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying allocate_many_raw adds size to used_size, and free_many_raw subtracts it back, returning usage to the original amount. +fn test_allocate_many_and_free_many() { + use std::collections::BTreeSet; + + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let used_before = allocator.used_size(); + let mut ptrs = [core::ptr::null_mut(); 4]; + let count = allocator.allocate_many_raw(size, &mut ptrs); + assert_eq!(count, ptrs.len()); + assert!(ptrs.iter().all(|ptr| !ptr.is_null())); + + let unique: BTreeSet = ptrs.iter().map(|ptr| *ptr as usize).collect(); + assert_eq!(unique.len(), ptrs.len()); + + allocator.free_many_raw(size, &ptrs); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying that to_offset and from_offset roundtrip correctly +fn test_offset_roundtrip() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + + let offset = allocator.to_offset(ptr); + let roundtrip = allocator.from_offset::(offset); + assert_eq!(roundtrip as usize, ptr as usize); + + allocator.free_raw(ptr, size); +} + +#[test] +// Verifying that reserving at zero returns a null pointer and does not add to used_size +fn test_reserve_raw_at_zero() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let used_before = allocator.used_size(); + let ptr = allocator.reserve_raw(0, size); + assert!(ptr.is_null()); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying that allocating too large returns a null pointer and does not add to used_size +fn test_allocate_raw_too_large_returns_null() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let used_before = allocator.used_size(); + let ptr = allocator.allocate_raw(allocator.total_size() * 2); + assert!(ptr.is_null()); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying that refcnt is zero on allocate +fn test_refcnt_zero_on_allocate() { + use core::sync::atomic::Ordering; + + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + + let refcnt = allocator.refcnt(ptr); + assert!(!refcnt.is_null()); + let value = unsafe { (*refcnt).load(Ordering::SeqCst) }; + assert_eq!(value, 0); + + allocator.free_raw(ptr, size); +} + +#[test] +// Stress testing the allocator with random allocations and frees +fn stress_test() { + use std::hash::{BuildHasher, Hasher, RandomState}; + let allocator = BuddyAllocatorImpl::new(0x10000000); + allocator.set_caching(false); + let mut v = vec![]; + let random = |limit: usize| { + let x: u64 = RandomState::new().build_hasher().finish(); + x as usize % limit + }; + for _ in 0..100000 { + let used_before = allocator.used_size(); + let remaining = allocator.total_size() - used_before; + let size = random(core::cmp::min(1 << 21, remaining / 2)); + let alloc = Box::<[u8], BuddyAllocatorImpl>::new_uninit_slice_in(size, allocator.clone()); + let used_after = allocator.used_size(); + assert!(used_after >= used_before + size); + if !v.is_empty() && size % 3 == 0 { + let number = random(v.len()); + for _ in 0..number { + let index = random(v.len()); + v.remove(index); + } + } + v.push(alloc); + } +} + +#[test] +// Test splitting large blocks into smaller ones +fn test_block_splitting() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let small_size = BuddyAllocatorImpl::MIN_ALLOCATION; + let large_size = small_size * 4; + + // Allocate and free a large block + let large_ptr = allocator.allocate_raw(large_size); + assert!(!large_ptr.is_null()); + allocator.free_raw(large_ptr, large_size); + + // Now allocate multiple small blocks - should split the large one + let mut small_ptrs = vec![]; + for _ in 0..4 { + let ptr = allocator.allocate_raw(small_size); + assert!(!ptr.is_null()); + small_ptrs.push(ptr); + } + + // Clean up + for ptr in small_ptrs { + allocator.free_raw(ptr, small_size); + } +} + +#[test] +fn test_reserve_specific_addresses() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Get a definitely-available block + let p = allocator.allocate_raw(size); + assert!(!p.is_null()); + let address = allocator.to_offset(p); + allocator.free_raw(p, size); + + // Now we should be able to reserve that exact address + let ptr1 = allocator.reserve_raw(address, size); + assert!(!ptr1.is_null()); + assert_eq!(allocator.to_offset(ptr1), address); + + // Reserving again should fail + let ptr2 = allocator.reserve_raw(address, size); + assert!(ptr2.is_null()); + + allocator.free_raw(ptr1, size); +} + +#[test] +// Test reserving overlapping regions +fn test_reserve_overlapping_regions() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Reserve a block + let ptr1 = allocator.reserve_raw(size * 5, size); + assert!(!ptr1.is_null()); + + // Try to reserve a larger block that would overlap + let ptr2 = allocator.reserve_raw(size * 4, size * 4); + assert!(ptr2.is_null()); // Should fail because it overlaps with ptr1 + + allocator.free_raw(ptr1, size); +} + +#[test] +// Test allocating all available memory +fn test_exhaust_memory() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let mut ptrs = vec![]; + + // Allocate until we can't anymore + loop { + let ptr = allocator.allocate_raw(size); + if ptr.is_null() { + break; + } + ptrs.push(ptr); + } + + // Verify we actually allocated something + assert!(!ptrs.is_empty()); + + // Try one more allocation - should fail + let ptr = allocator.allocate_raw(size); + assert!(ptr.is_null()); + + // Free everything + for ptr in ptrs { + allocator.free_raw(ptr, size); + } + + // Should be able to allocate again + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + allocator.free_raw(ptr, size); +} + +#[test] +// Test mixed allocation sizes +fn test_mixed_allocation_sizes() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + let small = BuddyAllocatorImpl::MIN_ALLOCATION; + let medium = small * 4; + let large = small * 16; + + let ptr1 = allocator.allocate_raw(small); + let ptr2 = allocator.allocate_raw(large); + let ptr3 = allocator.allocate_raw(medium); + let ptr4 = allocator.allocate_raw(small); + + assert!(!ptr1.is_null()); + assert!(!ptr2.is_null()); + assert!(!ptr3.is_null()); + assert!(!ptr4.is_null()); + + // Verify they're all different + let ptrs = [ptr1, ptr2, ptr3, ptr4]; + for i in 0..ptrs.len() { + for j in (i + 1)..ptrs.len() { + assert_ne!(ptrs[i], ptrs[j]); + } + } + + allocator.free_raw(ptr2, large); + allocator.free_raw(ptr1, small); + allocator.free_raw(ptr4, small); + allocator.free_raw(ptr3, medium); +} + +#[test] +// Test freeing in different order than allocation +fn test_free_reverse_order() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut ptrs = vec![]; + for _ in 0..10 { + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + ptrs.push(ptr); + } + + let used_peak = allocator.used_size(); + + // Free in reverse order + for ptr in ptrs.iter().rev() { + allocator.free_raw(*ptr, size); + } + + assert!(allocator.used_size() < used_peak); +} + +#[test] +// Test allocation size rounding. Test after exhausting, allocator should still be usable -- no lock leak +fn allocation_rounds_up_to_pow2_and_min() { + let a = BuddyAllocatorImpl::new(1 << 24); + + // Request sizes that aren't powers of 2 + let ptr1 = a.allocate_raw(5000); // Should round to 8192 + let ptr2 = a.allocate_raw(1000); // Should round to 4096 + let ptr3 = a.allocate_raw(10000); // Should round to 16384 + + assert!(!ptr1.is_null()); + assert!(!ptr2.is_null()); + assert!(!ptr3.is_null()); + + a.free_raw(ptr1, 5000); + a.free_raw(ptr2, 1000); + a.free_raw(ptr3, 10000); + + let used0 = a.used_size(); + let p = a.allocate_raw(5000); // rounds to 8192 (and >= 4096) + assert!(!p.is_null()); + assert_eq!(a.used_size(), used0 + 8192); + + a.free_raw(p, 5000); // free uses same rounding path + assert_eq!(a.used_size(), used0); +} + +#[test] +// Confirm there is no overlap between levels +fn test_offset_calculation() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + let mut ranges = vec![]; + for level in allocator.inner.meta.level_range.clone() { + let offset = allocator.inner.offset_of_level_words(level); + let size = allocator.inner.size_of_level_words(level); + ranges.push((offset, offset + size, level)); + } + ranges.sort_by_key(|(start, _, _)| *start); + + for w in ranges.windows(2) { + let (_s1, e1, l1) = w[0]; + let (s2, _e2, l2) = w[1]; + assert!(e1 <= s2, "overlap between level {} and level {}", l1, l2); + } +} + +#[test] +// Test bitmap boundaries +fn test_bitmap_boundaries() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + for level in allocator.inner.meta.level_range.clone() { + let bits = allocator.inner.size_of_level_bits(level); + let words = allocator.inner.size_of_level_words(level); + + // Verify words is enough to hold bits + assert!( + words * 64 >= bits, + "Level {} needs {} bits but only has {} words ({} bits)", + level, + bits, + words, + words * 64 + ); + } +} + +#[test] +// Test try_allocate_many_raw where everything should succeed easily +fn test_try_allocate_many_no_contention() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut ptrs = [core::ptr::null_mut(); 10]; + let result = allocator.try_allocate_many_raw(size, &mut ptrs); + + assert_eq!(result, Some(10)); + assert!(ptrs.iter().all(|p| !p.is_null())); + + allocator.free_many_raw(size, &ptrs); +} + +#[test] +// Testing allocating more pointers than space available, making sure bulk alloc stops cleanly when space out, +// and partial success allowed, reported successes are valid. Currently hanging +fn test_allocate_many_partial_success() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Request more blocks than available + let mut ptrs = [core::ptr::null_mut(); 10000]; + let count = allocator.allocate_many_raw(size, &mut ptrs); + + // Should have allocated some but not all + assert!(count > 0); + assert!(count < ptrs.len()); + + // All allocated pointers should be non-null + for i in 0..count { + assert!(!ptrs[i].is_null()); + } + + // Remaining should be null + for i in count..ptrs.len() { + assert!(ptrs[i].is_null()); + } + + // Clean up + allocator.free_many_raw(size, &ptrs[0..count]); +} + +#[test] +// Test that refcnt works for different allocation addresses +fn test_refcnt_different_addresses() { + use core::sync::atomic::Ordering; + + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let ptr1 = allocator.allocate_raw(size); + let ptr2 = allocator.allocate_raw(size); + + let refcnt1 = allocator.refcnt(ptr1); + let refcnt2 = allocator.refcnt(ptr2); + + // Should be different refcnt locations + assert_ne!(refcnt1, refcnt2); + + // Both should be 0 + assert_eq!(unsafe { (*refcnt1).load(Ordering::SeqCst) }, 0); + assert_eq!(unsafe { (*refcnt2).load(Ordering::SeqCst) }, 0); + + allocator.free_raw(ptr1, size); + allocator.free_raw(ptr2, size); +} + +#[test] +// Test null pointer refcnt +fn test_refcnt_null_pointer() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let refcnt = allocator.refcnt(core::ptr::null::()); + assert!(refcnt.is_null()); +} + +#[test] +// Test usage calculation +fn test_usage_calculation() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let initial_usage = allocator.usage(); + + let ptr = allocator.allocate_raw(size); + let usage_after = allocator.usage(); + + assert!(usage_after > initial_usage); + assert!(usage_after <= 1.0); + assert!(usage_after >= 0.0); + + allocator.free_raw(ptr, size); +} + +#[test] +// Test request counting +fn test_request_counting() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut before = [0; 64]; + let mut after = [0; 64]; + + allocator.requests(&mut before); + + let ptr = allocator.allocate_raw(size); + allocator.free_raw(ptr, size); + + allocator.requests(&mut after); + + // Should have incremented request count for the size level + let level = size.next_power_of_two().ilog2() as usize; + assert!(after[level] > before[level]); +} + +#[test] +fn test_alignment_requirements() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let base = allocator.base() as usize; + + for power in 12..20 { + let size = 1 << power; + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + + let addr = ptr as usize; + assert_eq!( + (addr - base) % size, + 0, + "Allocation of size {} not aligned within arena", + size + ); + + allocator.free_raw(ptr, size); + } +} + +#[test] +// Test clone and drop behavior +fn test_clone_and_drop() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + let ptr1 = allocator.allocate_raw(4096); + assert!(!ptr1.is_null()); + + { + let clone = allocator.clone(); + let ptr2 = clone.allocate_raw(4096); + assert!(!ptr2.is_null()); + clone.free_raw(ptr2, 4096); + // clone drops here + } + + // Original should still work + let ptr3 = allocator.allocate_raw(4096); + assert!(!ptr3.is_null()); + + allocator.free_raw(ptr1, 4096); + allocator.free_raw(ptr3, 4096); +} + +#[test] +// Allocate one block, compute its buddy address, and verify that reserving the buddy returns that address (if it’s free). +fn buddy_address_math_matches_reserve() { + let a = BuddyAllocatorImpl::new(1 << 24); + let base = a.base() as usize; + + for power in 12..18 { + let size = 1usize << power; + let p = a.allocate_raw(size); + assert!(!p.is_null()); + + let off = a.to_offset(p); + let idx = off / size; + let buddy_idx = idx ^ 1; + let buddy_off = buddy_idx * size; + + // If the buddy is free, reserve_raw must return exactly that address. + let b = a.reserve_raw(buddy_off, size); + if !b.is_null() { + assert_eq!(a.to_offset(b), buddy_off); + a.free_raw(b, size); + } + + a.free_raw(p, size); + + // (optional) base-relative alignment property + assert_eq!(((p as usize) - base) % size, 0); + } +} + +#[test] +// 'Create' a known free block at a known offset by allocating a large block, then freeing it, then reserving/allocating inside it. +fn split_large_block_into_smaller_blocks() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let big = 1usize << 16; // 64KiB + let small = 1usize << 12; // 4KiB + let factor = big / small; + + let p = a.allocate_raw(big); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, big); + + // Now reserve all 4KiB blocks inside that 64KiB region. + let mut blocks = Vec::new(); + for i in 0..factor { + let q = a.reserve_raw(off + i * small, small); + assert!(!q.is_null(), "failed to reserve sub-block {}", i); + blocks.push(q); + } + + // Free them back + for q in blocks { + a.free_raw(q, small); + } +} + +#[test] +// Reserve two buddy halves, free them, verify you can reserve the parent block at the exact parent address. +fn coalesce_two_buddies_into_parent() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + // Create a known free parent block at a known offset. + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + // Reserve both children (buddies). + let c0 = a.reserve_raw(off, child); + let c1 = a.reserve_raw(off + child, child); + assert!(!c0.is_null() && !c1.is_null()); + + // Free both; this should coalesce into the parent. + a.free_raw(c0, child); + a.free_raw(c1, child); + + // Now reserving the parent at 'off' should succeed. + let p2 = a.reserve_raw(off, parent); + assert!( + !p2.is_null(), + "parent block did not reappear after coalescing" + ); + assert_eq!(a.to_offset(p2), off); + + a.free_raw(p2, parent); +} + +#[test] +// Hold one child, free the other, ensure parent reservation fails at that exact parent address. +fn no_coalesce_if_only_one_buddy_free() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + let c0 = a.reserve_raw(off, child); + let c1 = a.reserve_raw(off + child, child); + assert!(!c0.is_null() && !c1.is_null()); + + // Free only one child + a.free_raw(c0, child); + + // Parent must NOT be reservable while the other buddy is still held. + let parent_try = a.reserve_raw(off, parent); + assert!( + parent_try.is_null(), + "parent became available with one buddy still reserved" + ); + + // Cleanup + a.free_raw(c1, child); + + // Now parent should be available (coalesced) + let parent_ok = a.reserve_raw(off, parent); + assert!(!parent_ok.is_null()); + a.free_raw(parent_ok, parent); +} + +#[test] +// Free child1 then child0; ensure parent becomes available. +fn coalesce_is_order_independent() { + let a = BuddyAllocatorImpl::new(1 << 24); + let parent = 1usize << 15; // 32KiB + let child = 1usize << 14; // 16KiB + + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + let c0 = a.reserve_raw(off, child); + let c1 = a.reserve_raw(off + child, child); + assert!(!c0.is_null() && !c1.is_null()); + + a.free_raw(c1, child); + a.free_raw(c0, child); + + let p2 = a.reserve_raw(off, parent); + assert!(!p2.is_null()); + a.free_raw(p2, parent); +} + +#[test] +// Free 4 children → coalesce to 2 parents → coalesce to 1 grandparent. +fn multi_level_coalesce_cascades() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let grand = 1usize << 15; // 32KiB + let child = 1usize << 13; // 8KiB + let n = grand / child; // 4 + + let p = a.allocate_raw(grand); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, grand); + + let mut kids = Vec::new(); + for i in 0..n { + let k = a.reserve_raw(off + i * child, child); + assert!(!k.is_null()); + kids.push(k); + } + + // Free all kids -> should coalesce up to grand + for k in kids { + a.free_raw(k, child); + } + + let g = a.reserve_raw(off, grand); + assert!( + !g.is_null(), + "expected full cascade coalesce to grand block" + ); + a.free_raw(g, grand); +} + +#[test] +// Size rounding edge cases: allocate_raw rounds up to power-of-two and MIN_ALLOCATION, ensuring allocations don’t fail just because size isn’t a power of two. +// Currently failing; needs to be fixed? +fn reserve_out_of_range_returns_null() { + let a = BuddyAllocatorImpl::new(1 << 24); + let size = 1usize << 12; + + // definitely beyond arena + let ptr = a.reserve_raw(a.len() + size, size); + assert!(ptr.is_null()); +} + +#[test] +// Testing allocate_many_raw where partial failure does not poison the lock +// Currently hanging because partial failures is not working, test after that is fixed +fn allocate_many_partial_failure_does_not_poison_lock() { + let a = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut ptrs = [core::ptr::null_mut(); 10000]; + let n = a.allocate_many_raw(size, &mut ptrs); + + assert!(n > 0); + assert!(n < ptrs.len()); + + a.free_many_raw(size, &ptrs[..n]); + + // If the lock leaked, this would hang. + let p = a.allocate_raw(size); + assert!(!p.is_null()); + a.free_raw(p, size); +} + +#[test] +// ensures try_* returns None when lock is held. +fn try_allocate_many_returns_none_when_locked() { + let a = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Manually lock allocator and ensure try_* fails. + unsafe { + a.inner.lock(); + } + let mut ptrs = [core::ptr::null_mut(); 4]; + let r = a.try_allocate_many_raw(size, &mut ptrs); + assert_eq!(r, None); + unsafe { + a.inner.unlock(); + } + + // Now it should work + let r2 = a.try_allocate_many_raw(size, &mut ptrs); + assert_eq!(r2, Some(4)); + a.free_many_raw(size, &ptrs); +} + +#[test] +// Interleaved patterns: A,B,C,D where (A,B) and (C,D) are buddy pairs. +// Freeing B and C alone should NOT make either parent available; +// freeing A then enables AB coalesce; freeing D then enables CD coalesce; then both parents can coalesce further. +fn interleaved_buddy_pairs_coalesce_independently_then_merge() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let grand = 1usize << 15; // 32KiB + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + // Known free 32KiB region + let g = a.allocate_raw(grand); + assert!(!g.is_null()); + let off = a.to_offset(g); + a.free_raw(g, grand); + + // Reserve A,B,C,D as 8KiB blocks at offsets 0,1,2,3 within the 32KiB region + let a0 = a.reserve_raw(off + 0 * child, child); // A + let b0 = a.reserve_raw(off + 1 * child, child); // B (buddy of A) + let c0 = a.reserve_raw(off + 2 * child, child); // C + let d0 = a.reserve_raw(off + 3 * child, child); // D (buddy of C) + assert!(!a0.is_null() && !b0.is_null() && !c0.is_null() && !d0.is_null()); + + // Free B and C only -> neither 16KiB parent should be reservable yet. + a.free_raw(b0, child); + a.free_raw(c0, child); + + assert!( + a.reserve_raw(off + 0 * parent, parent).is_null(), + "AB parent should not exist yet" + ); + assert!( + a.reserve_raw(off + 1 * parent, parent).is_null(), + "CD parent should not exist yet" + ); + + // Free A -> AB should coalesce to first 16KiB parent at off + a.free_raw(a0, child); + let p0 = a.reserve_raw(off + 0 * parent, parent); + assert!(!p0.is_null(), "AB should coalesce to 16KiB"); + a.free_raw(p0, parent); + + // Free D -> CD should coalesce to second 16KiB parent at off + 16KiB + a.free_raw(d0, child); + let p1 = a.reserve_raw(off + 1 * parent, parent); + assert!(!p1.is_null(), "CD should coalesce to 16KiB"); + a.free_raw(p1, parent); + + // Now both 16KiB parents are free -> should coalesce into 32KiB grandparent at off + let g2 = a.reserve_raw(off, grand); + assert!( + !g2.is_null(), + "two free 16KiB parents should coalesce to 32KiB" + ); + a.free_raw(g2, grand); +} + +#[test] +// Fragmentation scenario: partial coalescing with an obstacle. +// If one leaf remains reserved, upper levels must not fully coalesce; once obstacle freed, full coalesce should happen. +fn fragmentation_blocks_full_coalesce_until_obstacle_removed() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let big = 1usize << 16; // 64KiB region we control + let leaf = 1usize << 12; // 4KiB + let n = big / leaf; // 16 leaves + + // Known free 64KiB region + let p = a.allocate_raw(big); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, big); + + // Reserve all leaves, keep one as "obstacle", free the rest. + let mut leaves = Vec::new(); + for i in 0..n { + let q = a.reserve_raw(off + i * leaf, leaf); + assert!(!q.is_null()); + leaves.push(q); + } + + let obstacle = leaves[7]; // arbitrary leaf to hold + for (_i, q) in leaves.iter().enumerate() { + if *q == obstacle { + continue; + } + a.free_raw(*q, leaf); + } + + // With one 4KiB still reserved, the full 64KiB block must NOT be available. + assert!( + a.reserve_raw(off, big).is_null(), + "should not fully coalesce with an obstacle leaf reserved" + ); + + // Now free the obstacle leaf -> full coalesce should become possible. + a.free_raw(obstacle, leaf); + let big2 = a.reserve_raw(off, big); + assert!( + !big2.is_null(), + "after removing obstacle, should fully coalesce back to 64KiB" + ); + a.free_raw(big2, big); +} + +#[test] +// Reserved blocks shouldn't participate in coalescing: +// if one buddy is permanently reserved (held), the parent must not become available. +fn reserved_block_prevents_coalescing() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + // Known free parent region + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + // Reserve both children, but "reserve" one as a held block (simulate reservation that shouldn't coalesce). + let held = a.reserve_raw(off, child); + let other = a.reserve_raw(off + child, child); + assert!(!held.is_null() && !other.is_null()); + + // Free only the other -> parent must not appear + a.free_raw(other, child); + assert!( + a.reserve_raw(off, parent).is_null(), + "parent should not coalesce while one child is held/reserved" + ); + + // Once held is freed too, parent should become available + a.free_raw(held, child); + let p2 = a.reserve_raw(off, parent); + assert!(!p2.is_null()); + a.free_raw(p2, parent); +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 5af14d80..66425c37 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,30 +1,29 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![allow(stable_features, unused_features)] #![feature(allocator_api)] -#![feature(box_as_ptr)] +#![feature(new_range_api)] #![feature(fn_traits)] -#![feature(layout_for_ptr)] -#![feature(maybe_uninit_as_bytes)] +#![feature(slice_ptr_get)] +#![feature(sync_unsafe_cell)] +#![cfg_attr(feature = "std", feature(layout_for_ptr))] #![feature(negative_impls)] -#![feature(new_range_api)] #![feature(ptr_metadata)] -#![feature(slice_from_ptr_range)] -#![feature(sync_unsafe_cell)] -#![feature(try_trait_v2)] -#![feature(test)] +#![cfg_attr(test, feature(test))] #![feature(unboxed_closures)] -#![cfg_attr(feature = "std", feature(thread_id_value))] #![cfg_attr(feature = "thread_local_cache", feature(thread_local))] pub mod buddy; pub mod refcnt; pub use buddy::BuddyAllocator; pub mod arrayvec; +pub mod bitpack; pub mod controlreg; pub mod elfloader; pub mod ipaddr; +pub mod pipe; +pub mod protocol; pub mod sendable; pub mod util; -pub mod vhost; #[cfg(feature = "std")] pub mod mmap; @@ -51,13 +50,24 @@ pub struct SymtabRecord { } pub mod hypercall { + use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize}; + pub const EXIT: u64 = 0; pub const LOG: u64 = 1; pub const SYMNAME: u64 = 2; pub const MEMSET: u64 = 3; pub const MEMCLR: u64 = 4; - pub const SERVERREAD: u64 = 5; - pub const SERVERWRITE: u64 = 6; - pub const CLIENTREAD: u64 = 7; - pub const CLIENTWRITE: u64 = 8; + + pub const NOTIFY_READ: u64 = 16; + pub const NOTIFY_WRITE: u64 = 17; + + #[derive(Debug, Default)] + pub struct TcpInfo { + pub ip: u32, + pub port: u16, + pub id: AtomicU64, + pub buf: usize, + pub len: AtomicUsize, + pub done: AtomicBool, + } } diff --git a/common/src/pipe.rs b/common/src/pipe.rs new file mode 100644 index 00000000..ed1ef456 --- /dev/null +++ b/common/src/pipe.rs @@ -0,0 +1,7 @@ +mod bi; +mod error; +mod uni; + +pub use bi::{pipe, Pipe}; +pub use error::{Error, Result}; +pub use uni::{channel, Reader, Writer}; diff --git a/common/src/pipe/bi.rs b/common/src/pipe/bi.rs new file mode 100644 index 00000000..712ec4c9 --- /dev/null +++ b/common/src/pipe/bi.rs @@ -0,0 +1,83 @@ +use super::error::Result; +use super::uni::{channel, Reader, Writer}; + +#[derive(Debug)] +pub struct Pipe { + rx: Reader, + tx: Writer, +} + +pub fn pipe(len: usize) -> (Pipe, Pipe) { + let (r0, w0) = channel(len); + let (r1, w1) = channel(len); + (Pipe { rx: r0, tx: w1 }, Pipe { rx: r1, tx: w0 }) +} + +impl Pipe { + pub fn read(&mut self, data: &mut [u8]) -> Result { + self.rx.read(data) + } + + pub fn can_read(&self) -> bool { + !self.rx.is_empty() + } + + pub fn write(&mut self, data: &[u8]) -> Result { + self.tx.write(data) + } + + pub fn can_write(&self) -> bool { + !self.tx.is_empty() + } + + pub fn into_inner(self) -> (Reader, Writer) { + (self.rx, self.tx) + } + + /// # Safety + /// The reader and writer must correspond to the two halves of a pipe, as previously returned + /// from into_inner. + pub unsafe fn from_inner(rx: Reader, tx: Writer) -> Self { + Pipe { rx, tx } + } +} + +#[cfg(test)] +mod tests { + #[test] + pub fn test_ping_pong() { + let (mut p, mut q) = super::pipe(1024); + std::thread::spawn(move || loop { + let mut buf = [0; 8]; + loop { + let result = q.read(&mut buf); + if result.is_ok() { + break; + } + std::thread::yield_now(); + } + let i = u64::from_le_bytes(buf); + buf = u64::to_le_bytes(i + 1); + let _ = q.write(&buf); + }); + let mut bytes = u64::to_le_bytes(0); + let mut i = 0; + loop { + p.write(&bytes).unwrap(); + loop { + let result = p.read(&mut bytes); + if result.is_ok() { + break; + } + std::thread::yield_now(); + } + let j = u64::from_le_bytes(bytes); + assert_eq!(j, i + 1); + i = j + 1; + bytes = u64::to_le_bytes(j + 1); + if i >= 1024 { + return; + } + } + } +} diff --git a/common/src/pipe/error.rs b/common/src/pipe/error.rs new file mode 100644 index 00000000..92f01fc5 --- /dev/null +++ b/common/src/pipe/error.rs @@ -0,0 +1,18 @@ +use core::fmt; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + WouldBlock, + Closed, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::WouldBlock => write!(f, "operation would block"), + Error::Closed => write!(f, "pipe closed by peer"), + } + } +} + +pub type Result = core::result::Result; diff --git a/common/src/pipe/uni.rs b/common/src/pipe/uni.rs new file mode 100644 index 00000000..02f9867e --- /dev/null +++ b/common/src/pipe/uni.rs @@ -0,0 +1,304 @@ +extern crate alloc; +use crate::BuddyAllocator; +use alloc::boxed::Box; +use alloc::sync::Arc; +use core::alloc::Allocator; +use core::alloc::Layout; +use core::cell::SyncUnsafeCell; +use core::sync::atomic::Ordering; +use core::sync::atomic::{AtomicBool, AtomicUsize}; + +use super::error::{Error, Result}; + +#[derive(Debug)] +#[repr(C)] +pub struct RingBuffer { + read_cursor: AtomicUsize, + write_cursor: AtomicUsize, + writer_closed: AtomicBool, + reader_closed: AtomicBool, + buffer: SyncUnsafeCell<[u8]>, +} + +impl RingBuffer { + fn new(len: usize) -> Arc { + let layout = Layout::new::() + .extend(Layout::new::()) + .unwrap() + .0 + .extend(Layout::new::()) + .unwrap() + .0 + .extend(Layout::new::()) + .unwrap() + .0 + .extend(Layout::new::().repeat(len).unwrap().0) + .unwrap() + .0 + .pad_to_align(); + let p: *mut u8 = BuddyAllocator + .allocate_zeroed(layout) + .expect("could not allocate shared memory for ringbuffer") + .as_mut_ptr(); + let (base, _) = p.to_raw_parts(); + let p: *mut RingBuffer = core::ptr::from_raw_parts_mut(base, len); + unsafe { + let b = Box::from_raw_in(p, BuddyAllocator); + b.into() + } + } + + fn len(&self) -> usize { + core::mem::size_of_val(&self.buffer) + } + + unsafe fn readable_bytes(&self) -> *const [u8] { + let start = self.read_cursor.load(Ordering::SeqCst) % self.len(); + let mut end = self.write_cursor.load(Ordering::SeqCst) % self.len(); + if end < start { + end = self.len() + } + let len = end - start; + let base = self.buffer.get() as *const u8; + unsafe { core::ptr::slice_from_raw_parts(base.add(start), len) } + } + + unsafe fn read(&self, bytes: usize) { + let start = self.read_cursor.load(Ordering::SeqCst); + let end = start + bytes; + self.read_cursor.store(end, Ordering::SeqCst); + } + + unsafe fn can_read(&self) -> bool { + let read = self.read_cursor.load(Ordering::SeqCst); + let write = self.write_cursor.load(Ordering::SeqCst); + read != write + } + + unsafe fn writer_open(&self) -> bool { + !self.writer_closed.load(Ordering::SeqCst) + } + + unsafe fn read_hangup(&self) { + self.reader_closed.store(true, Ordering::SeqCst); + } + + unsafe fn writeable_bytes(&self) -> *mut [u8] { + let start = self.write_cursor.load(Ordering::SeqCst) % self.len(); + let mut end = (self.read_cursor.load(Ordering::SeqCst) + self.len() - 1) % self.len(); + if end < start { + end = self.len(); + } + let len = end - start; + let base = self.buffer.get() as *mut u8; + unsafe { core::ptr::slice_from_raw_parts_mut(base.add(start), len) } + } + + unsafe fn write(&self, bytes: usize) { + let start = self.write_cursor.load(Ordering::SeqCst); + let end = start + bytes % self.len(); + self.write_cursor.store(end, Ordering::SeqCst); + } + + unsafe fn can_write(&self) -> bool { + let read = self.read_cursor.load(Ordering::SeqCst); + let write = self.write_cursor.load(Ordering::SeqCst); + (read + self.len() - 1) % self.len() != write % self.len() + } + + unsafe fn reader_open(&self) -> bool { + !self.reader_closed.load(Ordering::SeqCst) + } + + unsafe fn write_hangup(&self) { + self.writer_closed.store(true, Ordering::SeqCst); + } +} + +pub fn channel(len: usize) -> (Reader, Writer) { + let ring = RingBuffer::new(len); + ( + Reader { + ring: Some(ring.clone()), + }, + Writer { ring: Some(ring) }, + ) +} + +#[derive(Debug)] +pub struct Reader { + ring: Option>, +} + +impl Reader { + pub fn read(&mut self, data: &mut [u8]) -> Result { + unsafe { + // Drain any buffered bytes before reporting the writer's hangup, so a + // reader still sees data the writer sent immediately before closing + // (e.g. a Close-ack the peer wrote just before dropping its end). + if self.ring.as_ref().unwrap().can_read() { + let bytes = self.ring.as_ref().unwrap().readable_bytes(); + let len = core::cmp::min(data.len(), bytes.len()); + data[..len].copy_from_slice(&(&(*bytes))[..len]); + self.ring.as_mut().unwrap().read(len); + return Ok(len); + } + if self.is_closed() { + return Err(Error::Closed); + } + Err(Error::WouldBlock) + } + } + + pub fn len(&self) -> usize { + unsafe { self.ring.as_ref().unwrap().readable_bytes().len() } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn is_closed(&self) -> bool { + unsafe { !self.ring.as_ref().unwrap().writer_open() } + } + + pub fn into_inner(mut self) -> Arc { + self.ring.take().unwrap() + } + + /// # Safety + /// The provided buffer must be a valid RingBuffer, and must not be in use by any other + /// Readers. + pub unsafe fn from_inner(ring: Arc) -> Self { + Self { ring: Some(ring) } + } +} + +impl Drop for Reader { + fn drop(&mut self) { + if let Some(ring) = self.ring.as_mut() { + unsafe { + ring.read_hangup(); + } + } + } +} + +#[derive(Debug)] +pub struct Writer { + ring: Option>, +} + +impl Writer { + pub fn write(&mut self, data: &[u8]) -> Result { + unsafe { + if self.is_closed() { + return Err(Error::Closed); + } + if !self.ring.as_ref().unwrap().can_write() { + return Err(Error::WouldBlock); + } + let bytes = self.ring.as_ref().unwrap().writeable_bytes(); + let len = core::cmp::min(data.len(), bytes.len()); + (&mut (*bytes))[..len].copy_from_slice(&data[..len]); + self.ring.as_ref().unwrap().write(len); + Ok(len) + } + } + + pub fn len(&self) -> usize { + unsafe { self.ring.as_ref().unwrap().writeable_bytes().len() } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn is_closed(&self) -> bool { + unsafe { !self.ring.as_ref().unwrap().reader_open() } + } + + pub fn into_inner(mut self) -> Arc { + self.ring.take().unwrap() + } + + /// # Safety + /// The provided buffer must be a valid RingBuffer, and must not be in use by any other + /// Writers. + pub unsafe fn from_inner(ring: Arc) -> Self { + Self { ring: Some(ring) } + } +} + +impl Drop for Writer { + fn drop(&mut self) { + if let Some(ring) = self.ring.as_mut() { + unsafe { + ring.write_hangup(); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::Error; + + #[test] + pub fn test_send_recv() { + let (mut rx, mut tx) = super::channel(1024); + assert_eq!(tx.len(), 1023); + assert_eq!(rx.len(), 0); + + let len = tx.write(b"hello"); + assert_eq!(len, Ok(5)); + assert_eq!(tx.len(), 1018); + assert_eq!(rx.len(), 5); + + let mut buf = [0; 5]; + let len = rx.read(&mut buf); + assert_eq!(len, Ok(5)); + assert_eq!(rx.len(), 0); + assert_eq!(&buf, b"hello"); + } + + #[test] + pub fn test_send_recv_wrapping() { + let (mut rx, mut tx) = super::channel(4); + assert_eq!(tx.len(), 3); + assert_eq!(rx.len(), 0); + + let len = tx.write(b"hello"); + assert_eq!(len, Ok(3)); + assert_eq!(tx.len(), 0); + assert_eq!(rx.len(), 3); + + let mut buf = [0; 3]; + let len = rx.read(&mut buf); + assert_eq!(len, Ok(3)); + assert_eq!(rx.len(), 0); + assert_eq!(tx.len(), 1); + assert_eq!(&buf, b"hel"); + + let len = tx.write(b"lo"); + assert_eq!(len, Ok(1)); + assert_eq!(tx.len(), 2); + assert_eq!(rx.len(), 1); + } + + #[test] + pub fn test_would_block() { + let (mut rx, mut tx) = super::channel(4); + + tx.write(b"xx").unwrap(); + let mut buf = [0; 2]; + rx.read(&mut buf).unwrap(); + + let len = tx.write(b"yyzz"); + assert_eq!(len, Ok(2)); + let len = tx.write(b"zz"); + assert_eq!(len, Ok(1)); + let len = tx.write(b"z"); + assert_eq!(len, Err(Error::WouldBlock)); + } +} diff --git a/common/src/protocol.rs b/common/src/protocol.rs new file mode 100644 index 00000000..bfcb7e44 --- /dev/null +++ b/common/src/protocol.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +pub mod control; +pub mod file; +pub mod listener; +pub mod stream; + +#[derive(Debug, Serialize, Deserialize)] +pub enum Error {} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FileDescriptor(pub usize); + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StreamDescriptor(pub usize); + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ListenerDescriptor(pub usize); diff --git a/common/src/protocol/control.rs b/common/src/protocol/control.rs new file mode 100644 index 00000000..a6d315cf --- /dev/null +++ b/common/src/protocol/control.rs @@ -0,0 +1,91 @@ +extern crate alloc; +use alloc::string::String; +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; + +pub use embedded_io::ErrorKind; + +#[derive(Debug, Serialize, Deserialize)] +pub enum Request { + GetArgs, + Exit(i32), + Open(String, FileMode), + Mkdir(String), + Listen { ip: [u8; 4], port: u16 }, + Connect { host: String, port: u16 }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Args(Vec), + Pipe(PipeData), + Ack, + Err(IoErrorKind), +} + +/// Wire-serializable mirror of [`embedded_io::ErrorKind`], which is +/// `#[non_exhaustive]` and not `serde`-derivable. Carries a host I/O failure +/// back to the guest, where it converts into an `embedded_io::ErrorKind`. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum IoErrorKind { + NotFound, + PermissionDenied, + AlreadyExists, + InvalidInput, + InvalidData, + Unsupported, + OutOfMemory, + Interrupted, + Other, +} + +impl From for ErrorKind { + fn from(e: IoErrorKind) -> Self { + match e { + IoErrorKind::NotFound => ErrorKind::NotFound, + IoErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + IoErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + IoErrorKind::InvalidInput => ErrorKind::InvalidInput, + IoErrorKind::InvalidData => ErrorKind::InvalidData, + IoErrorKind::Unsupported => ErrorKind::Unsupported, + IoErrorKind::OutOfMemory => ErrorKind::OutOfMemory, + IoErrorKind::Interrupted => ErrorKind::Interrupted, + IoErrorKind::Other => ErrorKind::Other, + } + } +} + +#[cfg(feature = "std")] +impl From for IoErrorKind { + fn from(e: std::io::ErrorKind) -> Self { + match e { + std::io::ErrorKind::NotFound => IoErrorKind::NotFound, + std::io::ErrorKind::PermissionDenied => IoErrorKind::PermissionDenied, + std::io::ErrorKind::AlreadyExists => IoErrorKind::AlreadyExists, + std::io::ErrorKind::InvalidInput => IoErrorKind::InvalidInput, + std::io::ErrorKind::InvalidData => IoErrorKind::InvalidData, + std::io::ErrorKind::Unsupported => IoErrorKind::Unsupported, + std::io::ErrorKind::OutOfMemory => IoErrorKind::OutOfMemory, + std::io::ErrorKind::Interrupted => IoErrorKind::Interrupted, + _ => IoErrorKind::Other, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PipeData { + pub rx_ptr: usize, + pub rx_len: usize, + pub tx_ptr: usize, + pub tx_len: usize, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct FileMode { + pub read: bool, + pub write: bool, + pub create: bool, + pub append: bool, + pub truncate: bool, +} diff --git a/common/src/protocol/file.rs b/common/src/protocol/file.rs new file mode 100644 index 00000000..d3d61454 --- /dev/null +++ b/common/src/protocol/file.rs @@ -0,0 +1,27 @@ +extern crate alloc; +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum Request { + Read(usize), + Write(Vec), + Seek(Whence), + Close, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Bytes(Vec), + Length(usize), + Offset(u64), + Ack, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Whence { + Start(u64), + Current(i64), + End(i64), +} diff --git a/common/src/protocol/listener.rs b/common/src/protocol/listener.rs new file mode 100644 index 00000000..266c7ed8 --- /dev/null +++ b/common/src/protocol/listener.rs @@ -0,0 +1,16 @@ +extern crate alloc; + +use super::control::PipeData; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum Request { + Accept, + Close, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Pipe(PipeData), + Ack, +} diff --git a/common/src/protocol/stream.rs b/common/src/protocol/stream.rs new file mode 100644 index 00000000..530990a1 --- /dev/null +++ b/common/src/protocol/stream.rs @@ -0,0 +1,18 @@ +extern crate alloc; +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum Request { + Send(Vec), + Receive(usize), + Close, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Length(usize), + Bytes(Vec), + Ack, +} diff --git a/common/src/util/concurrent_trie.rs b/common/src/util/concurrent_trie.rs index afb8c67d..b4ee0969 100644 --- a/common/src/util/concurrent_trie.rs +++ b/common/src/util/concurrent_trie.rs @@ -38,7 +38,7 @@ impl SyncBox { unsafe { Some(Box::from_raw( self.ptr - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| { + .try_update(Ordering::SeqCst, Ordering::SeqCst, |old| { if old.is_null() { None } else { @@ -54,7 +54,7 @@ impl SyncBox { let ptr = Box::into_raw(value); unsafe { self.ptr - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| { + .try_update(Ordering::SeqCst, Ordering::SeqCst, |old| { if old.is_null() { Some(ptr) } else { diff --git a/common/src/vhost.rs b/common/src/vhost.rs deleted file mode 100644 index 865c6d30..00000000 --- a/common/src/vhost.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[repr(C)] -#[derive(Copy, Clone)] -pub struct VSockMetadata { - pub rx: VirtQueueMetadata, - pub tx: VirtQueueMetadata, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct VirtQueueMetadata { - pub descriptors: usize, - pub desc: usize, - pub used: usize, - pub avail: usize, -} diff --git a/fix/.cargo/config.toml b/fix/.cargo/config.toml new file mode 100644 index 00000000..34803b4e --- /dev/null +++ b/fix/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.x86_64-unknown-none] +rustflags = [ + "-C", "force-frame-pointers=on", + "-C", "code-model=large", + "-C", "relocation-model=static", + # "-C", "target-feature=+crt-static", +] +runner = "cargo run -p vmm --" diff --git a/fix/Cargo.toml b/fix/Cargo.toml index 4198dea6..7de77902 100644 --- a/fix/Cargo.toml +++ b/fix/Cargo.toml @@ -18,7 +18,9 @@ debugcon = ["kernel/debugcon"] [dependencies] arca = { path = "../arca", features = ["serde"] } kernel = { path = "../kernel" } +macros = { path = "../macros" } common = { path = "../common", default-features = false } +fixhandle = { path = "handle" } log = "0.4.27" serde = { version = "1.0.219", default-features = false, features = ["alloc", "derive"] } chrono = { version = "0.4.41", default-features = false, features = ["alloc", "serde"] } @@ -30,9 +32,16 @@ trait-variant = "0.1.2" futures = { version = "0.3.31", default-features = false, features = ["alloc", "async-await"] } user = { path = "../user", artifact = "bin", target = "x86_64-unknown-none" } async-lock = { version = "3.4.1", default-features = false } +bytemuck = "1.24.0" +bitfield-struct = "0.11.0" +blake3 = { version = "1.8.5", default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +bitint = "0.1.1" [build-dependencies] +fixshell = { path = "shell", artifact="staticlib", target = "x86_64-unknown-none" } anyhow = "1.0.98" +bindgen = "0.72.1" cc = "1.2.30" autotools = "0.2.7" cmake = "0.1.54" diff --git a/fix/build.rs b/fix/build.rs index 80cb7f47..c6067989 100644 --- a/fix/build.rs +++ b/fix/build.rs @@ -1,20 +1,20 @@ -use std::env; -use std::ffi::OsStr; use std::fs::create_dir_all; use std::io::ErrorKind; +use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::OnceLock; +use std::{env, fs}; use anyhow::{Result, anyhow}; use cmake::Config; use include_directory::{Dir, include_directory}; -static FIX_SHELL: Dir<'_> = include_directory!("$CARGO_MANIFEST_DIR/fix-shell"); +static FIX_SHELL_INC: Dir<'_> = include_directory!("$CARGO_MANIFEST_DIR/shell/inc"); +static FIX_SHELL_ETC: Dir<'_> = include_directory!("$CARGO_MANIFEST_DIR/shell/etc"); static INTERMEDIATEOUT: OnceLock = OnceLock::new(); -static ARCAPREFIX: OnceLock = OnceLock::new(); static WASM2C: OnceLock = OnceLock::new(); static WAT2WASM: OnceLock = OnceLock::new(); @@ -72,7 +72,11 @@ fn wasm2c(wasm: &[u8]) -> Result<(Vec, Vec)> { } fn c2elf(c: &[u8], h: &[u8]) -> Result> { - FIX_SHELL.extract(INTERMEDIATEOUT.get().unwrap())?; + FIX_SHELL_INC.extract(INTERMEDIATEOUT.get().unwrap())?; + FIX_SHELL_ETC.extract(INTERMEDIATEOUT.get().unwrap())?; + + let mut wasm_rt = INTERMEDIATEOUT.get().unwrap().clone(); + wasm_rt.push("wasm-rt.c"); let mut c_file = INTERMEDIATEOUT.get().unwrap().clone(); c_file.push("module.c"); @@ -80,19 +84,13 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { let mut h_file = INTERMEDIATEOUT.get().unwrap().clone(); h_file.push("module.h"); - std::fs::write(c_file, c)?; + std::fs::write(c_file.clone(), c)?; std::fs::write(h_file, h)?; - let mut src = vec![]; - let exts = [OsStr::new("c"), OsStr::new("S")]; - for f in std::fs::read_dir(INTERMEDIATEOUT.get().unwrap())? { - let f = f?; - if let Some(ext) = f.path().extension() - && exts.contains(&ext) - { - src.push(f.path()); - } - } + let mut src = vec![c_file, wasm_rt]; + + let shell_top = env::var_os("CARGO_STATICLIB_FILE_FIXSHELL_fixshell").unwrap(); + src.push(PathBuf::from(shell_top)); println!("{src:?}"); @@ -102,10 +100,7 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { let mut memmap = INTERMEDIATEOUT.get().unwrap().clone(); memmap.push("memmap.ld"); - let prefix = ARCAPREFIX.get().unwrap(); - let gcc = prefix.join("bin/musl-gcc"); - - let cc = Command::new(gcc) + let cc = Command::new("gcc") .args([ "-o", o_file.to_str().unwrap(), @@ -116,11 +111,15 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { "-frounding-math", // "-fsignaling-nans", "-ffreestanding", - // "-nostdlib", + "-nostdlib", "-nostartfiles", - "-mcmodel=large", "--verbose", - "-Wl,-no-pie", + "-mcmodel=large", + "-static", + // "-fno-pic", + // "-fno-pie", + // "-Wl,-no-pie", + // "-static", ]) .args(src) .status().map_err(|e| if let ErrorKind::NotFound = e.kind() {anyhow!("Compilation failed. Please make sure you have installed gcc-multilib if you are on Ubuntu.")} else {e.into()})?; @@ -136,26 +135,12 @@ fn main() -> Result<()> { let mut intermediateout: PathBuf = out_dir.clone().into(); intermediateout.push("inter-out"); - if !intermediateout.exists() { - create_dir_all(&intermediateout)? + if intermediateout.exists() { + std::fs::remove_dir_all(&intermediateout)?; } + create_dir_all(&intermediateout)?; INTERMEDIATEOUT.set(intermediateout).unwrap(); - let mut prefix: PathBuf = out_dir.clone().into(); - prefix.push("arca-musl-large"); - - if !prefix.exists() { - create_dir_all(&prefix)? - } - - let prefix = autotools::Config::new("../modules/arca-musl") - .cflag("-mcmodel=large") - .cxxflag("-mcmodel=large") - .out_dir(prefix) - .build(); - - ARCAPREFIX.set(prefix).unwrap(); - let mut dst: PathBuf = out_dir.clone().into(); dst.push("wabt"); if !dst.exists() { @@ -166,8 +151,6 @@ fn main() -> Result<()> { .define("BUILD_TESTS", "OFF") .define("BUILD_LIBWASM", "OFF") .define("BUILD_TOOLS", "ON") - .cflag("-fPIE") - .cxxflag("-fPIE") .out_dir(dst) .build(); @@ -187,12 +170,17 @@ fn main() -> Result<()> { let wasm = wat2wasm(&wat)?; let (c, h) = wasm2c(&wasm)?; let elf = c2elf(&c, &h)?; - std::fs::write(dst, elf)?; + std::fs::write(&dst, elf)?; + + let link = Path::new(&out_dir).ancestors().nth(4).unwrap().join(base); + let _ = fs::remove_file(&link); + symlink(dst, link)?; } - let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - println!("cargo::rerun-if-changed={dir}/etc/memmap.ld"); - println!("cargo::rustc-link-arg=-T{dir}/etc/memmap.ld"); + let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + println!("cargo::rerun-if-changed={cwd}/etc/memmap.ld"); + println!("cargo::rustc-link-arg=-T{cwd}/etc/memmap.ld"); println!("cargo::rustc-link-arg=-no-pie"); Ok(()) diff --git a/fix/fix-shell/main.c b/fix/fix-shell/main.c deleted file mode 100644 index fe3f9122..00000000 --- a/fix/fix-shell/main.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "module.h" -#include "wasm-rt.h" - - -#include -#include -#include - -#define SELF_PAGE_TABLE 0 - -extern wasm_rt_memory_t *WASM_MEMORIES[128]; -extern size_t WASM_MEMORIES_N; - -static int len(const char *s) { - int i = 0; - while (s[i]) - i++; - return i; -} - -static void error_append(const char *msg) { - arca_debug_log((const uint8_t *)msg, len(msg)); -} - -static void error_append_int(const char *msg, int value) { - arca_debug_log_int((const uint8_t* )msg, len(msg), value); -} - -[[noreturn]] void trap(const char *msg) { - error_append(msg); - arca_exit(0); -} - -[[noreturn]] void abort(void) { - error_append("abort"); - arca_exit(0); -} - -uint64_t check(int64_t ret) { - assert(ret >= 0); - return ret; -} - -wasm_rt_externref_t w2c_fixpoint_create_blob_i64(struct w2c_fixpoint *instance, - uint64_t val) { - return check(arca_word_create(val)); -} - -wasm_rt_externref_t w2c_fixpoint_get_tree_entry(struct w2c_fixpoint *instance, - wasm_rt_externref_t handle, - uint32_t index) { - arcad entry = check(arca_tuple_get(handle, index)); - return entry; -} - -static size_t bytes_to_wasm_pages(size_t bytes) { - return (bytes + PAGE_SIZE - 1) / PAGE_SIZE; -} - -static arcad create_wasm_pages(size_t wasm_pages) { - size_t bytes = wasm_pages * PAGE_SIZE; - size_t pages = (bytes + 4095) / 4096; - arcad table = arca_table_create(bytes); - for (size_t i = 0; i < pages; i++) { - struct arca_entry entry; - entry.mode = __MODE_read_write; - entry.data = check(arca_page_create(4096)); - arca_table_map(table, (void *)(i * 4096), &entry); - } - return table; -} - -static struct arca_entry map_table(void *addr, arcad table, bool write) { - struct arca_entry entry; - entry.mode = write ? __MODE_read_write: __MODE_read_only; - entry.data = table; - check(arca_mmap(addr, &entry)); - return entry; -} - -void w2c_fixpoint_attach_blob(struct w2c_fixpoint *instance, uint32_t n, - wasm_rt_externref_t handle) { - assert(n < WASM_MEMORIES_N); - wasm_rt_memory_t *memory = WASM_MEMORIES[n]; - void *addr = (void *)((size_t)n << 32); - - size_t nbytes; - check(arca_length(handle, &nbytes)); - size_t npages = bytes_to_wasm_pages(nbytes); - memory->size = nbytes; - memory->pages = npages; - - // TODO: map these blobs as read-only - arcad pages; - struct arca_entry entry; - switch (arca_type(handle)) { - case __TYPE_word: { - assert(npages == 1); - pages = create_wasm_pages(npages); - entry = map_table(addr, pages, true); - assert(entry.mode == __MODE_none); - arca_word_read(handle, addr); - arca_mmap(addr, &entry); - assert(entry.mode == __MODE_read_write); - entry.mode = __MODE_read_only; - arca_mmap(addr, &entry); - if (entry.mode != __MODE_none) { - arca_drop(entry.data); - } - return; - } - - case __TYPE_blob: { - pages = check(create_wasm_pages(npages)); - entry = map_table(addr, pages, true); - arca_blob_read(handle, 0, addr, nbytes); - arca_mmap(addr, &entry); - entry.mode = __MODE_read_only; - arca_mmap(addr, &entry); - if (entry.mode != __MODE_none) { - arca_drop(entry.data); - } - return; - } - - case __TYPE_page: { - pages = check(create_wasm_pages(npages)); - entry = map_table(addr, pages, true); - arca_page_read(handle, 0, addr, nbytes); - arca_mmap(addr, &entry); - entry.mode = __MODE_read_only; - arca_mmap(addr, &entry); - if (entry.mode != __MODE_none) { - arca_drop(entry.data); - } - return; - } - - case __TYPE_table: { - map_table(addr, handle, false); - return; - } - - default: - assert(false); - } - - return; -} - -[[noreturn]] void fmain(void) { - w2c_module module; - wasm2c_module_instantiate(&module, (struct w2c_fixpoint *)&module); - wasm_rt_externref_t argument = arca_argument(); - wasm_rt_externref_t result = w2c_module_0x5Ffixpoint_apply(&module, argument); - arca_exit(result); -} diff --git a/fix/fix-shell/start.S b/fix/fix-shell/start.S deleted file mode 100644 index 1b67c571..00000000 --- a/fix/fix-shell/start.S +++ /dev/null @@ -1,9 +0,0 @@ -.intel_syntax noprefix - -.extern fmain -.extern __stack_top -.globl _start -_start: - lea rsp, __stack_top[rip] - call fmain - int3 diff --git a/fix/fix-shell/wasm-rt-impl.c b/fix/fix-shell/wasm-rt-impl.c deleted file mode 100644 index e3ce5b20..00000000 --- a/fix/fix-shell/wasm-rt-impl.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2018 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "wasm-rt-impl.h" -#include -#include "wasm-rt.h" - -#include -#include -#include -#include - -wasm_rt_memory_t *WASM_MEMORIES[128]; -size_t WASM_MEMORIES_N = 0; - -uint64_t check(int64_t ret); -[[noreturn]] void trap(const char *msg); - -void wasm_rt_trap(wasm_rt_trap_t code) { - assert(code != WASM_RT_TRAP_NONE); - switch (code) { - case WASM_RT_TRAP_NONE: - trap("Wasm Runtime Trap: None"); - case WASM_RT_TRAP_OOB: - trap( - "Wasm Runtime Trap: Out-of-bounds access in linear memory or a table."); - case WASM_RT_TRAP_INT_OVERFLOW: - trap("Wasm Runtime Trap: Integer overflow on divide or truncation."); - case WASM_RT_TRAP_DIV_BY_ZERO: - trap("Wasm Runtime Trap: Integer divide by zero"); - case WASM_RT_TRAP_INVALID_CONVERSION: - trap("Wasm Runtime Trap: Conversion from NaN to integer."); - case WASM_RT_TRAP_UNREACHABLE: - trap("Wasm Runtime Trap: Unreachable instruction executed."); - case WASM_RT_TRAP_CALL_INDIRECT: /** Invalid call_indirect, for any reason. - */ - trap("Wasm Runtime Trap: Invalid call_indirect."); - case WASM_RT_TRAP_UNCAUGHT_EXCEPTION: - trap("Wasm Runtime Trap: Exception thrown and not caught."); - case WASM_RT_TRAP_UNALIGNED: - trap("Wasm Runtime Trap: Unaligned atomic instruction executed."); -#if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS - case WASM_RT_TRAP_EXHAUSTION = WASM_RT_TRAP_OOB: -#else - case WASM_RT_TRAP_EXHAUSTION: - trap("Wasm Runtime Trap: Call stack exhausted."); -#endif - }; - abort(); -} - -void wasm_rt_init(void) {} - -bool wasm_rt_is_initialized(void) { return true; } - -void wasm_rt_free(void) {} - -void wasm_rt_allocate_memory(wasm_rt_memory_t *memory, uint64_t initial_pages, - uint64_t max_pages, bool is64) { - size_t n = WASM_MEMORIES_N++; - assert(n < 128); - WASM_MEMORIES[n] = memory; - - assert(max_pages <= (1ul << 32) / PAGE_SIZE); - - memory->data = (void *)(n << 32); - uint64_t byte_length = initial_pages * PAGE_SIZE; - memory->size = byte_length; - memory->pages = initial_pages; - memory->max_pages = max_pages; - memory->is64 = is64; - - for (uint64_t i = 0; i < byte_length >> 12; i++) { - arcad page = check(arca_page_create(1 << 12)); - check(arca_mmap(memory->data + i * 4096, &(struct arca_entry){ - .mode = __MODE_read_write, - .data = page, - })); - } - return; -} - -uint64_t wasm_rt_grow_memory(wasm_rt_memory_t *memory, uint64_t delta) { - uint64_t old_pages = memory->pages; - uint64_t new_pages = memory->pages + delta; - if (new_pages == 0) { - return 0; - } - if (new_pages < old_pages || new_pages > memory->max_pages) { - return (uint64_t)-1; - } - uint64_t old_size = old_pages * PAGE_SIZE; - uint64_t new_size = new_pages * PAGE_SIZE; - uint64_t delta_size = delta * PAGE_SIZE; - - for (uint64_t i = 0; i < delta_size >> 12; i++) { - arcad page = check(arca_page_create(1 << 12)); - check(arca_mmap(memory->data + +memory->size + i * 4096, - &(struct arca_entry){ - .mode = __MODE_read_write, - .data = page, - })); - } - - memory->pages = new_pages; - memory->size = new_size; - return old_pages; -} - -void wasm_rt_free_memory(wasm_rt_memory_t *memory) { return; } - -#define DEFINE_TABLE_OPS(type) \ - void wasm_rt_allocate_##type##_table(wasm_rt_##type##_table_t *table, \ - uint32_t elements, \ - uint32_t max_elements) { \ - abort(); \ - } \ - void wasm_rt_free_##type##_table(wasm_rt_##type##_table_t *table) { \ - abort(); \ - } \ - uint32_t wasm_rt_grow_##type##_table(wasm_rt_##type##_table_t *table, \ - uint32_t delta, \ - wasm_rt_##type##_t init) { \ - abort(); \ - } - -DEFINE_TABLE_OPS(funcref) -DEFINE_TABLE_OPS(externref) - -const char *wasm_rt_strerror(wasm_rt_trap_t trap) { - switch (trap) { - case WASM_RT_TRAP_NONE: - return "No error"; - case WASM_RT_TRAP_OOB: -#if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS - return "Out-of-bounds access in linear memory or a table, or call stack " - "exhausted"; -#else - return "Out-of-bounds access in linear memory or a table"; - case WASM_RT_TRAP_EXHAUSTION: - return "Call stack exhausted"; -#endif - case WASM_RT_TRAP_INT_OVERFLOW: - return "Integer overflow on divide or truncation"; - case WASM_RT_TRAP_DIV_BY_ZERO: - return "Integer divide by zero"; - case WASM_RT_TRAP_INVALID_CONVERSION: - return "Conversion from NaN to integer"; - case WASM_RT_TRAP_UNREACHABLE: - return "Unreachable instruction executed"; - case WASM_RT_TRAP_CALL_INDIRECT: - return "Invalid call_indirect or return_call_indirect"; - case WASM_RT_TRAP_UNCAUGHT_EXCEPTION: - return "Uncaught exception"; - case WASM_RT_TRAP_UNALIGNED: - return "Unaligned atomic memory access"; - } - return "invalid trap code"; -} diff --git a/fix/handle/Cargo.toml b/fix/handle/Cargo.toml new file mode 100644 index 00000000..d93ad375 --- /dev/null +++ b/fix/handle/Cargo.toml @@ -0,0 +1,14 @@ +cargo-features = ["per-package-target"] + +[package] +name = "fixhandle" +version = "0.1.0" +edition = "2024" + +[dependencies] +derive_more = { version = "2.0.1", default-features = false, features = ["full"] } +common = { path = "../../common", default-features = false } +bitint = "0.1.1" + +[features] +testing-mode = [] diff --git a/fix/handle/src/lib.rs b/fix/handle/src/lib.rs new file mode 100644 index 00000000..7a199523 --- /dev/null +++ b/fix/handle/src/lib.rs @@ -0,0 +1,383 @@ +#![no_std] +#![allow(dead_code)] +#![allow(unused_features)] +#![feature(portable_simd)] + +use bitint::U5; +pub use common::bitpack::BitPack; +use derive_more::{From, Into, TryUnwrap, Unwrap}; + +const fn bitmask256() -> [u8; 32] { + assert!(I + WIDTH <= 256); + let mut out = [0u8; 32]; + let mut i = I; + loop { + if i >= I + WIDTH { + break; + } + + let byte = i / 8; + let off = i % 8; + out[byte as usize] |= 1u8 << off; + + i += 1; + } + out +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap, From)] +#[try_unwrap(ref)] +pub enum Handle { + Ref(Ref), + Object(Object), + Thunk(Thunk), + Encode(Encode), +} + +impl Handle { + pub fn len(&self) -> usize { + match self { + Handle::Ref(x) => x.len(), + Handle::Object(x) => x.len(), + Handle::Thunk(x) => x.len(), + Handle::Encode(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap, From)] +#[try_unwrap(ref)] +pub enum Ref { + Blob(Blob), + Tree(Tree), +} + +impl Ref { + pub fn len(&self) -> usize { + match self { + Ref::Blob(x) => x.len(), + Ref::Tree(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap, From)] +#[try_unwrap(ref)] +pub enum Object { + Blob(Blob), + Tree(Tree), +} + +impl Object { + pub fn len(&self) -> usize { + match self { + Object::Blob(x) => x.len(), + Object::Tree(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, Unwrap)] +pub enum Thunk { + Identification(Ref), + Application(Tree), + Selection(Tree), +} + +impl Thunk { + pub fn len(&self) -> usize { + match self { + Thunk::Identification(x) => x.len(), + Thunk::Application(x) => x.len(), + Thunk::Selection(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap)] +#[try_unwrap(ref)] +pub enum Encode { + Strict(Thunk), + Shallow(Thunk), +} + +impl Encode { + pub fn len(&self) -> usize { + match self { + Encode::Strict(x) => x.len(), + Encode::Shallow(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap)] +#[try_unwrap(ref)] +pub enum Tree { + Tree(TreeName), + Tag(TreeName), +} + +impl Tree { + pub fn len(&self) -> usize { + match self { + Tree::Tree(x) => x.len(), + Tree::Tag(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Copy, Clone, Eq, PartialEq, TryUnwrap, Unwrap)] +#[try_unwrap(ref)] +pub enum Blob { + Blob(BlobName), + Literal(LiteralName), +} + +impl Blob { + pub fn len(&self) -> usize { + match self { + Blob::Blob(x) => x.len(), + Blob::Literal(x) => x.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, From, Into)] +pub struct BlobName(RawName); + +#[derive(Debug, Copy, Clone, Eq, PartialEq, From, Into)] +pub struct LiteralName { + bytes: [u8; 30], + len: U5, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, From, Into)] +pub struct TreeName(RawName); + +impl BlobName { + /// # Safety + /// This name must be valid within the scope of the storage(s) it'll be used with. + pub unsafe fn new(name: RawName) -> Self { + Self(name) + } + + pub fn name(&self) -> RawName { + self.0 + } + + pub fn len(&self) -> usize { + self.0.size.to_primitive() as usize + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl LiteralName { + pub fn new(contents: &[u8]) -> Self { + assert!(contents.len() <= 30); + let len = U5::new(contents.len() as u8).unwrap(); + let mut bytes = [0; 30]; + bytes[..contents.len()].copy_from_slice(contents); + Self { bytes, len } + } + + pub fn bytes(&self) -> &[u8] { + let len = self.len.to_primitive() as usize; + &self.bytes[..len] + } + + pub fn len(&self) -> usize { + self.len.to_primitive() as usize + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl TreeName { + /// # Safety + /// This name must be valid within the scope of the storage(s) it'll be used with. + pub unsafe fn new(name: RawName) -> Self { + Self(name) + } + + pub fn name(&self) -> RawName { + self.0 + } + + pub fn len(&self) -> usize { + self.0.size.to_primitive() as usize + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl common::bitpack::BitPack for BlobName { + const TAGBITS: u32 = 240; + + fn pack(&self) -> [u8; 32] { + self.0.into() + } + + fn unpack(content: [u8; 32]) -> Self { + unsafe { Self::new(RawName::forge(content)) } + } +} + +impl common::bitpack::BitPack for LiteralName { + const TAGBITS: u32 = 245; + + fn pack(&self) -> [u8; 32] { + let mut bytes = [0; 32]; + bytes[0..30].copy_from_slice(&self.bytes); + bytes[30] = self.len.to_primitive(); + bytes + } + + fn unpack(content: [u8; 32]) -> Self { + let mut bytes = [0; 30]; + bytes.copy_from_slice(&content[0..30]); + let len = content[30] & 0b11111; + let len = U5::new(len).unwrap(); + Self { bytes, len } + } +} + +impl common::bitpack::BitPack for TreeName { + const TAGBITS: u32 = 240; + + fn pack(&self) -> [u8; 32] { + self.0.into() + } + + fn unpack(content: [u8; 32]) -> Self { + unsafe { Self::new(RawName::forge(content)) } + } +} + +impl From for Handle { + fn from(value: Blob) -> Handle { + Handle::Object(Object::Blob(value)) + } +} + +impl From for Handle { + fn from(value: Tree) -> Handle { + Handle::Object(Object::Tree(value)) + } +} + +impl From for Blob { + fn from(value: BlobName) -> Blob { + Blob::Blob(value) + } +} + +impl From for Tree { + fn from(value: TreeName) -> Tree { + Tree::Tree(value) + } +} + +impl From for TreeName { + fn from(value: Tree) -> TreeName { + match value { + Tree::Tree(x) => x, + Tree::Tag(x) => x, + } + } +} + +use core::convert::{From, Into}; +use core::simd::u8x32; + +use bitint::U48; + +/// A raw Fix name. This struct conveys no information about the validity of the contained name. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(C, packed)] +pub struct RawName { + pub name: [u8; 24], + pub size: U48, + pub meta: u16, +} + +impl RawName { + pub fn forge(bytes: [u8; 32]) -> Self { + let mut name = [0; 24]; + name.copy_from_slice(&bytes[..24]); + let mut size = [0; 8]; + size[..6].copy_from_slice(&bytes[24..30]); + let size = u64::from_le_bytes(size); + let size = U48::new(size).unwrap(); + let mut meta = [0; 2]; + meta.copy_from_slice(&bytes[30..32]); + let meta = u16::from_le_bytes(meta); + Self { name, size, meta } + } + + pub fn as_bytes(&self) -> [u8; 32] { + let mut bytes = [0; 32]; + bytes[0..24].copy_from_slice(&self.name); + let size: [u8; 8] = self.size.to_primitive().to_le_bytes(); + bytes[24..30].copy_from_slice(&size[..6]); + bytes[30..32].copy_from_slice(&self.meta.to_le_bytes()); + bytes + } +} + +impl From for [u8; 32] { + fn from(value: RawName) -> [u8; 32] { + value.as_bytes() + } +} + +impl From for u8x32 { + fn from(value: RawName) -> u8x32 { + value.as_bytes().into() + } +} + +impl core::fmt::Display for Handle { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + // write!(f, "{self:?}") + let bytes = Handle::pack(self); + for byte in bytes { + write!(f, "{byte:02x}")?; + } + Ok(()) + } +} diff --git a/fix/shell/Cargo.toml b/fix/shell/Cargo.toml new file mode 100644 index 00000000..60ed64df --- /dev/null +++ b/fix/shell/Cargo.toml @@ -0,0 +1,22 @@ +cargo-features = ["per-package-target"] + +[package] +name = "fixshell" +version = "0.1.0" +edition = "2024" +forced-target = "x86_64-unknown-none" + +[lib] +name = "fixshell" +crate-type = ["staticlib"] + +[dependencies] +arca = { path = "../../arca" } +user = { path = "../../user" } +arcane = { path = "../../arcane/"} +fixhandle = { path = "../handle", default-features = false} + +[build-dependencies] +anyhow = "1.0.100" +bindgen = "0.72.1" +cc = "1.2.57" diff --git a/fix/shell/build.rs b/fix/shell/build.rs new file mode 100644 index 00000000..242a365a --- /dev/null +++ b/fix/shell/build.rs @@ -0,0 +1,25 @@ +use std::{env, path::PathBuf}; + +use anyhow::Result; + +fn main() -> Result<()> { + println!("cargo::rerun-if-changed=etc/memmap.ld"); + let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + println!("cargo::rustc-link-arg=-T{dir}/etc/memmap.ld"); + println!("cargo::rustc-link-arg=-no-pie"); + println!("cargo::rustc-link-arg=-no-pic"); + + let bindings = bindgen::Builder::default() + .header("inc/wasm-rt.h") + .use_core() + .ignore_functions() + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("wasm_rt.rs")) + .expect("Couldn't write bindings!"); + Ok(()) +} diff --git a/fix/fix-shell/memmap.ld b/fix/shell/etc/memmap.ld similarity index 98% rename from fix/fix-shell/memmap.ld rename to fix/shell/etc/memmap.ld index 6e466dea..c2cc57a3 100644 --- a/fix/fix-shell/memmap.ld +++ b/fix/shell/etc/memmap.ld @@ -28,6 +28,7 @@ SECTIONS { . = ALIGN(16); _sgot = .; *(.got*) + *(.got.plt) . = ALIGN(16); _egot = .; } > memory diff --git a/fix/shell/etc/wasm-rt.c b/fix/shell/etc/wasm-rt.c new file mode 100644 index 00000000..fde5deb0 --- /dev/null +++ b/fix/shell/etc/wasm-rt.c @@ -0,0 +1,18 @@ +#include + +#include "wasm-rt.h" +#include "module.h" + +void wasm_rt_init(void) { +} + +bool wasm_rt_is_initialized(void) { + return true; +} + +void wasm_rt_free(void) { +} + +size_t wasm_rt_module_size(void) { + return sizeof(w2c_module); +} diff --git a/fix/fix-shell/wasm-rt-impl.h b/fix/shell/inc/wasm-rt-impl.h similarity index 100% rename from fix/fix-shell/wasm-rt-impl.h rename to fix/shell/inc/wasm-rt-impl.h diff --git a/fix/fix-shell/wasm-rt.h b/fix/shell/inc/wasm-rt.h similarity index 98% rename from fix/fix-shell/wasm-rt.h rename to fix/shell/inc/wasm-rt.h index 95575134..255ae83c 100644 --- a/fix/fix-shell/wasm-rt.h +++ b/fix/shell/inc/wasm-rt.h @@ -129,10 +129,13 @@ typedef struct { static const wasm_rt_funcref_t wasm_rt_funcref_null_value; /** The type of an external reference (opaque to WebAssembly). */ -typedef int64_t wasm_rt_externref_t; +typedef unsigned char __attribute__((vector_size(32))) u8x32; +typedef struct { + uint8_t bytes[32]; +} wasm_rt_externref_t; /** Default (null) value of an externref */ -static const wasm_rt_externref_t wasm_rt_externref_null_value = 0; +static const wasm_rt_externref_t wasm_rt_externref_null_value = {0}; /** A Memory object. */ typedef struct { diff --git a/fix/shell/src/fixpoint.rs b/fix/shell/src/fixpoint.rs new file mode 100644 index 00000000..66946837 --- /dev/null +++ b/fix/shell/src/fixpoint.rs @@ -0,0 +1,164 @@ +use core::ffi::c_void; + +use crate::rt::{PAGE_SIZE, wasm_rt_externref_t, wasm_rt_externref_table_t, wasm_rt_memory_t}; +use crate::shell; + +#[repr(C)] +pub struct w2c_fixpoint(()); + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_attach_blob( + fixpoint: *mut w2c_fixpoint, + memory_idx: u32, + handle: wasm_rt_externref_t, +) { + assert!(memory_idx < 64); + unsafe { + let memory = crate::rt::MEMORIES[memory_idx as usize]; + if (memory.is_null()) { + return; + } + let addr = (1usize << 32) * memory_idx as usize; + let len = shell::fixpoint_attach_blob(addr as *mut c_void, handle.bytes); + // TODO: this math is wrong + (*memory).pages = (len as u64 / PAGE_SIZE as u64) + 1; + (*memory).max_pages = (1u64 << 32) / PAGE_SIZE as u64; + (*memory).size = len as u64; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_attach_tree( + fixpoint: *mut w2c_fixpoint, + table_idx: u32, + handle: wasm_rt_externref_t, +) { + assert!(table_idx < 63); + unsafe { + let table = crate::rt::TABLES[table_idx as usize]; + if (table.is_null()) { + return; + } + let addr = (1usize << 32) * (64 + table_idx as usize); + let len = shell::fixpoint_attach_tree(addr as *mut c_void, handle.bytes); + (*table).size = len as u32; + (*table).max_size = (1 << (32 - 5)) as u32; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_tree( + fixpoint: *mut w2c_fixpoint, + table_idx: u32, +) -> wasm_rt_externref_t { + assert!(table_idx < 63); + unsafe { + let table = crate::rt::TABLES[table_idx as usize]; + let addr = (1usize << 32) * (64 + table_idx as usize); + wasm_rt_externref_t { + bytes: shell::fixpoint_create_tree(core::slice::from_raw_parts( + addr as *const u8, + (*table).size as usize, + )), + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_tag( + fixpoint: *mut w2c_fixpoint, + table_idx: u32, +) -> wasm_rt_externref_t { + assert!(table_idx < 63); + unsafe { + let table = crate::rt::TABLES[table_idx as usize]; + let addr = (1usize << 32) * (64 + table_idx as usize); + wasm_rt_externref_t { + bytes: shell::fixpoint_create_tag(core::slice::from_raw_parts( + addr as *const u8, + (*table).size as usize, + )), + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_blob_i64( + fixpoint: *mut w2c_fixpoint, + value: u64, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: unsafe { shell::fixpoint_create_blob_i64(value) }, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_blob_i32( + fixpoint: *mut w2c_fixpoint, + value: u32, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: unsafe { shell::fixpoint_create_blob_i32(value) }, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_is_blob_obj( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> i32 { + shell::fixpoint_is_blob_obj(handle.bytes) as i32 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_is_object( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> i32 { + shell::fixpoint_is_object(handle.bytes) as i32 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_is_data( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> i32 { + shell::fixpoint_is_data(handle.bytes) as i32 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_is_tag( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> i32 { + shell::fixpoint_is_tag(handle.bytes) as i32 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_is_equal( + fixpoint: *mut w2c_fixpoint, + lhs: wasm_rt_externref_t, + rhs: wasm_rt_externref_t, +) -> i32 { + shell::fixpoint_is_equal(lhs.bytes, rhs.bytes) as i32 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_application_thunk( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_application_thunk(handle.bytes), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_strict_encode( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_strict_encode(handle.bytes), + } +} diff --git a/fix/shell/src/lib.rs b/fix/shell/src/lib.rs new file mode 100644 index 00000000..49ccf099 --- /dev/null +++ b/fix/shell/src/lib.rs @@ -0,0 +1,119 @@ +#![no_std] +#![allow(unused)] +#![feature(slice_from_ptr_range)] +#![feature(atomic_ptr_null)] + +use core::{ + arch::{asm, global_asm}, + ffi::c_void, + ops::Range, +}; + +use user::Runtime; +use user::error::log as arca_log; +use user::{error, os, prelude::*}; + +use crate::{ + fixpoint::w2c_fixpoint, + rt::{wasm_rt_externref_t, wasm_rt_free, wasm_rt_init, wasm_rt_module_size}, +}; + +mod fixpoint; +mod rt; +pub mod shell; + +global_asm!( + r#" +.section .text.start +.extern _rsstart +.extern __stack_top +.globl _start +_start: + lea rsp, __stack_top[rip] + call _rsstart +.halt: + int3 + jmp .halt +.globl bail +bail: + mov rdi, 0 + mov rax, 3 + syscall + int3 +.section .text +"# +); + +pub static mut _PROCEDURE: [u8; 32] = [0; 32]; + +unsafe extern "C" { + static mut _sbss: c_void; + static mut _ebss: c_void; + fn wasm2c_module_instantiate(module: *mut c_void, combination: *const w2c_fixpoint); + fn wasm2c_module_free(module: *mut c_void); + fn w2c_module_0x5Ffixpoint_apply( + module: *const c_void, + combination: wasm_rt_externref_t, + ) -> wasm_rt_externref_t; +} + +/// The Rust entrypoint for Fix-on-Arca programs. This function performs the minimal runtime setup +/// to ensure other Rust code can run correctly. +/// +/// # Safety +/// +/// This function must be called exactly once, before any other Rust code has run. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _rsstart() -> ! { + unsafe { + let bss = core::slice::from_mut_ptr_range(Range { + start: &raw mut _sbss as *mut u8, + end: &raw mut _ebss as *mut u8, + }); + + bss.fill(0); + } + + main(); +} + +static mut MODULE_BUF: [u8; 1024] = [0; 1024]; + +pub fn main() -> ! { + let combination = os::argument(); + let combination = + Blob::try_from(combination).expect("fix programs must receive a handle as input"); + let mut handle = [0; 32]; + combination.read(0, &mut handle); + let result = unsafe { + wasm_rt_init(); + let module_size = wasm_rt_module_size(); + let module = unsafe { + assert!(module_size <= 1024); + &raw mut MODULE_BUF[0] as *mut c_void + }; + wasm2c_module_instantiate(module, core::ptr::null()); + + /// Read procedure handle from combination + { + let result: Result = Function::symbolic("get_tree") + .apply(Blob::new(&handle)) + .call_with_current_continuation() + .try_into(); + + let Ok(tree) = result else { + arca_log("prelogue: failed to get TreeData"); + panic!() + }; + + let procedure_ref = &raw mut _PROCEDURE; + tree.read(0, &mut *procedure_ref); + } + + let wasm_rt_externref_t { bytes: result } = + w2c_module_0x5Ffixpoint_apply(module, wasm_rt_externref_t { bytes: handle }); + wasm_rt_free(); + result + }; + os::exit(&result[..]); +} diff --git a/fix/shell/src/rt.rs b/fix/shell/src/rt.rs new file mode 100644 index 00000000..9dbcfc05 --- /dev/null +++ b/fix/shell/src/rt.rs @@ -0,0 +1,212 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use core::sync::atomic::{AtomicUsize, Ordering}; + +use arcane::{__MODE_read_write, arca_compat_mmap}; +use user::error; + +include!(concat!(env!("OUT_DIR"), "/wasm_rt.rs")); + +unsafe extern "C" { + pub fn wasm_rt_init(); + pub fn wasm_rt_module_size() -> usize; + pub fn wasm_rt_free(); +} + +pub static mut MEMORY_IDX: usize = 0; +pub static mut TABLE_IDX: usize = 0; +pub static mut FUNCREF_TABLE_IDX: usize = 0; + +pub static mut MEMORIES: [*mut wasm_rt_memory_t; 64] = [core::ptr::null_mut(); 64]; +pub static mut TABLES: [*mut wasm_rt_externref_table_t; 32] = [core::ptr::null_mut(); 32]; +pub static mut FUNCREF_TABLES: [*mut wasm_rt_funcref_table_t; 32] = [core::ptr::null_mut(); 32]; + +/** + * Initialize a Memory object with an initial page size of `initial_pages` and + * a maximum page size of `max_pages`, indexed with an i32 or i64. + * + * ``` + * wasm_rt_memory_t my_memory; + * // 1 initial page (65536 bytes), and a maximum of 2 pages, + * // indexed with an i32 + * wasm_rt_allocate_memory(&my_memory, 1, 2, false); + * ``` + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_allocate_memory( + memory: *mut wasm_rt_memory_t, + initial_pages: u64, + max_pages: u64, + is64: bool, +) { + unsafe { + let idx = MEMORY_IDX; + MEMORY_IDX += 1; + assert!(idx < 64); + MEMORIES[idx] = memory; + assert!(!is64); + assert!(max_pages <= (1u64 << 32) / PAGE_SIZE as u64); + let data = ((1 << 32) * idx) as *mut u8; + let size = initial_pages * PAGE_SIZE as u64; + arca_compat_mmap(data as *mut _, size as usize, __MODE_read_write); + memory.write(wasm_rt_memory_t { + data, + pages: initial_pages, + max_pages, + size, + is64, + }); + } +} + +/** + * Grow a Memory object by `pages`, and return the previous page count. If + * this new page count is greater than the maximum page count, the grow fails + * and 0xffffffffu (UINT32_MAX) is returned instead. + * + * ``` + * wasm_rt_memory_t my_memory; + * ... + * // Grow memory by 10 pages. + * uint32_t old_page_size = wasm_rt_grow_memory(&my_memory, 10); + * if (old_page_size == UINT32_MAX) { + * // Failed to grow memory. + * } + * ``` + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_grow_memory(memory: *mut wasm_rt_memory_t, pages: u64) -> u32 { + let memory = unsafe { &mut *memory }; + let current = memory.pages; + if current + pages >= memory.max_pages { + return u32::MAX; + } + let start = unsafe { memory.data.byte_add(current as usize * PAGE_SIZE as usize) }; + let size = pages * PAGE_SIZE as u64; + unsafe { + arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + memory.pages += pages; + memory.size += size; + } + current as u32 +} + +/** + * Initialize an externref Table object with an element count + * of `elements` and a maximum size of `max_elements`. + * Usage as per wasm_rt_allocate_funcref_table. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_allocate_externref_table( + table: *mut wasm_rt_externref_table_t, + elements: u32, + mut max_elements: u32, +) { + unsafe { + let idx = TABLE_IDX; + TABLE_IDX += 1; + assert!(idx < 31); + TABLES[idx] = table; + if max_elements > (1 << (32 - 5)) { + max_elements = 1 << (32 - 5); + } + let data = ((1 << 32) * (64 + idx)) as *mut u8; + arca_compat_mmap(data as *mut _, (elements * 32) as usize, __MODE_read_write); + table.write(wasm_rt_externref_table_t { + data: data as *mut _, + size: elements, + max_size: max_elements, + }); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_grow_externref_table( + table: *mut wasm_rt_externref_table_t, + delta: u32, + init: wasm_rt_externref_t, +) -> u32 { + let table = unsafe { &mut *table }; + let current = table.size; + if current + delta > table.max_size { + return u32::MAX; + } + + let start = unsafe { table.data.byte_add(current as usize * 32) }; + let size = delta * 32; + unsafe { + arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + table.size += delta; + } + current +} + +/** + * Initialize an funcref Table object with an element count + * of `elements` and a maximum size of `max_elements`. + * Usage as per wasm_rt_allocate_funcref_table. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_allocate_funcref_table( + table: *mut wasm_rt_funcref_table_t, + elements: u32, + mut max_elements: u32, +) { + unsafe { + let idx = FUNCREF_TABLE_IDX; + FUNCREF_TABLE_IDX += 1; + assert!(idx < 31); + FUNCREF_TABLES[idx] = table; + if max_elements > (1 << (32 - 5)) { + max_elements = 1 << (32 - 5); + } + let data = ((1 << 32) * (64 + 32 + idx)) as *mut u8; + arca_compat_mmap( + data as *mut _, + (elements as usize * core::mem::size_of::()) as usize, + __MODE_read_write, + ); + table.write(wasm_rt_funcref_table_t { + data: data as *mut _, + size: elements, + max_size: max_elements, + }); + } +} + +/** + * Free a Memory object. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_free_memory(memory: *mut wasm_rt_memory_t) { + todo!(); +} + +/** + * Free an externref Table object. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_free_externref_table(table: *mut wasm_rt_externref_table_t) { + todo!(); +} + +/** + * Free an externref Table object. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_free_funcref_table(table: *mut wasm_rt_funcref_table_t) { + todo!(); +} + +/** + * Stop execution immediately and jump back to the call to `wasm_rt_impl_try`. + * The result of `wasm_rt_impl_try` will be the provided trap reason. + * + * This is typically called by the generated code, and not the embedder. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_trap(trap: wasm_rt_trap_t) { + panic!("wasm rt trap: {trap}"); +} diff --git a/fix/shell/src/shell.rs b/fix/shell/src/shell.rs new file mode 100644 index 00000000..a22f94c5 --- /dev/null +++ b/fix/shell/src/shell.rs @@ -0,0 +1,261 @@ +use crate::_PROCEDURE; +use arca::{Blob, Function, Table, Word}; +use arca::{Runtime as _, Tuple}; +use arcane::{ + __MODE_read_only, __MODE_read_write, __NR_length, __TYPE_table, arca_argument, + arca_blob_create, arca_blob_read, arca_compat_mmap, arca_entry, arca_mmap, arca_table_map, + arcad, +}; + +use core::arch::x86_64::*; +use core::ffi::c_void; +use fixhandle::*; + +use user::ArcaError; +use user::Ref; +use user::Runtime; +use user::error::log as arca_log; +use user::error::log_int as arca_log_int; + +pub fn fixpoint_create_blob_i32(val: u32) -> [u8; 32] { + let bytes = val.to_le_bytes(); + unsafe { fixpoint_create_blob(&bytes) } +} + +pub fn fixpoint_create_blob_i64(val: u64) -> [u8; 32] { + let bytes = val.to_le_bytes(); + unsafe { fixpoint_create_blob(&bytes) } +} + +/// Attaches a blob to a region of memory. Returns the size (in bytes) of the mapped blob. +/// +/// # Safety +/// +/// [addr] must refer to an unused region of memory which is large enough to fit the blob; there +/// must be no Rust references pointing to this region. +pub unsafe fn fixpoint_attach_blob(addr: *mut c_void, handle: [u8; 32]) -> usize { + if (!fixpoint_is_blob(handle)) { + arca_log("attach_blob: handle does not refer to a BlobObject"); + panic!() + } + + let result: Result, ArcaError> = Function::symbolic("get_blob") + .apply(Runtime::create_blob(&handle)) + .call_with_current_continuation() + .try_into() + .map_err(|_| ArcaError::BadType); + + let Ok(blob) = result else { + arca_log("attach_blob: failed to get BlobData"); + panic!() + }; + let len = fixpoint_len(handle); + + unsafe { + arca_compat_mmap(addr, len, __MODE_read_write); + blob.read(0, core::slice::from_raw_parts_mut(addr as *mut u8, len)); + }; + // user::error::log_int("attached memory", len as u64); + len +} + +/// Attaches a tree to a region of memory. Returns the size (in elements) of the tree. +/// +/// # Safety +/// +/// [addr] must refer to an unused region of memory which is large enough to fit the tree; there +/// must be no Rust references pointing to this region. Each entry of the tree takes 32 bytes. +pub unsafe fn fixpoint_attach_tree(addr: *mut c_void, handle: [u8; 32]) -> usize { + if (!fixpoint_is_tree(handle)) { + arca_log("attach_tree: handle does not refer to a TreeObject"); + panic!() + } + + let result: Result, ArcaError> = Function::symbolic("get_tree") + .apply(Runtime::create_blob(&handle)) + .call_with_current_continuation() + .try_into() + .map_err(|_| ArcaError::BadType); + + let Ok(tree) = result else { + arca_log("attach_tree: failed to get TreeData"); + panic!() + }; + + let len = fixpoint_len(handle); + // user::error::log_int("attached tree", len as u64); + + unsafe { + arca_compat_mmap(addr, len * 32, __MODE_read_write); + let slice = core::slice::from_raw_parts_mut(addr as *mut u8, len * 32); + tree.read(0, slice) + }; + len +} + +/// Creates a blob from a region of memory. Returns the handle, +/// +/// # Safety +/// +/// [addr] must refer to an region of memory which is large enough for the specified [len]; +pub unsafe fn fixpoint_create_blob(slice: &[u8]) -> [u8; 32] { + unsafe { + let result: Blob = Function::symbolic("create_blob") + .apply(slice) + .call_with_current_continuation() + .try_into() + .expect("create_tree failed"); + let mut buf = [0u8; 32]; + Runtime::read_blob(&result, 0, &mut buf); + buf + } +} + +/// Creates a tree from a region of memory. Returns the handle, +/// +/// # Safety +/// +/// [addr] must refer to an region of memory which is large enough for the specified [len]; +/// Each entry of the tree takes 32 bytes. +pub unsafe fn fixpoint_create_tree(slice: &[u8]) -> [u8; 32] { + unsafe { + let result: Blob = Function::symbolic("create_tree") + .apply(slice) + .call_with_current_continuation() + .try_into() + .expect("create_tree failed"); + let mut buf = [0u8; 32]; + Runtime::read_blob(&result, 0, &mut buf); + buf + } +} + +/// Creates a tag from a region of memory. Returns the handle, +/// +/// # Safety +/// +/// [addr] must refer to an region of memory which is large enough for the specified [len]; +/// Each entry of the tree takes 32 bytes. _PROCEDURE must be initialized before first invocation +/// of this function. +pub unsafe fn fixpoint_create_tag(slice: &[u8]) -> [u8; 32] { + /// Check that the author field matches with current procedure + let author_field = &slice[..32]; + + let procedure_ref = &raw mut _PROCEDURE; + + if unsafe { (&*procedure_ref).as_slice() } != author_field { + arca_log("create_tag: author does not match current procedure"); + panic!() + }; + + let result = unsafe { fixpoint_create_tree(slice) }; + let handle = Handle::unpack(result); + + let result: Result = handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_tree_ref().map_err(|_| ArcaError::BadType)) + .map(|h| match h { + Tree::Tree(n) => *n, + Tree::Tag(n) => *n, + }); + + let Ok(handle) = result else { + arca_log("create_tag: created not a tree"); + panic!() + }; + + Handle::Object(Object::Tree(Tree::Tag(handle))).pack() +} + +pub fn fixpoint_is_blob_obj(handle: [u8; 32]) -> bool { + let handle = Handle::unpack(handle); + handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_blob_ref().map_err(|_| ArcaError::BadType)) + .is_ok() +} + +pub fn fixpoint_is_blob(handle: [u8; 32]) -> bool { + let handle = Handle::unpack(handle); + handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_blob_ref().map_err(|_| ArcaError::BadType)) + .is_ok() + || handle + .try_unwrap_ref_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_blob_ref().map_err(|_| ArcaError::BadType)) + .is_ok() +} + +pub fn fixpoint_is_tree(handle: [u8; 32]) -> bool { + let handle = Handle::unpack(handle); + handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_tree_ref().map_err(|_| ArcaError::BadType)) + .is_ok() + || handle + .try_unwrap_ref_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_tree_ref().map_err(|_| ArcaError::BadType)) + .is_ok() +} + +pub fn fixpoint_is_object(handle: [u8; 32]) -> bool { + let handle = Handle::unpack(handle); + handle.try_unwrap_object_ref().is_ok() +} + +pub fn fixpoint_is_data(handle: [u8; 32]) -> bool { + let handle = Handle::unpack(handle); + handle.try_unwrap_object_ref().is_ok() || handle.try_unwrap_ref_ref().is_ok() +} + +pub fn fixpoint_is_tag(handle: [u8; 32]) -> bool { + let handle = Handle::unpack(handle); + + let res = handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_tree_ref().map_err(|_| ArcaError::BadType)) + .map(|h| match h { + Tree::Tag(_) => true, + Tree::Tree(_) => false, + }); + + res.unwrap_or_default() +} + +pub fn fixpoint_is_equal(lhs: [u8; 32], rhs: [u8; 32]) -> bool { + let result: Word = Function::symbolic("is_equal") + .apply(Runtime::create_blob(&lhs)) + .apply(Runtime::create_blob(&rhs)) + .call_with_current_continuation() + .try_into() + .expect("is_equal: return type is not a word"); + + let result = result.read(); + result == 1 +} + +pub fn fixpoint_create_application_thunk(handle: [u8; 32]) -> [u8; 32] { + let handle = Handle::unpack(handle); + // TODO: handle refs + let thunk: Handle = Thunk::Application(handle.unwrap_object().unwrap_tree()).into(); + thunk.pack() +} + +pub fn fixpoint_create_strict_encode(handle: [u8; 32]) -> [u8; 32] { + let handle = Handle::unpack(handle); + let encode: Handle = Encode::Strict(handle.unwrap_thunk()).into(); + encode.pack() +} + +fn fixpoint_len(handle: [u8; 32]) -> usize { + let handle = Handle::unpack(handle); + handle.len() +} diff --git a/fix/src/coupon.rs b/fix/src/coupon.rs new file mode 100644 index 00000000..2c5c218d --- /dev/null +++ b/fix/src/coupon.rs @@ -0,0 +1,84 @@ +#[allow(unused)] +#[repr(u32)] +pub enum CouponTrades { + EqTree = 0, + EqApplication = 1, + ForceResultEq = 2, + EqStrictEncode = 3, + ThinkApplication = 4, + ThinkToForce = 5, + ForceToEncodeStric = 6, + EvalEq = 7, + EvalBlobObj = 8, + EvalTreeObj = 9, + EqSym = 10, + EqTrans = 11, + EqSelf = 12, +} + +impl TryFrom for CouponTrades { + type Error = (); + + fn try_from(value: usize) -> Result { + match value { + 0 => Ok(CouponTrades::EqTree), + 1 => Ok(CouponTrades::EqApplication), + 2 => Ok(CouponTrades::ForceResultEq), + 3 => Ok(CouponTrades::EqStrictEncode), + 4 => Ok(CouponTrades::ThinkApplication), + 5 => Ok(CouponTrades::ThinkToForce), + 6 => Ok(CouponTrades::ForceToEncodeStric), + 7 => Ok(CouponTrades::EvalEq), + 8 => Ok(CouponTrades::EvalBlobObj), + 9 => Ok(CouponTrades::EvalTreeObj), + 10 => Ok(CouponTrades::EqSym), + 11 => Ok(CouponTrades::EqTrans), + 12 => Ok(CouponTrades::EqSelf), + _ => Err(()), + } + } +} + +impl TryFrom<&str> for CouponTrades { + type Error = (); + + fn try_from(value: &str) -> Result { + match value { + "EqTree" => Ok(CouponTrades::EqTree), + "EqApplication" => Ok(CouponTrades::EqApplication), + "ForceResultEq" => Ok(CouponTrades::ForceResultEq), + "EqStrictEncode" => Ok(CouponTrades::EqStrictEncode), + "ThinkApplication" => Ok(CouponTrades::ThinkApplication), + "ThinkToForce" => Ok(CouponTrades::ThinkToForce), + "ForceToEncodeStrict" => Ok(CouponTrades::ForceToEncodeStric), + "EvalEq" => Ok(CouponTrades::EvalEq), + "EvalBlobObj" => Ok(CouponTrades::EvalBlobObj), + "EvalTreeObj" => Ok(CouponTrades::EvalTreeObj), + "EqSym" => Ok(CouponTrades::EqSym), + "EqTrans" => Ok(CouponTrades::EqTrans), + "EqSelf" => Ok(CouponTrades::EqSelf), + _ => Err(()), + } + } +} + +#[allow(unused)] +#[repr(usize)] +pub enum FixOp { + Eval = 0, + Trade = 1, + Apply = 2, +} + +impl TryFrom for FixOp { + type Error = (); + + fn try_from(value: usize) -> Result { + match value { + 0 => Ok(FixOp::Eval), + 1 => Ok(FixOp::Trade), + 2 => Ok(FixOp::Apply), + _ => Err(()), + } + } +} diff --git a/fix/src/evaluator.rs b/fix/src/evaluator.rs new file mode 100644 index 00000000..52981e89 --- /dev/null +++ b/fix/src/evaluator.rs @@ -0,0 +1,108 @@ +use crate::handle::*; +use crate::runtime::Runtime; +use crate::storage::Storage; +use kernel::prelude::*; + +// use fixhandle::rawhandle::{Encode, Handle, Object, Ref, Thunk, TreeName}; + +// use fixruntime::{ +// common::CouponTrades, +// fixruntime::{FixRuntime, FixTreeData}, +// runtime::{DeterministicEquivRuntime, Executor}, +// storage::FixData, +// }; + +// use common::bitpack::BitPack; +// use kernel::prelude::*; + +pub struct Evaluator { + runtime: R, +} + +impl Evaluator { + pub fn new(runtime: R) -> Self { + Self { runtime } + } + + pub fn runtime(&self) -> &R { + &self.runtime + } + + pub fn storage(&self) -> &dyn Storage { + self.runtime.storage() + } + + fn apply(&self, combination: Tree) -> Handle { + self.runtime.execute(combination) + } + + fn lift(&self, handle: Handle) -> Handle { + match handle { + Handle::Ref(r) => match r { + Ref::Tree(t) => Object::Tree(t).into(), + Ref::Blob(b) => Object::Blob(b).into(), + }, + _ => handle, + } + } + + fn lower(&self, handle: Handle) -> Handle { + match handle { + Handle::Object(r) => match r { + Object::Tree(t) => Ref::Tree(t).into(), + Object::Blob(b) => Ref::Blob(b).into(), + }, + _ => handle, + } + } + + fn think(&self, thunk: Thunk) -> Handle { + match thunk { + Thunk::Identification(_) => todo!(), + Thunk::Selection(_) => todo!(), + Thunk::Application(tree) => { + let evaled = self.eval_tree(tree); + self.apply(evaled) + } + } + } + + fn force(&self, thunk: Thunk) -> Handle { + let thought = self.think(thunk); + match thought { + Handle::Object(_) => thought, + Handle::Ref(_) => self.lift(thought), + Handle::Thunk(_) | Handle::Encode(_) => todo!(), + } + } + + fn encode(&self, encode: Encode) -> Handle { + match encode { + Encode::Strict(thunk) => self.lift(self.force(thunk)), + Encode::Shallow(thunk) => self.lower(self.force(thunk)), + } + } + + fn eval_tree(&self, handle: Tree) -> Tree { + let tree = self.runtime.storage().get_tree(handle).unwrap(); + let evaled: Vec = tree + .as_ref() + .iter() + .copied() + .map(|x| self.eval(x)) + .collect(); + self.runtime.storage().add_tree(&evaled) + } + + pub fn eval(&self, handle: Handle) -> Handle { + println!("evaluating {handle}"); + match handle { + Handle::Thunk(_) | Handle::Ref(_) => todo!(), + Handle::Object(obj) => match obj { + Object::Blob(x) => x.into(), + Object::Tree(tree) => self.eval_tree(tree).into(), + }, + Handle::Encode(e) => self.eval(self.encode(e)), + } + } +} diff --git a/fix/src/lib.rs b/fix/src/lib.rs new file mode 100644 index 00000000..c09efee6 --- /dev/null +++ b/fix/src/lib.rs @@ -0,0 +1,15 @@ +#![no_std] + +pub mod evaluator; +pub mod parser; +pub mod runtime; +pub mod storage; + +pub mod handle { + pub use fixhandle::*; +} + +pub use evaluator::*; +pub use handle::*; +pub use runtime::*; +pub use storage::*; diff --git a/fix/src/main.rs b/fix/src/main.rs index eaf3406b..a3b96dbc 100644 --- a/fix/src/main.rs +++ b/fix/src/main.rs @@ -1,32 +1,170 @@ #![no_main] #![no_std] -#![feature(try_blocks)] -#![feature(try_trait_v2)] -#![feature(iterator_try_collect)] -#![feature(box_patterns)] -#![feature(never_type)] -#![allow(dead_code)] - -use arca::Runtime; +use kernel::host::fs::{self, File, Whence}; +use kernel::host::os; use kernel::prelude::*; +use fix::arca::FixOnArca; +use fix::parser::*; +use fix::*; + extern crate alloc; +use alloc::collections::BTreeMap; -const MODULE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/addblob")); +use derive_more::Unwrap; #[kmain] -async fn main(_: &[usize]) { - let f = common::elfloader::load_elf(MODULE).expect("Failed to load elf"); - let mut tree = Runtime::create_tuple(4); - let dummy = Runtime::create_word(0xcafeb0ba); - - tree.set(0, dummy); - tree.set(1, dummy); - tree.set(2, Runtime::create_word(7)); - tree.set(3, Runtime::create_word(1024)); - - let f = Runtime::apply_function(f, arca::Value::Tuple(tree)); - let word: Word = f.force().try_into().unwrap(); - log::info!("{:?}", word.read()); - assert_eq!(word.read(), 1031); +fn main() { + let argv = os::argv(); + + // Subcommand dispatch: `fix init` | `fix eval `. + match argv.get(1).map(String::as_str) { + Some("init") => init(), + Some("eval") => { + let filename = argv.get(2).expect("fix eval: expected a command file"); + eval_file(filename); + } + Some(other) => panic!("fix: unknown command '{other}' (expected: init | eval )"), + None => panic!("fix: expected a command (init | eval )"), + } + + kernel::shutdown(); +} + +/// `fix init`: create the on-disk `.fix` store with its `objects/` and +/// `labels/` subdirs. `mkdir` maps to host `create_dir_all`, so re-running on an +/// existing store is harmless (matches git's "reinitialized existing repository"). +fn init() { + for dir in [".fix/objects", ".fix/labels"] { + if let Err(e) = fs::mkdir(dir) { + println!("fix init: failed to create {dir}: {e:?}"); + kernel::exit(1); + } + } + println!("initialized empty fix store in .fix"); +} + +/// `fix eval `: read, parse, and evaluate a command file. +fn eval_file(filename: &str) { + let mut file = File::open(filename, true, false, false, false, false).unwrap(); + let len = file.seek(Whence::End(0)) as usize; + file.seek(Whence::Start(0)); + let mut buf = vec![0; len]; + file.read_exact(&mut buf); + + let file = core::str::from_utf8(&buf).unwrap(); + + let lexer = Lexer::new(&file); + let tokens = lexer.tokenize().unwrap(); + let mut parser = Parser::new(&tokens); + let program = parser.parse_program().unwrap(); + + let runtime = FixOnArca::default(); + let evaluator = Evaluator::new(runtime); + + let mut context = BTreeMap::new(); + for statement in program { + match statement { + Statement::Assign { name, expr } => { + let result = eval(&evaluator, &expr, &mut context); + context.insert(name, result); + } + Statement::Print(expr) | Statement::Expr(expr) => { + let x = eval(&evaluator, &expr, &mut context); + match x { + Value::Handle(x) => { + println!("handle: {x}"); + if let Some(blob) = x + .try_unwrap_object() + .ok() + .and_then(|x| x.try_unwrap_blob().ok()) + { + let contents = evaluator.storage().get_blob(blob).unwrap(); + println!("result is a Blob: {contents:?}"); + if contents.len() == 8 { + let bytes: [u8; 8] = (*contents).try_into().unwrap(); + let value = u64::from_le_bytes(bytes); + println!("\tas a u64: {value}"); + } + } + } + Value::Int(x) => { + println!("int: {x}"); + } + Value::String(x) => { + println!("string: {x}"); + } + Value::Path(x) => { + println!("path: {x}"); + } + } + } + } + } +} + +#[derive(Clone, Debug, Unwrap)] +#[unwrap(ref)] +enum Value { + Handle(Handle), + Int(i64), + String(String), + Path(String), +} + +fn eval(evaluator: &Evaluator, e: &Expr, ctx: &mut BTreeMap) -> Value { + match e { + Expr::Number(x) => Value::Int(*x), + Expr::Identifier(x) => ctx.get(x).expect("undefined identifier").clone(), + Expr::String(x) => Value::String(x.clone()), + Expr::Call { name, args } => { + let args: Vec = args.into_iter().map(|x| eval(evaluator, x, ctx)).collect(); + match name.as_str() { + "Int" => args[0].clone(), + "create_blob" => match args[0] { + Value::Handle(_) => panic!("create blob with handle?"), + Value::Int(x) => { + let bytes = i64::to_le_bytes(x); + Value::Handle(evaluator.storage().add_blob(&bytes).into()) + } + Value::String(ref x) => { + Value::Handle(evaluator.storage().add_blob(x.as_bytes()).into()) + } + Value::Path(ref x) => { + let mut file = File::open(x, true, false, false, false, false).unwrap(); + let len = file.seek(Whence::End(0)); + file.seek(Whence::Start(0)); + let mut buf = vec![0; len as usize]; + file.read_exact(&mut buf); + core::mem::forget(file); + Value::Handle(evaluator.storage().add_blob(&buf).into()) + } + }, + "create_tree" => { + let handles: Vec = args.into_iter().map(Value::unwrap_handle).collect(); + Value::Handle(evaluator.storage().add_tree(&handles).into()) + } + "create_application_thunk" => Value::Handle( + Thunk::Application( + args[0] + .clone() + .unwrap_handle() + .unwrap_object() + .unwrap_tree(), + ) + .into(), + ), + "create_strict_encode" => Value::Handle( + Encode::Strict(args[0].clone().unwrap_handle().unwrap_thunk()).into(), + ), + "eval" => Value::Handle(evaluator.eval(args[0].clone().unwrap_handle())), + "Path" => match args[0] { + Value::String(ref x) => Value::Path(x.clone()), + _ => panic!("bad path"), + }, + name => todo!("call {name} {args:?}"), + } + } + Expr::Group(x) => eval(evaluator, x, ctx), + } } diff --git a/fix/fix-shell/fix.c b/fix/src/name.rs similarity index 100% rename from fix/fix-shell/fix.c rename to fix/src/name.rs diff --git a/fix/src/parser.rs b/fix/src/parser.rs new file mode 100644 index 00000000..e2d9f742 --- /dev/null +++ b/fix/src/parser.rs @@ -0,0 +1,7 @@ +pub mod lexer; +pub mod parser; +pub mod token; + +pub use lexer::Lexer; +pub use parser::Parser; +pub use token::*; diff --git a/fix/src/parser/lexer.rs b/fix/src/parser/lexer.rs new file mode 100644 index 00000000..ecfd58c9 --- /dev/null +++ b/fix/src/parser/lexer.rs @@ -0,0 +1,95 @@ +use super::token::Token; +use core::iter::Peekable; +use core::str::Chars; +use kernel::prelude::*; + +pub struct Lexer<'a> { + characters: Peekable>, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a str) -> Self { + Self { + characters: input.chars().peekable(), + } + } + + pub fn tokenize(mut self) -> Result, String> { + let mut tokens = Vec::new(); + loop { + let token = self.next_token()?; + if token == Token::Eof { + tokens.push(token); + break; + } + tokens.push(token); + } + Ok(tokens) + } + + pub fn next_token(&mut self) -> Result { + // skip whitespace + self.take(String::new(), |ch| ch.is_whitespace()); + let Some(character) = self.characters.next() else { + return Ok(Token::Eof); + }; + + let token = match character { + ';' => Token::Semicolon, + '(' => Token::LParen, + ')' => Token::RParen, + ',' => Token::Comma, + '=' => Token::Equals, + '"' => { + let text = self.take(String::new(), |ch| ch != '"'); + if self.characters.next() != Some('"') { + return Err(String::from("unterminated string")); + } + Token::String(text) + } + // Inline comments + '/' => { + if self.characters.next() == Some('/') { + self.take(String::new(), |ch| ch != '\n'); + return self.next_token(); + } + return Err(String::from("unexpected character: '/'")); + } + // Negative numbers + '-' if self.peek(|character| character.is_ascii_digit()) => { + let number = self.take(String::new(), |ch| ch.is_ascii_digit()); + Token::Number(-number.parse::().map_err(|error| error.to_string())?) + } + character if character.is_ascii_digit() => { + let number = self.take(String::from(character), |ch| ch.is_ascii_digit()); + Token::Number(number.parse::().map_err(|error| error.to_string())?) + } + character if Self::is_identifier(character) => { + Token::Identifier(self.take(String::from(character), Self::is_identifier)) + } + other => return Err(format!("unexpected character: {other:?}")), + }; + Ok(token) + } + + fn peek(&mut self, function: F) -> bool + where + F: FnOnce(&char) -> bool, + { + self.characters.peek().is_some_and(function) + } + + fn take(&mut self, mut text: String, mut condition: F) -> String + where + F: FnMut(char) -> bool, + { + while let Some(next) = self.characters.next_if(|&ch| condition(ch)) { + text.push(next); + } + text + } + + fn is_identifier(character: char) -> bool { + character.is_ascii_alphabetic() || character == '_' + } +} diff --git a/fix/src/parser/parser.rs b/fix/src/parser/parser.rs new file mode 100644 index 00000000..10432b06 --- /dev/null +++ b/fix/src/parser/parser.rs @@ -0,0 +1,124 @@ +use super::{Expr, Statement, Token}; +use kernel::prelude::*; + +pub struct Parser<'a> { + tokens: &'a [Token], + position: usize, +} + +impl<'a> Parser<'a> { + pub fn new(tokens: &'a [Token]) -> Self { + Self { + tokens, + position: 0, + } + } + + pub fn parse_program(&mut self) -> Result, String> { + let mut program = Vec::new(); + loop { + self.skip_separators(); + if self.peek(self.position) == Some(&Token::Eof) { + break; + } + program.push(self.parse_statement()?); + self.skip_separators(); + } + Ok(program) + } + + pub fn parse_statement(&mut self) -> Result { + // 'print()' is special built-in + match (self.peek(self.position), self.peek(self.position + 1)) { + (Some(Token::Identifier(name)), Some(Token::LParen)) if name == "print" => { + // consume 'print''(' + self.advance(); + self.advance(); + let expr = self.parse_expr()?; + self.expect(&Token::RParen, "expected ')' for print")?; + Ok(Statement::Print(expr)) + } + (Some(Token::Identifier(name)), Some(Token::Equals)) => { + let name = name.clone(); + // consume 'identifier' '=' + self.advance(); + self.advance(); + Ok(Statement::Assign { + name, + expr: self.parse_expr()?, + }) + } + _ => Ok(Statement::Expr(self.parse_expr()?)), + } + } + + fn parse_expr(&mut self) -> Result { + let mut expr = self.parse_primitive()?; + + while self.matches(&Token::LParen) { + let Expr::Identifier(name) = expr else { + return Err(String::from("functions must be named")); + }; + + // arguments for function calls + let mut args = Vec::new(); + if self.peek(self.position) != Some(&Token::RParen) { + loop { + args.push(self.parse_expr()?); + if !self.matches(&Token::Comma) { + break; + } + } + } + self.expect(&Token::RParen, "expected ')' for function call")?; + expr = Expr::Call { name, args }; + } + + Ok(expr) + } + + fn parse_primitive(&mut self) -> Result { + match self.advance() { + Token::Number(value) => Ok(Expr::Number(value)), + Token::Identifier(value) => Ok(Expr::Identifier(value)), + Token::String(value) => Ok(Expr::String(value)), + Token::LParen => { + let expr = self.parse_expr()?; + self.expect(&Token::RParen, "expected ')' for grouping")?; + Ok(Expr::Group(Box::new(expr))) + } + token => Err(format!("unexpected token: {token:?}")), + } + } + + fn skip_separators(&mut self) { + while self.matches(&Token::Semicolon) {} + } + + fn expect(&mut self, token: &Token, message: &str) -> Result<(), String> { + if self.matches(token) { + Ok(()) + } else { + Err(String::from(message)) + } + } + + fn matches(&mut self, token: &Token) -> bool { + if self.peek(self.position) == Some(token) { + self.position += 1; + true + } else { + false + } + } + + fn peek(&self, position: usize) -> Option<&Token> { + self.tokens.get(position) + } + + fn advance(&mut self) -> Token { + let token = self.peek(self.position).cloned().unwrap_or(Token::Eof); + self.position += 1; + token + } +} diff --git a/fix/src/parser/token.rs b/fix/src/parser/token.rs new file mode 100644 index 00000000..6f86bc23 --- /dev/null +++ b/fix/src/parser/token.rs @@ -0,0 +1,59 @@ +use core::{clone::Clone, fmt}; +use kernel::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Token { + Identifier(String), + Number(i64), + String(String), + LParen, + RParen, + Comma, + Semicolon, + Equals, + Eof, +} + +#[derive(Debug, Clone)] +pub enum Expr { + Number(i64), + Identifier(String), + String(String), + Call { name: String, args: Vec }, + Group(Box), +} + +#[derive(Debug, Clone)] +pub enum Statement { + Assign { name: String, expr: Expr }, + Print(Expr), + Expr(Expr), +} + +#[derive(Debug, Clone)] +pub enum Value { + Handle(H), + BlobData(B), + TreeData(T), + Int(i64), + String(String), + Unit, +} + +impl fmt::Display for Value +where + H: fmt::Debug, + B: fmt::Debug, + T: fmt::Debug, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Handle(handle) => write!(formatter, "{handle:?}"), + Self::BlobData(data) => write!(formatter, "{data:?}"), + Self::TreeData(data) => write!(formatter, "{data:?}"), + Self::Int(value) => write!(formatter, "{value}"), + Self::String(value) => write!(formatter, "{value}"), + Self::Unit => write!(formatter, "()"), + } + } +} diff --git a/fix/src/runtime.rs b/fix/src/runtime.rs new file mode 100644 index 00000000..9061cc03 --- /dev/null +++ b/fix/src/runtime.rs @@ -0,0 +1,11 @@ +extern crate alloc; + +use super::*; + +pub mod arca; + +/// A Fix runtime. A Runtime is effectively a data store that can additionally execute procedures. +pub trait Runtime { + fn storage(&self) -> &dyn Storage; + fn execute(&self, combination: Tree) -> Handle; +} diff --git a/fix/src/runtime/arca.rs b/fix/src/runtime/arca.rs new file mode 100644 index 00000000..098b724c --- /dev/null +++ b/fix/src/runtime/arca.rs @@ -0,0 +1,135 @@ +use crate::handle::*; +use crate::runtime::Runtime; +use crate::storage::Storage; +use crate::storage::memory::MemoryStorage; +use common::bitpack::BitPack; +use kernel::prelude::{Blob as ArcaBlob, Function, Tuple, Value, Vec}; +use kernel::println; + +#[derive(Debug, Default)] +pub struct FixOnArca { + storage: MemoryStorage, +} + +impl Runtime for FixOnArca { + fn storage(&self) -> &dyn Storage { + &self.storage + } + + fn execute(&self, combination: Tree) -> Handle { + println!("applying {}", Handle::from(combination)); + let contents = self.storage().get_tree(combination).unwrap(); + let procedure = contents.get(0).expect("empty combination"); + let elf = self + .storage() + .get_blob(procedure.unwrap_object().unwrap_blob()) + .unwrap(); + let f: Function = common::elfloader::load_elf(&elf).unwrap(); + let blob = pack_handle(combination); + let f = f.apply(blob); + let result = self.run(f); + result + } +} + +impl FixOnArca { + fn run(&self, mut f: Function) -> Handle { + loop { + let result = f.force(); + if let Value::Blob(b) = result { + return unpack_handle(&b); + } else { + let Value::Function(g) = result else { + panic!("expected Fix program to return a handle or an effect") + }; + let data = g.into_inner().read(); + let Value::Tuple(mut data) = data else { + unreachable!() + }; + let t: ArcaBlob = data.take(0).try_into().unwrap(); + assert_eq!(&*t, b"Symbolic"); + let effect: ArcaBlob = data.take(1).try_into().unwrap(); + let args: Tuple = data.take(2).try_into().unwrap(); + let mut args: Vec = args.into_iter().collect(); + let Some(Value::Function(k)) = args.pop() else { + panic!("unexpected non-effect return"); + }; + + f = match &*effect { + b"create_blob_i32" => { + let Some(Value::Word(w)) = args.pop() else { + panic!() + }; + k.apply(pack_handle( + self.storage().add_blob(&u32::to_le_bytes(w.read() as u32)), + )) + } + b"create_blob_i64" => { + let Some(Value::Word(w)) = args.pop() else { + panic!() + }; + k.apply(pack_handle( + self.storage().add_blob(&u64::to_le_bytes(w.read())), + )) + } + b"create_blob" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + k.apply(pack_handle(self.storage().add_blob(&b))) + } + b"create_tree" => { + let Some(Value::Blob(t)) = args.pop() else { + panic!() + }; + let mut tree = Vec::new(); + for handle in t.chunks(32) { + tree.push(Handle::unpack(handle.try_into().unwrap())); + } + k.apply(pack_handle(self.storage().add_tree(&tree))) + } + b"get_blob" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + let b = self + .storage() + .get_blob(unpack_handle(&b).unwrap_object().unwrap_blob()) + .unwrap(); + k.apply(ArcaBlob::new(b)) + } + b"get_tree" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + let t = self + .storage() + .get_tree(unpack_handle(&b).unwrap_object().unwrap_tree()) + .unwrap(); + let mut tree = Vec::new(); + for x in t { + tree.extend_from_slice(&Handle::pack(&x)); + } + k.apply(ArcaBlob::new(tree)) + } + _ => { + todo!("handle effect {:?}", &*effect); + } + }; + } + } + } +} + +fn pack_handle(handle: impl Into) -> ArcaBlob { + let raw = handle.into().pack(); + ArcaBlob::new(&raw) +} + +fn unpack_handle(blob: &ArcaBlob) -> Handle { + let mut buf = [0u8; 32]; + if blob.read(0, &mut buf) != 32 { + panic!("Failed to parse Arca Blob to Fix Handle") + } + Handle::unpack(buf) +} diff --git a/fix/src/storage.rs b/fix/src/storage.rs new file mode 100644 index 00000000..4cd48a21 --- /dev/null +++ b/fix/src/storage.rs @@ -0,0 +1,24 @@ +extern crate alloc; + +use super::*; +use alloc::boxed::Box; +use core::option::Option; + +pub mod memory; + +/// An object store, capable of saving and retrieving Fix objects. +pub trait Storage { + fn add_blob(&self, data: &[u8]) -> Blob; + fn add_tree(&self, data: &[Handle]) -> Tree; + + fn get_blob(&self, name: Blob) -> Option>; + fn get_tree(&self, name: Tree) -> Option>; + + fn has_blob(&self, name: Blob) -> bool { + self.get_blob(name).is_some() + } + + fn has_tree(&self, name: Tree) -> bool { + self.get_tree(name).is_some() + } +} diff --git a/fix/src/storage/memory.rs b/fix/src/storage/memory.rs new file mode 100644 index 00000000..1c46d7ad --- /dev/null +++ b/fix/src/storage/memory.rs @@ -0,0 +1,74 @@ +extern crate alloc; + +use super::*; +use alloc::boxed::Box; +use alloc::vec::Vec; +use bitint::U48; +use kernel::kthread::KMutex; + +/// An object store which stores its data in RAM. Names are indices into the tables; the indices +/// are stored inverted for visual distinctiveness. +#[derive(Debug, Default)] +pub struct MemoryStorage { + blobs: KMutex>>, + trees: KMutex>>, +} + +impl Storage for MemoryStorage { + fn add_blob(&self, data: &[u8]) -> Blob { + let mut blobs = self.blobs.lock(); + let i = blobs.len(); + let len = data.len(); + if len < 30 { + return Blob::Literal(LiteralName::new(data)); + } + blobs.push(data.into()); + let mut name = [0; 24]; + name[0..8].copy_from_slice(&usize::to_le_bytes(!i)); + unsafe { + BlobName::new(RawName { + name, + size: U48::new(len as u64).unwrap(), + meta: 0, + }) + .into() + } + } + + fn add_tree(&self, data: &[Handle]) -> Tree { + let mut trees = self.trees.lock(); + let i = trees.len(); + let len = data.len(); + trees.push(data.into()); + let mut name = [0; 24]; + name[0..8].copy_from_slice(&usize::to_le_bytes(!i)); + unsafe { + TreeName::new(RawName { + name, + size: U48::new(len as u64).unwrap(), + meta: 0, + }) + .into() + } + } + + fn get_blob(&self, name: Blob) -> Option> { + let blobs = self.blobs.lock(); + let mut i = [0; 8]; + let name = match name { + Blob::Blob(name) => name, + Blob::Literal(name) => return Some(name.bytes().into()), + }; + i.copy_from_slice(&BlobName::from(name).name().name[0..8]); + let i = !usize::from_le_bytes(i); + blobs.get(i).cloned() + } + + fn get_tree(&self, name: Tree) -> Option> { + let trees = self.trees.lock(); + let mut i = [0; 8]; + i.copy_from_slice(&TreeName::from(name).name().name[0..8]); + let i = !usize::from_le_bytes(i); + trees.get(i).cloned() + } +} diff --git a/fix/src/testing.rs b/fix/src/testing.rs new file mode 100644 index 00000000..26aecaeb --- /dev/null +++ b/fix/src/testing.rs @@ -0,0 +1,5 @@ +pub fn test_runner(tests: &[&dyn Fn()]) { + for test in tests { + test(); + } +} diff --git a/fix/wasm/addblob.wat b/fix/wasm/addblob.wat index 18c9bbe5..d4f1683e 100644 --- a/fix/wasm/addblob.wat +++ b/fix/wasm/addblob.wat @@ -1,17 +1,16 @@ (module (import "fixpoint" "create_blob_i64" (func $create_blob_i64 (param i64) (result externref))) (import "fixpoint" "attach_blob" (func $attach_blob (param i32) (param externref))) - (import "fixpoint" "get_tree_entry" (func $get_tree_entry (param externref) (param i32) (result externref))) - ;; memories intended for rw-usage + (import "fixpoint" "attach_tree" (func $attach_tree (param i32) (param externref))) (memory $mem_0 1) (memory $mem_1 0) (memory $mem_2 0) + (table $tab_0 0 externref) (func (export "_fixpoint_apply") (param $encode externref) (result externref) - ;; getting an entry of a tree multiple times - (call $get_tree_entry - (local.get $encode) - (i32.const 2)) - drop + ;; attach combination tree + (call $attach_tree + (i32.const 0) + (local.get $encode)) ;; grow rw-memory (memory.grow (memory $mem_0) @@ -19,14 +18,10 @@ drop (call $attach_blob (i32.const 1) - (call $get_tree_entry - (local.get $encode) - (i32.const 2))) + (table.get $tab_0 (i32.const 1))) (call $attach_blob (i32.const 2) - (call $get_tree_entry - (local.get $encode) - (i32.const 3))) + (table.get $tab_0 (i32.const 2))) ;; write to rw-memory (i64.store (memory $mem_0) (i32.const 0) diff --git a/fix/wasm/coupon.wat b/fix/wasm/coupon.wat new file mode 100644 index 00000000..f7b36c35 --- /dev/null +++ b/fix/wasm/coupon.wat @@ -0,0 +1,685 @@ +(module + (import "fixpoint" "is_equal" (func $is_equal (param externref) (param externref) (result i32))) + (import "fixpoint" "is_tag" (func $is_tag (param externref) (result i32))) + (import "fixpoint" "attach_blob" (func $attach_blob (param i32) (param externref))) + (import "fixpoint" "attach_tree" (func $attach_tree (param i32) (param externref))) + (import "fixpoint" "create_blob_i32" (func $create_blob_i32 (param i32) (result externref))) + (import "fixpoint" "create_tag" (func $create_tag (param i32) (result externref))) + (import "fixpoint" "create_application_thunk" (func $create_application_thunk (param externref) (result externref))) + (import "fixpoint" "create_strict_encode" (func $create_strict_encode (param externref) (result externref))) + (import "fixpoint" "create_shallow_encode" (func $create_shallow_encode (param externref) (result externref))) + (import "fixpoint" "is_blob_obj" (func $is_blob_obj (param externref) (result i32))) + (import "fixpoint" "is_data" (func $is_data (param externref) (result i32))) + (import "fixpoint" "is_object" (func $is_object (param externref) (result i32))) + (table $encode 0 externref) + (table $coupon_scratch 0 externref) + (table $coupons 0 externref) + (table $lhstree 0 externref) + (table $rhstree 0 externref) + (table $output_coupon_scratch 4 externref) + (memory $mem_0 0) + (memory $mem_1 0) + (global $Eq i32 (i32.const 0)) + (global $Eval i32 (i32.const 1)) + (global $Apply i32 (i32.const 2)) + (global $Force i32 (i32.const 3)) + (global $Think i32 (i32.const 4)) + (global $Storage i32 (i32.const 5)) + (type $make_coupon_t (func (param externref externref) (result externref))) + (func $is_coupon (param $tag externref) (param $type i32) (result i32) + (call $is_tag (local.get $tag)) + (if (result i32) + (then + ;; Attach the tag + (call $attach_tree (i32.const 1) (local.get $tag)) + ;; Check if the tag was authored by us + (call $is_equal (table.get $encode (i32.const 0)) (table.get $coupon_scratch (i32.const 0))) + (if (result i32) + (then + ;; Check if the coupon type matches the input type + (call $attach_blob (i32.const 1) (table.get $coupon_scratch (i32.const 1))) + (i32.load (memory $mem_1) (i32.const 0)) + (local.get $type) + i32.eq + (if (result i32) + (then (i32.const 1)) + (else (i32.const 0)) + ) + ) + (else (i32.const 0)) + ) + ) + (else (i32.const 0)) + ) + ) + (func $is_eq_coupon (param $tag externref) (result i32) + (call $is_coupon (local.get $tag) (global.get $Eq)) + ) + (func $is_eval_coupon (param $tag externref) (result i32) + (call $is_coupon (local.get $tag) (global.get $Eval)) + ) + (func $is_apply_coupon (param $tag externref) (result i32) + (call $is_coupon (local.get $tag) (global.get $Apply)) + ) + (func $is_force_coupon (param $tag externref) (result i32) + (call $is_coupon (local.get $tag) (global.get $Force)) + ) + (func $is_think_coupon (param $tag externref) (result i32) + (call $is_coupon (local.get $tag) (global.get $Think)) + ) + (func $is_storage_coupon (param $tag externref) (result i32) + (call $is_coupon (local.get $tag) (global.get $Storage)) + ) + (func $create_coupon (param $type i32) (param $lhs externref) (param $rhs externref) (result externref) + (table.set $output_coupon_scratch (i32.const 0) (table.get $encode (i32.const 0))) + (table.set $output_coupon_scratch (i32.const 1) (call $create_blob_i32 (local.get $type))) + (table.set $output_coupon_scratch (i32.const 2) (local.get $lhs)) + (table.set $output_coupon_scratch (i32.const 3) (local.get $rhs)) + (call $create_tag (i32.const 5)) + ) + (func $create_eq_coupon (param $lhs externref) (param $rhs externref) (result externref) + (call $create_coupon (global.get $Eq) (local.get $lhs) (local.get $rhs)) + ) + (func $create_eval_coupon (param $lhs externref) (param $rhs externref) (result externref) + (call $create_coupon (global.get $Eval) (local.get $lhs) (local.get $rhs)) + ) + (func $create_force_coupon (param $lhs externref) (param $rhs externref) (result externref) + (call $create_coupon (global.get $Force) (local.get $lhs) (local.get $rhs)) + ) + (func $create_think_coupon (param $lhs externref) (param $rhs externref) (result externref) + (call $create_coupon (global.get $Think) (local.get $lhs) (local.get $rhs)) + ) + (func $get_coupon_lhs (param $coupon externref) (result externref) + (call $attach_tree (i32.const 1) (local.get $coupon)) + (table.get $coupon_scratch (i32.const 2)) + ) + (func $get_coupon_rhs (param $coupon externref) (result externref) + (call $attach_tree (i32.const 1) (local.get $coupon)) + (table.get $coupon_scratch (i32.const 3)) + ) + (func $attach_lhs_tree (param $lhs externref) + (call $attach_tree (i32.const 3) (local.get $lhs)) + ) + (func $attach_rhs_tree (param $rhs externref) + (call $attach_tree (i32.const 4) (local.get $rhs)) + ) + (func $get_tree_size_lhs (result i32) + table.size $lhstree + ) + (func $get_tree_size_rhs (result i32) + table.size $rhstree + ) + (func $get_tree_data_lhs (param $i i32) (result externref) + (table.get $lhstree (local.get $i)) + ) + (func $get_tree_data_rhs (param $i i32) (result externref) + (table.get $rhstree (local.get $i)) + ) + (func $make_eq_tree_coupon (export "make_eq_tree_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $c externref) (local $size i32) (local $i i32) + (local.set $size (table.size $coupons)) + ;; Check that all coupons are eq coupons + (local.set $i (i32.const 0)) + (block $exit + (loop $loop + (local.get $i) + (local.get $size) + i32.ge_s + br_if $exit + + (call $is_eq_coupon (table.get $coupons (local.get $i))) + (if + (then nop) + (else unreachable) + ) + + (local.set $i (i32.add (local.get $i) (i32.const 1))) + br $loop + ) + ) + + ;; Attach lhs tree and rhs tree + (call $attach_lhs_tree (local.get $lhs)) + (call $attach_rhs_tree (local.get $rhs)) + + ;; Check tree size of lhs and rhs + (i32.eq (call $get_tree_size_lhs) (local.get $size)) + (if (result externref) + (then + (i32.eq (call $get_tree_size_rhs) (local.get $size)) + (if (result externref) + (then + ;; Check that each coupon corresponds to one pair of tree entries + (local.set $i (i32.const 0)) + (block $exit + (loop $loop + (local.get $i) + (local.get $size) + i32.ge_s + br_if $exit + + (local.set $c (table.get $coupons (local.get $i))) + (call $is_equal (call $get_tree_data_lhs (local.get $i)) (call $get_coupon_lhs (local.get $c))) + (if + (then + (call $is_equal (call $get_tree_data_rhs (local.get $i)) (call $get_coupon_rhs (local.get $c))) + (if + (then nop) + (else unreachable) + ) + ) + (else + unreachable + ) + ) + + (local.set $i (i32.add (local.get $i) (i32.const 1))) + br $loop + ) + ) + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_eval_tree_coupon (export "make_eval_tree_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $c externref) (local $size i32) (local $i i32) + (local.set $size (table.size $coupons)) + ;; Check that all coupons are eval coupons + (local.set $i (i32.const 0)) + (block $exit + (loop $loop + (local.get $i) + (local.get $size) + i32.ge_s + br_if $exit + + (call $is_eval_coupon (table.get $coupons (local.get $i))) + (if + (then nop) + (else unreachable) + ) + + (local.set $i (i32.add (local.get $i) (i32.const 1))) + br $loop + ) + ) + + ;; Attach lhs tree and rhs tree + (call $attach_lhs_tree (local.get $lhs)) + (call $attach_rhs_tree (local.get $rhs)) + + ;; Check tree size of lhs and rhs + (i32.eq (call $get_tree_size_lhs) (local.get $size)) + (if (result externref) + (then + (i32.eq (call $get_tree_size_rhs) (local.get $size)) + (if (result externref) + (then + ;; Check that each coupon corresponds to one pair of tree entries + (local.set $i (i32.const 0)) + (block $exit + (loop $loop + (local.get $i) + (local.get $size) + i32.ge_s + br_if $exit + + (local.set $c (table.get $coupons (local.get $i))) + (call $is_equal (call $get_tree_data_lhs (local.get $i)) (call $get_coupon_lhs (local.get $c))) + (if + (then + (call $is_equal (call $get_tree_data_rhs (local.get $i)) (call $get_coupon_rhs (local.get $c))) + (if + (then nop) + (else unreachable) + ) + ) + (else + unreachable + ) + ) + + (local.set $i (i32.add (local.get $i) (i32.const 1))) + br $loop + ) + ) + (call $create_eval_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_force_result_eq_coupon (export "make_force_result_eq_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $f1 externref) (local $f2 externref) (local $e externref) + (local.set $f1 (table.get $coupons (i32.const 0))) + (local.set $f2 (table.get $coupons (i32.const 1))) + (local.set $e (table.get $coupons (i32.const 2))) + (call $is_force_coupon (local.get $f1)) + (if (result externref) + (then + (call $is_force_coupon (local.get $f2)) + (if (result externref) + (then + (call $is_eq_coupon (local.get $e)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $f1)) (call $get_coupon_lhs (local.get $e))) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $f2)) (call $get_coupon_rhs (local.get $e))) + (if (result externref) + (then + (call $is_equal (call $get_coupon_lhs (local.get $f1)) (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_lhs (local.get $f2)) (local.get $rhs)) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_eval_eq_coupon (export "make_eval_eq_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $c1 externref) (local $c2 externref) + (local.set $c1 (table.get $coupons (i32.const 0))) + (local.set $c2 (table.get $coupons (i32.const 1))) + (call $is_eval_coupon (local.get $c1)) + (if (result externref) + (then + (call $is_eq_coupon (local.get $c2)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_lhs (local.get $c1)) (call $get_coupon_lhs (local.get $c2))) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $c2)) (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $c1)) (local.get $rhs)) + (if (result externref) + (then + (call $create_eval_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (func $make_think_application_coupon (export "make_think_application_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $c1 externref) (local $c2 externref) + (local.set $c1 (table.get $coupons (i32.const 0))) + (local.set $c2 (table.get $coupons (i32.const 1))) + (call $is_eval_coupon (local.get $c1)) + (if (result externref) + (then + (call $is_apply_coupon (local.get $c2)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $c1)) (call $get_coupon_lhs (local.get $c2))) + (if (result externref) + (then + (call $is_equal (call $create_application_thunk (call $get_coupon_lhs (local.get $c1))) (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $c2)) (local.get $rhs)) + (if (result externref) + (then + (call $create_think_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_think_to_force_coupon (export "make_think_to_force_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $t externref) + (local.set $t (table.get $coupons (i32.const 0))) + (call $is_think_coupon (local.get $t)) + (if (result externref) + (then + (call $is_data (call $get_coupon_rhs (local.get $t))) + (if (result externref) + (then + (call $is_equal (call $get_coupon_lhs (local.get $t)) (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $t)) (local.get $rhs)) + (if (result externref) + (then + (call $create_force_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_force_to_encode_strict_coupon (export "make_force_to_encode_strict_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $t externref) + (local.set $t (table.get $coupons (i32.const 0))) + (call $is_force_coupon (local.get $t)) + (if (result externref) + (then + (call $is_object (call $get_coupon_rhs (local.get $t))) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $t)) (local.get $rhs)) + (if (result externref) + (then + (call $is_equal (call $create_strict_encode (call $get_coupon_lhs (local.get $t))) (local.get $lhs)) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) +(func $make_eval_blobobj_coupon (export "make_eval_blobobj_coupon") (param $lhs externref) (param $rhs externref) (result externref) + (call $is_blob_obj (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (local.get $lhs) (local.get $rhs)) + (if (result externref) + (then + (call $create_eval_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_eq_application_coupon (export "make_eq_application_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $e externref) + (local.set $e (table.get $coupons (i32.const 0))) + (call $is_eq_coupon (local.get $e)) + (if (result externref) + (then + (local.get $lhs) + (call $create_application_thunk (call $get_coupon_lhs (local.get $e))) + (call $is_equal) + (if (result externref) + (then + (local.get $rhs) + (call $create_application_thunk (call $get_coupon_rhs (local.get $e))) + (call $is_equal) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_eq_encode_strict_coupon (export "make_eq_encode_strict_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $e externref) + (local.set $e (table.get $coupons (i32.const 0))) + (call $is_eq_coupon (local.get $e)) + (if (result externref) + (then + (call $is_equal (call $create_strict_encode (call $get_coupon_lhs (local.get $e))) (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (call $create_strict_encode (call $get_coupon_rhs (local.get $e))) (local.get $rhs)) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_sym_coupon (export "make_sym_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $e externref) + (local.set $e (table.get $coupons (i32.const 0))) + (call $is_eq_coupon (local.get $e)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $e)) (local.get $lhs)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_lhs (local.get $e)) (local.get $rhs)) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_trans_coupon (export "make_trans_coupon") (param $lhs externref) (param $rhs externref) (result externref) (local $e1 externref) (local $e2 externref) + (local.set $e1 (table.get $coupons (i32.const 0))) + (local.set $e2 (table.get $coupons (i32.const 1))) + (call $is_eq_coupon (local.get $e1)) + (if (result externref) + (then + (call $is_eq_coupon (local.get $e2)) + (if (result externref) + (then + (call $is_equal (call $get_coupon_rhs (local.get $e1)) (call $get_coupon_lhs (local.get $e2))) + (if (result externref) + (then + (call $is_equal (local.get $lhs) (call $get_coupon_lhs (local.get $e1))) + (if (result externref) + (then + (call $is_equal (local.get $rhs) (call $get_coupon_rhs (local.get $e2))) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + ) + ) + (else + unreachable + ) + )) + (func $make_self_coupon (export "make_self_coupon") (param $lhs externref) (param $rhs externref) (result externref) + (call $is_equal (local.get $lhs) (local.get $rhs)) + (if (result externref) + (then + (call $create_eq_coupon (local.get $lhs) (local.get $rhs)) + ) + (else + unreachable + ) + )) + (table $dispatch_table funcref (elem (ref.func $make_eq_tree_coupon) + (ref.func $make_eq_application_coupon) + (ref.func $make_force_result_eq_coupon) + (ref.func $make_eq_encode_strict_coupon) + (ref.func $make_think_application_coupon) + (ref.func $make_think_to_force_coupon) + (ref.func $make_force_to_encode_strict_coupon) + (ref.func $make_eval_eq_coupon) + (ref.func $make_eval_blobobj_coupon) + (ref.func $make_eval_tree_coupon) + (ref.func $make_sym_coupon) + (ref.func $make_trans_coupon) + (ref.func $make_self_coupon))) + (func $make_coupon (export "make_coupon") (param $request i32) (param $lhs externref) (param $rhs externref) (result externref) + local.get $request + table.size $dispatch_table + i32.lt_u + if (result externref) + local.get $lhs + local.get $rhs + local.get $request + (call_indirect $dispatch_table (type $make_coupon_t)) + else + unreachable + end + ) + (func (export "_fixpoint_apply") (param $combination externref) (result externref) + ;; attach combination tree + (call $attach_tree + (i32.const 0) + (local.get $combination)) + ;; attach coupons + (call $attach_tree + (i32.const 2) + (table.get $encode (i32.const 2))) + ;; attach request field + (call $attach_blob + (i32.const 1) + (table.get $encode (i32.const 1))) + (call $make_coupon + (i32.load $mem_1 (i32.const 0)) + (table.get $encode (i32.const 3)) + (table.get $encode (i32.const 4)) + ) + ) + (export "coupons" (table $coupons)) +) diff --git a/justfile b/justfile new file mode 100644 index 00000000..6b848df3 --- /dev/null +++ b/justfile @@ -0,0 +1,43 @@ +alias b := build +alias r := run + +target := 'x86_64-unknown-none' + +release := if env('RELEASE', '0') != '0'{ '--release' } else { '' } + +default: + just --list + +build-all: + cargo build {{release}} + cargo build -p kernel {{release}} + cargo build -p fix {{release}} + cargo build -p user {{release}} + +test: + cargo test + cargo test -p kernel --target={{target}} + +build bin: + cargo build -p kernel --example={{bin}} --target={{target}} {{release}} + +run bin *args: + cargo run -p kernel --example={{bin}} --target={{target}} {{release}} -- {{args}} + +fix *args: + cargo run -p fix --target={{target}} {{release}} -- {{args}} + +fmt: + cargo fmt + cargo fmt -p kernel + cargo fmt -p fix + cargo fmt -p user + +lint: + cargo clippy + cargo clippy -p kernel + cargo clippy -p fix + cargo clippy -p user + +ctags: + ctags -R arca arcane common fix kernel macros user vmm diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 3ad4cffb..bf378dfb 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -6,10 +6,18 @@ version = "0.1.0" edition = "2021" forced-target = "x86_64-unknown-none" -[[bin]] +[[example]] name = "hello" test = false +[[example]] +name = "threads" +test = false + +[[example]] +name = "webserver" +test = false + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] @@ -35,8 +43,9 @@ cfg-if = "1.0.1" talc = "4.4.3" spin = "0.10.0" async-lock = { version = "3.4.1", default-features = false } -postcard = "1.1.3" -serde = { version = "1.0.228", default-features = false } +postcard = { version = "1.1.3", features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } + [build-dependencies] anyhow = "1.0.86" @@ -45,3 +54,4 @@ glob = "0.3.1" [dev-dependencies] postcard = { version = "1.1.3", features = ["alloc"] } +user = { path = "../user", artifact = "bin", target = "x86_64-unknown-none" } diff --git a/kernel/build.rs b/kernel/build.rs index a3fcca22..3a1f4901 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -7,6 +7,8 @@ fn main() -> Result<()> { println!("cargo::rerun-if-changed=src/interrupts.S"); cc::Build::new().file("src/util.S").compile("util"); println!("cargo::rerun-if-changed=src/util.S"); + cc::Build::new().file("src/cswitch.S").compile("cswitch"); + println!("cargo::rerun-if-changed=src/cswitch.S"); let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); println!("cargo::rerun-if-changed=etc/memmap.ld"); diff --git a/kernel/examples/hello.rs b/kernel/examples/hello.rs new file mode 100644 index 00000000..f6803451 --- /dev/null +++ b/kernel/examples/hello.rs @@ -0,0 +1,12 @@ +#![no_std] +#![no_main] + +use kernel::host::os; +use kernel::prelude::*; + +#[kmain] +fn main() { + let args = os::argv(); + let name = if args.len() >= 2 { &args[1] } else { "world" }; + log::info!("hello, {name}"); +} diff --git a/kernel/examples/threads.rs b/kernel/examples/threads.rs new file mode 100644 index 00000000..ea743c00 --- /dev/null +++ b/kernel/examples/threads.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +use kernel::{ + kthread::{self, yield_now}, + prelude::*, +}; + +#[kmain] +fn main() { + log::info!("about to spawn two threads"); + kthread::spawn(|| { + for i in 0..5 { + log::info!("thread 1 says {i}"); + yield_now(); + } + }); + kthread::spawn(|| { + for i in 100..105 { + log::info!("thread 2 says {i}"); + yield_now(); + } + }); +} diff --git a/kernel/examples/webserver.rs b/kernel/examples/webserver.rs new file mode 100644 index 00000000..6d1778dc --- /dev/null +++ b/kernel/examples/webserver.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +use common::elfloader; +use kernel::host::net::TcpListener; +use kernel::{kthread, prelude::*}; + +const HANDLER: &[u8] = include_bytes!(env!("CARGO_BIN_FILE_USER_webserver")); + +#[kmain] +fn main() { + kthread::wfi(); + let listener = TcpListener::bind(&[0, 0, 0, 0], 8080); + log::info!("listening on port 8080"); + let handler: Function = elfloader::load_elf(HANDLER).unwrap(); + loop { + let handler = handler.clone(); + let mut stream = listener.accept(); + kthread::spawn(move || { + let mut handler = handler; + loop { + let effect: Function = handler + .force() + .try_into() + .expect("handler returned non-effect"); + let mut data: Tuple = effect.read().try_into().expect("could not read effect"); + let t: Blob = data.take(0).try_into().unwrap(); + assert_eq!(&*t, b"Symbolic"); + let effect: Blob = data.take(1).try_into().expect("could not find effect name"); + let args: Tuple = data + .take(2) + .try_into() + .expect("could not find effect arguments"); + let mut args: Vec = args.into_iter().collect(); + let k: Function = args + .pop() + .and_then(|x| x.try_into().ok()) + .expect("could not find continuation"); + handler = match (&*effect, &*args) { + (b"read", &[Value::Word(fd), Value::Word(len)]) => { + assert_eq!(fd.read(), 0); + let mut v = vec![0; len.read() as usize]; + let len = stream.recv(&mut v); + v.truncate(len); + k.apply(Blob::new(v)) + } + (b"write", &[Value::Word(fd), Value::Blob(ref data)]) => { + assert_eq!(fd.read(), 1); + let len = stream.send(&*data); + k.apply(Word::new(len as u64)) + } + (b"close", &[Value::Word(_)]) => k.apply(None), + (b"exit", &[Value::Word(_)]) => { + break; + } + (e, _) => { + let e: &str = str::from_utf8(e).unwrap(); + panic!("unhandled effect: {e}"); + } + }; + } + }); + } +} diff --git a/kernel/examples/webserver2.rs b/kernel/examples/webserver2.rs new file mode 100644 index 00000000..51620645 --- /dev/null +++ b/kernel/examples/webserver2.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +use common::elfloader; +use kernel::host::net::TcpListener; +use kernel::{kthread, prelude::*}; + +const HANDLER: &[u8] = include_bytes!(env!("CARGO_BIN_FILE_USER_webserver2")); + +#[kmain] +fn main() { + kthread::wfi(); + let listener = Arc::new(TcpListener::bind(&[0, 0, 0, 0], 8081)); + log::info!("listening on port 8081"); + + for _ in 0..kernel::ncores() { + let listener = listener.clone(); + kthread::spawn(move || { + let mut handler: Function = elfloader::load_elf(HANDLER).unwrap(); + let mut current = None; + loop { + let effect: Function = handler + .force() + .try_into() + .expect("handler returned non-effect"); + let mut data: Tuple = effect.read().try_into().expect("could not read effect"); + let t: Blob = data.take(0).try_into().unwrap(); + assert_eq!(&*t, b"Symbolic"); + let effect: Blob = data.take(1).try_into().expect("could not find effect name"); + let args: Tuple = data + .take(2) + .try_into() + .expect("could not find effect arguments"); + let mut args: Vec = args.into_iter().collect(); + let k: Function = args + .pop() + .and_then(|x| x.try_into().ok()) + .expect("could not find continuation"); + handler = match (&*effect, &*args) { + (b"accept", &[]) => { + current = Some(listener.accept()); + k.apply(None) + } + (b"recv", &[Value::Word(len)]) => { + if let Some(ref mut stream) = current { + let mut v = vec![0; len.read() as usize]; + let len = stream.recv(&mut v); + v.truncate(len); + k.apply(Blob::new(v)) + } else { + k.apply(None) + } + } + (b"send", &[Value::Blob(ref data)]) => { + if let Some(ref mut stream) = current { + let len = stream.send(&*data); + k.apply(Word::new(len as u64)) + } else { + k.apply(None) + } + } + (b"hangup", &[]) => { + current.take(); + k.apply(None) + } + (b"exit", &[Value::Word(_)]) => { + break; + } + (e, _) => { + let e: &str = str::from_utf8(e).unwrap(); + panic!("unhandled effect: {e}"); + } + }; + } + }); + } +} diff --git a/kernel/src/aprofile.rs b/kernel/src/aprofile.rs deleted file mode 100644 index 937506f8..00000000 --- a/kernel/src/aprofile.rs +++ /dev/null @@ -1,101 +0,0 @@ -use core::{ - fmt::Write as _, - sync::atomic::{AtomicU64, Ordering}, - time::Duration, -}; - -pub use macros::profile; - -use alloc::collections::btree_map::BTreeMap; - -use crate::{debugcon, prelude::*}; - -static COUNTS: OnceLock<&'static [AtomicU64]> = OnceLock::new(); - -extern "C" { - static mut _srodata: u8; - static mut _edata: u8; -} - -pub(crate) unsafe fn init() { - COUNTS.get_or_init(|| { - let size = (&raw const _edata).offset_from_unsigned(&raw const _srodata); - let buffer = Box::new_zeroed_slice(size).assume_init(); - Box::leak(buffer) - }); -} - -pub fn reset() { - for count in *COUNTS { - count.store(0, Ordering::SeqCst); - } -} - -pub fn log_time_spent(f: &&'static str, duration: Duration) { - let ns = duration.as_nanos() as u64; - let start = &raw const _srodata; - let end = &raw const _edata; - let rip = f as *const &'static str as *const u8; - if rip < start || rip >= end { - return; - } - unsafe { - let offset = rip.offset_from_unsigned(start); - COUNTS[offset].fetch_add(ns, Ordering::SeqCst); - } -} - -pub fn entries() -> BTreeMap<&'static str, Duration> { - let start = &raw const _srodata as *const (); - - let mut entries = BTreeMap::new(); - for (i, count) in COUNTS.iter().enumerate() { - let count = count.load(Ordering::SeqCst); - - if count > 0 { - unsafe { - entries.insert( - *(start.byte_add(i) as *const &'static str), - Duration::from_nanos(count), - ); - } - } - } - entries -} - -pub fn report(entries: &mut [(&'static str, Duration)]) { - let start = &raw const _srodata; - static NULL: &str = "N/A"; - entries.fill((NULL, Duration::ZERO)); - - for (i, count) in COUNTS.iter().enumerate() { - let count = count.load(Ordering::SeqCst); - let dur = Duration::from_nanos(count); - - if dur > entries[0].1 { - unsafe { - entries[0] = (*(start.byte_add(i) as *const &'static str), dur); - } - } - - entries.sort_by_key(|x| x.1); - } - entries.reverse(); -} - -pub fn log(count: usize) { - let mut entries = vec![("", Duration::ZERO); count]; - report(&mut entries); - let mut console = debugcon::CONSOLE.lock(); - let _ = writeln!(&mut *console, "----- PROFILE -----"); - for (i, (p, n)) in entries.iter().enumerate() { - if *n == Duration::ZERO { - break; - } - let symname = *p; - let n = n.as_nanos(); - let _ = writeln!(&mut *console, "{i}. {n:.5e}\t{symname}"); - } - let _ = writeln!(&mut *console, "-------------------"); -} diff --git a/kernel/src/bin/hello.rs b/kernel/src/bin/hello.rs deleted file mode 100644 index ac63652a..00000000 --- a/kernel/src/bin/hello.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_std] -#![no_main] - -use kernel::prelude::*; -use kernel::rt; - -#[kmain] -async fn main(_: &[usize]) { - log::info!("hello, world"); -} diff --git a/kernel/src/cpu.rs b/kernel/src/cpu.rs index e62bea40..ea3919d8 100644 --- a/kernel/src/cpu.rs +++ b/kernel/src/cpu.rs @@ -152,7 +152,7 @@ pub struct ExitStatus { pub error: u64, } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum ExitReason { DivisionByZero, Debug, diff --git a/kernel/src/cswitch.S b/kernel/src/cswitch.S new file mode 100644 index 00000000..06524044 --- /dev/null +++ b/kernel/src/cswitch.S @@ -0,0 +1,37 @@ +.intel_syntax noprefix + +/* + * Cooperatively switches from one kthread to another. + * + * params: + * rdi: new stack pointer + * rsi: pointer to old stack pointer + */ +.globl cswitch +cswitch: + cli + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + mov [rsi], rsp + mov rsp, rdi + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + sti + ret + +.globl kthread_init +kthread_init: + pop rax + pop rdi + jmp rax diff --git a/kernel/src/debugcon.rs b/kernel/src/debugcon.rs index 6993239e..fb9b1509 100644 --- a/kernel/src/debugcon.rs +++ b/kernel/src/debugcon.rs @@ -60,3 +60,30 @@ impl log::Log for DebugLogger { } pub static DEBUG: DebugLogger = DebugLogger; + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => {{ + use ::core::fmt::Write; + let mut debug = ::kernel::debugcon::CONSOLE.lock(); + ::core::write!(debug, $($arg)*).unwrap(); + }}; +} + +#[macro_export] +macro_rules! println { + () => { + { + use ::core::fmt::Write; + let mut debug = ::kernel::debugcon::CONSOLE.lock(); + ::core::writeln!(debug).unwrap() + } + }; + ($($arg:tt)*) => { + { + use ::core::fmt::Write; + let mut debug = ::kernel::debugcon::CONSOLE.lock(); + ::core::writeln!(debug, $($arg)*).unwrap() + } + }; +} diff --git a/kernel/src/host.rs b/kernel/src/host.rs index 9a742af2..e43a7fb9 100644 --- a/kernel/src/host.rs +++ b/kernel/src/host.rs @@ -92,26 +92,277 @@ pub fn memclr(region: *mut [u8]) { } } -pub fn server_read(region: *mut [u8]) -> usize { - let (p, n) = region.to_raw_parts(); - let p = vm::ka2pa(p); - unsafe { crate::io::hypercall2(hypercall::SERVERREAD, p as u64, n as u64) as usize } +pub mod os { + use crate::prelude::*; + use common::protocol::*; + + pub fn argv() -> Vec { + let mut binding = crate::pipe::HOST.lock(); + let host = binding.get_mut().unwrap(); + let control::Response::Args(args) = host.request(&control::Request::GetArgs) else { + panic!("bad response"); + }; + args + } } -pub fn server_write(region: *const [u8]) -> usize { - let (p, n) = region.to_raw_parts(); - let p = vm::ka2pa(p); - unsafe { crate::io::hypercall2(hypercall::SERVERWRITE, p as u64, n as u64) as usize } +use super::pipe::HostPipe; +unsafe fn get_pipe(data: common::protocol::control::PipeData) -> HostPipe { + use common::pipe::{Pipe, Reader, Writer}; + let rxp: *const u8 = BuddyAllocator.from_offset(data.rx_ptr); + let txp: *const u8 = BuddyAllocator.from_offset(data.tx_ptr); + let rx = Arc::from_raw_in(core::ptr::from_raw_parts(rxp, data.rx_len), BuddyAllocator); + let rx = Reader::from_inner(rx); + let tx = Arc::from_raw_in(core::ptr::from_raw_parts(txp, data.tx_len), BuddyAllocator); + let tx = Writer::from_inner(tx); + let pipe = Pipe::from_inner(rx, tx); + HostPipe::new(pipe) } -pub fn client_read(region: *mut [u8]) -> usize { - let (p, n) = region.to_raw_parts(); - let p = vm::ka2pa(p); - unsafe { crate::io::hypercall2(hypercall::CLIENTREAD, p as u64, n as u64) as usize } +pub mod net { + use super::get_pipe; + use crate::pipe::*; + use crate::prelude::*; + use common::protocol::*; + + pub struct TcpListener { + pipe: KMutex, + } + + pub struct TcpStream { + pipe: StreamPipe, + } + + impl TcpListener { + pub fn bind(ip: &[u8; 4], port: u16) -> TcpListener { + let mut binding = crate::pipe::HOST.lock(); + let host = binding.get_mut().unwrap(); + let ip = *ip; + let control::Response::Pipe(id) = host.request(&control::Request::Listen { ip, port }) + else { + todo!(); + }; + unsafe { + TcpListener { + pipe: KMutex::new(ListenerPipe::new(get_pipe(id))), + } + } + } + + pub fn accept(&self) -> TcpStream { + let mut pipe = self.pipe.lock(); + let listener::Response::Pipe(id) = pipe.request(&listener::Request::Accept) else { + todo!(); + }; + unsafe { + TcpStream { + pipe: StreamPipe::new(get_pipe(id)), + } + } + } + } + + impl Drop for TcpListener { + fn drop(&mut self) { + let mut pipe = self.pipe.lock(); + let listener::Response::Ack = pipe.request(&listener::Request::Close) else { + todo!(); + }; + } + } + + impl TcpStream { + pub fn connect(hostname: &str, port: u16) -> TcpStream { + let mut binding = crate::pipe::HOST.lock(); + let host = binding.get_mut().unwrap(); + let control::Response::Pipe(id) = host.request(&control::Request::Connect { + host: hostname.into(), + port, + }) else { + todo!(); + }; + unsafe { + TcpStream { + pipe: StreamPipe::new(get_pipe(id)), + } + } + } + + pub fn send(&mut self, bytes: &[u8]) -> usize { + let stream::Response::Length(len) = + self.pipe.request(&stream::Request::Send(bytes.into())) + else { + panic!("bad response"); + }; + len + } + + pub fn recv(&mut self, bytes: &mut [u8]) -> usize { + let stream::Response::Bytes(buf) = + self.pipe.request(&stream::Request::Receive(bytes.len())) + else { + panic!("bad response"); + }; + bytes[..buf.len()].copy_from_slice(&buf); + bytes.len() + } + + pub fn close(self) { + let _ = self; + } + } + + impl Drop for TcpStream { + fn drop(&mut self) { + let stream::Response::Ack = self.pipe.request(&stream::Request::Close) else { + panic!("bad response"); + }; + } + } + + impl !Sync for TcpStream {} + + impl core::fmt::Write for TcpStream { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let mut s = s.as_bytes(); + while !s.is_empty() { + let len = self.send(s); + s = &s[len..]; + } + Ok(()) + } + } } -pub fn client_write(region: *const [u8]) -> usize { - let (p, n) = region.to_raw_parts(); - let p = vm::ka2pa(p); - unsafe { crate::io::hypercall2(hypercall::CLIENTWRITE, p as u64, n as u64) as usize } +pub mod fs { + use super::get_pipe; + use crate::pipe::*; + pub use common::protocol::file::Whence; + use common::{ + protocol::control::{ErrorKind, FileMode}, + protocol::*, + }; + + pub struct File { + pipe: FilePipe, + } + + /// Recursively creates a directory and all of its parents on the host, like + /// `mkdir -p`. Mirrors [`File::open`], but the host has nothing to stream + /// back, so success is a bare ack and failure carries an [`ErrorKind`]. + pub fn mkdir(path: &str) -> Result<(), ErrorKind> { + let mut binding = crate::pipe::HOST.lock(); + let host = binding.get_mut().unwrap(); + match host.request(&control::Request::Mkdir(path.into())) { + control::Response::Ack => Ok(()), + control::Response::Err(e) => Err(e.into()), + _ => Err(ErrorKind::Other), + } + } + + impl File { + pub fn open( + path: &str, + read: bool, + write: bool, + create: bool, + append: bool, + truncate: bool, + ) -> Result { + let mut binding = crate::pipe::HOST.lock(); + let host = binding.get_mut().unwrap(); + match host.request(&control::Request::Open( + path.into(), + FileMode { + read, + write, + create, + append, + truncate, + }, + )) { + control::Response::Pipe(id) => unsafe { + Ok(File { + pipe: FilePipe::new(get_pipe(id)), + }) + }, + control::Response::Err(e) => Err(e.into()), + _ => Err(ErrorKind::Other), + } + } + + pub fn close(self) {} + + pub fn read(&mut self, buf: &mut [u8]) -> usize { + let file::Response::Bytes(bytes) = self.pipe.request(&file::Request::Read(buf.len())) + else { + panic!("bad response"); + }; + buf[..bytes.len()].copy_from_slice(&bytes); + bytes.len() + } + + pub fn read_exact(&mut self, mut buf: &mut [u8]) -> usize { + let mut total = 0; + while !buf.is_empty() { + let n = self.read(buf); + if n == 0 { + break; + } + buf = &mut buf[n..]; + total += n; + } + total + } + + pub fn write(&mut self, buf: &[u8]) -> usize { + let file::Response::Length(len) = self.pipe.request(&file::Request::Write(buf.into())) + else { + panic!("bad response"); + }; + len + } + + pub fn write_exact(&mut self, mut buf: &[u8]) -> usize { + let mut total = 0; + while !buf.is_empty() { + let n = self.write(buf); + if n == 0 { + break; + } + buf = &buf[n..]; + total += n; + } + total + } + + pub fn seek(&mut self, whence: Whence) -> u64 { + let file::Response::Offset(offset) = self.pipe.request(&file::Request::Seek(whence)) + else { + panic!("bad response"); + }; + offset + } + } + + impl Drop for File { + fn drop(&mut self) { + let file::Response::Ack = self.pipe.request(&file::Request::Close) else { + panic!("bad response"); + }; + } + } + + impl core::fmt::Write for File { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let mut s = s.as_bytes(); + while !s.is_empty() { + let len = self.write(s); + s = &s[len..]; + } + Ok(()) + } + } + + impl !Sync for File {} } diff --git a/kernel/src/interrupts.rs b/kernel/src/interrupts.rs index cd9ddfe2..db9e65fa 100644 --- a/kernel/src/interrupts.rs +++ b/kernel/src/interrupts.rs @@ -80,10 +80,12 @@ pub fn critical(f: impl FnOnce() -> T) -> T { y } +#[allow(unused)] pub fn must_be_enabled() { assert!(enabled()); } +#[allow(unused)] pub fn must_be_disabled() { assert!(!enabled()); } @@ -91,12 +93,6 @@ pub fn must_be_disabled() { #[no_mangle] unsafe extern "C" fn isr_entry(registers: &mut IsrRegisterFile) { must_be_disabled(); - if registers.isr == 0x30 { - // log::warn!("got interrupt from virtio"); - INTERRUPTED.store(true, Ordering::Relaxed); - crate::lapic::LAPIC.borrow_mut().clear_interrupt(); - return; - } if registers.isr == 0x31 { INTERRUPTED.store(true, Ordering::Relaxed); if kvmclock::time_since_boot() > Duration::from_secs(1) { @@ -116,11 +112,8 @@ unsafe extern "C" fn isr_entry(registers: &mut IsrRegisterFile) { }); let _ = writeln!(&mut *console, "------ PROFILE ------"); crate::iprofile::log(20); - let _ = writeln!(&mut *console, "------ RUNTIME ------"); - crate::rt::profile(); let _ = writeln!(&mut *console, "---------------------"); crate::iprofile::reset(); - crate::rt::reset_stats(); } // crate::shutdown(); crate::lapic::LAPIC.borrow_mut().clear_interrupt(); diff --git a/kernel/src/kthread.rs b/kernel/src/kthread.rs new file mode 100644 index 00000000..7b93c029 --- /dev/null +++ b/kernel/src/kthread.rs @@ -0,0 +1,201 @@ +use core::{ + cell::RefCell, + mem::MaybeUninit, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use alloc::{boxed::Box, collections::vec_deque::VecDeque}; +use common::util::{initcell::LazyLock, spinlock::SpinLock}; + +use crate::{interrupts::INTERRUPTED, page::Page2MB}; + +pub mod kmutex; +pub use kmutex::{KMutex, KMutexGuard}; + +#[derive(Default, Debug)] +pub struct KThread { + tid: usize, + rsp: *const u8, + base: *mut MaybeUninit, + scheduler: bool, + exited: bool, + wfi: bool, + next: Option>, +} + +unsafe impl Send for KThread {} + +unsafe extern "C" { + fn cswitch(new_stack: *const u8, old_stack: *mut *const u8) -> *const u8; + fn kthread_init(); +} + +static CURRENT_TID: AtomicUsize = AtomicUsize::new(0); +static OUTSTANDING: AtomicUsize = AtomicUsize::new(0); + +static WFI: SpinLock>> = SpinLock::new(None); + +fn next_tid() -> usize { + CURRENT_TID.fetch_add(1, Ordering::SeqCst) +} + +#[core_local] +pub static SCHEDULER_THREAD: LazyLock> = LazyLock::new(|| { + RefCell::new(KThread { + tid: next_tid(), + rsp: core::ptr::null(), + base: core::ptr::null_mut(), + scheduler: true, + exited: false, + wfi: false, + next: None, + }) +}); + +#[core_local] +pub static CURRENT_THREAD: LazyLock>> = LazyLock::new(RefCell::default); + +pub fn tid() -> usize { + CURRENT_THREAD.borrow().tid +} + +pub fn scheduler() -> bool { + CURRENT_THREAD.borrow().scheduler +} + +pub fn exited() -> bool { + CURRENT_THREAD.borrow().exited +} + +pub fn init() { + LazyLock::force(&SCHEDULER_THREAD); + LazyLock::force(&CURRENT_THREAD); +} + +pub static THREAD_QUEUE: LazyLock>>> = + LazyLock::new(|| SpinLock::new(VecDeque::new())); + +pub fn spawn(f: impl FnOnce()) { + let f: Box> = Box::new(Box::new(f)); + let f = Box::into_raw(f); + let tid = next_tid(); + let stack = Box::::new_uninit(); + let base = Box::into_raw(stack); + let stack = unsafe { + &mut *core::mem::transmute::< + *mut MaybeUninit, + *mut [MaybeUninit; (1 << 21) / 8], + >(base) + }; + stack[stack.len() - 1].write(f as u64); // data ptr + stack[stack.len() - 2].write(start_thread as *const () as u64); // function ptr (aligned) + stack[stack.len() - 3].write(kthread_init as *const () as u64); // return address + stack[stack.len() - 4].write(0); // rbx (aligned) + stack[stack.len() - 5].write(0); // rbp + stack[stack.len() - 6].write(0); // r12 (aligned) + stack[stack.len() - 7].write(0); // r13 + stack[stack.len() - 8].write(0); // r14 (aligned) + stack[stack.len() - 9].write(0); // r15 + let rsp = &raw mut stack[stack.len() - 9] as *const u8; + let thread = KThread { + tid, + rsp, + base, + scheduler: false, + exited: false, + wfi: false, + next: None, + }; + let mut q = THREAD_QUEUE.lock(); + OUTSTANDING.fetch_add(1, Ordering::SeqCst); + q.push_back(thread.into()); +} + +/** + * Waits for tasks to be scheduled, then runs tasks until there are none left. + * + * # Safety + * + * This function should only be called from rsstart; its calls should never be nested. + */ +pub(crate) unsafe fn run_scheduler() { + while OUTSTANDING.load(Ordering::SeqCst) == 0 { + // wait for something to be scheduled + core::hint::spin_loop(); + } + while OUTSTANDING.load(Ordering::SeqCst) != 0 { + let Some(next) = THREAD_QUEUE.lock().pop_front() else { + sleep(); + if INTERRUPTED.swap(false, Ordering::SeqCst) { + let mut wfi = WFI.lock(); + let mut head = core::mem::take(&mut *wfi); + let mut q = THREAD_QUEUE.lock(); + while let Some(mut current) = head { + head = core::mem::take(&mut current.next); + log::trace!("awakening {}", current.tid); + q.push_back(current); + } + } + continue; + }; + log::trace!("scheduling thread {}", next.tid); + CURRENT_THREAD.replace(next); + let mut scheduler = SCHEDULER_THREAD.borrow_mut(); + let old_rsp = &raw mut scheduler.rsp; + core::mem::drop(scheduler); + let new_rsp = CURRENT_THREAD.borrow_mut().rsp; + cswitch(new_rsp, old_rsp); + let mut next = CURRENT_THREAD.take(); + if next.exited { + log::trace!("thread {} exited", next.tid); + let old_stack = next.base; + let ptr = Box::from_raw(old_stack); + core::mem::drop(ptr); + let left = OUTSTANDING.fetch_sub(1, Ordering::SeqCst) - 1; + log::trace!("{left} threads outstanding"); + } else if next.wfi { + log::trace!("thread {} is waiting", next.tid); + let mut wfi = WFI.lock(); + next.next = core::mem::take(&mut *wfi); + wfi.replace(next); + } else { + log::trace!("thread {} yielded", next.tid); + THREAD_QUEUE.lock().push_back(next); + } + } +} + +pub fn yield_now() { + log::trace!("exiting from {} into scheduler", tid()); + let next_rsp = SCHEDULER_THREAD.borrow_mut().rsp; + let mut current = CURRENT_THREAD.borrow_mut(); + let current_rsp = &raw mut current.rsp; + core::mem::drop(current); + unsafe { + cswitch(next_rsp, current_rsp); + } +} + +fn sleep() { + unsafe { + // io::outl(0xf4, 0); + core::arch::asm!("hlt"); + } +} + +fn exit() { + CURRENT_THREAD.borrow_mut().exited = true; + yield_now(); +} + +pub fn wfi() { + CURRENT_THREAD.borrow_mut().wfi = true; + yield_now(); +} + +unsafe extern "C" fn start_thread(f: *mut Box) { + log::trace!("starting thread {}", tid()); + let f = unsafe { f.read() }; + f(); + exit(); +} diff --git a/kernel/src/kthread/kmutex.rs b/kernel/src/kthread/kmutex.rs new file mode 100644 index 00000000..3f87bb65 --- /dev/null +++ b/kernel/src/kthread/kmutex.rs @@ -0,0 +1,95 @@ +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, + sync::atomic::{AtomicBool, Ordering}, +}; + +#[derive(Debug, Default)] +pub struct KMutex { + lock: AtomicBool, + data: UnsafeCell, +} + +#[derive(Debug)] +pub struct KMutexGuard<'a, T: ?Sized> { + lock: &'a KMutex, + data: &'a mut T, +} + +impl KMutex { + pub const fn new(data: T) -> KMutex + where + T: Sized, + { + KMutex { + lock: AtomicBool::new(false), + data: UnsafeCell::new(data), + } + } + + pub fn try_lock(&self) -> Option> { + if self + .lock + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + Some(KMutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + }) + } else { + None + } + } + + pub fn lock(&self) -> KMutexGuard<'_, T> { + loop { + if let Some(guard) = self.try_lock() { + return guard; + } + crate::kthread::yield_now(); + } + } + + pub fn get(&self) -> *mut T { + self.data.get() + } + + pub fn with(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut guard = self.lock(); + f(&mut guard) + } + + pub fn unlock(guard: KMutexGuard<'_, T>) { + KMutexGuard::unlock(guard); + } +} + +impl KMutexGuard<'_, T> { + pub fn unlock(_: Self) {} +} + +impl Drop for KMutexGuard<'_, T> { + fn drop(&mut self) { + self.lock.lock.store(false, Ordering::Release); + } +} + +unsafe impl Send for KMutex {} +unsafe impl Sync for KMutex {} + +impl !Send for KMutexGuard<'_, T> {} + +impl Deref for KMutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.data + } +} + +impl DerefMut for KMutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.data + } +} diff --git a/kernel/src/kvmclock.rs b/kernel/src/kvmclock.rs index 51b888a9..e913baf5 100644 --- a/kernel/src/kvmclock.rs +++ b/kernel/src/kvmclock.rs @@ -108,9 +108,8 @@ fn info_and_tsc_to_duration(info: CpuTimeInfo, tsc: u64) -> Duration { time >> -info.tsc_shift }; let time = time.widening_mul(info.tsc_to_system_mul as u64); - let time = (time.0 >> 32) | (time.1 << 32); - let time = time.wrapping_add(info.system_time); - Duration::from_nanos(time) + let time = time.wrapping_add(info.system_time as u128); + Duration::from_nanos(time as u64) } #[inline] diff --git a/kernel/src/lapic.rs b/kernel/src/lapic.rs index c2b56363..a0ce9fff 100644 --- a/kernel/src/lapic.rs +++ b/kernel/src/lapic.rs @@ -138,7 +138,8 @@ pub unsafe fn init() { lapic.set_spurious_interrupt_vector(0xff); lapic.set_apic_enabled(true); - lapic.set_divide_configuration(TimerDivider::One); + // TODO: why is this crashing? + // lapic.set_divide_configuration(TimerDivider::One); lapic.set_timer( TimerConfig::new() .with_mask(false) @@ -150,7 +151,7 @@ pub unsafe fn init() { // lapic.set_initial_count(0x400000); // 100ms - // lapic.set_initial_count(0x80000); // 10ms + lapic.set_initial_count(0x80000); // 10ms // lapic.set_initial_count(0x10000); // 1ms @@ -158,7 +159,7 @@ pub unsafe fn init() { // lapic.set_initial_count(0x2000); // 100us - lapic.set_initial_count(0x200); // 10us + // lapic.set_initial_count(0x200); // 10us // lapic.set_initial_count(0x40); // 1us diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index eae24096..a6836e30 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,15 +1,15 @@ #![no_main] #![no_std] +#![allow(stable_features, unused_features)] +#![feature(cfg_version)] #![feature(allocator_api)] -#![feature(widening_mul)] +#![cfg_attr(not(version("1.96")), feature(bigint_helper_methods))] +#![cfg_attr(version("1.96"), feature(widening_mul))] #![feature(box_as_ptr)] -#![feature(box_into_inner)] -#![feature(maybe_uninit_array_assume_init)] -#![feature(maybe_uninit_uninit_array_transpose)] #![feature(negative_impls)] #![feature(never_type)] #![feature(ptr_metadata)] -#![feature(slice_ptr_get)] +#![feature(atomic_ptr_null)] #![feature(custom_test_frameworks)] #![test_runner(testing::harness)] #![reexport_test_harness_main = "test_main"] @@ -23,20 +23,18 @@ use common::hypercall; pub use macros::core_local; pub mod allocator; -pub mod aprofile; pub mod cpu; pub mod debugcon; pub mod host; pub mod io; pub mod iprofile; +pub mod kthread; pub mod kvmclock; pub mod page; pub mod paging; pub mod prelude; -pub mod rt; pub mod tsc; pub mod types; -pub mod virtio; pub mod vm; mod gdt; @@ -44,6 +42,7 @@ mod idt; mod interrupts; mod lapic; mod msr; +mod pipe; mod registers; mod rsstart; mod tss; diff --git a/kernel/src/pipe.rs b/kernel/src/pipe.rs new file mode 100644 index 00000000..6528e693 --- /dev/null +++ b/kernel/src/pipe.rs @@ -0,0 +1,105 @@ +use crate::kthread; +use crate::prelude::*; +use common::pipe::Pipe as RawPipe; +pub use common::pipe::Result as PipeResult; +use core::cell::OnceCell; +use core::marker::PhantomData; + +pub static HOST: KMutex> = KMutex::new(OnceCell::new()); + +#[derive(Debug)] +pub struct HostPipe { + inner: RawPipe, +} + +impl HostPipe { + pub fn new(pipe: RawPipe) -> Self { + Self { inner: pipe } + } + + pub fn read(&mut self, bytes: &mut [u8]) -> PipeResult { + while !self.inner.can_read() { + // kthread::wfi(); + kthread::yield_now(); + } + self.inner.read(bytes) + } + + pub fn read_exact(&mut self, mut bytes: &mut [u8]) -> PipeResult<()> { + while !bytes.is_empty() { + match self.read(bytes) { + Ok(n) => { + bytes = &mut bytes[n..]; + } + Err(e) => { + return Err(e); + } + } + } + Ok(()) + } + + pub fn write(&mut self, bytes: &[u8]) -> PipeResult { + while !self.inner.can_write() { + // kthread::wfi(); + kthread::yield_now(); + } + let n = self.inner.write(bytes); + // unsafe { + // crate::io::hypercall0(crate::hypercall::NOTIFY_READ); + // } + n + } + + pub fn write_exact(&mut self, mut bytes: &[u8]) -> PipeResult<()> { + while !bytes.is_empty() { + match self.write(bytes) { + Ok(n) => { + bytes = &bytes[n..]; + } + Err(e) => { + return Err(e); + } + } + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct TypedPipe { + pipe: HostPipe, + _send: PhantomData, + _recv: PhantomData, +} + +impl serde::Deserialize<'a>> TypedPipe { + pub fn new(pipe: HostPipe) -> Self { + Self { + pipe, + _send: PhantomData, + _recv: PhantomData, + } + } + + pub fn request(&mut self, request: &S) -> R { + let bytes = postcard::to_allocvec(request).unwrap(); + let length = bytes.len().to_le_bytes(); + self.pipe.write_exact(&length).unwrap(); + self.pipe.write_exact(&bytes).unwrap(); + let mut length = [0; 8]; + self.pipe.read_exact(&mut length).unwrap(); + let length = usize::from_le_bytes(length); + let mut bytes = vec![0; length]; + self.pipe.read_exact(&mut bytes).unwrap(); + postcard::from_bytes(&bytes).unwrap() + } +} + +pub type ControlPipe = + TypedPipe; +pub type FilePipe = TypedPipe; +pub type ListenerPipe = + TypedPipe; +pub type StreamPipe = + TypedPipe; diff --git a/kernel/src/prelude.rs b/kernel/src/prelude.rs index c15fc338..59cb0828 100644 --- a/kernel/src/prelude.rs +++ b/kernel/src/prelude.rs @@ -6,6 +6,7 @@ pub(crate) use crate::{ pub use alloc::{ borrow::ToOwned, boxed::Box, + format, string::{String, ToString}, sync::{Arc, Weak}, vec, @@ -14,6 +15,7 @@ pub use alloc::{ pub use crate::{ cpu::{Cpu, Register, RegisterFile, CPU}, + kthread::KMutex, page::{CowPage, Page1GB, Page2MB, Page4KB, SharedPage, UniquePage}, paging::{ AugmentedEntry, AugmentedPageTable, AugmentedUnmappedPage, HardwarePage, HardwarePageTable, @@ -21,7 +23,7 @@ pub use crate::{ PageTable256TB, PageTable256TBEntry, PageTable2MB, PageTable2MBEntry, PageTable512GB, PageTable512GBEntry, }, - shutdown, + print, println, shutdown, types::{Arca, Blob, Entry, Function, Null, Page, Runtime, Table, Tuple, Value, Word}, }; diff --git a/kernel/src/rsstart.rs b/kernel/src/rsstart.rs index 55e49f8d..0a41c269 100644 --- a/kernel/src/rsstart.rs +++ b/kernel/src/rsstart.rs @@ -1,7 +1,7 @@ use core::{ arch::asm, - ptr::{addr_of, addr_of_mut, NonNull}, - sync::atomic::{AtomicBool, Ordering}, + ptr::{addr_of, addr_of_mut}, + sync::atomic::Ordering, }; use alloc::boxed::Box; @@ -18,10 +18,10 @@ use crate::{ vm, }; -use common::{buddy::BuddyAllocatorRawData, vhost::VSockMetadata, BuddyAllocator}; +use common::{buddy::BuddyAllocatorRawData, BuddyAllocator}; extern "C" { - fn kmain(argc: usize, argv: *const usize); + fn kmain(); fn set_gdt(gdtr: *const GdtDescriptor); static mut _sstack: u8; static mut _sbss: u8; @@ -57,14 +57,14 @@ pub(crate) static KERNEL_MAPPINGS: LazyLock ! { let mut id = 0; core::arch::x86_64::__rdtscp(&mut id); @@ -112,7 +112,6 @@ unsafe extern "C" fn _start( crate::tsc::init(); crate::kvmclock::init(); crate::iprofile::init(); - crate::aprofile::init(); let gdtr = GdtDescriptor::new(&**crate::gdt::GDT); @@ -138,29 +137,27 @@ unsafe extern "C" fn _start( set_pt(vm::ka2pa(Box::leak(pml4))); core::arch::asm!("sti"); + crate::kthread::init(); if id == 0 { - let argv: *mut usize = BuddyAllocator.from_offset(argv_offset); - let argv = NonNull::new(argv).unwrap(); - - let vsock_info = argv.as_ptr().read(); - let vsock_info: *const VSockMetadata = BuddyAllocator.from_offset(vsock_info); - let vsock = &crate::virtio::vsock::DRIVER; - assert!(vsock - .set(crate::virtio::vsock::Driver::new(vsock_info.read())) - .is_ok()); - let argv = argv.add(1); - let argc = argc - 1; + use crate::pipe::{ControlPipe, HostPipe}; + use common::pipe::{Pipe as RawPipe, Reader, Writer}; + let rxp: *const u8 = BuddyAllocator.from_offset(rxp); + let txp: *const u8 = BuddyAllocator.from_offset(txp); + let rx = Arc::from_raw_in(core::ptr::from_raw_parts(rxp, rxn), BuddyAllocator); + let rx = Reader::from_inner(rx); + let tx = Arc::from_raw_in(core::ptr::from_raw_parts(txp, txn), BuddyAllocator); + let tx = Writer::from_inner(tx); + let pipe = RawPipe::from_inner(rx, tx); + let pipe = HostPipe::new(pipe); + let host = crate::pipe::HOST.lock(); + host.set(ControlPipe::new(pipe)).unwrap(); BuddyAllocator.set_caching(true); - kmain(argc, argv.as_ptr()); - START_RUNTIME.store(true, Ordering::Release); - } else { - while !START_RUNTIME.load(Ordering::Acquire) { - core::hint::spin_loop(); - } + crate::kthread::spawn(|| { + kmain(); + }); } - crate::rt::run(); - + crate::kthread::run_scheduler(); crate::shutdown(); } diff --git a/kernel/src/rt.rs b/kernel/src/rt.rs deleted file mode 100644 index d548742d..00000000 --- a/kernel/src/rt.rs +++ /dev/null @@ -1,386 +0,0 @@ -use core::{ - future::Future, - pin::Pin, - sync::atomic::{AtomicBool, AtomicUsize, Ordering}, - task::{Context, Poll, Waker}, - time::Duration, -}; -pub use macros::profile; - -use alloc::{boxed::Box, sync::Arc, task::Wake}; -use common::util::{ - channel::{Receiver, Sender}, - concurrent_trie::Trie, - rwlock::RwLock, -}; - -use crate::{debugcon::CONSOLE, interrupts::INTERRUPTED, io, kvmclock, prelude::*}; - -pub static EXECUTOR: LazyLock = LazyLock::new(Executor::new); - -pub static SHUTDOWN: AtomicBool = AtomicBool::new(false); - -pub struct Executor { - todo_rx: Receiver>, - todo_tx: Sender>, - sleeping: Trie<2, Waker>, - wfi_rx: Receiver, - wfi_tx: Sender, - active: AtomicUsize, -} - -type BoxFuture = Pin + Send>>; - -#[core_local] -static LAST_TIME: SpinLock = SpinLock::new(0); - -static TIME_SLEEPING: AtomicUsize = AtomicUsize::new(0); -static TIME_WORKING: AtomicUsize = AtomicUsize::new(0); -static TIME_SCHEDULING: AtomicUsize = AtomicUsize::new(0); - -enum Entry { - Nothing, - Finished(Option), - Waiting(Waker), -} - -impl Executor { - fn new() -> Executor { - let (todo_tx, todo_rx) = channel::bounded(4096); - let (wfi_tx, wfi_rx) = channel::bounded(4096); - Executor { - todo_rx, - todo_tx, - sleeping: Trie::default(), - wfi_rx, - wfi_tx, - active: AtomicUsize::new(0), - } - } - - #[inline(never)] - async fn resolve(future: F, entry: Arc>>) - where - F: Future + Send, - T: Send, - { - let result = future.await; - let mut entry = entry.write(); - let mut replacement = Entry::Finished(Some(result)); - core::mem::swap(&mut *entry, &mut replacement); - match replacement { - Entry::Nothing => {} - Entry::Finished(_) => unreachable!(), - Entry::Waiting(waker) => { - waker.wake(); - } - } - } - - pub fn spawn(&self, future: F) -> JoinHandle - where - F: Future + Send + 'static, - T: Send + 'static, - { - let entry = Arc::new(RwLock::new(Entry::Nothing)); - let future = Self::resolve(future, entry.clone()); - let task = Arc::new(Task { - future: SpinLock::new(Box::pin(future)), - todo: self.todo_tx.clone(), - }); - let handle = JoinHandle { entry }; - self.active.fetch_add(1, Ordering::AcqRel); - self.todo_tx.try_send(task).unwrap(); - handle - } - - pub fn spawn_blocking<'a, F, T>(&self, future: F) -> T - where - F: Future + Send + 'a, - T: Send + 'a, - { - let entry = Arc::new(RwLock::new(Entry::Nothing)); - let future = Self::resolve(future, entry.clone()); - unsafe { - let pin: Pin + Send + 'a>> = Box::pin(future); - let pin: BoxFuture = core::mem::transmute(pin); - let task = Arc::new(Task { - future: SpinLock::new(pin), - todo: self.todo_tx.clone(), - }); - let handle = JoinHandle { entry }; - self.active.fetch_add(1, Ordering::AcqRel); - self.todo_tx.try_send(task).unwrap(); - handle.join() - } - } - - #[inline(never)] - fn wake_sleeping(&self) -> bool { - let Ok(Some(key)) = self.sleeping.try_first_key() else { - // No first key found. If we missed one because of a race it's not the end of the world anyway. - return false; - }; - let now = kvmclock::time_since_boot(); - let timestamp = now.as_nanos() as u64; - if key >= timestamp { - // The first task is still in the future. - return false; - } - let Some(value) = self.sleeping.remove(key) else { - // We saw a first key, but someone else grabbed it first. They can deal with this. - return false; - }; - value.wake(); - true - } - - fn diff(&self) -> usize { - let mut time = LAST_TIME.lock(); - let now = kvmclock::time_since_boot().as_nanos(); - let diff = now.wrapping_sub(*time); - *time = now; - diff as usize - } - - #[inline(never)] - fn run_pending(&self) -> bool { - let Ok(task) = self.todo_rx.try_recv() else { - return false; - }; - TIME_SCHEDULING.fetch_add(self.diff(), Ordering::SeqCst); - let result = task.poll(); - TIME_WORKING.fetch_add(self.diff(), Ordering::SeqCst); - if result.is_ready() { - self.active.fetch_sub(1, Ordering::AcqRel); - } - true - } - - #[inline(never)] - fn sleep(&self) { - if !INTERRUPTED.load(Ordering::Relaxed) { - TIME_SCHEDULING.fetch_add(self.diff(), Ordering::SeqCst); - unsafe { - io::outl(0xf4, 0); - core::arch::asm!("hlt"); - } - TIME_SLEEPING.fetch_add(self.diff(), Ordering::SeqCst); - } - } - - #[inline(never)] - fn wake_interrupted(&self) -> bool { - let mut anything = false; - if INTERRUPTED - .compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - while let Ok(waker) = self.wfi_rx.try_recv() { - anything = true; - waker.wake(); - } - } - anything - } - - pub fn tick(&self) { - crate::interrupts::must_be_enabled(); - let mut anything = false; - anything |= self.wake_interrupted(); - anything |= self.wake_sleeping(); - anything |= self.run_pending(); - if !anything { - self.sleep(); - } - } - - pub fn run(&self) { - self.diff(); - while self.active.load(Ordering::Acquire) != 0 { - self.tick(); - } - } -} - -pub struct JoinHandle { - entry: Arc>>, -} - -impl JoinHandle { - fn join(self) -> T { - loop { - if let Some(mut entry) = self.entry.try_write() { - if let Entry::Finished(value) = &mut *entry { - return value.take().unwrap(); - } - } - EXECUTOR.tick(); - } - } -} - -impl Future for JoinHandle { - type Output = T; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut result = self.entry.write(); - match &mut *result { - Entry::Nothing | Entry::Waiting(_) => { - *result = Entry::Waiting(cx.waker().clone()); - Poll::Pending - } - Entry::Finished(value) => Poll::Ready(value.take().unwrap()), - } - } -} - -pub fn spawn(future: F) -> JoinHandle -where - F: Future + Send + 'static, - T: Send + 'static, -{ - EXECUTOR.spawn(future) -} - -pub fn spawn_blocking(future: F) -> T -where - F: Future + Send, - T: Send, -{ - EXECUTOR.spawn_blocking(future) -} - -pub fn run() { - EXECUTOR.run(); -} - -struct Task { - future: SpinLock, - todo: Sender>, -} - -impl Task { - fn poll(self: Arc) -> Poll<()> { - let waker = self.clone().into(); - let mut cx = Context::from_waker(&waker); - let mut task_future = self.future.lock(); - task_future.as_mut().poll(&mut cx) - } -} - -impl Wake for Task { - fn wake(self: Arc) { - let todo = self.todo.clone(); - todo.try_send(self).unwrap(); - } -} - -struct Yield(AtomicBool); - -impl Future for Yield { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let done = self.0.load(Ordering::Acquire); - match done { - true => Poll::Ready(()), - false => { - self.0.store(true, Ordering::Release); - cx.waker().wake_by_ref(); - Poll::Pending - } - } - } -} - -pub fn yield_now() -> impl Future { - Yield(AtomicBool::new(false)) -} - -struct Delay(Duration); - -impl Future for Delay { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let now = kvmclock::time_since_boot(); - if now >= self.0 { - return Poll::Ready(()); - } - let waker = cx.waker().clone(); - EXECUTOR.sleeping.insert(self.0.as_nanos() as u64, waker); - Poll::Pending - } -} - -pub fn delay_for(duration: Duration) -> impl Future { - delay_until(kvmclock::time_since_boot() + duration) -} - -pub fn delay_until(time: Duration) -> impl Future { - Delay(time) -} - -pub fn reset_stats() { - TIME_SLEEPING.store(0, Ordering::SeqCst); - TIME_WORKING.store(0, Ordering::SeqCst); - TIME_SCHEDULING.store(0, Ordering::SeqCst); -} - -pub fn profile() { - let scheduling = TIME_SCHEDULING.load(Ordering::SeqCst) as f64; - let working = TIME_WORKING.load(Ordering::SeqCst) as f64; - let sleeping = TIME_SLEEPING.load(Ordering::SeqCst) as f64; - let total = scheduling + working + sleeping; - use core::fmt::Write as _; - let mut console = CONSOLE.lock(); - writeln!(console, "***** RUNTIME *****").unwrap(); - writeln!( - console, - "time spent sleeping: {sleeping:12} ({:3.2}%)", - sleeping * 100. / total - ) - .unwrap(); - writeln!( - console, - "time spent working: {working:12} ({:3.2}%)", - working * 100. / total - ) - .unwrap(); - writeln!( - console, - "time spent scheduling: {scheduling:12} ({:3.2}%)", - scheduling * 100. / total - ) - .unwrap(); - writeln!(console, "*******************").unwrap(); -} - -struct WaitForInterrupt { - done: AtomicBool, - wfi: Sender, -} - -impl Future for WaitForInterrupt { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let done = self.done.load(Ordering::Acquire); - match done { - true => Poll::Ready(()), - false => { - self.done.store(true, Ordering::Release); - self.wfi.try_send(cx.waker().clone()).unwrap(); - Poll::Pending - } - } - } -} - -pub fn wfi() -> impl Future { - WaitForInterrupt { - done: AtomicBool::new(false), - wfi: EXECUTOR.wfi_tx.clone(), - } -} diff --git a/kernel/src/testing.rs b/kernel/src/testing.rs index cf133fd1..58b0df14 100644 --- a/kernel/src/testing.rs +++ b/kernel/src/testing.rs @@ -8,6 +8,6 @@ pub(crate) fn harness(tests: &[&dyn Fn()]) { } #[kmain] -async fn main(_: &[usize]) { +fn main() { super::test_main(); } diff --git a/kernel/src/tests/test_serde.rs b/kernel/src/tests/test_serde.rs index b9e038af..102de141 100644 --- a/kernel/src/tests/test_serde.rs +++ b/kernel/src/tests/test_serde.rs @@ -1,112 +1,125 @@ -use crate::prelude::*; -extern crate alloc; +// Serialization round-trip tests using postcard. +// Runs with: cargo test -p kernel --target=x86_64-unknown-none -#[test] -fn test_serde_null() { - let null = Value::Null(Null::new()); - let bytes_vec = postcard::to_allocvec(&null).unwrap(); - let deserialized_null: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_null, null); -} +#[cfg(test)] +mod tests { + extern crate alloc; -#[test] -fn test_serde_word() { - let word = Value::Word(1.into()); - let bytes_vec = postcard::to_allocvec(&word).unwrap(); - let deserialized_word: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_word, word); -} + use crate::prelude::*; -#[test] -fn test_serde_blob() { - let blob = Value::Blob("hello, world!".into()); - let bytes_vec = postcard::to_allocvec(&blob).unwrap(); - let deserialized_blob: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_blob, blob); -} + /// Verifies Null serializes and deserializes back to an equal value. + #[test] + fn test_serde_null() { + let null = Value::Null(Null::new()); + let bytes_vec = postcard::to_allocvec(&null).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, null); + } -#[test] -fn test_serde_tuple() { - let tuple = Value::Tuple((1, 2, 3).into()); - let bytes_vec = postcard::to_allocvec(&tuple).unwrap(); - let deserialized_tuple: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_tuple, tuple); -} + /// Verifies Word serializes and deserializes back to an equal value. + #[test] + fn test_serde_word() { + let word = Value::Word(1.into()); + let bytes_vec = postcard::to_allocvec(&word).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, word); + } -#[test] -fn test_serde_page() { - let page = Value::Page(Page::new(1)); - let bytes_vec = postcard::to_allocvec(&page).unwrap(); - let deserialized_page: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_page, page); -} + /// Verifies Blob serializes and deserializes back to an equal value. + #[test] + fn test_serde_blob() { + let blob = Value::Blob("hello, world!".into()); + let bytes_vec = postcard::to_allocvec(&blob).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, blob); + } -#[test] -fn test_serde_table() { - let table = Value::Table(Table::new(1)); - let bytes_vec = postcard::to_allocvec(&table).unwrap(); - let deserialized_table: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_table, table); -} + /// Verifies Tuple serializes and deserializes back to an equal value. + #[test] + fn test_serde_tuple() { + let tuple = Value::Tuple((1, 2, 3).into()); + let bytes_vec = postcard::to_allocvec(&tuple).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, tuple); + } -// #[test] -// fn test_serde_function() { -// let arca = Arca::new(); -// let inner_func: arca::Function = Function::from(arca); -// let func = Value::Function(inner_func); -// let bytes_vec = postcard::to_allocvec(&func).unwrap(); -// let deserialized_func: Value = postcard::from_bytes(&bytes_vec).unwrap(); -// assert_eq!(deserialized_func, func); -// } + /// Verifies Page serializes and deserializes back to an equal value. + #[test] + fn test_serde_page() { + let page = Value::Page(Page::new(1)); + let bytes_vec = postcard::to_allocvec(&page).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, page); + } -#[test] -fn test_serde_ropage() { - let ropage = Entry::ROPage(Page::new(1)); - let bytes_vec = postcard::to_allocvec(&ropage).unwrap(); - let deserialized_ropage: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_ropage, ropage); -} + /// Verifies Table serializes and deserializes back to an equal value. + #[test] + fn test_serde_table() { + let table = Value::Table(Table::new(1)); + let bytes_vec = postcard::to_allocvec(&table).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, table); + } -#[test] -fn test_serde_rwpage() { - let rwpage = Entry::RWPage(Page::new(1)); - let bytes_vec = postcard::to_allocvec(&rwpage).unwrap(); - let deserialized_rwpage: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_rwpage, rwpage); -} + /// Verifies a read-only page Entry round-trips through serde. + #[test] + fn test_serde_ropage() { + let ropage = Entry::ROPage(Page::new(1)); + let bytes_vec = postcard::to_allocvec(&ropage).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, ropage); + } -#[test] -fn test_serde_rotable() { - let rotable = Entry::ROTable(Table::new(1)); - let bytes_vec = postcard::to_allocvec(&rotable).unwrap(); - let deserialized_rotable: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_rotable, rotable); -} + /// Verifies a read-write page Entry round-trips through serde. + #[test] + fn test_serde_rwpage() { + let rwpage = Entry::RWPage(Page::new(1)); + let bytes_vec = postcard::to_allocvec(&rwpage).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, rwpage); + } -#[test] -fn test_serde_rwtable() { - let rwtable = Entry::RWTable(Table::new(1)); - let bytes_vec = postcard::to_allocvec(&rwtable).unwrap(); - let deserialized_rwtable: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_rwtable, rwtable); -} + /// Verifies a read-only table Entry round-trips through serde. + #[test] + fn test_serde_rotable() { + let rotable = Entry::ROTable(Table::new(1)); + let bytes_vec = postcard::to_allocvec(&rotable).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, rotable); + } -#[test] -fn test_value_error() { - let unknown_variant = [7, 0]; - let deserialized: Result = postcard::from_bytes(&unknown_variant); - let deserialized_error = deserialized.expect_err("should have been err"); - let error = - serde::de::Error::unknown_variant("7", &["Null", "Word", "Blob", "Tuple", "Page", "Table"]); - assert_eq!(deserialized_error, error); -} + /// Verifies a read-write table Entry round-trips through serde. + #[test] + fn test_serde_rwtable() { + let rwtable = Entry::RWTable(Table::new(1)); + let bytes_vec = postcard::to_allocvec(&rwtable).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, rwtable); + } + + /// Ensures deserializing an unknown Value variant produces the expected error. + #[test] + fn test_value_unknown_variant_error() { + let unknown_variant = [7, 0]; + let deserialized: Result = postcard::from_bytes(&unknown_variant); + let deserialized_error = deserialized.expect_err("should have been err"); + let error = serde::de::Error::unknown_variant( + "7", + &["Null", "Word", "Blob", "Tuple", "Page", "Table"], + ); + assert_eq!(deserialized_error, error); + } -#[test] -fn test_entry_error() { - let unknown_variant = [5, 0]; - let deserialized: Result = postcard::from_bytes(&unknown_variant); - let deserialized_error = deserialized.expect_err("should have been err"); - let error = - serde::de::Error::unknown_variant("5", &["Null", "ROPage", "RWPage", "ROTable", "RWTable"]); - assert_eq!(deserialized_error, error); + /// Ensures deserializing an unknown Entry variant produces the expected error. + #[test] + fn test_entry_unknown_variant_error() { + let unknown_variant = [5, 0]; + let deserialized: Result = postcard::from_bytes(&unknown_variant); + let deserialized_error = deserialized.expect_err("should have been err"); + let error = serde::de::Error::unknown_variant( + "5", + &["Null", "ROPage", "RWPage", "ROTable", "RWTable"], + ); + assert_eq!(deserialized_error, error); + } } diff --git a/kernel/src/types/arca.rs b/kernel/src/types/arca.rs index 534984f3..a033ec65 100644 --- a/kernel/src/types/arca.rs +++ b/kernel/src/types/arca.rs @@ -144,6 +144,34 @@ impl<'a> LoadedArca<'a> { unsafe { self.cpu.run(&mut self.register_file) } } + pub fn single_step(&mut self) -> ExitReason { + self.register_file[Register::RFLAGS] |= 0x100; + let result = self.run(); + self.register_file[Register::RFLAGS] &= !0x100; + result + } + + pub fn single_step_with(&mut self, mut f: impl FnMut(&mut Self)) -> ExitReason { + loop { + let step = self.single_step(); + if step == ExitReason::Debug { + f(self); + } else { + return step; + } + } + } + + pub fn trace(&mut self) -> ExitReason { + self.single_step_with(|this| { + log::info!( + "@{:#x} -> {:#x?}", + this.register_file[Register::RIP], + this.register_file + ); + }) + } + pub fn registers(&self) -> &RegisterFile { &self.register_file } diff --git a/kernel/src/types/blob.rs b/kernel/src/types/blob.rs index 5b7780e0..d29c5024 100644 --- a/kernel/src/types/blob.rs +++ b/kernel/src/types/blob.rs @@ -87,3 +87,40 @@ impl From<&str> for Blob { Blob::from(value.to_string()) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies len() and into_inner() return correct content. + #[test] + fn test_len_and_into_inner() { + let blob = Blob::new(b"hello".to_vec()); + assert_eq!(blob.len(), 5); + assert_eq!(&*blob.into_inner(), b"hello"); + } + + /// Verifies DerefMut allows in-place byte mutation. + #[test] + fn test_mutation() { + let mut blob = Blob::new(b"hello".to_vec()); + blob[0] = b'j'; + assert_eq!(&*blob.into_inner(), b"jello"); + } + + /// Ensures invalid UTF-8 bytes are preserved as raw data. + #[test] + fn test_invalid_utf8_preserved() { + let bytes = vec![0xffu8, 0xfeu8, 0xfdu8]; + let blob = Blob::new(bytes.clone()); + assert_eq!(&*blob.into_inner(), &bytes[..]); + } + + /// Verifies From<&str> constructs a blob with matching content. + #[test] + fn test_from_str() { + let blob = Blob::from("test"); + assert_eq!(blob.len(), 4); + assert_eq!(&*blob, b"test"); + } +} diff --git a/kernel/src/types/function.rs b/kernel/src/types/function.rs index 29d539ce..94c1aa24 100644 --- a/kernel/src/types/function.rs +++ b/kernel/src/types/function.rs @@ -166,3 +166,51 @@ impl Function { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies symbolic function parsing and read round-trip. + #[test] + fn test_symbolic_parse_and_read() { + let args = Tuple::from((1u64, "two")); + let value = Value::Tuple(Tuple::from(( + Blob::from("Symbolic"), + Value::Word(Word::new(5)), + Value::Tuple(args), + ))); + let func = Function::new(value.clone()).expect("symbolic parse failed"); + assert!(!func.is_arcane()); + assert_eq!(func.read(), value); + } + + /// Ensures unrecognized function tags are rejected. + #[test] + fn test_invalid_tag_rejected() { + let value = Value::Tuple(Tuple::from((Blob::from("Other"), Value::Null(Null::new())))); + assert!(Function::new(value).is_none()); + } + + /// Verifies arcane function parsing accepts a valid register/memory layout. + #[test] + fn test_arcane_parse_valid_layout() { + let mut registers = Tuple::new(18); + for i in 0..18 { + registers.set(i, Value::Null(Null::new())); + } + let mut data = Tuple::new(4); + data.set(0, Value::Tuple(registers)); + data.set(1, Value::Table(Table::new(1))); + data.set(2, Value::Tuple(Tuple::new(0))); + data.set(3, Value::Tuple(Tuple::new(0))); + + let value = Value::Tuple(Tuple::from(( + Blob::from("Arcane"), + Value::Tuple(data), + Value::Tuple(Tuple::new(0)), + ))); + let func = Function::new(value).expect("arcane parse failed"); + assert!(func.is_arcane()); + } +} diff --git a/kernel/src/types/function/syscall.rs b/kernel/src/types/function/syscall.rs index 58bfb293..baa2370c 100644 --- a/kernel/src/types/function/syscall.rs +++ b/kernel/src/types/function/syscall.rs @@ -76,7 +76,7 @@ pub fn handle_syscall(arca: &mut LoadedArca, argv: &mut VecDeque) -> Cont _ => { log::error!("invalid syscall {num}"); - panic!("invalid syscall @ {:#x}", regs[Register::RIP]); + panic!("invalid syscall @ {:#x} ({args:#x?})", regs[Register::RIP]); // Err(SyscallError::BadSyscall) } }; @@ -486,7 +486,7 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { p += Page2MB::SIZE; continue; } - if p.is_multiple_of(Page4KB::SIZE) && len >= Page4KB::SIZE { + if p.is_multiple_of(Page4KB::SIZE) { if mode == arcane::__MODE_none { let entry = Entry::Null(Page4KB::SIZE); arca.cpu().map(p, entry).unwrap(); @@ -498,7 +498,7 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { p += Page4KB::SIZE; continue; } - panic!("unaligned mmap or bad size"); + panic!("unaligned mmap or bad size: {p:#x}+{len:#x}"); } Ok(p - addr) } diff --git a/kernel/src/types/null.rs b/kernel/src/types/null.rs index 79cfa926..ed841266 100644 --- a/kernel/src/types/null.rs +++ b/kernel/src/types/null.rs @@ -12,3 +12,14 @@ impl Default for Null { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Ensures Null::new() and Null::default() produce identical values. + #[test] + fn test_new_equals_default() { + assert_eq!(Null::new(), Null::default()); + } +} diff --git a/kernel/src/types/page.rs b/kernel/src/types/page.rs index a666c7e1..1b77b737 100644 --- a/kernel/src/types/page.rs +++ b/kernel/src/types/page.rs @@ -120,3 +120,38 @@ impl DerefMut for Page { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies page size selection at tier boundaries (4KB, 2MB, 1GB). + #[test] + fn test_size_tiers() { + let small = Page::new(1); + assert_eq!(small.size(), 1 << 12); + + let mid = Page::new((1 << 12) + 1); + assert_eq!(mid.size(), 1 << 21); + + let large = Page::new((1 << 21) + 1); + assert_eq!(large.size(), 1 << 30); + } + + /// Verifies DerefMut write and Deref read on page bytes. + #[test] + fn test_write_and_read_back() { + let mut page = Page::new(1); + page[0] = 7; + assert_eq!(page[0], 7); + } + + /// Ensures shared() preserves written content. + #[test] + fn test_shared_preserves_content() { + let mut page = Page::new(1); + page[0] = 42; + let shared = page.shared(); + assert_eq!(shared[0], 42); + } +} diff --git a/kernel/src/types/runtime.rs b/kernel/src/types/runtime.rs index 691a2979..6d197837 100644 --- a/kernel/src/types/runtime.rs +++ b/kernel/src/types/runtime.rs @@ -68,7 +68,6 @@ impl arca::Runtime for Runtime { } fn read_blob(blob: &arca::Blob, offset: usize, buf: &mut [u8]) -> usize { - log::error!("read_blob: offset={}, buf_len={}", offset, buf.len()); let len = core::cmp::min(buf.len(), blob.len() - offset); buf[..len].copy_from_slice(&blob[offset..offset + len]); len diff --git a/kernel/src/types/table.rs b/kernel/src/types/table.rs index 2108dc7f..b30f8b21 100644 --- a/kernel/src/types/table.rs +++ b/kernel/src/types/table.rs @@ -29,7 +29,7 @@ impl Table { } else if size <= Table512GB::SIZE { Table::Table512GB(Default::default()) } else { - panic!(); + panic!("attempted to create table with size {size}"); } } } @@ -206,3 +206,36 @@ impl TryFrom for CowPage { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies table size selection at tier boundaries (2MB, 1GB). + #[test] + fn test_size_tiers() { + let small = Table::new(1); + assert_eq!(small.size(), 1 << 21); + + let large = Table::new((1 << 21) + 1); + assert_eq!(large.size(), 1 << 30); + } + + /// Ensures empty table slots return the correct default Null entry. + #[test] + fn test_get_returns_default_null() { + let table = Table::new(1); + let entry = table.get(10); + assert_eq!(entry, arca::Entry::Null(1 << 12)); + } + + /// Verifies set replaces the default entry and get retrieves it back. + #[test] + fn test_set_and_get_roundtrip() { + let mut table = Table::new(1); + let entry = arca::Entry::RWPage(arca::Page::from_inner(Page::new(1))); + let old = table.set(0, entry.clone()).unwrap(); + assert_eq!(old, arca::Entry::Null(1 << 12)); + assert_eq!(table.get(0), entry); + } +} diff --git a/kernel/src/types/tuple.rs b/kernel/src/types/tuple.rs index 8be33073..32573717 100644 --- a/kernel/src/types/tuple.rs +++ b/kernel/src/types/tuple.rs @@ -42,3 +42,28 @@ impl FromIterator for Tuple { Tuple::new(v) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies new_with_len creates a tuple filled with Null values. + #[test] + fn test_new_with_len_defaults_to_null() { + let tuple = Tuple::new_with_len(2); + assert_eq!(tuple.len(), 2); + assert!(matches!(tuple[0], Value::Null(_))); + assert!(matches!(tuple[1], Value::Null(_))); + } + + /// Verifies FromIterator collects values into a correctly sized tuple. + #[test] + fn test_from_iter() { + let values: alloc::vec::Vec = + alloc::vec![Value::Word(1u64.into()), Value::Blob("x".into()),]; + let tuple: Tuple = values.clone().into_iter().collect(); + assert_eq!(tuple.len(), 2); + assert_eq!(tuple[0], values[0]); + assert_eq!(tuple[1], values[1]); + } +} diff --git a/kernel/src/types/value.rs b/kernel/src/types/value.rs index b4f9f3ec..7fbd2c4b 100644 --- a/kernel/src/types/value.rs +++ b/kernel/src/types/value.rs @@ -62,3 +62,33 @@ macro_rules! impl_value_from { foreach_type_item! {impl_tryfrom_value} foreach_type_item! {impl_value_from} + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies Word -> Value -> Word conversion round-trips correctly. + #[test] + fn test_word_roundtrip() { + let word = Word::new(99); + let value: Value = word.clone().into(); + let roundtrip = Word::try_from(value).unwrap(); + assert_eq!(roundtrip, word); + } + + /// Verifies Blob -> Value -> Blob conversion round-trips correctly. + #[test] + fn test_blob_roundtrip() { + let blob = Blob::new(b"data".to_vec()); + let value: Value = blob.clone().into(); + let roundtrip = Blob::try_from(value).unwrap(); + assert_eq!(roundtrip, blob); + } + + /// Ensures TryFrom fails when converting to the wrong variant type. + #[test] + fn test_mismatched_conversion_fails() { + let value: Value = Word::new(1).into(); + assert!(Blob::try_from(value).is_err()); + } +} diff --git a/kernel/src/types/word.rs b/kernel/src/types/word.rs index 749877fe..291d4b5e 100644 --- a/kernel/src/types/word.rs +++ b/kernel/src/types/word.rs @@ -36,3 +36,31 @@ impl AsMut for Word { &mut self.value } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies Word::read returns the value passed to new. + #[test] + fn test_read() { + let word = Word::new(123); + assert_eq!(word.read(), 123); + } + + /// Verifies From and Into round-trip correctly. + #[test] + fn test_from_u64_roundtrip() { + let word = Word::from(0xdeadbeef_u64); + assert_eq!(u64::from(word), 0xdeadbeef); + } + + /// Verifies AsRef and AsMut provide access to the inner value. + #[test] + fn test_as_ref_as_mut() { + let mut word = Word::new(42); + assert_eq!(*word.as_ref(), 42); + *word.as_mut() = 99; + assert_eq!(word.read(), 99); + } +} diff --git a/kernel/src/virtio.rs b/kernel/src/virtio.rs deleted file mode 100644 index cdf3be49..00000000 --- a/kernel/src/virtio.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod virtqueue; -pub mod vsock; diff --git a/kernel/src/virtio/virtqueue.rs b/kernel/src/virtio/virtqueue.rs deleted file mode 100644 index 9d0e0049..00000000 --- a/kernel/src/virtio/virtqueue.rs +++ /dev/null @@ -1,273 +0,0 @@ -use core::{ - future::Future, - task::{Poll, Waker}, -}; - -use crate::{io, prelude::*}; -use common::util::rwlock::RwLock; - -mod desc; -mod idx; -mod vring; - -use common::vhost::VirtQueueMetadata; -use desc::*; -use idx::*; -use vring::*; - -#[repr(C, packed)] -#[derive(Debug, Copy, Clone)] -pub struct UsedElement { - id: u32, - len: u32, -} - -#[derive(Default)] -pub struct NotificationChannel { - waker: Option, - data: Option, -} - -pub struct Notification { - channel: Arc>, -} - -impl Future for Notification { - type Output = usize; - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll { - let mut channel = self.channel.lock(); - if let Some(value) = channel.data.take() { - Poll::Ready(value) - } else { - channel.waker = Some(cx.waker().clone()); - Poll::Pending - } - } -} - -pub struct VirtQueue { - _name: &'static str, - notifications: Box<[Arc>]>, - desc: RwLock, - used: RwLock>, - avail: RwLock>, -} - -impl VirtQueue { - /// # Safety - /// - /// `info` must describe a valid VirtQueue with attached device, where the negotiated features - /// match those available in this driver. The VirtQueue must not be attached to any other - /// driver. - pub unsafe fn new(name: &'static str, info: VirtQueueMetadata) -> Self { - let desc = core::ptr::from_raw_parts_mut( - vm::pa2ka::<()>(BuddyAllocator.from_offset::<()>(info.desc) as usize), - info.descriptors, - ); - let used = core::ptr::from_raw_parts_mut( - vm::pa2ka::<()>(BuddyAllocator.from_offset::<()>(info.used) as usize), - info.descriptors, - ); - let avail = core::ptr::from_raw_parts_mut( - vm::pa2ka::<()>(BuddyAllocator.from_offset::<()>(info.avail) as usize), - info.descriptors, - ); - let mut notifications = vec![]; - notifications.resize_with(info.descriptors, Default::default); - VirtQueue { - _name: name, - notifications: notifications.into(), - desc: RwLock::new(DescTable::new(name, desc)), - used: RwLock::new(DeviceRing::new(name, used)), - avail: RwLock::new(DriverRing::new(name, avail)), - } - } -} - -#[derive(Debug)] -pub struct BufferChain<'a> { - car: Buffer<'a>, - cdr: Option<&'a BufferChain<'a>>, -} - -impl<'a> BufferChain<'a> { - pub fn new(x: &'a [u8]) -> Self { - BufferChain { - car: Buffer::Immutable(x), - cdr: None, - } - } - - pub fn new_mut(x: &'a mut [u8]) -> Self { - BufferChain { - car: Buffer::Mutable(x), - cdr: None, - } - } - - pub fn cons<'b>(x: &'a [u8], other: Option<&'b BufferChain<'b>>) -> Self - where - 'b: 'a, - { - BufferChain { - car: Buffer::Immutable(x), - cdr: other, - } - } - - pub fn cons_mut<'b>(x: &'a mut [u8], other: Option<&'b BufferChain<'b>>) -> Self - where - 'b: 'a, - { - BufferChain { - car: Buffer::Mutable(x), - cdr: other, - } - } - - pub fn len(&self) -> usize { - 1 + match self.cdr { - Some(x) => x.len(), - None => 0, - } - } - - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub fn size(&self) -> usize { - self.car.len() - + match self.cdr { - Some(x) => x.size(), - None => 0, - } - } -} - -#[derive(Debug)] -pub enum Buffer<'a> { - Immutable(&'a [u8]), - Mutable(&'a mut [u8]), -} - -impl Buffer<'_> { - pub fn len(&self) -> usize { - match self { - Buffer::Immutable(items) => items.len(), - Buffer::Mutable(items) => items.len(), - } - } - - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl VirtQueue { - async fn load(&self, bufs: &BufferChain<'_>) -> DescriptorIndex { - unsafe { - let mut buf = Box::new_uninit_slice(bufs.len()); - let descs = loop { - let descs = { - let mut desc = self.desc.lock(); - desc.try_allocate_many(&mut buf) - }; - if let Some(descs) = descs { - break descs; - } - // TODO: handle descriptor unavailability better - log::warn!("out of descriptors"); - crate::rt::wfi().await; - }; - let mut head = None; - let mut previous = None; - let mut current = Some(bufs); - let mut i = 0; - while let Some(x) = current { - let idx = descs[i]; - let desc = self.desc.read(); - desc.get_mut_unchecked(idx).modify(|d| { - let (p, w) = match &x.car { - Buffer::Immutable(x) => (*x as *const [u8], false), - Buffer::Mutable(x) => (*x as *const [u8], true), - }; - let (addr, len) = p.to_raw_parts(); - d.addr = addr as *mut (); - d.len = len; - d.next = None; - d.device_writeable = w; - }); - if let Some(previous) = previous { - desc.get_mut_unchecked(previous).modify(|d| { - d.next = Some(idx); - }); - } - previous = Some(idx); - - if head.is_none() { - head = Some(idx); - } - current = x.cdr; - i += 1; - } - head.unwrap() - } - } - - async unsafe fn notification(&self, head: DescriptorIndex) -> usize { - Notification { - channel: self.notifications[head.get() as usize].clone(), - } - .await - } - - async unsafe fn mark_avail(&self, head: DescriptorIndex) { - let mut avail = self.avail.lock(); - avail.send(head); - } - - async unsafe fn mark_free(&self, head: DescriptorIndex) { - let mut desc = self.desc.lock(); - desc.liberate(head); - } - - async unsafe fn send_interrupt(&self) { - if !self.used.read().avail_notifications_suppressed() { - io::outl(0xf4, 0); - } - } - - pub async fn send(&self, bufs: &BufferChain<'_>) -> usize { - unsafe { - let head = self.load(bufs).await; - self.mark_avail(head).await; - self.send_interrupt().await; - let result = self.notification(head).await; - self.mark_free(head).await; - result - } - } - - pub fn poll(&self) { - let Some(mut used) = self.used.try_lock() else { - return; - }; - unsafe { - while let Some(used) = used.recv() { - let mut notification = self.notifications[used.id as usize].lock(); - notification.data = Some(used.len as usize); - if let Some(waker) = notification.waker.take() { - core::mem::drop(notification); - waker.wake(); - } - } - } - } -} diff --git a/kernel/src/virtio/virtqueue/desc.rs b/kernel/src/virtio/virtqueue/desc.rs deleted file mode 100644 index d27cb765..00000000 --- a/kernel/src/virtio/virtqueue/desc.rs +++ /dev/null @@ -1,155 +0,0 @@ -use core::{ - mem::MaybeUninit, - sync::atomic::{AtomicUsize, Ordering}, -}; - -use super::*; - -#[derive(Debug)] -pub struct DescTable { - name: &'static str, - free_index: Option, - free_descs: AtomicUsize, - table: *mut [Desc], -} - -unsafe impl Send for DescTable {} - -impl DescTable { - pub unsafe fn new(name: &'static str, p: *mut [Desc]) -> Self { - let mut x = Self { - name, - free_index: None, - free_descs: AtomicUsize::new(0), - table: p, - }; - for i in 0..p.len() { - x.liberate(i.into()); - } - x - } - - pub fn get_mut(&mut self, index: DescriptorIndex) -> &mut Desc { - unsafe { &mut (*self.table)[usize::from(index)] } - } - - #[allow(clippy::mut_from_ref)] - pub unsafe fn get_mut_unchecked(&self, index: DescriptorIndex) -> &mut Desc { - unsafe { &mut (*self.table)[usize::from(index)] } - } - - pub fn try_allocate(&mut self) -> Option { - let idx = self.free_index?; - // safety: since this was on the free list, the device isn't using it - self.free_index = self.get_mut(idx).modify(|desc| { - let next = desc.next; - desc.next = None; - next - }); - log::debug!("{} allocated {idx:?}", self.name); - self.free_descs.fetch_sub(1, Ordering::SeqCst); - Some(idx) - } - - pub fn try_allocate_many<'buf>( - &mut self, - buf: &'buf mut [MaybeUninit], - ) -> Option<&'buf mut [DescriptorIndex]> { - if buf.is_empty() { - return Some(unsafe { buf.assume_init_mut() }); - } - if self.free_descs.load(Ordering::SeqCst) < buf.len() { - return None; - } - let idx = self.try_allocate()?; - if self.try_allocate_many(&mut buf[1..]).is_none() { - self.liberate(idx); - None - } else { - let buf = unsafe { buf.assume_init_mut() }; - buf[0] = idx; - Some(buf) - } - } - - pub fn liberate(&mut self, idx: DescriptorIndex) { - let head = self.free_index; - log::debug!("{} liberated {idx:?}", self.name); - let current = self.get_mut(idx); - let mut tbd = None; - current.modify(|current| { - current.addr = core::ptr::null_mut(); - current.len = 0; - tbd = current.next; - current.next = head; - }); - self.free_index = Some(idx); - self.free_descs.fetch_add(1, Ordering::SeqCst); - if let Some(tbd) = tbd { - self.liberate(tbd); - } - } -} - -mod inner { - use super::*; - - #[repr(C)] - #[derive(Debug, Copy, Clone)] - pub struct Desc { - addr: u64, - len: u32, - flags: u16, - next: u16, - } - - pub struct DescData { - pub addr: *mut (), - pub len: usize, - pub device_writeable: bool, - pub next: Option, - } - - impl Desc { - pub fn get(&self) -> DescData { - let Desc { - addr, - len, - flags, - next, - } = unsafe { (&raw const *self).read_volatile() }; - DescData { - addr: vm::pa2ka(addr as usize), - len: len as usize, - device_writeable: flags & 2 == 2, - next: if flags & 1 == 1 { - Some(next.into()) - } else { - None - }, - } - } - - pub fn set(&mut self, value: DescData) { - let desc = Desc { - addr: vm::ka2pa(value.addr) as u64, - len: value.len as u32, - flags: if value.device_writeable { 2 } else { 0 } - | if value.next.is_some() { 1 } else { 0 }, - next: value.next.map(|x| x.get()).unwrap_or(0), - }; - unsafe { (&raw mut *self).write_volatile(desc) } - } - - pub fn modify(&mut self, f: impl FnOnce(&mut DescData) -> T) -> T { - let mut desc = self.get(); - let result = f(&mut desc); - self.set(desc); - result - } - } - - // TODO: is this necessary? - unsafe impl Sync for Desc {} -} -use inner::*; diff --git a/kernel/src/virtio/virtqueue/idx.rs b/kernel/src/virtio/virtqueue/idx.rs deleted file mode 100644 index afd6e398..00000000 --- a/kernel/src/virtio/virtqueue/idx.rs +++ /dev/null @@ -1,55 +0,0 @@ -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[repr(transparent)] -pub struct DescriptorIndex(u16); - -impl DescriptorIndex { - pub unsafe fn from(&self, idx: u16) -> Self { - Self(idx) - } - - pub fn get(&self) -> u16 { - self.0 - } -} - -impl From for DescriptorIndex { - fn from(value: u16) -> Self { - DescriptorIndex(value) - } -} - -impl From for DescriptorIndex { - fn from(value: u32) -> Self { - DescriptorIndex(value as u16) - } -} - -impl From for DescriptorIndex { - fn from(value: usize) -> Self { - DescriptorIndex(value as u16) - } -} - -impl From for u16 { - fn from(value: DescriptorIndex) -> Self { - value.0 - } -} - -impl From for u32 { - fn from(value: DescriptorIndex) -> Self { - value.0 as u32 - } -} - -impl From for usize { - fn from(value: DescriptorIndex) -> Self { - value.0 as usize - } -} - -impl AsRef for DescriptorIndex { - fn as_ref(&self) -> &u16 { - &self.0 - } -} diff --git a/kernel/src/virtio/virtqueue/vring.rs b/kernel/src/virtio/virtqueue/vring.rs deleted file mode 100644 index 41a113d6..00000000 --- a/kernel/src/virtio/virtqueue/vring.rs +++ /dev/null @@ -1,119 +0,0 @@ -#[repr(C)] -pub struct Ring { - flags: u16, - idx: u16, - ring: [T], -} - -impl Ring { - pub fn raw_index(&self) -> u16 { - unsafe { (&raw const self.idx).read_volatile() } - } - - pub fn len(&self) -> usize { - self.ring.len() - } - - pub fn get(&self, idx: u16) -> T { - unsafe { (&raw const self.ring[idx as usize % self.len()]).read_volatile() } - } - - pub fn set(&mut self, idx: u16, value: T) { - unsafe { - (&raw mut self.ring[idx as usize % self.len()]).write_volatile(value); - } - } - - pub fn inc(&mut self) { - unsafe { - (&raw mut self.idx).write_volatile(self.raw_index().wrapping_add(1)); - } - } - - pub fn flags(&self) -> u16 { - unsafe { (&raw const self.flags).read_volatile() } - } - - pub fn set_flags(&mut self, value: u16) { - unsafe { (&raw mut self.flags).write_volatile(value) } - } -} - -#[derive(Debug)] -pub struct DeviceRing { - name: &'static str, - next_read: u16, - ring: *mut Ring, -} - -impl DeviceRing { - pub unsafe fn new(name: &'static str, ring: *mut Ring) -> Self { - let mut this = Self { - name, - next_read: 0, - ring, - }; - this.ring_mut().set_flags(1); // we don't need notifications from Linux - this - } - - fn ring(&self) -> &Ring { - unsafe { &*self.ring } - } - - fn ring_mut(&mut self) -> &mut Ring { - unsafe { &mut *self.ring } - } - - pub unsafe fn recv(&mut self) -> Option { - if self.next_read != self.ring().raw_index() { - log::debug!("{} is receiving", self.name); - let value = self.ring().get(self.next_read); - self.next_read = self.next_read.wrapping_add(1); - Some(value) - } else { - None - } - } - - pub fn avail_notifications_suppressed(&self) -> bool { - self.ring().flags() != 0 - } -} - -#[derive(Debug)] -pub struct DriverRing { - name: &'static str, - ring: *mut Ring, -} - -impl DriverRing { - pub unsafe fn new(name: &'static str, ring: *mut Ring) -> Self { - Self { name, ring } - } - - fn ring(&self) -> &Ring { - unsafe { &*self.ring } - } - - fn ring_mut(&mut self) -> &mut Ring { - unsafe { &mut *self.ring } - } - - pub unsafe fn send(&mut self, value: T) { - log::debug!("{} is sending", self.name); - let idx = self.ring().raw_index(); - self.ring_mut().set(idx, value); - self.ring_mut().inc(); - } - - #[allow(unused)] - pub fn suppress_used_notifications(&mut self, suppressed: bool) { - self.ring_mut().set_flags(if suppressed { 1 } else { 0 }); - } -} - -unsafe impl Send for DeviceRing {} -unsafe impl Send for DriverRing {} -unsafe impl Sync for DeviceRing {} -unsafe impl Sync for DriverRing {} diff --git a/kernel/src/virtio/vsock.rs b/kernel/src/virtio/vsock.rs deleted file mode 100644 index 984ab04b..00000000 --- a/kernel/src/virtio/vsock.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::prelude::*; - -pub mod addr; -pub(crate) mod driver; -pub mod flow; -pub mod header; -pub mod listener; -pub mod message; -pub mod stream; - -pub use addr::*; -use common::util::channel::{RecvError, SendError}; -pub use driver::*; -pub use flow::*; -pub use header::*; -pub use listener::*; -pub use message::*; -pub use stream::*; - -use async_lock::RwLock; - -#[derive(Debug)] -pub enum SocketError { - InvalidAddress, - AddressInUse(SocketAddr), - ConnectionReset, - ConnectionClosed, - ConnectionFailed, -} - -pub type Result = core::result::Result; - -impl From> for SocketError { - fn from(_: SendError) -> Self { - SocketError::ConnectionClosed - } -} - -impl From for SocketError { - fn from(_: RecvError) -> Self { - SocketError::ConnectionClosed - } -} - -pub(crate) static DRIVER: OnceLock> = OnceLock::new(); - -pub(crate) async fn listen(addr: SocketAddr) -> Arc> { - let driver = &DRIVER; - driver.listen(addr).await -} - -pub(crate) async fn accept(flow: Flow) -> Arc> { - let driver = &DRIVER; - driver.accept(flow).await -} - -pub(crate) async fn connect(flow: Flow) -> Arc> { - let driver = &DRIVER; - driver.connect(flow).await -} - -pub(crate) async fn send(flow: Flow, buf: &[u8]) -> usize { - let driver = &DRIVER; - driver.send(flow, buf).await -} - -pub(crate) async fn shutdown(flow: Flow, rx: bool, tx: bool) { - let driver = &DRIVER; - driver.shutdown(flow, rx, tx).await -} - -pub(crate) async fn rst(flow: Flow) { - let driver = &DRIVER; - driver.rst(flow).await -} - -#[allow(unused)] -pub(crate) async fn listeners() -> Vec { - let driver = &DRIVER; - driver.listeners().await -} - -#[allow(unused)] -pub(crate) async fn streams() -> Vec { - let driver = &DRIVER; - driver.streams().await -} diff --git a/kernel/src/virtio/vsock/addr.rs b/kernel/src/virtio/vsock/addr.rs deleted file mode 100644 index c6c5629b..00000000 --- a/kernel/src/virtio/vsock/addr.rs +++ /dev/null @@ -1,34 +0,0 @@ -use core::str::FromStr; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub struct SocketAddr { - pub cid: u64, - pub port: u32, -} - -impl SocketAddr { - pub fn new(cid: u64, port: u32) -> SocketAddr { - Self { cid, port } - } -} - -pub struct InvalidSockAddr; - -impl FromStr for SocketAddr { - type Err = InvalidSockAddr; - - fn from_str(s: &str) -> Result { - let (cid, port) = s.split_once(":").ok_or(InvalidSockAddr)?; - let cid: u64 = str::parse(cid).map_err(|_| InvalidSockAddr)?; - let port: u32 = str::parse(port).map_err(|_| InvalidSockAddr)?; - Ok(SocketAddr { cid, port }) - } -} - -impl TryFrom<&str> for SocketAddr { - type Error = InvalidSockAddr; - - fn try_from(value: &str) -> Result { - SocketAddr::from_str(value) - } -} diff --git a/kernel/src/virtio/vsock/driver.rs b/kernel/src/virtio/vsock/driver.rs deleted file mode 100644 index 06b7c64f..00000000 --- a/kernel/src/virtio/vsock/driver.rs +++ /dev/null @@ -1,421 +0,0 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; - -use alloc::collections::btree_map::BTreeMap; -use alloc::collections::vec_deque::VecDeque; -use alloc::sync::Arc; -use common::vhost::VSockMetadata; - -use super::*; -use crate::rt; -use crate::virtio::virtqueue::*; -use async_lock::RwLock; - -pub(crate) struct Driver { - rx: VirtQueue, - tx: VirtQueue, - status: Status, - listeners: RwLock>>>, - streams: RwLock>>>, -} - -#[derive(Debug, Default)] -struct Status { - peer_buf_alloc: AtomicUsize, - peer_fwd_cnt: AtomicUsize, - tx_cnt: AtomicUsize, - buf_alloc: AtomicUsize, - fwd_cnt: AtomicUsize, -} - -impl Driver { - /// # Safety - /// - /// `info` must describe a valid VSock, which has an attached device but no driver. Feature - /// negotiation must have completed and match the features available in this driver. - pub unsafe fn new(info: VSockMetadata) -> Arc { - let len = info.rx.descriptors; - let rx = VirtQueue::new("rx", info.rx); - let tx = VirtQueue::new("tx", info.tx); - let this = Arc::new(Self { - rx, - tx, - status: Status::default(), - listeners: Default::default(), - streams: Default::default(), - }); - for _ in 0..len / 2 { - crate::rt::spawn(this.clone().recv_task()); - } - crate::rt::spawn(this.clone().poll_task()); - - this - } - - #[rt::profile] - pub async fn listen(&self, addr: SocketAddr) -> Arc> { - let mut addr = addr; - - let mut listeners = self.listeners.write().await; - - if addr.port == 0 { - let mut found = false; - for port in 49152..=65535 { - addr.port = port; - if !listeners.contains_key(&addr) { - found = true; - break; - } - } - if !found { - panic!("out of ports"); - } - } - - let socket = Arc::new(RwLock::new(ListenSocket { - addr, - waker: None, - queue: VecDeque::new(), - })); - let weak = Arc::downgrade(&socket); - if let Some(x) = listeners.get(&addr) { - if x.strong_count() >= 1 { - panic!("addr {addr:?} in use!"); - } - } - listeners.insert(addr, weak); - core::mem::drop(listeners); - socket - } - - #[rt::profile] - pub async fn accept(&self, flow: Flow) -> Arc> { - let mut streams = self.streams.write().await; - assert!(!streams.contains_key(&flow.reverse())); - let socket = Arc::new(RwLock::new(StreamSocket { - outbound: flow, - waker: None, - queue: VecDeque::new(), - })); - streams.insert(flow.reverse(), Arc::downgrade(&socket)); - core::mem::drop(streams); - self.response(flow).await; - socket - } - - #[rt::profile] - pub async fn connect(&self, flow: Flow) -> Arc> { - let mut flow = flow; - let mut streams = self.streams.write().await; - - if flow.src.port == 0 { - let mut found = false; - for port in 49152..=65535 { - flow.src.port = port; - if !streams.contains_key(&flow.reverse()) { - found = true; - break; - } - } - if !found { - panic!("out of ports"); - } - } - - let socket = Arc::new(RwLock::new(StreamSocket { - outbound: flow, - waker: None, - queue: VecDeque::new(), - })); - let weak = Arc::downgrade(&socket); - if let Some(x) = streams.get(&flow.reverse()) { - if x.strong_count() >= 1 { - panic!("flow {flow:?} in use!"); - } - } - streams.insert(flow.reverse(), weak); - core::mem::drop(streams); - self.request(flow).await; - socket - } - - #[rt::profile] - async fn send_message(&self, msg: OutgoingMessage, buffers: Option<&BufferChain<'_>>) -> usize { - unsafe { - log::debug!("-> {msg:?}"); - let mut header: Header = msg.into(); - let len = buffers.map(|x| x.size()).unwrap_or(0); - let mut waiting = false; - loop { - let status = &self.status; - let peer_buf_alloc = status.peer_buf_alloc.load(Ordering::SeqCst); - let peer_fwd_count = status.peer_fwd_cnt.load(Ordering::SeqCst); - let tx_cnt = status.tx_cnt.load(Ordering::SeqCst); - - let tx_free = peer_buf_alloc.wrapping_sub(tx_cnt.wrapping_sub(peer_fwd_count)); - if tx_free < len { - waiting = true; - log::info!("waiting for rx capacity {tx_free} {len}"); - crate::rt::wfi().await; - continue; - } - - if waiting { - log::warn!("rx okay"); - } - - let buf_alloc = status.buf_alloc.load(Ordering::SeqCst); - let fwd_count = status.fwd_cnt.load(Ordering::SeqCst); - - header.buf_alloc = buf_alloc as u32; - header.fwd_cnt = fwd_count as u32; - - status.tx_cnt.fetch_sub(len, Ordering::SeqCst); - - let header_buf: &mut [u8; 44] = core::mem::transmute(&mut header); - let buffers = BufferChain::cons(header_buf, buffers); - return self.tx.send(&buffers).await; - } - } - } - - #[rt::profile] - pub async fn rst(&self, flow: Flow) { - self.send_message( - OutgoingMessage { - flow, - message: Outgoing::Rst, - }, - None, - ) - .await; - } - - #[rt::profile] - pub async fn response(&self, flow: Flow) { - self.send_message( - OutgoingMessage { - flow, - message: Outgoing::Response, - }, - None, - ) - .await; - } - - #[rt::profile] - pub async fn request(&self, flow: Flow) { - self.send_message( - OutgoingMessage { - flow, - message: Outgoing::Request, - }, - None, - ) - .await; - } - - #[rt::profile] - pub async fn send(&self, flow: Flow, buf: &[u8]) -> usize { - let chain = BufferChain::new(buf); - self.send_message( - OutgoingMessage { - flow, - message: Outgoing::Write(buf.len()), - }, - Some(&chain), - ) - .await - } - - #[rt::profile] - pub async fn shutdown(&self, flow: Flow, rx: bool, tx: bool) { - self.send_message( - OutgoingMessage { - flow, - message: Outgoing::Shutdown { rx, tx }, - }, - None, - ) - .await; - } - - pub async fn update(&self, flow: Flow) { - self.send_message( - OutgoingMessage { - flow, - message: Outgoing::CreditUpdate, - }, - None, - ) - .await; - } - - #[rt::profile] - async fn recv_task(self: Arc) { - unsafe { - loop { - let mut payload_buf = vec![0; 65536]; - payload_buf.resize(65536, 0); - let mut header = Header::default(); - let status = &self.status; - let len = payload_buf.len(); - status.buf_alloc.fetch_add(len, Ordering::SeqCst); - - let header_buf: &mut [u8; 44] = core::mem::transmute(&mut header); - let payload_chain = BufferChain::new_mut(&mut payload_buf); - let chain = BufferChain::cons_mut(header_buf, Some(&payload_chain)); - - let read = self.rx.send(&chain).await; - - status.buf_alloc.fetch_sub(len, Ordering::SeqCst); - status.fwd_cnt.fetch_add(read - 44, Ordering::SeqCst); - status - .peer_buf_alloc - .store(header.buf_alloc as usize, Ordering::SeqCst); - status - .peer_fwd_cnt - .store(header.fwd_cnt as usize, Ordering::SeqCst); - - assert!(len >= 44); - let incoming = header.into(); - log::debug!("<- {incoming:?}"); - let IncomingMessage { flow, message } = incoming; - - match message { - Incoming::Invalid(_) => self.rst(flow).await, - Incoming::Request => { - let listeners = self.listeners.read().await; - if let Some(socket) = listeners.get(&flow.dst).and_then(Weak::upgrade) { - core::mem::drop(listeners); - let mut socket = socket.write().await; - socket.queue.push_back(flow.reverse()); - if let Some(waker) = socket.waker.take() { - core::mem::drop(socket); - waker.wake(); - } - } else { - core::mem::drop(listeners); - log::warn!("got incoming request {flow:?}, but no listener"); - log::warn!("current listeners: {:?}", self.listeners().await); - self.rst(flow.reverse()).await - }; - } - Incoming::Response => { - let streams = self.streams.read().await; - if let Some(socket) = streams.get(&flow).and_then(Weak::upgrade) { - core::mem::drop(streams); - let mut socket = socket.write().await; - socket.queue.push_back(StreamEvent::Connect); - if let Some(waker) = socket.waker.take() { - core::mem::drop(socket); - waker.wake(); - } - } else { - log::warn!("got response {flow:?}, but no stream"); - self.rst(flow.reverse()).await - }; - } - Incoming::Rst => { - let streams = self.streams.read().await; - if let Some(socket) = streams.get(&flow).and_then(Weak::upgrade) { - core::mem::drop(streams); - let mut socket = socket.write().await; - socket.queue.push_back(StreamEvent::Reset); - if let Some(waker) = socket.waker.take() { - core::mem::drop(socket); - waker.wake(); - } - } - } - Incoming::Shutdown { rx, tx } => { - let streams = self.streams.read().await; - if let Some(socket) = streams.get(&flow).and_then(Weak::upgrade) { - core::mem::drop(streams); - let mut socket = socket.write().await; - socket.queue.push_back(StreamEvent::Shutdown { rx, tx }); - if let Some(waker) = socket.waker.take() { - core::mem::drop(socket); - waker.wake(); - } - } else { - // log::warn!("got shutdown for {flow:?}, but no stream"); - }; - } - Incoming::Read(len) => { - let streams = self.streams.read().await; - payload_buf.truncate(len); - if let Some(socket) = streams.get(&flow).and_then(Weak::upgrade) { - core::mem::drop(streams); - let mut socket = socket.write().await; - socket - .queue - .push_back(StreamEvent::Data { data: payload_buf }); - if let Some(waker) = socket.waker.take() { - core::mem::drop(socket); - waker.wake(); - } - } else { - log::warn!("got data for {flow:?}, but no stream"); - self.rst(flow.reverse()).await - }; - } - Incoming::CreditUpdate => {} - Incoming::CreditRequest => self.update(flow.reverse()).await, - } - } - } - } - - async fn poll_task(self: Arc) { - loop { - self.poll(); - crate::rt::wfi().await; - } - } - - pub fn poll(&self) { - self.rx.poll(); - self.tx.poll(); - } - - pub async fn listeners(&self) -> Vec { - self.listeners - .read() - .await - .iter() - .filter_map(|(k, v)| { - if v.strong_count() >= 1 { - Some(*k) - } else { - None - } - }) - .collect() - } - - pub async fn streams(&self) -> Vec { - self.streams - .read() - .await - .iter() - .filter_map(|(k, v)| { - if v.strong_count() >= 1 { - Some(*k) - } else { - None - } - }) - .collect() - } -} - -#[derive(Debug)] -pub enum StreamEvent { - Reset, - Connect, - Shutdown { rx: bool, tx: bool }, - Data { data: Vec }, -} - -// TODO: why do we need this? -unsafe impl Sync for Driver {} diff --git a/kernel/src/virtio/vsock/flow.rs b/kernel/src/virtio/vsock/flow.rs deleted file mode 100644 index 9d622f72..00000000 --- a/kernel/src/virtio/vsock/flow.rs +++ /dev/null @@ -1,16 +0,0 @@ -use super::*; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub struct Flow { - pub src: SocketAddr, - pub dst: SocketAddr, -} - -impl Flow { - pub fn reverse(&self) -> Flow { - Flow { - dst: self.src, - src: self.dst, - } - } -} diff --git a/kernel/src/virtio/vsock/header.rs b/kernel/src/virtio/vsock/header.rs deleted file mode 100644 index e8506122..00000000 --- a/kernel/src/virtio/vsock/header.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[derive(Debug, Copy, Clone, Default)] -#[repr(C, packed)] -pub struct Header { - pub src_cid: u64, - pub dst_cid: u64, - pub src_port: u32, - pub dst_port: u32, - pub len: u32, - pub ptype: u16, - pub op: u16, - pub flags: u32, - pub buf_alloc: u32, - pub fwd_cnt: u32, -} - -const _: () = const { - assert!(core::mem::size_of::
() == 44); -}; diff --git a/kernel/src/virtio/vsock/listener.rs b/kernel/src/virtio/vsock/listener.rs deleted file mode 100644 index f4b106bc..00000000 --- a/kernel/src/virtio/vsock/listener.rs +++ /dev/null @@ -1,81 +0,0 @@ -use core::{ - future::Future, - pin::pin, - task::{Context, Poll, Waker}, -}; - -use alloc::collections::vec_deque::VecDeque; - -use crate::rt; - -use super::*; - -pub(crate) struct ListenSocket { - pub addr: SocketAddr, - pub waker: Option, - pub queue: VecDeque, -} - -impl Drop for ListenSocket { - fn drop(&mut self) { - panic!("who dropped a ListenSocket?"); - } -} - -pub struct StreamListener { - socket: Arc>, - pub addr: SocketAddr, -} - -impl Drop for StreamListener { - fn drop(&mut self) { - panic!("who dropped a StreamListener?"); - } -} - -struct Accept { - socket: Arc>, -} - -impl StreamListener { - #[rt::profile] - pub async fn bind(cid: u64, port: u32) -> Result { - let rx = listen(SocketAddr { cid, port }).await; - let addr = rx.read().await.addr; - Ok(StreamListener { socket: rx, addr }) - } - - #[rt::profile] - pub async fn accept(&self) -> Result { - let flow = Accept { - socket: self.socket.clone(), - } - .await; - let socket = accept(flow).await; - Ok(Stream::new(socket).await) - } -} - -impl Future for Accept { - type Output = Flow; - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - let mut fake_cx = Context::from_waker(Waker::noop()); - let mut socket = loop { - let socket = pin! {self.socket.write()}; - if let Poll::Ready(result) = Future::poll(socket, &mut fake_cx) { - break result; - } - core::hint::spin_loop(); - }; - if let Some(x) = socket.queue.pop_front() { - Poll::Ready(x) - } else { - socket.waker = Some(cx.waker().clone()); - Poll::Pending - } - } -} diff --git a/kernel/src/virtio/vsock/message.rs b/kernel/src/virtio/vsock/message.rs deleted file mode 100644 index 558c085f..00000000 --- a/kernel/src/virtio/vsock/message.rs +++ /dev/null @@ -1,123 +0,0 @@ -use super::*; - -#[repr(u8)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PacketOperation { - Request = 1, - Response = 2, - Rst = 3, - Shutdown = 4, - ReadWrite = 5, - CreditUpdate = 6, - CreditRequest = 7, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Outgoing { - Request, - Response, - Rst, - Shutdown { rx: bool, tx: bool }, - Write(usize), - CreditUpdate, - CreditRequest, -} - -impl From for PacketOperation { - fn from(value: Outgoing) -> Self { - match value { - Outgoing::Request => PacketOperation::Request, - Outgoing::Response => PacketOperation::Response, - Outgoing::Rst => PacketOperation::Rst, - Outgoing::Shutdown { rx: _, tx: _ } => PacketOperation::Shutdown, - Outgoing::Write(_) => PacketOperation::ReadWrite, - Outgoing::CreditUpdate => PacketOperation::CreditUpdate, - Outgoing::CreditRequest => PacketOperation::CreditRequest, - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct OutgoingMessage { - pub flow: Flow, - pub message: Outgoing, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Incoming { - Invalid(u16), - Request, - Response, - Rst, - Shutdown { rx: bool, tx: bool }, - Read(usize), - CreditUpdate, - CreditRequest, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct IncomingMessage { - pub flow: Flow, - pub message: Incoming, -} - -impl From
for IncomingMessage { - fn from(value: Header) -> Self { - let flow = Flow { - src: SocketAddr { - cid: value.src_cid, - port: value.src_port, - }, - dst: SocketAddr { - cid: value.dst_cid, - port: value.dst_port, - }, - }; - let message = match value.op { - 1 => Incoming::Request, - 2 => Incoming::Response, - 3 => Incoming::Rst, - 4 => Incoming::Shutdown { - rx: value.flags & 1 == 1, - tx: value.flags & 2 == 2, - }, - 5 => Incoming::Read(value.len as usize), - 6 => Incoming::CreditUpdate, - 7 => Incoming::CreditRequest, - x => Incoming::Invalid(x), - }; - IncomingMessage { flow, message } - } -} - -impl From for Header { - fn from(value: OutgoingMessage) -> Self { - if value.flow.dst.cid != 2 { - log::error!("trying to send outgoing message to {:?}", value.flow); - } - Header { - src_cid: value.flow.src.cid, - src_port: value.flow.src.port, - dst_cid: value.flow.dst.cid, - dst_port: value.flow.dst.port, - len: if let Outgoing::Write(x) = value.message { - x as u32 - } else { - 0 - }, - ptype: 1, // TODO: seqpacket support - op: PacketOperation::from(value.message) as u16, - flags: if let Outgoing::Shutdown { rx: true, tx: _ } = value.message { - 1 - } else { - 0 - } | if let Outgoing::Shutdown { rx: _, tx: true } = value.message { - 2 - } else { - 0 - }, - buf_alloc: 0, - fwd_cnt: 0, - } - } -} diff --git a/kernel/src/virtio/vsock/stream.rs b/kernel/src/virtio/vsock/stream.rs deleted file mode 100644 index fbb12143..00000000 --- a/kernel/src/virtio/vsock/stream.rs +++ /dev/null @@ -1,199 +0,0 @@ -use core::{ - future::Future, - pin::pin, - sync::atomic::{AtomicBool, Ordering}, - task::{Context, Poll, Waker}, -}; - -use alloc::collections::vec_deque::VecDeque; - -use crate::rt; - -use super::*; - -pub(crate) struct StreamSocket { - pub outbound: Flow, - pub waker: Option, - pub queue: VecDeque, -} - -pub struct Stream { - socket: Arc>, - peer_rx_closed: AtomicBool, - peer_tx_closed: AtomicBool, - closed: AtomicBool, - last_read: Mutex>>, - outbound: Flow, -} - -pub struct Read { - socket: Arc>, -} - -impl Stream { - pub(crate) async fn new(socket: Arc>) -> Stream { - let outbound = socket.read().await.outbound; - Stream { - socket, - peer_rx_closed: false.into(), - peer_tx_closed: false.into(), - closed: false.into(), - last_read: Mutex::new(None), - outbound, - } - } - - #[rt::profile] - pub async fn connect(cid: u64, peer: impl TryInto) -> Result { - let local = SocketAddr { cid, port: 0 }; - let outbound = Flow { - src: local, - dst: peer.try_into().map_err(|_| SocketError::InvalidAddress)?, - }; - let socket = connect(outbound).await; - let result = Read { - socket: socket.clone(), - } - .await; - let StreamEvent::Connect = result else { - return Err(SocketError::ConnectionFailed); - }; - Ok(Stream::new(socket).await) - } - - #[rt::profile] - pub async fn recv(&self, bytes: &mut [u8]) -> Result { - if bytes.is_empty() { - return Ok(0); - } - let mut last_read = self.last_read.lock().await; - if let Some(rest) = last_read.as_mut() { - let n = core::cmp::min(bytes.len(), rest.len()); - for item in bytes.iter_mut().take(n) { - *item = rest.pop_front().unwrap(); - } - if rest.is_empty() { - *last_read = None; - } - Ok(n) - } else { - loop { - let result = Read { - socket: self.socket.clone(), - } - .await; - return match result { - StreamEvent::Reset => Err(SocketError::ConnectionReset), - StreamEvent::Connect => Err(SocketError::ConnectionReset), - StreamEvent::Shutdown { rx, tx } => { - let rx = if rx { - self.peer_rx_closed.store(true, Ordering::SeqCst); - true - } else { - self.peer_rx_closed.load(Ordering::SeqCst) - }; - let tx = if tx { - self.peer_tx_closed.store(true, Ordering::SeqCst); - true - } else { - self.peer_tx_closed.load(Ordering::SeqCst) - }; - if rx && tx { - rst(self.outbound).await; - } - if tx { - Err(SocketError::ConnectionClosed) - } else { - continue; - } - } - StreamEvent::Data { data } => { - let n = core::cmp::min(data.len(), bytes.len()); - let mut rest: VecDeque = data.into(); - for item in bytes.iter_mut().take(n) { - *item = rest.pop_front().unwrap(); - } - if !rest.is_empty() { - *last_read = Some(rest); - } - return Ok(n); - } - }; - } - } - } - - #[rt::profile] - pub async fn send(&self, buf: &[u8]) -> Result { - if self.peer_rx_closed.load(Ordering::SeqCst) { - Err(SocketError::ConnectionClosed) - } else { - send(self.outbound, buf).await; - Ok(buf.len()) - } - } - - #[rt::profile] - async fn close_internal(&mut self) -> Result<()> { - if self.closed.fetch_or(true, Ordering::SeqCst) { - return Ok(()); - } - shutdown(self.outbound, true, true).await; - - loop { - let result = Read { - socket: self.socket.clone(), - } - .await; - if let StreamEvent::Reset = result { - return Ok(()); - }; - } - } - - #[rt::profile] - pub async fn close(mut self) -> Result<()> { - self.close_internal().await?; - Ok(()) - } - - pub fn peer(&self) -> SocketAddr { - self.outbound.dst - } - - pub fn local(&self) -> SocketAddr { - self.outbound.src - } -} - -impl Drop for Stream { - fn drop(&mut self) { - if !self.closed.load(Ordering::SeqCst) { - let _ = crate::rt::spawn_blocking(self.close_internal()); - } - } -} - -impl Future for Read { - type Output = StreamEvent; - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll { - let mut fake_cx = Context::from_waker(Waker::noop()); - let mut socket = loop { - let socket = pin! {self.socket.write()}; - if let Poll::Ready(result) = Future::poll(socket, &mut fake_cx) { - break result; - } - core::hint::spin_loop(); - }; - if let Some(x) = socket.queue.pop_front() { - Poll::Ready(x) - } else { - socket.waker = Some(cx.waker().clone()); - Poll::Pending - } - } -} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e7c43a03..266a8912 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" proc-macro-crate = "3.1.0" proc-macro2 = "1.0.86" quote = "1.0.36" -syn = "2.0.67" +syn = { version = "2.0.67", features = ["full"] } [lib] proc-macro = true diff --git a/macros/src/bitpack.rs b/macros/src/bitpack.rs new file mode 100644 index 00000000..e535c0be --- /dev/null +++ b/macros/src/bitpack.rs @@ -0,0 +1,158 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use proc_macro_crate::{crate_name, FoundCrate}; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Ident}; + +fn common_ident() -> Ident { + let found_crate = crate_name("common").expect("common is present in `Cargo.toml`"); + + match found_crate { + FoundCrate::Itself => format_ident!("crate"), + FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), + } +} + +pub fn bitpack(input: TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + + match input.data { + Data::Enum(de) => bitpack_enum(&name, de), + Data::Struct(_) => compile_error("Unable to create bitpack for struct"), + Data::Union(_) => compile_error("Unable to create bitpack for union"), + } +} + +fn compile_error(msg: &str) -> TokenStream { + syn::Error::new(proc_macro2::Span::call_site(), msg) + .to_compile_error() + .into() +} + +const fn ceil_log2(n: u32) -> u32 { + if n <= 1 { + 0 + } else { + 32 - (n - 1).leading_zeros() + } +} + +struct Variant { + index: u32, + pat: proc_macro2::TokenStream, + construct: proc_macro2::TokenStream, + width: proc_macro2::TokenStream, + unpack: proc_macro2::TokenStream, +} + +fn bitpack_enum(name: &Ident, de: DataEnum) -> TokenStream { + let common = common_ident(); + let mut variants = Vec::new(); + for (index, v) in de.variants.iter().enumerate() { + let ident = v.ident.clone(); + + let ty = match &v.fields { + syn::Fields::Named(fields_named) => { + if fields_named.named.len() != 1 { + return compile_error("Unable to create bitpack for variants not of 1 field"); + } + &fields_named.named.first().unwrap().ty + } + syn::Fields::Unnamed(fields_unnamed) => { + if fields_unnamed.unnamed.len() != 1 { + return compile_error("Unable to create bitpack for variants not of 1 field"); + } + &fields_unnamed.unnamed.first().unwrap().ty + } + syn::Fields::Unit => { + return compile_error("Unable to create bitpack for variants not of 1 field") + } + }; + + let pat = quote! { #name::#ident(inner) }; + let construct = quote! { Self::#ident }; + let width = quote! { #ty::TAGBITS }; + let unpack = quote! { #ty::unpack }; + + variants.push(Variant { + index: index as u32, + pat, + construct, + width, + unpack, + }) + } + + let child_widths = variants.iter().map(|v| &v.width); + let max_child_widths = quote! { + { + let mut m: u32 = 0; + #( { + let w = #child_widths; if w > m { m = w; } + })* + m + } + }; + let curr_width = ceil_log2(variants.len().try_into().unwrap()); + + let tag_bits = quote! { #max_child_widths + #curr_width }; + let tag_mask = quote! { bitmask256::<#max_child_widths, #curr_width>() }; + + let unpack_arms = variants.iter().map(|v| { + let index: u64 = v.index.into(); + let construct = &v.construct; + let unpack = &v.unpack; + quote! { + #index => { #construct( #unpack( content )) } + } + }); + + let pack_arms = variants.iter().map(|v| { + let index = v.index; + let pat = &v.pat; + quote! { + #pat => { + use #common::bitpack::BitPack; + let mut result = inner.pack(); + for i in 0..32 { + result[i] &= !Self::TAGMASK[i]; + } + let field: &mut [u16; 16] = unsafe { core::mem::transmute( &mut result ) }; + field[15] |= (#index << (#max_child_widths - 240)) as u16; + result + } + } + }); + + let output = quote! { + impl #name { + const TAGMASK: [u8; 32] = #tag_mask; + } + + impl #common::bitpack::BitPack for #name { + const TAGBITS: u32 = #tag_bits; + + fn pack(&self) -> [u8; 32] { + match self { + #(#pack_arms)* + } + } + + fn unpack(content: [u8; 32]) -> Self { + let mut tag = content; + for i in 0..32 { + tag[i] &= Self::TAGMASK[i]; + } + let field: &[u16; 16] = unsafe { core::mem::transmute( &tag ) }; + let tag = field[15] >> (#max_child_widths - 240); + match tag as u64 { + #(#unpack_arms)* + _ => todo!() + } + } + + } + }; + output.into() +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9a79cc43..8ce770b1 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,6 @@ use proc_macro::TokenStream; +mod bitpack; mod core_local; mod testing; mod util; @@ -33,3 +34,8 @@ pub fn arca_test(_attr: TokenStream, item: TokenStream) -> TokenStream { pub fn kmain(attr: TokenStream, item: TokenStream) -> TokenStream { util::kmain(attr, item) } + +#[proc_macro_derive(BitPack)] +pub fn bitpack(input: TokenStream) -> TokenStream { + bitpack::bitpack(input) +} diff --git a/macros/src/util.rs b/macros/src/util.rs index 108c33a6..bd97da07 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -1,35 +1,16 @@ use proc_macro::TokenStream; -use proc_macro2::Span; -use proc_macro_crate::{crate_name, FoundCrate}; -use quote::{format_ident, quote}; -use syn::{parse_macro_input, Ident, ItemFn}; - -fn kernel_ident() -> Ident { - let found_crate = crate_name("kernel").expect("kernel is present in `Cargo.toml`"); - - match found_crate { - FoundCrate::Itself => format_ident!("crate"), - FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), - } -} +use quote::quote; +use syn::{parse_macro_input, ItemFn}; pub fn kmain(_: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as ItemFn); let ident = item.sig.ident.clone(); - let kernel = kernel_ident(); quote! { #[no_mangle] - extern "C" fn kmain(argc: usize, argv: *const usize) { + extern "C" fn kmain() { #item - let slice = unsafe { - core::slice::from_raw_parts(argv, argc) - }; - - #kernel::rt::spawn(async { - #ident(slice).await; - #kernel::shutdown(); - }); + #ident(); } } diff --git a/modules/arca-musl b/modules/arca-musl index a88bc699..a83796a9 160000 --- a/modules/arca-musl +++ b/modules/arca-musl @@ -1 +1 @@ -Subproject commit a88bc6999eb736d93a0aab0afe07c99e4e1ec559 +Subproject commit a83796a98c009ea92b9dc47a526b24b818b325b7 diff --git a/user/build.rs b/user/build.rs index 075c69f4..e3e1c2a3 100644 --- a/user/build.rs +++ b/user/build.rs @@ -6,10 +6,12 @@ fn main() { let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); println!("cargo::rustc-link-arg=-T{dir}/etc/memmap.ld"); - let prefix = autotools::build("../modules/arca-musl") + let prefix = autotools::Config::new("../modules/arca-musl") + .build() .as_os_str() .to_string_lossy() .into_owned(); + println!("cargo::rerun-if-changed=../modules/arca-musl"); println!("cargo::rustc-link-search={prefix}/lib"); println!("cargo::rustc-link-lib=static=c"); } diff --git a/user/src/bin/webserver.rs b/user/src/bin/webserver.rs new file mode 100644 index 00000000..f5f92096 --- /dev/null +++ b/user/src/bin/webserver.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +extern crate user; + +use user::{io::File, prelude::*, print, println}; + +/// A simple webserver program; expects a standard filesystem effect interface +/// (open/close/read/write), where stdin is connected to the HTTP request and stdout is connected +/// to the HTTP response. +#[unsafe(no_mangle)] +pub extern "C" fn _rsstart() -> ! { + let mut stdin = File::STDIN; + let mut bytes: [u8; 1024] = [0; 1024]; + stdin.read(&mut bytes).unwrap(); + + let message = "hello, world"; + let len = message.len(); + + print!("HTTP/1.1 200 OK\r\n"); + print!("Content-Length: {len}\r\n"); + print!("Content-Type: text/html\r\n"); + print!("\r\n"); + + println!("{message}"); + io::exit(0) +} diff --git a/user/src/bin/webserver2.rs b/user/src/bin/webserver2.rs new file mode 100644 index 00000000..2336b23f --- /dev/null +++ b/user/src/bin/webserver2.rs @@ -0,0 +1,72 @@ +#![no_std] +#![no_main] + +extern crate user; + +use core::fmt::Write; + +use user::prelude::*; + +fn accept() { + Function::symbolic("accept").call_with_current_continuation(); +} + +#[derive(Debug)] +struct Error; + +fn recv(bytes: &mut [u8]) -> Result { + let result: Blob = Function::symbolic("recv") + .apply(bytes.len()) + .call_with_current_continuation() + .try_into() + .map_err(|_| Error)?; + Ok(result.read(0, bytes)) +} + +fn send(bytes: &[u8]) -> Result { + let result: Word = Function::symbolic("send") + .apply(bytes) + .call_with_current_continuation() + .try_into() + .map_err(|_| Error)?; + let result = result.read() as i64; + if result < 0 { + Err(Error) + } else { + Ok(result as usize) + } +} + +fn hangup() { + Function::symbolic("hangup").call_with_current_continuation(); +} + +struct Stream; +impl Write for Stream { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + send(s.as_bytes()).map(|_| ()).map_err(|_| core::fmt::Error) + } +} + +/// A simple webserver program; expects a standard filesystem effect interface +/// (open/close/read/write), where stdin is connected to the HTTP request and stdout is connected +/// to the HTTP response. +#[unsafe(no_mangle)] +pub extern "C" fn _rsstart() -> ! { + loop { + accept(); + let mut bytes: [u8; 1024] = [0; 1024]; + recv(&mut bytes).unwrap(); + + let message = "hello, world"; + let len = message.len(); + + write!(Stream, "HTTP/1.1 200 OK\r\n").unwrap(); + write!(Stream, "Content-Length: {len}\r\n").unwrap(); + write!(Stream, "Content-Type: text/html\r\n").unwrap(); + write!(Stream, "\r\n").unwrap(); + + write!(Stream, "{message}").unwrap(); + hangup(); + } +} diff --git a/user/src/io.rs b/user/src/io.rs index d7f6ddd5..762ec549 100644 --- a/user/src/io.rs +++ b/user/src/io.rs @@ -9,7 +9,7 @@ use crate::prelude::*; extern crate alloc; pub struct File { - pub content: Table, + pub fd: u32, } #[derive(Clone, Debug)] @@ -34,99 +34,94 @@ pub enum SeekFrom { } impl File { + pub fn open(file: &str) -> Result { + OpenOptions::default().read(true).open(file) + } + + pub const STDIN: File = File { fd: 0 }; + pub const STDOUT: File = File { fd: 1 }; + pub const STDERR: File = File { fd: 2 }; + pub fn options() -> OpenOptions { OpenOptions::default() } - //pub fn read(&mut self, bytes: &mut [u8]) -> Result { - // let result: Blob = Function::symbolic("read") - // .apply(self.fd) - // .apply(bytes.len()) - // .call_with_current_continuation() - // .try_into() - // .map_err(|_| Error)?; - // Ok(result.read(0, bytes)) - //} - - //pub fn write(&mut self, bytes: &[u8]) -> Result { - // let result: Word = Function::symbolic("write") - // .apply(self.fd) - // .apply(bytes) - // .call_with_current_continuation() - // .try_into() - // .map_err(|_| Error)?; - // let result = result.read() as i64; - // if result < 0 { - // Err(Error) - // } else { - // Ok(result as usize) - // } - //} - - //pub fn seek(&mut self, from: SeekFrom) -> Result { - // let (whence, offset) = match from { - // SeekFrom::Start(offset) => (arcane::SEEK_SET, offset as usize), - // SeekFrom::End(offset) => (arcane::SEEK_END, offset as usize), - // SeekFrom::Current(offset) => (arcane::SEEK_CUR, offset as usize), - // }; - // let result: Word = Function::symbolic("seek") - // .apply(self.fd) - // .apply(offset) - // .apply(whence) - // .call_with_current_continuation() - // .try_into() - // .map_err(|_| Error)?; - // let result = result.read() as i64; - // if result < 0 { - // Err(Error) - // } else { - // Ok(result as usize) - // } - //} + pub fn read(&mut self, bytes: &mut [u8]) -> Result { + let result: Blob = Function::symbolic("read") + .apply(self.fd) + .apply(bytes.len()) + .call_with_current_continuation() + .try_into() + .map_err(|_| Error)?; + Ok(result.read(0, bytes)) + } + + pub fn write(&mut self, bytes: &[u8]) -> Result { + let result: Word = Function::symbolic("write") + .apply(self.fd) + .apply(bytes) + .call_with_current_continuation() + .try_into() + .map_err(|_| Error)?; + let result = result.read() as i64; + if result < 0 { + Err(Error) + } else { + Ok(result as usize) + } + } + + pub fn seek(&mut self, from: SeekFrom) -> Result { + let (whence, offset) = match from { + SeekFrom::Start(offset) => (arcane::SEEK_SET, offset as usize), + SeekFrom::End(offset) => (arcane::SEEK_END, offset as usize), + SeekFrom::Current(offset) => (arcane::SEEK_CUR, offset as usize), + }; + let result: Word = Function::symbolic("seek") + .apply(self.fd) + .apply(offset) + .apply(whence) + .call_with_current_continuation() + .try_into() + .map_err(|_| Error)?; + let result = result.read() as i64; + if result < 0 { + Err(Error) + } else { + Ok(result as usize) + } + } +} + +impl Drop for File { + fn drop(&mut self) { + Function::symbolic("close") + .apply(self.fd) + .call_with_current_continuation(); + } +} + +impl Write for File { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write(s.as_bytes()).map_err(|_| core::fmt::Error)?; + Ok(()) + } } -//impl Clone for File { -// fn clone(&self) -> Self { -// let result: Word = Function::symbolic("dup") -// .apply(self.fd) -// .call_with_current_continuation() -// .try_into() -// .unwrap(); -// let fd = result.read() as i64; -// assert!(fd >= 0); -// let fd = fd as u32; -// Self { fd } -// } -//} -// -//impl Drop for File { -// fn drop(&mut self) { -// Function::symbolic("close") -// .apply(self.fd) -// .call_with_current_continuation(); -// } -//} - -//impl Write for File { -// fn write_str(&mut self, s: &str) -> core::fmt::Result { -// self.write(s.as_bytes()).map_err(|_| core::fmt::Error)?; -// Ok(()) -// } -//} -// impl Iterator for File { -// type Item = u8; -// -// fn next(&mut self) -> Option { -// let mut bytes = [0]; -// crate::error::log("reading"); -// if let Ok(1) = self.read(&mut bytes) { -// crate::error::log_int("read", bytes[0] as u64); -// Some(bytes[0]) -// } else { -// None -// } -// } -// } +impl Iterator for File { + type Item = u8; + + fn next(&mut self) -> Option { + let mut bytes = [0]; + crate::error::log("reading"); + if let Ok(1) = self.read(&mut bytes) { + crate::error::log_int("read", bytes[0] as u64); + Some(bytes[0]) + } else { + None + } + } +} impl OpenOptions { pub fn read(&mut self, read: bool) -> &mut Self { @@ -174,20 +169,19 @@ impl OpenOptions { flags |= arcane::O_TRUNC; } let mode = 0o655; - let result: Table = Function::symbolic("open") + let result: Word = Function::symbolic("open") .apply(path) .apply(flags) .apply(mode) .call_with_current_continuation() .try_into() .map_err(|_| Error)?; - // let result = result.read() as i64; - //if result < 0 { - // Err(Error) - //} else { - // Ok(File { fd: result as u32 }) - //} - Ok(File { content: result }) + let result = result.read() as i64; + if result < 0 { + Err(Error) + } else { + Ok(File { fd: result as u32 }) + } } } @@ -207,133 +201,111 @@ pub fn fork() -> Result> { Ok(NonZeroUsize::new(result)) } -pub struct Monitor { - fd: u64, -} +#[cfg(feature = "allocator")] +mod buf { + extern crate alloc; + use core::ops::{Deref, DerefMut}; -impl Monitor { - pub fn new() -> Monitor { - let result: Word = Function::symbolic("monitor-new") - .call_with_current_continuation() - .try_into() - .unwrap(); - Monitor { fd: result.read() } + use super::*; + use alloc::vec::Vec; + + pub struct Buffered { + file: File, + pending: Vec, } - pub fn enter(&self, f: impl FnOnce(&mut MonitorContext) -> Value) -> Value { - if let Ok(k) = os::continuation() { - Function::symbolic("monitor-enter") - .apply(self.fd) - .apply(k) - .call_with_current_continuation() - } else { - let mut ctx = MonitorContext(PhantomData); - let value = f(&mut ctx); - Function::symbolic("exit") - .apply(value) - .call_with_current_continuation() + impl Buffered { + pub fn new(file: File) -> Self { + Self { + file, + pending: Vec::new(), + } } - } - pub fn set(&self, value: impl Into) -> Value { - self.enter(|ctx| ctx.set(value.into())) - } + pub fn read(&mut self, buf: &mut [u8]) -> Result { + let n = core::cmp::min(buf.len(), self.pending.len()); + buf[..n].copy_from_slice(&self.pending[..n]); + self.pending = self.pending[n..].to_vec(); + self.file.read(&mut buf[n..]) + } - pub fn get(&self) -> Value { - self.enter(|ctx| ctx.get()) + pub fn read_until(&mut self, end: u8) -> Result> { + let mut buffer = [0; 1024]; + loop { + if let Some(i) = self.pending.iter().position(|x| *x == end) { + let head = self.pending[..i + 1].to_vec(); + let rest = self.pending[i + 1..].to_vec(); + self.pending = rest; + return Ok(head); + } + let n = self.file.read(&mut buffer)?; + let slice = &buffer[..n]; + self.pending.extend_from_slice(slice); + } + } } -} -pub struct MonitorContext(PhantomData<()>); + impl Deref for Buffered { + type Target = File; -impl MonitorContext { - pub fn get(&self) -> Value { - Function::symbolic("get").call_with_current_continuation() + fn deref(&self) -> &Self::Target { + &self.file + } } - pub fn set(&mut self, value: impl Into) -> Value { - Function::symbolic("set") - .apply(value) - .call_with_current_continuation() + impl DerefMut for Buffered { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.file + } } +} - pub fn wait(&self) { - Function::symbolic("wait").call_with_current_continuation(); - } +pub use buf::Buffered; + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => {{ + use ::core::fmt::Write; + ::core::write!(File::STDOUT, $($arg)*).unwrap(); + }}; } -impl Default for Monitor { - fn default() -> Self { - Self::new() - } +#[macro_export] +macro_rules! println { + () => { + { + use ::core::fmt::Write; + ::core::writeln!(File::STDOUT).unwrap() + } + }; + ($($arg:tt)*) => { + { + use ::core::fmt::Write; + ::core::writeln!(File::STDOUT, $($arg)*).unwrap() + } + }; } -impl Drop for Monitor { - fn drop(&mut self) { - Function::symbolic("close") - .apply(self.fd) - .call_with_current_continuation(); - } +#[macro_export] +macro_rules! eprint { + ($($arg:tt)*) => {{ + use ::core::fmt::Write; + ::core::write!(File::STDERR).unwrap(); + }}; } -//#[cfg(feature = "allocator")] -//mod buf { -// extern crate alloc; -// use core::ops::{Deref, DerefMut}; -// -// use super::*; -// use alloc::vec::Vec; -// -// #[derive(Clone)] -// pub struct Buffered { -// file: File, -// pending: Vec, -// } -// -// impl Buffered { -// pub fn new(file: File) -> Self { -// Self { -// file, -// pending: Vec::new(), -// } -// } -// -// pub fn read(&mut self, buf: &mut [u8]) -> Result { -// let n = core::cmp::min(buf.len(), self.pending.len()); -// buf[..n].copy_from_slice(&self.pending[..n]); -// self.pending = self.pending[n..].to_vec(); -// self.file.read(&mut buf[n..]) -// } -// -// pub fn read_until(&mut self, end: u8) -> Result> { -// let mut buffer = [0; 1024]; -// loop { -// if let Some(i) = self.pending.iter().position(|x| *x == end) { -// let head = self.pending[..i + 1].to_vec(); -// let rest = self.pending[i + 1..].to_vec(); -// self.pending = rest; -// return Ok(head); -// } -// let n = self.file.read(&mut buffer)?; -// let slice = &buffer[..n]; -// self.pending.extend_from_slice(slice); -// } -// } -// } -// -// impl Deref for Buffered { -// type Target = File; -// -// fn deref(&self) -> &Self::Target { -// &self.file -// } -// } -// -// impl DerefMut for Buffered { -// fn deref_mut(&mut self) -> &mut Self::Target { -// &mut self.file -// } -// } -//} -// -//pub use buf::Buffered; +#[macro_export] +macro_rules! eprintln { + () => { + { + use ::core::fmt::Write; + ::core::writeln!(File::STDERR).unwrap() + } + }; + ($($arg:tt)*) => { + { + use ::core::fmt::Write; + ::core::writeln!(File::STDERR, $($arg)*).unwrap() + } + }; +} diff --git a/user/src/lib.rs b/user/src/lib.rs index 86feb60d..90ece304 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -80,15 +80,15 @@ impl Drop for Ref { } impl Ref { - fn from_raw(idx: u32) -> Self { + pub fn from_raw(idx: u32) -> Self { Ref { idx: Some(idx) } } - fn into_raw(mut self) -> u32 { + pub fn into_raw(mut self) -> u32 { self.idx.take().unwrap() } - fn as_raw(&self) -> u32 { + pub fn as_raw(&self) -> u32 { self.idx.unwrap() } } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index edc2bf2f..0f8b5779 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -26,9 +26,11 @@ nix = { version = "0.30.1", features = ["mman", "socket"] } vsock = "0.5.1" thread-priority = "3.0.0" elevate = "0.6.1" -clap = { version = "4.5.48", features = ["derive"] } +clap = { version = "4.5.48", features = ["derive", "env"] } rlimit = "0.10.2" core_affinity = "0.8.3" +postcard = { version = "1.1.3", features = ["use-std"] } +serde = "1.0.228" [build-dependencies] anyhow = "1.0.97" diff --git a/vmm/build.rs b/vmm/build.rs deleted file mode 100644 index 10be6704..00000000 --- a/vmm/build.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::{env, path::PathBuf}; - -use anyhow::Result; -fn main() -> Result<()> { - let bindings = bindgen::Builder::default() - .header("src/vhost_bindings.h") - .default_enum_style(bindgen::EnumVariation::ModuleConsts) - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .clang_macro_fallback() - .generate() - .expect("Unable to generate bindings"); - - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - bindings - .write_to_file(out_path.join("vhost_bindings.rs")) - .expect("Couldn't write bindings!"); - Ok(()) -} diff --git a/vmm/src/comm.rs b/vmm/src/comm.rs new file mode 100644 index 00000000..61172a54 --- /dev/null +++ b/vmm/src/comm.rs @@ -0,0 +1,153 @@ +use crate::pipe::{ControlPipe, FilePipe, ListenerPipe, StreamPipe}; +use common::protocol::control::PipeData; +use common::BuddyAllocator; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::sync::Arc; + +fn decompose_pipe(pipe: common::pipe::Pipe) -> PipeData { + let (rx, tx) = pipe.into_inner(); + let rx = rx.into_inner(); + let tx = tx.into_inner(); + let (rx_ptr, rx_len) = Arc::into_raw_with_allocator(rx).0.to_raw_parts(); + let rx_ptr = BuddyAllocator.to_offset(rx_ptr); + let (tx_ptr, tx_len) = Arc::into_raw_with_allocator(tx).0.to_raw_parts(); + let tx_ptr = BuddyAllocator.to_offset(tx_ptr); + PipeData { + rx_ptr, + rx_len, + tx_ptr, + tx_len, + } +} + +pub fn control_thread(argv: Vec, mut pipe: ControlPipe) { + use common::protocol::control::*; + loop { + let response = match pipe.recv() { + Request::GetArgs => Response::Args(argv.clone()), + Request::Exit(code) => std::process::exit(code), + Request::Open(path, mode) => { + let f = OpenOptions::new() + .read(mode.read) + .write(mode.write) + .create(mode.create) + .append(mode.append) + .truncate(mode.truncate) + .open(path); + match f { + Ok(f) => { + let (p, q) = common::pipe::pipe(1024); + std::thread::spawn(move || { + let pipe = crate::pipe::GuestPipe::new(q); + file_thread(f, FilePipe::new(pipe)); + }); + Response::Pipe(decompose_pipe(p)) + } + Err(e) => Response::Err(e.kind().into()), + } + } + Request::Mkdir(path) => match std::fs::create_dir_all(&path) { + Ok(()) => Response::Ack, + Err(e) => Response::Err(e.kind().into()), + }, + Request::Listen { ip, port } => { + let listener = TcpListener::bind(SocketAddr::from((ip, port))).unwrap(); + let (p, q) = common::pipe::pipe(1024); + std::thread::spawn(move || { + let pipe = crate::pipe::GuestPipe::new(q); + listener_thread(listener, ListenerPipe::new(pipe)); + }); + Response::Pipe(decompose_pipe(p)) + } + Request::Connect { host, port } => { + let stream = TcpStream::connect((host.as_str(), port)).unwrap(); + let (p, q) = common::pipe::pipe(1024); + std::thread::spawn(move || { + let pipe = crate::pipe::GuestPipe::new(q); + stream_thread(stream, StreamPipe::new(pipe)); + }); + Response::Pipe(decompose_pipe(p)) + } + }; + pipe.send(&response); + } +} + +pub fn file_thread(mut file: File, mut pipe: FilePipe) { + use common::protocol::file::*; + loop { + let response = match pipe.recv() { + Request::Read(len) => { + let mut buf = vec![0; len]; + let len = file.read(&mut buf).unwrap(); + buf.truncate(len); + Response::Bytes(buf) + } + Request::Write(bytes) => { + let len = file.write(&bytes).unwrap(); + Response::Length(len) + } + Request::Seek(whence) => { + let from = match whence { + Whence::Start(x) => SeekFrom::Start(x), + Whence::Current(x) => SeekFrom::Current(x), + Whence::End(x) => SeekFrom::End(x), + }; + let offset = file.seek(from).unwrap(); + Response::Offset(offset) + } + Request::Close => { + pipe.send(&Response::Ack); + return; + } + }; + pipe.send(&response); + } +} + +pub fn listener_thread(listener: TcpListener, mut pipe: ListenerPipe) { + use common::protocol::listener::*; + loop { + let response = match pipe.recv() { + Request::Accept => { + let (stream, _) = listener.accept().unwrap(); + let (p, q) = common::pipe::pipe(1024); + std::thread::spawn(move || { + let pipe = crate::pipe::GuestPipe::new(q); + stream_thread(stream, StreamPipe::new(pipe)); + }); + Response::Pipe(decompose_pipe(p)) + } + Request::Close => { + pipe.send(&Response::Ack); + return; + } + }; + pipe.send(&response); + } +} + +pub fn stream_thread(mut stream: TcpStream, mut pipe: StreamPipe) { + use common::protocol::stream::*; + loop { + let response = match pipe.recv() { + Request::Receive(len) => { + let mut buf = vec![0; len]; + let len = stream.read(&mut buf).unwrap(); + buf.truncate(len); + Response::Bytes(buf) + } + Request::Send(bytes) => { + let len = stream.write(&bytes).unwrap(); + Response::Length(len) + } + Request::Close => { + pipe.send(&Response::Ack); + return; + } + }; + pipe.send(&response); + } +} diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index a76cd09c..98cdc436 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1,11 +1,10 @@ +#![allow(stable_features, unused_features)] #![feature(allocator_api)] #![feature(ptr_metadata)] -#![feature(box_into_inner)] #![feature(str_from_raw_parts)] -#![feature(negative_impls)] #![feature(exitcode_exit_method)] #![feature(cstr_display)] -#![feature(test)] +pub mod comm; +pub mod pipe; pub mod runtime; -pub mod vhost; diff --git a/vmm/src/main.rs b/vmm/src/main.rs index c0219cc3..c90abde3 100644 --- a/vmm/src/main.rs +++ b/vmm/src/main.rs @@ -1,7 +1,3 @@ -#![feature(allocator_api)] -#![feature(thread_sleep_until)] -#![feature(future_join)] - use std::path::PathBuf; use clap::Parser; @@ -10,10 +6,9 @@ use vmm::runtime::Runtime; #[derive(Parser, Debug)] struct Args { kernel: PathBuf, - #[arg(short, long)] + #[arg(short, long, env = "ARCA_SMP")] smp: Option, - #[arg(short, long, default_value = "3")] - cid: usize, + argv: Vec, } fn main() -> anyhow::Result<()> { @@ -22,13 +17,14 @@ fn main() -> anyhow::Result<()> { let args = Args::parse(); let smp = args .smp - // .or_else(|| std::thread::available_parallelism().ok().map(|x| x.get())) + .or_else(|| std::thread::available_parallelism().ok().map(|x| x.get())) .unwrap_or(1); - let cid = args.cid; - let bin = std::fs::read(args.kernel)?; - let mut rt = Runtime::new(cid, smp, 1 << 34, bin.into()); - rt.run(&[]); + let bin = std::fs::read(args.kernel.clone())?; + let mut rt = Runtime::new(smp, 1 << 34, bin.into()); + let mut argv = args.argv; + argv.insert(0, args.kernel.into_os_string().into_string().unwrap()); + rt.run(argv); Ok(()) } diff --git a/vmm/src/pipe.rs b/vmm/src/pipe.rs new file mode 100644 index 00000000..b57c1f21 --- /dev/null +++ b/vmm/src/pipe.rs @@ -0,0 +1,99 @@ +use common::pipe::Pipe as RawPipe; +pub use common::pipe::{Error, Result}; +use std::marker::PhantomData; + +#[derive(Debug)] +pub struct GuestPipe { + inner: RawPipe, +} + +impl GuestPipe { + pub fn new(pipe: RawPipe) -> Self { + Self { inner: pipe } + } + + pub fn read(&mut self, bytes: &mut [u8]) -> Result { + while !self.inner.can_read() { + // self.read_fd.read().unwrap(); + std::thread::yield_now(); + } + self.inner.read(bytes) + } + + pub fn read_exact(&mut self, mut bytes: &mut [u8]) -> Result<()> { + while !bytes.is_empty() { + match self.read(bytes) { + Ok(n) => { + bytes = &mut bytes[n..]; + } + Err(e) => { + return Err(e); + } + } + } + Ok(()) + } + + pub fn write(&mut self, bytes: &[u8]) -> Result { + while !self.inner.can_write() { + // self.write_fd.read().unwrap(); + std::thread::yield_now(); + } + self.inner.write(bytes) + } + + pub fn write_exact(&mut self, mut bytes: &[u8]) -> Result<()> { + while !bytes.is_empty() { + match self.write(bytes) { + Ok(n) => { + bytes = &bytes[n..]; + } + Err(e) => { + return Err(e); + } + } + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct TypedPipe { + pipe: GuestPipe, + _send: PhantomData, + _recv: PhantomData, +} + +impl serde::Deserialize<'a>> TypedPipe { + pub fn new(pipe: GuestPipe) -> Self { + Self { + pipe, + _send: PhantomData, + _recv: PhantomData, + } + } + + pub fn recv(&mut self) -> R { + let mut length = [0; 8]; + self.pipe.read_exact(&mut length).unwrap(); + let length = usize::from_le_bytes(length); + let mut bytes = vec![0; length]; + self.pipe.read_exact(&mut bytes).unwrap(); + postcard::from_bytes(&bytes).unwrap() + } + + pub fn send(&mut self, request: &S) { + let bytes = postcard::to_allocvec(request).unwrap(); + let length = bytes.len().to_le_bytes(); + self.pipe.write_exact(&length).unwrap(); + self.pipe.write_exact(&bytes).unwrap(); + } +} + +pub type ControlPipe = + TypedPipe; +pub type FilePipe = TypedPipe; +pub type ListenerPipe = + TypedPipe; +pub type StreamPipe = + TypedPipe; diff --git a/vmm/src/runtime.rs b/vmm/src/runtime.rs index 3a5b0b00..511cf170 100644 --- a/vmm/src/runtime.rs +++ b/vmm/src/runtime.rs @@ -19,8 +19,6 @@ pub use common::mmap::Mmap; use libc::EFD_NONBLOCK; use vmm_sys_util::eventfd::EventFd; -use crate::vhost::VSockBackend; - const MEM_BASE: u64 = 0x1_0000_0000; fn new_cpu<'scope>( @@ -132,7 +130,7 @@ fn new_cpu<'scope>( vcpu_regs.rdx = args[2]; vcpu_regs.rcx = args[3]; vcpu_regs.r8 = args[4]; - vcpu_regs.r8 = args[5]; + vcpu_regs.r9 = args[5]; vcpu_regs.rflags = 2; vcpu_fd.set_regs(&vcpu_regs).unwrap(); @@ -193,7 +191,13 @@ fn run_cpu(mut vcpu_fd: VcpuFd, elf: &ElfBytes, exit: Arc let args = &[regs.rdi, regs.rsi, regs.rcx, regs.r10, regs.r8, regs.r9]; match code { hypercall::EXIT => { - ExitCode::from(args[0] as u8).exit_process(); + // let code = args[0] as u8; + std::process::exit(args[0] as i32); + // if code == 0 { + // ExitCode::from(args[0] as u8).exit_process(); + // } else { + // ExitCode::from(args[0] as u8).exit_process(); + // } } hypercall::LOG => { let record: *const common::LogRecord = @@ -286,6 +290,14 @@ fn run_cpu(mut vcpu_fd: VcpuFd, elf: &ElfBytes, exit: Arc regs.rax = mem.as_ptr() as u64; } } + hypercall::NOTIFY_READ => { + todo!(); + // read_fd.write(1).unwrap(); + } + hypercall::NOTIFY_WRITE => { + todo!(); + // write_fd.write(1).unwrap(); + } x => unimplemented!("hypercall {x}"), }; vcpu_fd.set_regs(®s).unwrap(); @@ -302,15 +314,6 @@ fn run_cpu(mut vcpu_fd: VcpuFd, elf: &ElfBytes, exit: Arc ); } }, - // VcpuExit::MmioRead(addr, _) => { - // println!("Received an MMIO Read Request for the address {addr:#x}.",); - // } - // VcpuExit::MmioWrite(addr, data) => { - // println!( - // "Received an MMIO Write Request to the address {:#x}: {:x}.", - // addr, data[0] - // ); - // } r => { let error = format!("{r:?}"); let regs = vcpu_fd.get_regs().unwrap(); @@ -328,13 +331,12 @@ fn run_cpu(mut vcpu_fd: VcpuFd, elf: &ElfBytes, exit: Arc pub struct Runtime { kvm: Kvm, vm: VmFd, - vsock: VSockBackend, cores: usize, elf: Arc<[u8]>, } impl Runtime { - pub fn new(cid: usize, cores: usize, ram: usize, elf: Arc<[u8]>) -> Self { + pub fn new(cores: usize, ram: usize, elf: Arc<[u8]>) -> Self { let kvm = Kvm::new().unwrap(); let vm = kvm.create_vm().unwrap(); vm.create_irq_chip().unwrap(); @@ -376,12 +378,9 @@ impl Runtime { }) .unwrap(); - let vsock = VSockBackend::new(cid as u64, 1024, kick, call).unwrap(); - let mut x = Self { kvm, vm, - vsock, cores, elf: elf.clone(), }; @@ -448,8 +447,7 @@ impl Runtime { } } - pub fn run(&mut self, args: &[usize]) { - self.vsock.set_running(true).unwrap(); + pub fn run(&mut self, argv: Vec) { let elf = ElfBytes::::minimal_parse(&self.elf) .expect("could not read kernel elf file"); @@ -457,14 +455,29 @@ impl Runtime { let allocator_raw = Box::into_raw_with_allocator(Box::new_in(allocator_raw, BuddyAllocator)).0; - // let args = args.to_vec_in(BuddyAllocator); - let mut inner_args = Vec::new_in(BuddyAllocator); - let vsock_meta = Box::new_in(self.vsock.metadata(), BuddyAllocator); - inner_args.push(BuddyAllocator.to_offset(Box::into_raw_with_allocator(vsock_meta).0)); - inner_args.extend_from_slice(args); - std::thread::scope(|s| { let mut cpus = vec![]; + + let (p, q) = common::pipe::pipe(8192); + // let read = EventFd::new(0).unwrap(); + // let write = EventFd::new(0).unwrap(); + // let read_fd = read.try_clone().unwrap(); + // let write_fd = write.try_clone().unwrap(); + let comm = s.spawn(move || { + crate::comm::control_thread( + argv, + crate::pipe::ControlPipe::new(crate::pipe::GuestPipe::new(q)), + ); + }); + + let (rx, tx) = p.into_inner(); + let rx = rx.into_inner(); + let tx = tx.into_inner(); + let (rxp, rxn) = Arc::into_raw_with_allocator(rx).0.to_raw_parts(); + let rxp = BuddyAllocator.to_offset(rxp); + let (txp, txn) = Arc::into_raw_with_allocator(tx).0.to_raw_parts(); + let txp = BuddyAllocator.to_offset(txp); + for i in 0..self.cores { let vcpu_fd = self.vm.create_vcpu(i as u64).unwrap(); // TODO: ensure all needed features are present and disable unneeded ones @@ -482,20 +495,20 @@ impl Runtime { vcpu_fd.set_msrs(&msrs).unwrap(); let allocator_raw_offset = BuddyAllocator.to_offset(allocator_raw); - assert!(!inner_args.is_empty()); - let inner_args_offset = BuddyAllocator.to_offset(inner_args.as_ptr()); cpus.push(new_cpu( i, s, vcpu_fd, + // read.try_clone().unwrap(), + // write.try_clone().unwrap(), &elf, &[ self.cores as u64, allocator_raw_offset as u64, - inner_args.len() as u64, - inner_args_offset as u64, - 0, - 0, + rxp as u64, + rxn as u64, + txp as u64, + txn as u64, ], &kvm_cpuid, )); @@ -503,10 +516,7 @@ impl Runtime { for cpu in cpus { cpu.join().unwrap(); } + comm.join().unwrap(); }); } - - pub fn cid(&self) -> u64 { - self.vsock.cid() - } } diff --git a/vmm/src/vhost.rs b/vmm/src/vhost.rs deleted file mode 100644 index b04b3158..00000000 --- a/vmm/src/vhost.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::{ - alloc::{Allocator, Global, Layout}, - ffi::CStr, - fs::File, - os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, - ptr::NonNull, -}; - -use common::{ - vhost::{VSockMetadata, VirtQueueMetadata}, - BuddyAllocator, -}; - -use anyhow::{anyhow, Result}; -use vmm_sys_util::eventfd::EventFd; - -mod bindings { - #![allow(non_upper_case_globals)] - #![allow(non_camel_case_types)] - #![allow(unused)] - - include!(concat!(env!("OUT_DIR"), "/vhost_bindings.rs")); -} - -macro_rules! check_syscall { - ($e: expr) => {{ - let result = $e; - if result == -1 { - let errno = libc::__errno_location().read(); - let error = CStr::from_ptr(libc::strerror(errno)); - log::error!("system call returned {errno}"); - Err(anyhow::anyhow!( - "system call {} failed: {}", - stringify!($e), - error.display() - )) - } else { - Ok(()) - } - }}; -} - -pub struct VSockBackend { - fd: OwnedFd, - cid: u64, - rx: VirtQueue, - tx: VirtQueue, - _kick: EventFd, - _call: EventFd, -} - -impl VSockBackend { - pub fn new(cid: u64, descriptors: usize, kick: EventFd, call: EventFd) -> Result { - let file = File::options() - .read(true) - .write(true) - .open("/dev/vhost-vsock") - .expect("could not open /dev/vhost-vsock"); - let owned = unsafe { - let raw = file.into_raw_fd(); - OwnedFd::from_raw_fd(raw) - }; - - unsafe { - check_syscall!(libc::ioctl( - owned.as_raw_fd(), - bindings::VHOST_SET_OWNER as u64, - ))?; - - check_syscall!(libc::ioctl( - owned.as_raw_fd(), - bindings::VHOST_VSOCK_SET_GUEST_CID as u64, - &cid, - ))?; - - let mut features: u64 = 0; - check_syscall!(libc::ioctl( - owned.as_raw_fd(), - bindings::VHOST_GET_FEATURES as u64, - &mut features - ))?; - - if features & (1 << 1) == 0 { - return Err(anyhow!("This host does not support seqpacket vsock.")); - } - if features & (1 << 2) != 0 { - return Err(anyhow!("This host does not support stream vsock.")); - } - - features = (1 << 1) // supports seqpacket - | (1 << 28) // supports indirect descriptors - | (1 << 32); - - check_syscall!(libc::ioctl( - owned.as_raw_fd(), - bindings::VHOST_SET_FEATURES as u64, - &features - ))?; - - let layout = Layout::new::() - .extend(Layout::new::()) - .unwrap() - .0; - let ptr = Global.allocate_zeroed(layout).unwrap(); - - let ptr: *mut bindings::vhost_memory = - std::mem::transmute(&raw mut (*NonNull::as_ptr(ptr))[0]); - let mut memory = Box::from_raw(ptr); - memory.nregions = 1; - let regions = memory.regions.as_mut_slice(1); - regions[0] = bindings::vhost_memory_region { - guest_phys_addr: 0, - memory_size: BuddyAllocator.len() as u64, - userspace_addr: BuddyAllocator.base() as u64, - flags_padding: 0, - }; - - check_syscall!(libc::ioctl( - owned.as_raw_fd(), - bindings::VHOST_SET_MEM_TABLE as u64, - memory - ))?; - } - - let mut rx = VirtQueue::new(descriptors); - let mut tx = VirtQueue::new(descriptors); - - rx.attach(owned.as_fd(), VirtQueueType::Receive, &kick, &call)?; - tx.attach(owned.as_fd(), VirtQueueType::Transmit, &kick, &call)?; - - Ok(Self { - fd: owned, - cid, - rx, - tx, - _kick: kick, - _call: call, - }) - } - - pub fn cid(&self) -> u64 { - self.cid - } - - pub fn set_running(&mut self, running: bool) -> Result<()> { - unsafe { - let running: std::ffi::c_int = if running { 1 } else { 0 }; - check_syscall!(libc::ioctl( - self.fd.as_raw_fd(), - bindings::VHOST_VSOCK_SET_RUNNING as u64, - &running - ))?; - } - Ok(()) - } - - pub fn metadata(&self) -> VSockMetadata { - VSockMetadata { - rx: self.rx.metadata(), - tx: self.tx.metadata(), - } - } -} - -impl Drop for VSockBackend { - fn drop(&mut self) { - self.set_running(false).unwrap(); - } -} - -#[repr(u32)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum VirtQueueType { - Receive = 0, - Transmit = 1, -} - -#[derive(Clone, Debug)] -struct VirtQueue { - pub descriptors: usize, - pub desc: Box<[u8], BuddyAllocator>, - pub used: Box<[u8], BuddyAllocator>, - pub avail: Box<[u8], BuddyAllocator>, -} - -impl VirtQueue { - pub fn new(descriptors: usize) -> Self { - let new_buffer = |size: usize| { - let slice = Box::new_zeroed_slice_in(2 * size, BuddyAllocator); - unsafe { slice.assume_init() } - }; - let desc = new_buffer(descriptors * 16); - let used = new_buffer(descriptors * 8 + 6); - let avail = new_buffer(descriptors * 2 + 6); - - VirtQueue { - descriptors, - desc, - used, - avail, - } - } - - pub fn metadata(&self) -> VirtQueueMetadata { - let desc = (&raw const *self.desc).to_raw_parts(); - let used = (&raw const *self.used).to_raw_parts(); - let avail = (&raw const *self.avail).to_raw_parts(); - - let desc = BuddyAllocator.to_offset(desc.0); - let used = BuddyAllocator.to_offset(used.0); - let avail = BuddyAllocator.to_offset(avail.0); - - VirtQueueMetadata { - descriptors: self.descriptors, - desc, - used, - avail, - } - } - - pub fn attach( - &mut self, - fd: BorrowedFd, - qtype: VirtQueueType, - kick: &EventFd, - call: &EventFd, - ) -> Result<()> { - unsafe { - let vring_addr = bindings::vhost_vring_addr { - index: qtype as u32, - flags: 0, - desc_user_addr: self.desc.as_ptr() as u64, - used_user_addr: self.used.as_ptr() as u64, - avail_user_addr: self.avail.as_ptr() as u64, - log_guest_addr: 0, // logging is disabled - }; - - check_syscall!(libc::ioctl( - fd.as_raw_fd(), - bindings::VHOST_SET_VRING_ADDR as u64, - &vring_addr - ))?; - - let vring_num = bindings::vhost_vring_state { - index: qtype as u32, - num: self.descriptors as u32, - }; - check_syscall!(libc::ioctl( - fd.as_raw_fd(), - bindings::VHOST_SET_VRING_NUM as u64, - &vring_num - ))?; - - let vring_file = bindings::vhost_vring_file { - index: qtype as u32, - fd: kick.as_raw_fd(), - }; - check_syscall!(libc::ioctl( - fd.as_raw_fd(), - bindings::VHOST_SET_VRING_KICK as u64, - &vring_file - ))?; - - let vring_file = bindings::vhost_vring_file { - index: qtype as u32, - fd: call.as_raw_fd(), - }; - check_syscall!(libc::ioctl( - fd.as_raw_fd(), - bindings::VHOST_SET_VRING_CALL as u64, - &vring_file - ))?; - } - Ok(()) - } -} diff --git a/vmm/src/vhost_bindings.h b/vmm/src/vhost_bindings.h deleted file mode 100644 index 60854afe..00000000 --- a/vmm/src/vhost_bindings.h +++ /dev/null @@ -1,3 +0,0 @@ -#include -#include -#include