diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2279e15 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "r -p xtask --" diff --git a/Cargo.lock b/Cargo.lock index 0c53369..d6b4d51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,43 +3,100 @@ version = 3 [[package]] -name = "ahash" -version = "0.8.3" +name = "addr2line" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" dependencies = [ "cfg-if", + "const-random", + "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] -name = "allocator-api2" -version = "0.2.15" +name = "argh" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" - -[[package]] -name = "ariadne" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" dependencies = [ - "unicode-width", - "yansi", + "argh_derive", + "argh_shared", ] [[package]] -name = "beef" -version = "0.5.2" +name = "argh_derive" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn 2.0.38", +] [[package]] -name = "bytemuck" -version = "1.13.1" +name = "argh_shared" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -48,212 +105,315 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "convert_case" -version = "0.4.0" +name = "color-eyre" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "delegate" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d358e0ec5c59a5e1603b933def447096886121660fc680dc1e64a0753981fe3c" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", ] [[package]] -name = "derive_more" -version = "0.99.17" +name = "color-spantrace" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "const-random" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "11df32a13d7892ec42d51d3d175faba5211ffe13ed25d4fb348ac9e9ce835593" dependencies = [ - "ahash", + "const-random-macro", ] [[package]] -name = "hashbrown" -version = "0.14.0" +name = "const-random-macro" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "ahash", - "allocator-api2", + "getrandom", + "once_cell", + "tiny-keccak", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "hbasm" version = "0.1.0" dependencies = [ - "ariadne", - "bytemuck", - "hashbrown 0.14.0", - "hbbytecode", - "lasso", - "literify", - "logos", "paste", + "rhai", + "with_builtin_macros", ] [[package]] name = "hbbytecode" version = "0.1.0" +dependencies = [ + "with_builtin_macros", +] [[package]] name = "hbvm" version = "0.1.0" dependencies = [ - "delegate", - "derive_more", - "hashbrown 0.13.2", "hbbytecode", - "log", - "paste", - "static_assertions", ] [[package]] -name = "lasso" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2" -dependencies = [ - "ahash", - "hashbrown 0.13.2", -] - -[[package]] -name = "literify" +name = "hbvm_aos_on_linux" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e4d365df794ed78b4ce1061886f82eae7afa8e3a98ce4c4b0bfd0c777b1175" dependencies = [ - "litrs", - "proc-macro2", - "quote", + "hbvm", + "nix", ] [[package]] -name = "litrs" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f17c3668f3cc1132437cdadc93dab05e52d592f06948d3f64828430c36e4a70" +name = "hbxrt" +version = "0.1.0" dependencies = [ - "proc-macro2", + "hbvm", + "nix", ] [[package]] -name = "log" -version = "0.4.17" +name = "indenter" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] -name = "logos" -version = "0.13.0" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ - "logos-derive", + "adler", ] [[package]] -name = "logos-codegen" -version = "0.13.0" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax", - "syn 2.0.18", + "bitflags", + "cfg-if", + "libc", ] [[package]] -name = "logos-derive" -version = "0.13.0" +name = "num-traits" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ - "logos-codegen", + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "rhai" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "206cee941730eaf90a22c84235b25193df661393688162e15551164f92f09eca" dependencies = [ - "semver", + "ahash", + "bitflags", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", ] [[package]] -name = "semver" -version = "1.0.17" +name = "rhai_codegen" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "853977598f084a492323fe2f7896b4100a86284ee8473612de60021ea341310f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] [[package]] name = "static_assertions" @@ -274,9 +434,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -284,16 +444,76 @@ dependencies = [ ] [[package]] -name = "unicode-ident" -version = "1.0.9" +name = "thread_local" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] [[package]] -name = "unicode-width" -version = "0.1.10" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" @@ -302,7 +522,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "yansi" -version = "0.5.1" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "with_builtin_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "argh", + "color-eyre", + "once_cell", +] + +[[package]] +name = "zerocopy" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ffc046c9f849405a42c87e82e2f2f861d1f0a06b855910c76c2bd1e87be900c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246c000cfc5f942bac7ff99fde24a9ebb589d92e024bc758c6c733c15a02a73e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] diff --git a/Cargo.toml b/Cargo.toml index 7fc1ade..02f5350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["hbasm", "hbbytecode", "hbvm"] +resolver = "2" +members = ["hbasm", "hbbytecode", "hbvm", "hbvm_aos_on_linux", "hbxrt", "xtask"] diff --git a/c-abi.md b/c-abi.md new file mode 100644 index 0000000..4a830e0 --- /dev/null +++ b/c-abi.md @@ -0,0 +1,29 @@ +# C ABI (proposal) + +## C datatypes +| C Type | Description | Size (B) | +|:------------|:-------------------------|-------------:| +| char | Character / byte | 8 | +| short | Short integer | 16 | +| int | Integer | 32 | +| long | Long integer | 64 | +| long long | Long long integer | 64 | +| T* | Pointer | 64 | +| float | Single-precision float | 32 | +| double | Double-precision float | 64 | +| long double | Extended-precision float | **Bikeshed** | + +## Registers +| Register | ABI Name | Description | Saver | +|:---------|:---------|:---------------|:-------| +| `r0` | — | Zero register | N/A | +| `r1` | `ra` | Return address | Caller | +| `r2` | `sp` | Stack pointer | Callee | +| `r3` | `tp` | Thread pointer | N/A | + +**TODO:** Parameters + +**TODO:** Saved + +**TODO:** Temp + diff --git a/examples/asm/hello-name.hba b/examples/asm/hello-name.hba new file mode 100644 index 0000000..ee5f92a --- /dev/null +++ b/examples/asm/hello-name.hba @@ -0,0 +1,100 @@ +jmp entry + +puts: +-- Write string to console +-- r2: [IN] *const u8 String pointer +-- r3: [IN] usize String length + + li8 r1, 0x1 -- Write syscall + brc r2, r3, 2 -- Copy parameters + li8 r2, 0x1 -- STDOUT + eca + + jal r0, r31, 0 + +gets: +-- Read string until end of buffer or LF +-- r2: [IN] *mut u8 Buffer +-- r3: [IN] usize Buffer length + + -- Register allocations: + -- r33: *mut u8 Buffer end + -- r34: u8 Immediate char + -- r35: u8 Const [0x0A = LF] + + li8 r35, 0x0A + add64 r33, r2, r3 + + -- Setup syscall + li8 r2, 0x1 -- Stdin + cp r3, r2 + li8 r4, 0x1 -- Read one char + + jeq r3, r33, end + loop: + li8 r1, 0x1 -- Read syscall + eca + addi64 r3, r3, 1 + ld r34, r3, 0, 1 + jeq r34, r35, end + jne r3, r33, loop + + end: + -- Set copied amount + sub64 r1, r33, r3 + addi64 r1, -1 + jal r0, r31, 0 + +alloc-pages: +-- Allocate pages +-- r1: [OUT] *mut u8 Pointer to page +-- r2: [IN] u16 Page count + + muli16 r3, r2, 4096 -- page count + li8 r1, 0x9 -- mmap syscall + li8 r2, 0x0 -- no address set, kernel chosen + li8 r4, 0x2 -- PROT_WRITE + li8 r5, 0x20 -- MAP_ANONYMOUS + li64 r6, -1 -- Doesn't map file + li8 r7, 0x0 -- Doesn't map file + eca + + jal r0, r31, 0 + +entry: +-- Program entrypoint + + -- Register allocations: + -- r32: *mut u8 Buffer + -- r36: usize Read buffer length + + -- Allocate one page (4096 KiB) + li8 r2, 1 + jal r31, 0, alloc-pages + cp r32, r1 + + -- Print message + lra16 r2, r0, #enter-your-name + li8 r3, 17 + jal r31, r0, puts + + -- Read name + cp r2, r32 + li16 r3, 4096 + jal r31, r0, gets + cp r36, r1 + + -- Print your name is + lra16 r2, r0, #your-name-is + li8 r3, 15 + jal r31, r0, puts + + -- And now print the name + cp r2, r32 + cp r3, r36 + jal r31, r0, puts + + tx + +#enter-your-name: "Enter your name: " +#your-name-is : "\nYour name is: " \ No newline at end of file diff --git a/examples/bytecode/addition.hbf b/examples/bytecode/addition.hbf new file mode 100644 index 0000000..38ffdea Binary files /dev/null and b/examples/bytecode/addition.hbf differ diff --git a/examples/bytecode/linux-hello.hbf b/examples/bytecode/linux-hello.hbf new file mode 100644 index 0000000..1b962c9 Binary files /dev/null and b/examples/bytecode/linux-hello.hbf differ diff --git a/hbasm/Cargo.toml b/hbasm/Cargo.toml index bd9e98e..3cac566 100644 --- a/hbasm/Cargo.toml +++ b/hbasm/Cargo.toml @@ -4,19 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -ariadne = "0.3" -bytemuck = "1.13" -hashbrown = "0.14" -hbbytecode = { path = "../hbbytecode" } -literify = "0.1" -paste = "1.0" - -[dependencies.lasso] -version = "0.7" -default-features = false -features = ["no-std"] - -[dependencies.logos] -version = "0.13" -default-features = false -features = ["export_derive"] +paste = "1.0" +rhai = "1.16" +with_builtin_macros = "0.0.3" diff --git a/hbasm/assets/serial_driver.hbasm b/hbasm/assets/serial_driver.hbasm deleted file mode 100644 index 13bcb3a..0000000 --- a/hbasm/assets/serial_driver.hbasm +++ /dev/null @@ -1,18 +0,0 @@ -jmp r0, start -start: - jmp r0, init_serial_port - --- Uses r20 to set the port -init_serial_port: - add r20, r30, r10 - li r20, 00 - - -- outb(PORT + 1, 0x00); // Disable all interrupts - -- outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) - -- outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud - -- outb(PORT + 1, 0x00); // (hi byte) - -- outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit - -- outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold - -- outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set - -- outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip - -- outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte) \ No newline at end of file diff --git a/hbasm/examples/ableos/main.rhai b/hbasm/examples/ableos/main.rhai new file mode 100644 index 0000000..0302a2c --- /dev/null +++ b/hbasm/examples/ableos/main.rhai @@ -0,0 +1,13 @@ +import "hbasm/examples/ableos/std" as std; + +fn main(){ + std::Error(":+)"); + std::Warn("Your mom fell in a well!"); + std::Info("Hello, world!"); + std::Debug("ABC"); + std::Trace("Trace Deez"); + + tx(); +} + +main(); \ No newline at end of file diff --git a/hbasm/examples/ableos/std.rhai b/hbasm/examples/ableos/std.rhai new file mode 100644 index 0000000..5ff9dee --- /dev/null +++ b/hbasm/examples/ableos/std.rhai @@ -0,0 +1,24 @@ +fn ipc_send(buffer_id, mem_addr, length){ + // set the ecall + li8(r1, 3); + // Set the buffer ID to be the BufferID + li64(r2, buffer_id); + lra(r3, r0, mem_addr); + // set the length + li64(r4, length); + // ecall + eca(); +} + +private fn log(log_level, string){ + let str = data::str(string); + ipc_send(1, str, str.len); +} + +fn Error(string) {log(0, string);} +fn Warn(string) {log(1, string);} +fn Info(string) {log(2, string);} +// Due to rhai limitations this cannot be debug +// because of this all of the log levels are upper case +fn Debug(string) {log(3, string);} +fn Trace(string) {log(4, string);} diff --git a/hbasm/examples/hello-linux.rhai b/hbasm/examples/hello-linux.rhai new file mode 100644 index 0000000..326a80a --- /dev/null +++ b/hbasm/examples/hello-linux.rhai @@ -0,0 +1,9 @@ +let hello = data::str("Hello, world!"); + +li8 (r1, 1); // Write syscall +li8 (r2, 1); // Stdout FD +lra16 (r3, r0, hello); // String buffer +li8 (r4, hello.len); // String length +eca (); // System call + +tx (); // End program \ No newline at end of file diff --git a/hbasm/examples/macros.rhai b/hbasm/examples/macros.rhai new file mode 100644 index 0000000..afd7bd2 --- /dev/null +++ b/hbasm/examples/macros.rhai @@ -0,0 +1,33 @@ +li8(r1, 69); +li8(r2, 0); + +if_eq(r1, r2, + || puts("Equals!"), + || puts("Not equals!"), +); + + +tx(); // END OF MAIN + +/// Inline function – write text to stdout +fn puts(string) { + let d = data::str(string); + li8 (r1, 1); // Write syscall + li8 (r2, 1); // Stdout handle + lra16 (r3, r0, d); + li64 (r4, d.len); + eca (); +} + +fn if_eq(a, b, thenblk, elseblk) { + let elselbl = declabel(); + let endlbl = declabel(); + + jne(a, b, elselbl); + thenblk.call(); + jmp16(endlbl); + + elselbl.here(); + elseblk.call(); + endlbl.here(); +} \ No newline at end of file diff --git a/hbasm/src/data.rs b/hbasm/src/data.rs new file mode 100644 index 0000000..a95c255 --- /dev/null +++ b/hbasm/src/data.rs @@ -0,0 +1,85 @@ +use rhai::{CustomType, Engine, ImmutableString}; + +use { + crate::{object::SymbolRef, SharedObject}, + rhai::Module, +}; + +macro_rules! gen_data_insertions { + ($module:expr, $obj:expr, [$($ty:ident),* $(,)?] $(,)?) => {{ + let (module, obj) = ($module, $obj); + $({ + let obj = ::std::rc::Rc::clone(obj); + let hash = module.set_native_fn(stringify!($ty), move |arr: ::rhai::Array| { + let obj = &mut *obj.borrow_mut(); + let symbol = obj.symbol($crate::object::Section::Data); + + obj.sections + .data + .reserve(arr.len() * ::std::mem::size_of::<$ty>()); + + for item in arr { + obj.sections.data.extend( + match item.as_int() { + Ok(num) => $ty::try_from(num).map_err(|_| "i64".to_owned()), + Err(ty) => Err(ty.to_owned()), + } + .map_err(|err| { + ::rhai::EvalAltResult::ErrorMismatchDataType( + stringify!($ty).to_owned(), + err, + ::rhai::Position::NONE, + ) + })? + .to_le_bytes(), + ); + } + + Ok(DataRef { + symbol, + len: obj.sections.data.len() - symbol.0, + }) + }); + + module.update_fn_namespace(hash, ::rhai::FnNamespace::Global); + })* + }}; +} + +#[derive(Clone, Copy, Debug)] +pub struct DataRef { + pub symbol: SymbolRef, + pub len: usize, +} + +impl CustomType for DataRef { + fn build(mut builder: rhai::TypeBuilder) { + builder + .with_name("DataRef") + .with_get("symbol", |this: &mut Self| this.symbol) + .with_get("len", |this: &mut Self| this.len as u64 as i64); + } +} + +pub fn module(engine: &mut Engine, obj: SharedObject) -> Module { + let mut module = Module::new(); + gen_data_insertions!(&mut module, &obj, [i8, i16, i32, i64]); + + { + let hash = module.set_native_fn("str", move |s: ImmutableString| { + let obj = &mut *obj.borrow_mut(); + let symbol = obj.symbol(crate::object::Section::Data); + + obj.sections.data.extend(s.as_bytes()); + Ok(DataRef { + symbol, + len: s.len(), + }) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + engine.build_type::(); + module +} diff --git a/hbasm/src/ins.rs b/hbasm/src/ins.rs new file mode 100644 index 0000000..590e23d --- /dev/null +++ b/hbasm/src/ins.rs @@ -0,0 +1,226 @@ +use { + crate::object::Object, + rhai::{FnNamespace, Module}, + std::{cell::RefCell, rc::Rc}, +}; + +mod optypes { + use { + crate::{ + label::UnboundLabel, + object::{Object, RelocKey, RelocType, SymbolRef}, + }, + rhai::{Dynamic, EvalAltResult, ImmutableString, Position}, + }; + + pub type R = u8; + pub type B = i8; + pub type H = i16; + pub type W = i32; + pub type D = i64; + + pub type A = Dynamic; + pub type O = Dynamic; + pub type P = Dynamic; + + pub fn insert_reloc( + obj: &mut Object, + ty: RelocType, + val: &Dynamic, + ) -> Result<(), EvalAltResult> { + match () { + _ if val.is::() => { + obj.relocation(RelocKey::Symbol(val.clone_cast::().0), ty) + } + _ if val.is::() => { + obj.relocation(RelocKey::Symbol(val.clone_cast::().0), ty) + } + _ if val.is::() => { + obj.relocation(RelocKey::Symbol(val.clone_cast::().symbol.0), ty) + } + _ if val.is_string() => { + obj.relocation(RelocKey::Label(val.clone_cast::()), ty) + } + _ if val.is_int() => { + let int = val.clone_cast::(); + match ty { + RelocType::Rel32 => obj.sections.text.extend((int as i32).to_le_bytes()), + RelocType::Rel16 => obj.sections.text.extend((int as i16).to_le_bytes()), + RelocType::Abs64 => obj.sections.text.extend(int.to_le_bytes()), + } + } + _ => { + return Err(EvalAltResult::ErrorMismatchDataType( + "SybolRef, UnboundLabel, String or Int".to_owned(), + val.type_name().to_owned(), + Position::NONE, + )) + } + } + + Ok(()) + } + + macro_rules! gen_insert { + (le_bytes: [$($lety:ident),* $(,)?]) => { + macro_rules! insert { + $(($thing:expr, $obj: expr, $lety) => { + $obj.sections.text.extend($thing.to_le_bytes()); + };)* + + ($thing:expr, $obj:expr, A) => { + $crate::ins::optypes::insert_reloc( + $obj, + $crate::object::RelocType::Abs64, + $thing + )? + }; + ($thing:expr, $obj:expr, O) => { + $crate::ins::optypes::insert_reloc( + $obj, + $crate::object::RelocType::Rel32, + $thing + )? + }; + ($thing:expr, $obj:expr, P) => { + $crate::ins::optypes::insert_reloc( + $obj, + $crate::object::RelocType::Rel16, + $thing + )? + }; + } + }; + } + + gen_insert!(le_bytes: [R, B, H, W, D]); + + #[allow(clippy::single_component_path_imports)] + pub(super) use insert; + + use crate::data::DataRef; +} + +mod rity { + pub use super::optypes::{A, O, P, R}; + pub type B = i64; + pub type H = i64; + pub type W = i64; + pub type D = i64; +} + +mod generic { + use {crate::object::Object, rhai::EvalAltResult}; + + pub(super) fn convert_op(from: A) -> Result + where + B: TryFrom, + >::Error: std::error::Error + Sync + Send + 'static, + { + B::try_from(from).map_err(|e| { + EvalAltResult::ErrorSystem("Data conversion error".to_owned(), Box::new(e)) + }) + } + + macro_rules! gen_ins { + ($($($name:ident : $ty:ty),*;)*) => { + paste::paste! { + $(#[inline] + pub fn [<$($ty:lower)*>]( + obj: &mut Object, + opcode: u8, + $($name: $crate::ins::optypes::$ty),*, + ) -> Result<(), EvalAltResult> { + obj.sections.text.push(opcode); + $($crate::ins::optypes::insert!(&$name, obj, $ty);)* + Ok(()) + })* + + macro_rules! gen_ins_fn { + $(($obj:expr, $opcode:expr, [<$($ty)*>]) => { + move |$($name: $crate::ins::rity::$ty),*| { + $crate::ins::generic::[<$($ty:lower)*>]( + &mut *$obj.borrow_mut(), + $opcode, + $( + $crate::ins::generic::convert_op::< + _, + $crate::ins::optypes::$ty + >($name)? + ),* + )?; + Ok(()) + } + };)* + + ($obj:expr, $opcode:expr, N) => { + move || { + $crate::ins::generic::n(&mut *$obj.borrow_mut(), $opcode); + Ok(()) + } + }; + } + } + }; + } + + #[inline] + pub fn n(obj: &mut Object, opcode: u8) { + obj.sections.text.push(opcode); + } + + gen_ins! { + o0: R, o1: R; + o0: R, o1: R, o2: R; + o0: R, o1: R, o2: R, o3: R; + o0: R, o1: R, o2: B; + o0: R, o1: R, o2: H; + o0: R, o1: R, o2: W; + o0: R, o1: R, o2: D; + o0: R, o1: B; + o0: R, o1: H; + o0: R, o1: W; + o0: R, o1: D; + o0: R, o1: R, o2: A; + o0: R, o1: R, o2: A, o3: H; + o0: R, o1: R, o2: O, o3: H; + o0: R, o1: R, o2: P, o3: H; + o0: R, o1: R, o2: O; + o0: R, o1: R, o2: P; + o0: O; + o0: P; + } + + #[allow(clippy::single_component_path_imports)] + pub(super) use gen_ins_fn; +} + +macro_rules! instructions { + ( + ($module:expr, $obj:expr $(,)?) + { $($opcode:expr, $mnemonic:ident, $ops:ident, $doc:literal;)* } + ) => {{ + let (module, obj) = ($module, $obj); + $({ + let obj = Rc::clone(&obj); + let hash = module.set_native_fn( + paste::paste!(stringify!([<$mnemonic:lower>])), + generic::gen_ins_fn!( + obj, + $opcode, + $ops + ) + ); + + module.update_fn_namespace(hash, FnNamespace::Global); + })* + }}; +} + +pub fn setup(module: &mut Module, obj: Rc>) { + with_builtin_macros::with_builtin! { + let $spec = include_from_root!("../hbbytecode/instructions.in") in { + instructions!((module, obj) { $spec }); + } + } +} diff --git a/hbasm/src/label.rs b/hbasm/src/label.rs new file mode 100644 index 0000000..d154737 --- /dev/null +++ b/hbasm/src/label.rs @@ -0,0 +1,76 @@ +use { + crate::SharedObject, + rhai::{Engine, ImmutableString, Module}, +}; + +#[derive(Clone, Copy, Debug)] +pub struct UnboundLabel(pub usize); + +pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) { + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("label", move || { + let mut obj = object.borrow_mut(); + let symbol = obj.symbol(crate::object::Section::Text); + Ok(symbol) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("label", move |label: ImmutableString| { + let mut obj = object.borrow_mut(); + let symbol = obj.symbol(crate::object::Section::Text); + obj.labels.insert(label, symbol.0); + + Ok(symbol) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("declabel", move || { + let mut obj = object.borrow_mut(); + + let index = obj.symbols.len(); + obj.symbols.push(None); + + Ok(UnboundLabel(index)) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("declabel", move |label: ImmutableString| { + let mut obj = object.borrow_mut(); + + let index = obj.symbols.len(); + obj.symbols.push(None); + obj.labels.insert(label, index); + + Ok(UnboundLabel(index)) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + module.set_native_fn("here", move |label: UnboundLabel| { + let mut obj = object.borrow_mut(); + obj.symbols[label.0] = Some(crate::object::SymbolEntry { + location: crate::object::Section::Text, + offset: obj.sections.text.len(), + }); + + Ok(()) + }); + } + + engine.register_type_with_name::("UnboundLabel"); +} diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 391b251..440de14 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -1,58 +1,45 @@ -#![no_std] +mod data; +mod ins; +mod label; +mod linker; +mod object; -extern crate alloc; +use { + object::Object, + rhai::{Engine, Module}, + std::{cell::RefCell, rc::Rc}, +}; -mod macros; +type SharedObject = Rc>; -use {alloc::vec::Vec, hashbrown::HashSet}; +pub fn assembler( + linkout: &mut impl std::io::Write, + loader: impl FnOnce(&mut Engine) -> Result<(), Box>, +) -> Result<(), Box> { + let mut engine = Engine::new(); + let mut module = Module::new(); + let obj = Rc::new(RefCell::new(Object::default())); + ins::setup(&mut module, Rc::clone(&obj)); + label::setup(&mut engine, &mut module, Rc::clone(&obj)); -#[derive(Default)] -pub struct Assembler { - pub buf: Vec, - pub sub: HashSet, -} - -macros::impl_both!( - bbbb(p0: R, p1: R, p2: R, p3: R) - => [DIR, DIRF, FMAF], - bbb(p0: R, p1: R, p2: R) - => [ADD, SUB, MUL, AND, OR, XOR, SL, SR, SRS, CMP, CMPU, /*BRC,*/ ADDF, SUBF, MULF], - bbdh(p0: R, p1: R, p2: I, p3: u16) - => [LD, ST], - bbd(p0: R, p1: R, p2: I) - => [ADDI, MULI, ANDI, ORI, XORI, SLI, SRI, SRSI, CMPI, CMPUI, - BMC, JAL, JEQ, JNE, JLT, JGT, JLTU, JGTU, ADDFI, MULFI], - bb(p0: R, p1: R) - => [NEG, NOT, CP, SWA, NEGF, ITF, FTI], - bd(p0: R, p1: I) - => [LI], - n() - => [NOP, ECALL], -); - -impl Assembler { - // Special-cased - #[inline(always)] - pub fn i_brc(&mut self, p0: u8, p1: u8, p2: u8) { - self.i_param_bbb(hbbytecode::opcode::BRC, p0, p1, p2) + // Registers + for n in 0_u8..=255 { + module.set_var(format!("r{n}"), n); } -} -pub trait Imm { - fn insert(&self, asm: &mut Assembler); -} + module.set_native_fn("reg", |n: i64| { + Ok(u8::try_from(n).map_err(|_| { + rhai::EvalAltResult::ErrorRuntime("Invalid register value".into(), rhai::Position::NONE) + })?) + }); -macro_rules! impl_imm_le_bytes { - ($($ty:ty),* $(,)?) => { - $( - impl Imm for $ty { - #[inline(always)] - fn insert(&self, asm: &mut Assembler) { - asm.buf.extend(self.to_le_bytes()); - } - } - )* - }; -} + module.set_native_fn("as_i64", |n: u8| Ok(n as i64)); -impl_imm_le_bytes!(u64, i64, f64); + let datamod = Rc::new(data::module(&mut engine, SharedObject::clone(&obj))); + engine.register_global_module(Rc::new(module)); + engine.register_static_module("data", datamod); + engine.register_type_with_name::("SymbolRef"); + loader(&mut engine)?; + linker::link(obj, linkout)?; + Ok(()) +} diff --git a/hbasm/src/linker.rs b/hbasm/src/linker.rs new file mode 100644 index 0000000..d31c73b --- /dev/null +++ b/hbasm/src/linker.rs @@ -0,0 +1,35 @@ +use { + crate::{ + object::{RelocKey, RelocType, Section}, + SharedObject, + }, + std::io::Write, +}; + +pub fn link(object: SharedObject, out: &mut impl Write) -> std::io::Result<()> { + let obj = &mut *object.borrow_mut(); + for (&loc, entry) in &obj.relocs { + let value = match &entry.key { + RelocKey::Symbol(sym) => obj.symbols[*sym], + RelocKey::Label(label) => obj.symbols[obj.labels[label]], + } + .ok_or_else(|| std::io::Error::other("Invalid symbol"))?; + + let offset = match value.location { + Section::Text => value.offset, + Section::Data => value.offset + obj.sections.text.len(), + }; + + match entry.ty { + RelocType::Rel32 => obj.sections.text[loc..loc + 4] + .copy_from_slice(&((offset as isize - loc as isize) as i32).to_le_bytes()), + RelocType::Rel16 => obj.sections.text[loc..loc + 2] + .copy_from_slice(&((offset as isize - loc as isize) as i16).to_le_bytes()), + RelocType::Abs64 => obj.sections.text[loc..loc + 8] + .copy_from_slice(&(offset as isize - loc as isize).to_le_bytes()), + } + } + + out.write_all(&obj.sections.text)?; + out.write_all(&obj.sections.data) +} diff --git a/hbasm/src/macros/asm.rs b/hbasm/src/macros/asm.rs deleted file mode 100644 index fc8366c..0000000 --- a/hbasm/src/macros/asm.rs +++ /dev/null @@ -1,71 +0,0 @@ -macro_rules! impl_asm_opcodes { - ( - $generic:ident - ($($param_i:ident: $param_ty:ty),*) - => [] - ) => {}; - - ( - $generic:ident - ($($param_i:ident: $param_ty:ty),*) - => [$opcode:ident, $($rest:tt)*] - ) => { - paste::paste! { - #[inline(always)] - pub fn [](&mut self, $($param_i: $param_ty),*) { - self.$generic(hbbytecode::opcode::$opcode, $($param_i),*) - } - } - - macros::asm::impl_asm_opcodes!( - $generic($($param_i: $param_ty),*) - => [$($rest)*] - ); - }; -} - -macro_rules! impl_asm_insert { - ($self:expr, $id:ident, I) => { - Imm::insert(&$id, $self) - }; - - ($self:expr, $id:ident, $_:ident) => { - $self.buf.extend($id.to_le_bytes()) - }; -} - -macro_rules! impl_asm { - ( - $( - $ityn:ident - ($($param_i:ident: $param_ty:ident),* $(,)?) - => [$($opcode:ident),* $(,)?], - )* - ) => { - paste::paste! { - $( - fn [](&mut self, opcode: u8, $($param_i: macros::asm::ident_map_ty!($param_ty)),*) { - self.buf.push(opcode); - $(macros::asm::impl_asm_insert!(self, $param_i, $param_ty);)* - } - - macros::asm::impl_asm_opcodes!( - []($($param_i: macros::asm::ident_map_ty!($param_ty)),*) - => [$($opcode,)*] - ); - )* - } - }; -} - -#[rustfmt::skip] -macro_rules! ident_map_ty { - (R) => { u8 }; - (I) => { impl Imm }; - ($id:ident) => { $id }; -} - -pub(crate) use {ident_map_ty, impl_asm, impl_asm_opcodes}; - -#[allow(clippy::single_component_path_imports)] -pub(crate) use impl_asm_insert; diff --git a/hbasm/src/macros/mod.rs b/hbasm/src/macros/mod.rs deleted file mode 100644 index 6fd1e58..0000000 --- a/hbasm/src/macros/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod asm; -pub mod text; - -macro_rules! impl_both { - ($($tt:tt)*) => { - impl Assembler { - $crate::macros::asm::impl_asm!($($tt)*); - } - - $crate::macros::text::gen_text!($($tt)*); - }; -} - -pub(crate) use impl_both; diff --git a/hbasm/src/macros/text.rs b/hbasm/src/macros/text.rs deleted file mode 100644 index 93acfae..0000000 --- a/hbasm/src/macros/text.rs +++ /dev/null @@ -1,219 +0,0 @@ -macro_rules! gen_text { - ( - $( - $ityn:ident - ($($param_i:ident: $param_ty:ident),* $(,)?) - => [$($opcode:ident),* $(,)?], - )* - ) => { - pub mod text { - use { - crate::{ - Assembler, - macros::text::*, - }, - hashbrown::HashMap, - lasso::{Key, Rodeo, Spur}, - logos::{Lexer, Logos, Span}, - }; - - paste::paste!(literify::literify! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Logos)] - #[logos(extras = Rodeo)] - #[logos(skip r"[ \t\t]+")] - #[logos(skip r"-- .*")] - pub enum Token { - $($(#[token(~([<$opcode:lower>]), |_| hbbytecode::opcode::[<$opcode:upper>])])*)* - #[token("brc", |_| hbbytecode::opcode::BRC)] // Special-cased - Opcode(u8), - - #[regex("[0-9]+", |l| l.slice().parse().ok())] - #[regex( - "-[0-9]+", - |lexer| { - Some(u64::from_ne_bytes(lexer.slice().parse::().ok()?.to_ne_bytes())) - }, - )] Integer(u64), - - #[regex( - "r[0-9]+", - |lexer| match lexer.slice()[1..].parse() { - Ok(n) => Some(n), - _ => None - }, - )] Register(u8), - - #[regex( - r"\p{XID_Start}\p{XID_Continue}*:", - |lexer| lexer.extras.get_or_intern(&lexer.slice()[..lexer.slice().len() - 1]), - )] Label(Spur), - - #[regex( - r"\p{XID_Start}\p{XID_Continue}*", - |lexer| lexer.extras.get_or_intern(lexer.slice()), - )] Symbol(Spur), - - #[token("\n")] - #[token(";")] ISep, - #[token(",")] PSep, - } - }); - - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub enum ErrorKind { - UnexpectedToken, - InvalidToken, - UnexpectedEnd, - InvalidSymbol, - } - - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct Error { - pub kind: ErrorKind, - pub span: Span, - } - - pub fn assemble(asm: &mut Assembler, code: &str) -> Result<(), Error> { - pub struct TextAsm<'a> { - asm: &'a mut Assembler, - lexer: Lexer<'a, Token>, - symloc: HashMap, - } - - impl<'a> TextAsm<'a> { - fn next(&mut self) -> Result { - match self.lexer.next() { - Some(Ok(t)) => Ok(t), - Some(Err(())) => Err(ErrorKind::InvalidToken), - None => Err(ErrorKind::UnexpectedEnd), - } - } - - #[inline(always)] - fn run(&mut self) -> Result<(), ErrorKind> { - loop { - match self.lexer.next() { - Some(Ok(Token::Opcode(op))) => { - match op { - $( - $(hbbytecode::opcode::$opcode)|* => paste::paste!({ - param_extract_itm!(self, $($param_i: $param_ty),*); - self.asm.[](op, $($param_i),*); - }), - )* - // Special-cased - hbbytecode::opcode::BRC => { - param_extract_itm!( - self, - p0: R, - p1: R, - p2: u8 - ); - - self.asm.i_param_bbb(op, p0, p1, p2); - } - _ => unreachable!(), - } - } - Some(Ok(Token::Label(lbl))) => { - self.symloc.insert(lbl, self.asm.buf.len()); - } - Some(Ok(Token::ISep)) => (), - Some(Ok(_)) => return Err(ErrorKind::UnexpectedToken), - Some(Err(())) => return Err(ErrorKind::InvalidToken), - None => return Ok(()), - } - } - } - } - - let mut asm = TextAsm { - asm, - lexer: Token::lexer(code), - symloc: HashMap::default(), - }; - - asm.run() - .map_err(|kind| Error { kind, span: asm.lexer.span() })?; - - for &loc in &asm.asm.sub { - let val = asm.symloc - .get( - &Spur::try_from_usize(bytemuck::pod_read_unaligned::(&asm.asm.buf[loc..loc+core::mem::size_of::()]) as _) - .unwrap() - ) - .ok_or(Error { kind: ErrorKind::InvalidSymbol, span: 0..0 })? - .to_le_bytes(); - - asm.asm.buf[loc..] - .iter_mut() - .zip(val) - .for_each(|(dst, src)| *dst = src); - } - - Ok(()) - } - - enum InternalImm { - Const(u64), - Named(Spur), - } - - impl $crate::Imm for InternalImm { - #[inline] - fn insert(&self, asm: &mut Assembler) { - match self { - Self::Const(a) => a.insert(asm), - Self::Named(a) => { - asm.sub.insert(asm.buf.len()); - asm.buf.extend((a.into_usize() as u64).to_le_bytes()); - }, - } - } - } - } - }; -} - -macro_rules! extract_pat { - ($self:expr, $pat:pat) => { - let $pat = $self.next()? - else { return Err(ErrorKind::UnexpectedToken) }; - }; -} - -macro_rules! extract { - ($self:expr, R, $id:ident) => { - extract_pat!($self, Token::Register($id)); - }; - - ($self:expr, I, $id:ident) => { - let $id = match $self.next()? { - Token::Integer(a) => InternalImm::Const(a), - Token::Symbol(a) => InternalImm::Named(a), - _ => return Err(ErrorKind::UnexpectedToken), - }; - }; - - ($self:expr, u8, $id:ident) => { - extract_pat!($self, Token::Integer($id)); - let $id = u8::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; - }; - - ($self:expr, u16, $id:ident) => { - extract_pat!($self, Token::Integer($id)); - let $id = u16::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; - }; -} - -macro_rules! param_extract_itm { - ($self:expr, $($id:ident: $ty:ident)? $(, $($tt:tt)*)?) => { - $(extract!($self, $ty, $id);)? - $( - extract_pat!($self, Token::PSep); - param_extract_itm!($self, $($tt)*); - )? - }; -} - -pub(crate) use {extract, extract_pat, gen_text, param_extract_itm}; diff --git a/hbasm/src/main.rs b/hbasm/src/main.rs index 844f2d6..ca9a258 100644 --- a/hbasm/src/main.rs +++ b/hbasm/src/main.rs @@ -1,55 +1,8 @@ -use std::io::Write; - -use hbasm::Assembler; - -use { - ariadne::{ColorGenerator, Label, Report, ReportKind, Source}, - std::{ - error::Error, - io::{stdin, Read}, - }, -}; - -fn main() -> Result<(), Box> { - let mut code = String::new(); - stdin().read_to_string(&mut code)?; - - let mut assembler = Assembler::default(); - if let Err(e) = hbasm::text::assemble(&mut assembler, &code) { - let mut colors = ColorGenerator::new(); - - let e_code = match e.kind { - hbasm::text::ErrorKind::UnexpectedToken => 1, - hbasm::text::ErrorKind::InvalidToken => 2, - hbasm::text::ErrorKind::UnexpectedEnd => 3, - hbasm::text::ErrorKind::InvalidSymbol => 4, - }; - let message = match e.kind { - hbasm::text::ErrorKind::UnexpectedToken => "This token is not expected!", - hbasm::text::ErrorKind::InvalidToken => "The token is not valid!", - hbasm::text::ErrorKind::UnexpectedEnd => { - "The assembler reached the end of input unexpectedly!" - } - hbasm::text::ErrorKind::InvalidSymbol => { - "This referenced symbol doesn't have a corresponding label!" - } - }; - let a = colors.next(); - - Report::build(ReportKind::Error, "engine_internal", e.span.clone().start) - .with_code(e_code) - .with_message(format!("{:?}", e.kind)) - .with_label( - Label::new(("engine_internal", e.span)) - .with_message(message) - .with_color(a), - ) - .finish() - .eprint(("engine_internal", Source::from(&code))) - .unwrap(); - } else { - std::io::stdout().lock().write_all(&assembler.buf).unwrap(); - } +use std::{io::stdout, path::PathBuf}; +fn main() -> Result<(), Box> { + let path = PathBuf::from(std::env::args().nth(1).ok_or("Missing path")?); + hbasm::assembler(&mut stdout(), |engine| engine.run_file(path))?; + Ok(()) } diff --git a/hbasm/src/object.rs b/hbasm/src/object.rs new file mode 100644 index 0000000..bae43df --- /dev/null +++ b/hbasm/src/object.rs @@ -0,0 +1,77 @@ +use {rhai::ImmutableString, std::collections::HashMap}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Section { + Text, + Data, +} + +#[derive(Clone, Copy, Debug)] +pub struct SymbolEntry { + pub location: Section, + pub offset: usize, +} + +#[derive(Clone, Debug)] +pub enum RelocKey { + Symbol(usize), + Label(ImmutableString), +} + +#[derive(Clone, Copy, Debug)] +pub enum RelocType { + Rel32, + Rel16, + Abs64, +} + +#[derive(Clone, Debug)] +pub struct RelocEntry { + pub key: RelocKey, + pub ty: RelocType, +} + +#[derive(Clone, Debug, Default)] +pub struct Sections { + pub text: Vec, + pub data: Vec, +} + +#[derive(Clone, Debug, Default)] +pub struct Object { + pub sections: Sections, + pub symbols: Vec>, + pub labels: HashMap, + pub relocs: HashMap, +} + +#[derive(Clone, Copy, Debug)] +#[repr(transparent)] +pub struct SymbolRef(pub usize); + +impl Object { + pub fn symbol(&mut self, section: Section) -> SymbolRef { + let section_buf = match section { + Section::Text => &mut self.sections.text, + Section::Data => &mut self.sections.data, + }; + + self.symbols.push(Some(SymbolEntry { + location: section, + offset: section_buf.len(), + })); + + SymbolRef(self.symbols.len() - 1) + } + + pub fn relocation(&mut self, key: RelocKey, ty: RelocType) { + self.relocs + .insert(self.sections.text.len(), RelocEntry { key, ty }); + + self.sections.text.extend(match ty { + RelocType::Rel32 => &[0_u8; 4] as &[u8], + RelocType::Rel16 => &[0; 2], + RelocType::Abs64 => &[0; 8], + }); + } +} diff --git a/hbbytecode/Cargo.toml b/hbbytecode/Cargo.toml index fa9f970..ecfb1db 100644 --- a/hbbytecode/Cargo.toml +++ b/hbbytecode/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "hbbytecode" version = "0.1.0" -edition = "2021" +edition = "2018" [dependencies] +with_builtin_macros = "0.0.3" diff --git a/hbbytecode/hbbytecode.h b/hbbytecode/hbbytecode.h index 307ca92..5ede044 100644 --- a/hbbytecode/hbbytecode.h +++ b/hbbytecode/hbbytecode.h @@ -1,5 +1,8 @@ /* HoleyBytes Bytecode representation in C * Requires C23 compiler or better + * + * Uses MSVC pack pragma extension, + * proved to work with Clang and GNU® GCC™. */ #pragma once @@ -10,15 +13,16 @@ static_assert(CHAR_BIT == 8, "Cursed architectures are not supported"); enum hbbc_Opcode: uint8_t { - hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , hbbc_Op_AND , hbbc_Op_OR , - hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , hbbc_Op_CMP , hbbc_Op_CMPU , - hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , hbbc_Op_MULI , hbbc_Op_ANDI , - hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , hbbc_Op_SRSI , hbbc_Op_CMPI , - hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , hbbc_Op_LD , hbbc_Op_ST , - hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JEQ , hbbc_Op_JNE , hbbc_Op_JLT , - hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , hbbc_Op_ADDF , hbbc_Op_SUBF , - hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , hbbc_Op_ITF , hbbc_Op_FTI , - hbbc_Op_ADDFI , hbbc_Op_MULFI , + hbbc_Op_UN , hbbc_Op_TX , hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , + hbbc_Op_AND , hbbc_Op_OR , hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , + hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , + hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , + hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , + hhbc_Op_LRA , hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_LDR , hhbc_Op_STR , hbbc_Op_BMC , + hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JMPR , hbbc_Op_JAL , hbbc_Op_JALR , hbbc_Op_JEQ , + hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , + hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , + hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , } typedef hbbc_Opcode; static_assert(sizeof(hbbc_Opcode) == 1); @@ -39,11 +43,22 @@ struct hbbc_ParamBBDH typedef hbbc_ParamBBDH; static_assert(sizeof(hbbc_ParamBBDH) == 96 / 8); +struct hbbc_ParamBBWH + { uint8_t _0; uint8_t _1; uint32_t _2; uint16_t _3; } + typedef hbbc_ParamBBWH; + static_assert(sizeof(hbbc_ParamBBWH) == 64 / 8); + + struct hbbc_ParamBBD { uint8_t _0; uint8_t _1; uint64_t _2; } typedef hbbc_ParamBBD; static_assert(sizeof(hbbc_ParamBBD) == 80 / 8); +struct hbbc_ParamBBW + { uint8_t _0; uint8_t _1; uint32_t _2; } + typedef hbbc_ParamBBW; + static_assert(sizeof(hbbc_ParamBBW) == 48 / 8); + struct hbbc_ParamBB { uint8_t _0; uint8_t _1; } typedef hbbc_ParamBB; diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in new file mode 100644 index 0000000..f57ce9f --- /dev/null +++ b/hbbytecode/instructions.in @@ -0,0 +1,120 @@ +0x00, UN, N, "Cause an unreachable code trap" ; +0x01, TX, N, "Termiante execution" ; +0x02, NOP, N, "Do nothing" ; +0x03, ADD8, RRR, "Addition (8b)" ; +0x04, ADD16, RRR, "Addition (16b)" ; +0x05, ADD32, RRR, "Addition (32b)" ; +0x06, ADD64, RRR, "Addition (64b)" ; +0x07, SUB8, RRR, "Subtraction (8b)" ; +0x08, SUB16, RRR, "Subtraction (16b)" ; +0x09, SUB32, RRR, "Subtraction (32b)" ; +0x0A, SUB64, RRR, "Subtraction (64b)" ; +0x0B, MUL8, RRR, "Multiplication (8b)" ; +0x0C, MUL16, RRR, "Multiplication (16b)" ; +0x0D, MUL32, RRR, "Multiplication (32b)" ; +0x0E, MUL64, RRR, "Multiplication (64b)" ; +0x0F, AND, RRR, "Bitand" ; +0x10, OR, RRR, "Bitor" ; +0x11, XOR, RRR, "Bitxor" ; +0x12, SLU8, RRR, "Unsigned left bitshift (8b)" ; +0x13, SLU16, RRR, "Unsigned left bitshift (16b)" ; +0x14, SLU32, RRR, "Unsigned left bitshift (32b)" ; +0x15, SLU64, RRR, "Unsigned left bitshift (64b)" ; +0x16, SRU8, RRR, "Unsigned right bitshift (8b)" ; +0x17, SRU16, RRR, "Unsigned right bitshift (16b)" ; +0x18, SRU32, RRR, "Unsigned right bitshift (32b)" ; +0x19, SRU64, RRR, "Unsigned right bitshift (64b)" ; +0x1A, SRS8, RRR, "Signed right bitshift (8b)" ; +0x1B, SRS16, RRR, "Signed right bitshift (16b)" ; +0x1C, SRS32, RRR, "Signed right bitshift (32b)" ; +0x1D, SRS64, RRR, "Signed right bitshift (64b)" ; +0x1E, CMPU, RRR, "Unsigned comparsion" ; +0x1F, CMPS, RRR, "Signed comparsion" ; +0x20, DIRU8, RRRR, "Merged divide-remainder (unsigned 8b)" ; +0x21, DIRU16, RRRR, "Merged divide-remainder (unsigned 16b)" ; +0x22, DIRU32, RRRR, "Merged divide-remainder (unsigned 32b)" ; +0x23, DIRU64, RRRR, "Merged divide-remainder (unsigned 64b)" ; +0x24, DIRS8, RRRR, "Merged divide-remainder (signed 8b)" ; +0x25, DIRS16, RRRR, "Merged divide-remainder (signed 16b)" ; +0x26, DIRS32, RRRR, "Merged divide-remainder (signed 32b)" ; +0x27, DIRS64, RRRR, "Merged divide-remainder (signed 64b)" ; +0x28, NEG, RR, "Bit negation" ; +0x29, NOT, RR, "Logical negation" ; +0x2A, SXT8, RR, "Sign extend 8b to 64b" ; +0x2B, SXT16, RR, "Sign extend 16b to 64b" ; +0x2C, SXT32, RR, "Sign extend 32b to 64b" ; +0x2D, ADDI8, RRB, "Addition with immediate (8b)" ; +0x2E, ADDI16, RRH, "Addition with immediate (16b)" ; +0x2F, ADDI32, RRW, "Addition with immediate (32b)" ; +0x30, ADDI64, RRD, "Addition with immediate (64b)" ; +0x31, MULI8, RRW, "Multiplication with immediate (8b)" ; +0x32, MULI16, RRH, "Multiplication with immediate (16b)" ; +0x33, MULI32, RRW, "Multiplication with immediate (32b)" ; +0x34, MULI64, RRD, "Multiplication with immediate (64b)" ; +0x35, ANDI, RRD, "Bitand with immediate" ; +0x36, ORI, RRD, "Bitor with immediate" ; +0x37, XORI, RRD, "Bitxor with immediate" ; +0x38, SLUI8, RRB, "Unsigned left bitshift with immedidate (8b)" ; +0x39, SLUI16, RRB, "Unsigned left bitshift with immedidate (16b)"; +0x3A, SLUI32, RRB, "Unsigned left bitshift with immedidate (32b)"; +0x3B, SLUI64, RRB, "Unsigned left bitshift with immedidate (64b)"; +0x3C, SRUI8, RRB, "Unsigned right bitshift with immediate (8b)" ; +0x3D, SRUI16, RRB, "Unsigned right bitshift with immediate (16b)"; +0x3E, SRUI32, RRB, "Unsigned right bitshift with immediate (32b)"; +0x3F, SRUI64, RRB, "Unsigned right bitshift with immediate (64b)"; +0x40, SRSI8, RRB, "Signed right bitshift with immediate" ; +0x41, SRSI16, RRB, "Signed right bitshift with immediate" ; +0x42, SRSI32, RRB, "Signed right bitshift with immediate" ; +0x43, SRSI64, RRB, "Signed right bitshift with immediate" ; +0x44, CMPUI, RRD, "Unsigned compare with immediate" ; +0x45, CMPSI, RRD, "Signed compare with immediate" ; +0x46, CP, RR, "Copy register" ; +0x47, SWA, RR, "Swap registers" ; +0x48, LI8, RB, "Load immediate (8b)" ; +0x49, LI16, RH, "Load immediate (16b)" ; +0x4A, LI32, RW, "Load immediate (32b)" ; +0x4B, LI64, RD, "Load immediate (64b)" ; +0x4C, LRA, RRO, "Load relative address" ; +0x4D, LD, RRAH, "Load from absolute address" ; +0x4E, ST, RRAH, "Store to absolute address" ; +0x4F, LDR, RROH, "Load from relative address" ; +0x50, STR, RROH, "Store to relative address" ; +0x51, BMC, RRH, "Copy block of memory" ; +0x52, BRC, RRB, "Copy register block" ; +0x53, JMP, O, "Relative jump" ; +0x54, JAL, RRO, "Linking relative jump" ; +0x55, JALA, RRA, "Linking absolute jump" ; +0x56, JEQ, RRP, "Branch on equal" ; +0x57, JNE, RRP, "Branch on nonequal" ; +0x58, JLTU, RRP, "Branch on lesser-than (unsigned)" ; +0x59, JGTU, RRP, "Branch on greater-than (unsigned)" ; +0x5A, JLTS, RRP, "Branch on lesser-than (signed)" ; +0x5B, JGTS, RRP, "Branch on greater-than (signed)" ; +0x5C, ECA, N, "Environment call trap" ; +0x5D, EBP, N, "Environment breakpoint" ; +0x5E, FADD32, RRR, "Floating point addition (32b)" ; +0x5F, FADD64, RRR, "Floating point addition (64b)" ; +0x60, FSUB32, RRR, "Floating point subtraction (32b)" ; +0x61, FSUB64, RRR, "Floating point subtraction (64b)" ; +0x62, FMUL32, RRR, "Floating point multiply (32b)" ; +0x63, FMUL64, RRR, "Floating point multiply (64b)" ; +0x64, FDIV32, RRR, "Floating point division (32b)" ; +0x65, FDIV64, RRR, "Floating point division (64b)" ; +0x66, FMA32, RRRR, "Float fused multiply-add (32b)" ; +0x67, FMA64, RRRR, "Float fused multiply-add (64b)" ; +0x68, FINV32, RR, "Float reciprocal (32b)" ; +0x69, FINV64, RR, "Float reciprocal (64b)" ; +0x6A, FCMPLT32, RRR, "Flaot compare less than (32b)" ; +0x6B, FCMPLT64, RRR, "Flaot compare less than (64b)" ; +0x6C, FCMPGT32, RRR, "Flaot compare greater than (32b)" ; +0x6D, FCMPGT64, RRR, "Flaot compare greater than (64b)" ; +0x6E, ITF32, RR, "Int to 32 bit float" ; +0x6F, ITF64, RR, "Int to 64 bit float" ; +0x70, FTI32, RRB, "Float 32 to int" ; +0x71, FTI64, RRB, "Float 64 to int" ; +0x72, FC32T64, RR, "Float 64 to Float 32" ; +0x73, FC64T32, RRB, "Float 32 to Float 64" ; +0x74, LRA16, RRP, "Load relative immediate (16 bit)" ; +0x75, LDR16, RRPH, "Load from relative address (16 bit)" ; +0x76, STR16, RRPH, "Store to relative address (16 bit)" ; +0x77, JMP16, P, "Relative jump (16 bit)" ; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 9b4f60b..80960a0 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -1,107 +1,119 @@ #![no_std] -macro_rules! constmod { - ($vis:vis $mname:ident($repr:ty) { - $(#![doc = $mdoc:literal])? - $($cname:ident = $val:expr $(,$doc:literal)?;)* - }) => { - $(#[doc = $mdoc])? - $vis mod $mname { +use core::convert::TryFrom; + +type OpR = u8; + +type OpA = u64; +type OpO = i32; +type OpP = i16; + +type OpB = u8; +type OpH = u16; +type OpW = u32; +type OpD = u64; + +/// # Safety +/// Has to be valid to be decoded from bytecode. +pub unsafe trait BytecodeItem {} +macro_rules! define_items { + ($($name:ident ($($item:ident),* $(,)?)),* $(,)?) => { + $( + #[derive(Clone, Copy, Debug)] + #[repr(packed)] + pub struct $name($(pub $item),*); + unsafe impl BytecodeItem for $name {} + )* + }; +} + +define_items! { + OpsRR (OpR, OpR ), + OpsRRR (OpR, OpR, OpR ), + OpsRRRR (OpR, OpR, OpR, OpR), + OpsRRB (OpR, OpR, OpB ), + OpsRRH (OpR, OpR, OpH ), + OpsRRW (OpR, OpR, OpW ), + OpsRRD (OpR, OpR, OpD ), + OpsRB (OpR, OpB ), + OpsRH (OpR, OpH ), + OpsRW (OpR, OpW ), + OpsRD (OpR, OpD ), + OpsRRA (OpR, OpR, OpA ), + OpsRRAH (OpR, OpR, OpA, OpH), + OpsRROH (OpR, OpR, OpO, OpH), + OpsRRPH (OpR, OpR, OpP, OpH), + OpsRRO (OpR, OpR, OpO ), + OpsRRP (OpR, OpR, OpP ), + OpsO (OpO, ), + OpsP (OpP, ), + OpsN ( ), +} + +unsafe impl BytecodeItem for u8 {} + +::with_builtin_macros::with_builtin! { + let $spec = include_from_root!("instructions.in") in { + /// Invoke macro with bytecode definition + /// + /// # Format + /// ```text + /// Opcode, Mnemonic, Type, Docstring; + /// ``` + /// + /// # Type + /// ```text + /// Types consist of letters meaning a single field + /// | Type | Size (B) | Meaning | + /// |:-----|:---------|:------------------------| + /// | N | 0 | Empty | + /// | R | 1 | Register | + /// | A | 8 | Absolute address | + /// | O | 4 | Relative address offset | + /// | P | 2 | Relative address offset | + /// | B | 1 | Immediate | + /// | H | 2 | Immediate | + /// | W | 4 | Immediate | + /// | D | 8 | Immediate | + /// ``` + #[macro_export] + macro_rules! invoke_with_def { + ($macro:path) => { + $macro! { $spec } + }; + } + } +} + +macro_rules! gen_opcodes { + ($($opcode:expr, $mnemonic:ident, $_ty:ident, $doc:literal;)*) => { + pub mod opcode { $( - $(#[doc = $doc])? - pub const $cname: $repr = $val; + #[doc = $doc] + pub const $mnemonic: u8 = $opcode; )* } }; } -constmod!(pub opcode(u8) { - //! Opcode constant module +/// Rounding mode +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum RoundingMode { + NearestEven = 0, + Truncate = 1, + Up = 2, + Down = 3, +} - NOP = 0, "N; Do nothing"; +impl TryFrom for RoundingMode { + type Error = (); - ADD = 1, "BBB; #0 ← #1 + #2"; - SUB = 2, "BBB; #0 ← #1 - #2"; - MUL = 3, "BBB; #0 ← #1 × #2"; - AND = 4, "BBB; #0 ← #1 & #2"; - OR = 5, "BBB; #0 ← #1 | #2"; - XOR = 6, "BBB; #0 ← #1 ^ #2"; - SL = 7, "BBB; #0 ← #1 « #2"; - SR = 8, "BBB; #0 ← #1 » #2"; - SRS = 9, "BBB; #0 ← #1 » #2 (signed)"; - CMP = 10, "BBB; #0 ← #1 <=> #2"; - CMPU = 11, "BBB; #0 ← #1 <=> #2 (unsigned)"; - DIR = 12, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - NEG = 13, "BB; #0 ← -#1"; - NOT = 14, "BB; #0 ← !#1"; + fn try_from(value: u8) -> Result { + (value <= 3) + .then(|| unsafe { core::mem::transmute(value) }) + .ok_or(()) + } +} - ADDI = 15, "BBD; #0 ← #1 + imm #2"; - MULI = 16, "BBD; #0 ← #1 × imm #2"; - ANDI = 17, "BBD; #0 ← #1 & imm #2"; - ORI = 18, "BBD; #0 ← #1 | imm #2"; - XORI = 19, "BBD; #0 ← #1 ^ imm #2"; - SLI = 20, "BBD; #0 ← #1 « imm #2"; - SRI = 21, "BBD; #0 ← #1 » imm #2"; - SRSI = 22, "BBD; #0 ← #1 » imm #2 (signed)"; - CMPI = 23, "BBD; #0 ← #1 <=> imm #2"; - CMPUI = 24, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; - - CP = 25, "BB; Copy #0 ← #1"; - SWA = 26, "BB; Swap #0 and #1"; - LI = 27, "BD; #0 ← imm #1"; - LD = 28, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing"; - ST = 29, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing"; - BMC = 30, "BBD; [#0] ← [#1], imm #2 bytes"; - BRC = 31, "BBB; #0 ← #1, imm #2 registers"; - - JAL = 32, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; - JEQ = 33, "BBD; if #0 = #1 → jump imm #2"; - JNE = 34, "BBD; if #0 ≠ #1 → jump imm #2"; - JLT = 35, "BBD; if #0 < #1 → jump imm #2"; - JGT = 36, "BBD; if #0 > #1 → jump imm #2"; - JLTU = 37, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; - JGTU = 38, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; - ECALL = 39, "N; Issue system call"; - - ADDF = 40, "BBB; #0 ← #1 +. #2"; - SUBF = 41, "BBB; #0 ← #1 -. #2"; - MULF = 42, "BBB; #0 ← #1 +. #2"; - DIRF = 43, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - FMAF = 44, "BBBB; #0 ← (#1 * #2) + #3"; - NEGF = 45, "BB; #0 ← -#1"; - ITF = 46, "BB; #0 ← #1 as float"; - FTI = 47, "BB; #0 ← #1 as int"; - - ADDFI = 48, "BBD; #0 ← #1 +. imm #2"; - MULFI = 49, "BBD; #0 ← #1 *. imm #2"; -}); - -#[repr(packed)] -pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8); - -#[repr(packed)] -pub struct ParamBBB(pub u8, pub u8, pub u8); - -#[repr(packed)] -pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16); - -#[repr(packed)] -pub struct ParamBBD(pub u8, pub u8, pub u64); - -#[repr(packed)] -pub struct ParamBB(pub u8, pub u8); - -#[repr(packed)] -pub struct ParamBD(pub u8, pub u64); - -/// # Safety -/// Has to be valid to be decoded from bytecode. -pub unsafe trait OpParam {} -unsafe impl OpParam for ParamBBBB {} -unsafe impl OpParam for ParamBBB {} -unsafe impl OpParam for ParamBBDH {} -unsafe impl OpParam for ParamBBD {} -unsafe impl OpParam for ParamBB {} -unsafe impl OpParam for ParamBD {} -unsafe impl OpParam for u64 {} -unsafe impl OpParam for () {} +invoke_with_def!(gen_opcodes); diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index a6b6a50..e8302d2 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -6,11 +6,10 @@ edition = "2021" [profile.release] lto = true +[features] +default = ["alloc"] +alloc = [] +nightly = [] + [dependencies] -delegate = "0.9" -derive_more = "0.99" -hashbrown = "0.13" -hbbytecode.path = "../hbbytecode" -log = "0.4" -paste = "1.0" -static_assertions = "1.0" +hbbytecode.path = "../hbbytecode" diff --git a/hbvm/assets/add.hb b/hbvm/assets/add.hb new file mode 100644 index 0000000..321efa5 Binary files /dev/null and b/hbvm/assets/add.hb differ diff --git a/hbvm/assets/celsius_converter.hb b/hbvm/assets/celsius_converter.hb new file mode 100644 index 0000000..77f97e9 Binary files /dev/null and b/hbvm/assets/celsius_converter.hb differ diff --git a/hbvm/assets/ecall.hb b/hbvm/assets/ecall.hb new file mode 100644 index 0000000..9183271 Binary files /dev/null and b/hbvm/assets/ecall.hb differ diff --git a/hbvm/assets/inf_loop.hb b/hbvm/assets/inf_loop.hb index 65168fa..0a4fe61 100644 Binary files a/hbvm/assets/inf_loop.hb and b/hbvm/assets/inf_loop.hb differ diff --git a/hbvm/assets/memory.hb b/hbvm/assets/memory.hb new file mode 100644 index 0000000..9fe3575 Binary files /dev/null and b/hbvm/assets/memory.hb differ diff --git a/hbvm/fuzz/.gitignore b/hbvm/fuzz/.gitignore new file mode 100644 index 0000000..615384a --- /dev/null +++ b/hbvm/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +artifacts +corpus +coverage +Cargo.lock \ No newline at end of file diff --git a/hbvm/fuzz/Cargo.toml b/hbvm/fuzz/Cargo.toml new file mode 100644 index 0000000..42566bd --- /dev/null +++ b/hbvm/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "hbvm-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.hbvm] +path = ".." + +[dependencies.hbbytecode] +path = "../../hbbytecode" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "vm" +path = "fuzz_targets/vm.rs" +test = false +doc = false diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs new file mode 100644 index 0000000..aa6d7cc --- /dev/null +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -0,0 +1,82 @@ +#![no_main] + +use { + hbvm::{ + mem::{ + softpaging::{ + paging::{PageTable, Permission}, + HandlePageFault, PageSize, SoftPagedMem, + }, + Address, MemoryAccessReason, + }, + Vm, + }, + libfuzzer_sys::fuzz_target, +}; + +fuzz_target!(|data: &[u8]| { + let mut vm = unsafe { + Vm::<_, 16384>::new( + SoftPagedMem::<_, true> { + pf_handler: TestTrapHandler, + program: data, + root_pt: Box::into_raw(Default::default()), + icache: Default::default(), + }, + Address::new(4), + ) + }; + + // Alloc and map some memory + let pages = [ + alloc_and_map(&mut vm.memory, 0), + alloc_and_map(&mut vm.memory, 4096), + ]; + + // Run VM + let _ = vm.run(); + + // Unmap and dealloc the memory + for (i, page) in pages.into_iter().enumerate() { + unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); + } + + let _ = unsafe { Box::from_raw(vm.memory.root_pt) }; +}); + +fn alloc_and_map(memory: &mut SoftPagedMem, at: u64) -> *mut u8 { + let ptr = Box::into_raw(Box::::default()).cast(); + unsafe { + memory + .map(ptr, Address::new(at), Permission::Write, PageSize::Size4K) + .unwrap() + }; + ptr +} + +fn unmap_and_dealloc(memory: &mut SoftPagedMem, ptr: *mut u8, from: u64) { + memory.unmap(Address::new(from)).unwrap(); + let _ = unsafe { Box::from_raw(ptr.cast::()) }; +} + +#[repr(align(4096))] +struct Page([u8; 4096]); +impl Default for Page { + fn default() -> Self { + unsafe { std::mem::MaybeUninit::zeroed().assume_init() } + } +} + +struct TestTrapHandler; +impl HandlePageFault for TestTrapHandler { + fn page_fault( + &mut self, + _: MemoryAccessReason, + _: &mut PageTable, + _: Address, + _: PageSize, + _: *mut u8, + ) -> bool { + false + } +} diff --git a/hbvm/src/bmc.rs b/hbvm/src/bmc.rs new file mode 100644 index 0000000..21d30b6 --- /dev/null +++ b/hbvm/src/bmc.rs @@ -0,0 +1,135 @@ +//! Block memory copier state machine + +use { + super::{mem::MemoryAccessReason, Memory, VmRunError}, + crate::mem::Address, + core::{mem::MaybeUninit, task::Poll}, +}; + +/// Buffer size (defaults to 4 KiB, a smallest page size on most platforms) +const BUF_SIZE: usize = 4096; + +/// Buffer of possibly uninitialised bytes, aligned to [`BUF_SIZE`] +#[repr(align(4096))] +struct AlignedBuf([MaybeUninit; BUF_SIZE]); + +/// State for block memory copy +pub struct BlockCopier { + /// Source address + src: Address, + /// Destination address + dst: Address, + /// How many buffer sizes to copy? + n_buffers: usize, + /// …and what remainds after? + rem: usize, +} + +impl BlockCopier { + /// Construct a new one + #[inline] + pub fn new(src: Address, dst: Address, count: usize) -> Self { + Self { + src, + dst, + n_buffers: count / BUF_SIZE, + rem: count % BUF_SIZE, + } + } + + /// Copy one block + /// + /// # Safety + /// - Same as for [`Memory::load`] and [`Memory::store`] + pub unsafe fn poll(&mut self, memory: &mut impl Memory) -> Poll> { + // Safety: Assuming uninit of array of MaybeUninit is sound + let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); + + // We have at least one buffer size to copy + if self.n_buffers != 0 { + if let Err(e) = act( + memory, + self.src, + self.dst, + buf.0.as_mut_ptr().cast(), + BUF_SIZE, + ) { + return Poll::Ready(Err(e)); + } + + // Bump source and destination address + self.src += BUF_SIZE; + self.dst += BUF_SIZE; + + self.n_buffers -= 1; + + return if self.n_buffers + self.rem == 0 { + // If there is nothing left, we are done + Poll::Ready(Ok(())) + } else { + // Otherwise let's advice to run it again + Poll::Pending + }; + } + + if self.rem != 0 { + if let Err(e) = act( + memory, + self.src, + self.dst, + buf.0.as_mut_ptr().cast(), + self.rem, + ) { + return Poll::Ready(Err(e)); + } + } + + Poll::Ready(Ok(())) + } +} + +/// Load to buffer and store from buffer +#[inline] +unsafe fn act( + memory: &mut impl Memory, + src: Address, + dst: Address, + buf: *mut u8, + count: usize, +) -> Result<(), BlkCopyError> { + // Load to buffer + memory + .load(src, buf, count) + .map_err(|super::mem::LoadError(addr)| BlkCopyError { + access_reason: MemoryAccessReason::Load, + addr, + })?; + + // Store from buffer + memory + .store(dst, buf, count) + .map_err(|super::mem::StoreError(addr)| BlkCopyError { + access_reason: MemoryAccessReason::Store, + addr, + })?; + + Ok(()) +} + +/// Error occured when copying a block of memory +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BlkCopyError { + /// Kind of access + access_reason: MemoryAccessReason, + /// VM Address + addr: Address, +} + +impl From for VmRunError { + fn from(value: BlkCopyError) -> Self { + match value.access_reason { + MemoryAccessReason::Load => Self::LoadAccessEx(value.addr), + MemoryAccessReason::Store => Self::StoreAccessEx(value.addr), + } + } +} diff --git a/hbvm/src/float/aarch64.rs b/hbvm/src/float/aarch64.rs new file mode 100644 index 0000000..034105d --- /dev/null +++ b/hbvm/src/float/aarch64.rs @@ -0,0 +1,66 @@ +use {core::arch::asm, hbbytecode::RoundingMode}; + +macro_rules! fnsdef { + {$( + $(#[$attr:meta])* + $vis:vis fn $name:ident[$inreg:ident -> $outreg:ident]($from:ident -> $to:ident): $ins:literal; + )*} => {$( + $(#[$attr])* + $vis fn $name(val: $from, mode: RoundingMode) -> $to { + let result: $to; + unsafe { + set_rounding_mode(mode); + asm!( + $ins, + out($outreg) result, + in($inreg) val, + ); + default_rounding_mode(); + } + result + } + )*}; +} + +fnsdef! { + /// Convert [`f64`] to [`f32`] with chosen rounding mode + pub fn conv64to32[vreg -> vreg](f64 -> f32): "fcvt {:s}, {:d}"; + + /// Convert [`f32`] to [`i64`] with chosen rounding mode + pub fn f32toint[vreg -> reg](f32 -> i64): "fcvtzs {}, {:s}"; + + /// Convert [`f64`] to [`i64`] with chosen rounding mode + pub fn f64toint[vreg -> reg](f64 -> i64): "fcvtzs {}, {:d}"; +} + +/// Set rounding mode +/// +/// # Safety +/// - Do not call if rounding mode isn't [`RoundingMode::NearestEven`] +/// - Do not perform any Rust FP operations until reset using +/// [`default_rounding_mode`], you have to rely on inline assembly +#[inline(always)] +unsafe fn set_rounding_mode(mode: RoundingMode) { + if mode == RoundingMode::NearestEven { + return; + } + + let fpcr: u64; + asm!("mrs {}, fpcr", out(reg) fpcr); + + let fpcr = fpcr & !(0b11 << 22) + | (match mode { + RoundingMode::NearestEven => 0b00, + RoundingMode::Truncate => 0b11, + RoundingMode::Up => 0b01, + RoundingMode::Down => 0b10, + }) << 22; + + asm!("msr fpcr, {}", in(reg) fpcr); +} + +#[inline(always)] +unsafe fn default_rounding_mode() { + // I hope so much it gets optimised + set_rounding_mode(RoundingMode::NearestEven); +} diff --git a/hbvm/src/float/mod.rs b/hbvm/src/float/mod.rs new file mode 100644 index 0000000..f8b3ea6 --- /dev/null +++ b/hbvm/src/float/mod.rs @@ -0,0 +1,17 @@ +macro_rules! arch_specific { + { + $({$($cfg:tt)*} : $mod:ident;)* + } => {$( + #[cfg($($cfg)*)] + mod $mod; + + #[cfg($($cfg)*)] + pub use $mod::*; + )*}; +} + +arch_specific! { + {target_arch = "x86_64" }: x86_64; + {target_arch = "riscv64"}: riscv64; + {target_arch = "aarch64"}: aarch64; +} diff --git a/hbvm/src/float/riscv64.rs b/hbvm/src/float/riscv64.rs new file mode 100644 index 0000000..79ba8ef --- /dev/null +++ b/hbvm/src/float/riscv64.rs @@ -0,0 +1,59 @@ +use {core::arch::asm, hbbytecode::RoundingMode}; + +macro_rules! roundm_op_litmode_internal { + ($ins:literal, $in:expr, $out:expr => $outy:ident, $mode:literal $(,)?) => { + asm!( + concat!($ins, " {}, {}, ", $mode), + out($outy) $out, + in(freg) $in, + ) + }; +} + +macro_rules! gen_roundm_op_litmode { + [$($ty:ident => $reg:ident),* $(,)?] => { + macro_rules! roundm_op_litmode { + $( + ($ins:literal, $in:expr, $out:expr => $ty, $mode:literal) => { + roundm_op_litmode_internal!($ins, $in, $out => $reg, $mode) + }; + )* + } + }; +} + +gen_roundm_op_litmode![ + f32 => freg, + f64 => freg, + i64 => reg, +]; + +macro_rules! fnsdef { + {$( + $(#[$attr:meta])* + $vis:vis fn $name:ident($from:ident -> $to:ident): $ins:literal; + )*} => {$( + $(#[$attr])* + $vis fn $name(val: $from, mode: RoundingMode) -> $to { + let result: $to; + unsafe { + match mode { + RoundingMode::NearestEven => roundm_op_litmode!($ins, val, result => $to, "rne"), + RoundingMode::Truncate => roundm_op_litmode!($ins, val, result => $to, "rtz"), + RoundingMode::Up => roundm_op_litmode!($ins, val, result => $to, "rup"), + RoundingMode::Down => roundm_op_litmode!($ins, val, result => $to, "rdn"), + } + } + result + } + )*}; +} + +fnsdef! { + /// Convert [`f64`] to [`f32`] with chosen rounding mode + pub fn conv64to32(f64 -> f32): "fcvt.s.d"; + /// Convert [`f32`] to [`i64`] with chosen rounding mode + pub fn f32toint(f32 -> i64): "fcvt.l.s"; + /// Convert [`f64`] to [`i64`] with chosen rounding mode + pub fn f64toint(f64 -> i64): "fcvt.l.d"; +} diff --git a/hbvm/src/float/x86_64.rs b/hbvm/src/float/x86_64.rs new file mode 100644 index 0000000..fd83bf0 --- /dev/null +++ b/hbvm/src/float/x86_64.rs @@ -0,0 +1,71 @@ +use { + core::arch::{asm, x86_64 as arin}, + hbbytecode::RoundingMode, +}; + +macro_rules! gen_op { + [$($ty:ident => $reg:ident),* $(,)?] => { + macro_rules! op { + $( + ($ins:literal, $in:expr, $out:expr => $ty) => { + asm!(concat!($ins, " {}, {}"), out($reg) $out, in(xmm_reg) $in) + }; + )* + } + }; +} + +gen_op![ + f32 => xmm_reg, + f64 => xmm_reg, + i64 => reg, +]; + +macro_rules! fnsdef { + {$( + $(#[$attr:meta])* + $vis:vis fn $name:ident($from:ident -> $to:ident): $ins:literal; + )*} => {$( + $(#[$attr])* + $vis fn $name(val: $from, mode: RoundingMode) -> $to { + let result: $to; + unsafe { + set_rounding_mode(mode); + op!($ins, val, result => $to); + default_rounding_mode(); + } + result + } + )*}; +} + +fnsdef! { + /// Convert [`f64`] to [`f32`] with chosen rounding mode + pub fn conv64to32(f64 -> f32): "cvtsd2ss"; + /// Convert [`f32`] to [`i64`] with chosen rounding mode + pub fn f32toint(f32 -> i64): "cvttss2si"; + /// Convert [`f64`] to [`i64`] with chosen rounding mode + pub fn f64toint(f64 -> i64): "cvttsd2si"; +} + +/// Set rounding mode +/// +/// # Safety +/// - Do not call if rounding mode isn't [`RoundingMode::NearestEven`] +/// - Do not perform any Rust FP operations until reset using +/// [`default_rounding_mode`], you have to rely on inline assembly +#[inline(always)] +unsafe fn set_rounding_mode(mode: RoundingMode) { + arin::_MM_SET_ROUNDING_MODE(match mode { + RoundingMode::NearestEven => return, + RoundingMode::Truncate => arin::_MM_ROUND_TOWARD_ZERO, + RoundingMode::Up => arin::_MM_ROUND_UP, + RoundingMode::Down => arin::_MM_ROUND_DOWN, + }) +} + +#[inline(always)] +fn default_rounding_mode() { + // SAFETY: This is said to be the default mode, do not trust me. + unsafe { arin::_MM_SET_ROUNDING_MODE(arin::_MM_ROUND_NEAREST) }; +} diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 8255984..9a6c034 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -1,7 +1,140 @@ -#![doc = include_str!("../README.md")] -#![no_std] +//! HoleyBytes Virtual Machine +//! +//! # Alloc feature +//! - Enabled by default +//! - Provides mapping / unmapping, as well as [`Default`] and [`Drop`] +//! implementations for soft-paged memory implementation +// # General safety notice: +// - Validation has to assure there is 256 registers (r0 - r255) +// - Instructions have to be valid as specified (values and sizes) +// - Mapped pages should be at least 4 KiB + +#![no_std] +#![cfg_attr(feature = "nightly", feature(fn_align))] + +#[cfg(feature = "alloc")] extern crate alloc; -pub mod validate; -pub mod vm; +pub mod mem; +pub mod value; + +mod bmc; +mod float; +mod utils; +mod vmrun; + +use { + bmc::BlockCopier, + mem::{Address, Memory}, + value::{Value, ValueVariant}, +}; + +/// HoleyBytes Virtual Machine +pub struct Vm { + /// Holds 256 registers + /// + /// Writing to register 0 is considered undefined behaviour + /// in terms of HoleyBytes program execution + pub registers: [Value; 256], + + /// Memory implementation + pub memory: Mem, + + /// Program counter + pub pc: Address, + + /// Program timer + timer: usize, + + /// Saved block copier + copier: Option, +} + +impl Vm +where + Mem: Memory, +{ + /// Create a new VM with program and trap handler + /// + /// # Safety + /// Program code has to be validated + pub unsafe fn new(memory: Mem, entry: Address) -> Self { + Self { + registers: [Value::from(0_u64); 256], + memory, + pc: entry, + timer: 0, + copier: None, + } + } + + /// Read register + #[inline(always)] + pub fn read_reg(&self, n: u8) -> Value { + unsafe { *self.registers.get_unchecked(n as usize) } + } + + /// Write a register. + /// Writing to register 0 is no-op. + #[inline(always)] + pub fn write_reg(&mut self, n: u8, value: T) { + if n != 0 { + unsafe { + core::ptr::copy_nonoverlapping( + (&value as *const T).cast::(), + self.registers.as_mut_ptr().add(n.into()).cast::(), + core::mem::size_of::(), + ); + }; + } + } +} + +/// Virtual machine halt error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum VmRunError { + /// Tried to execute invalid instruction + InvalidOpcode(u8), + + /// Unhandled load access exception + LoadAccessEx(Address), + + /// Unhandled instruction load access exception + ProgramFetchLoadEx(Address), + + /// Unhandled store access exception + StoreAccessEx(Address), + + /// Register out-of-bounds access + RegOutOfBounds, + + /// Address out-of-bounds + AddrOutOfBounds, + + /// Reached unreachable code + Unreachable, + + /// Invalid operand + InvalidOperand, + + /// Unimplemented feature + Unimplemented, +} + +/// Virtual machine halt ok +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum VmRunOk { + /// Program has eached its end + End, + + /// Program was interrupted by a timer + Timer, + + /// Environment call + Ecall, + + /// Breakpoint + Breakpoint, +} diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs deleted file mode 100644 index 79ced04..0000000 --- a/hbvm/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -use hbvm::vm::mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}; - -use { - hbvm::{validate::validate, vm::Vm}, - std::io::{stdin, Read}, -}; - -fn main() -> Result<(), Box> { - let mut prog = vec![]; - stdin().read_to_end(&mut prog)?; - - if let Err(e) = validate(&prog) { - eprintln!("Program validation error: {e:?}"); - return Ok(()); - } else { - unsafe { - let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler); - vm.memory.insert_test_page(); - println!("Program interrupt: {:?}", vm.run()); - println!("{:?}", vm.registers); - } - } - Ok(()) -} - -pub fn time() -> u32 { - 9 -} - -struct TestTrapHandler; -impl HandlePageFault for TestTrapHandler { - fn page_fault( - &mut self, - _: MemoryAccessReason, - _: &mut Memory, - _: u64, - _: PageSize, - _: *mut u8, - ) -> bool { - false - } -} diff --git a/hbvm/src/mem/addr.rs b/hbvm/src/mem/addr.rs new file mode 100644 index 0000000..2a162af --- /dev/null +++ b/hbvm/src/mem/addr.rs @@ -0,0 +1,131 @@ +//! Virtual(?) memory address + +use { + crate::utils::impl_display, + core::{fmt::Debug, ops}, +}; + +/// Memory address +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Address(u64); +impl Address { + /// A null address + pub const NULL: Self = Self(0); + + /// Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead of overflowing. + #[inline] + pub fn saturating_add(self, rhs: T) -> Self { + Self(self.0.saturating_add(rhs.cast_u64())) + } + + /// Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds instead of overflowing. + #[inline] + pub fn saturating_sub(self, rhs: T) -> Self { + Self(self.0.saturating_sub(rhs.cast_u64())) + } + + /// Wrapping integer addition. Computes self + rhs, wrapping the numeric bounds. + #[inline] + pub fn wrapping_add(self, rhs: T) -> Self { + Self(self.0.wrapping_add(rhs.cast_u64())) + } + + /// Wrapping integer subtraction. Computes self + rhs, wrapping the numeric bounds. + #[inline] + pub fn wrapping_sub(self, rhs: T) -> Self { + Self(self.0.wrapping_sub(rhs.cast_u64())) + } + + /// Cast or if smaller, truncate to [`usize`] + pub fn truncate_usize(self) -> usize { + self.0 as _ + } + + /// Get inner value + #[inline(always)] + pub fn get(self) -> u64 { + self.0 + } + + /// Construct new address + #[inline(always)] + pub fn new(val: u64) -> Self { + Self(val) + } + + /// Do something with inner value + #[inline(always)] + pub fn map(self, f: impl Fn(u64) -> u64) -> Self { + Self(f(self.0)) + } +} + +impl_display!(for Address => + |Address(a)| "{a:0x}" +); + +impl ops::Add for Address { + type Output = Self; + + #[inline] + fn add(self, rhs: T) -> Self::Output { + Self(self.0.wrapping_add(rhs.cast_u64())) + } +} + +impl ops::Sub for Address { + type Output = Self; + + #[inline] + fn sub(self, rhs: T) -> Self::Output { + Self(self.0.wrapping_sub(rhs.cast_u64())) + } +} + +impl ops::AddAssign for Address { + fn add_assign(&mut self, rhs: T) { + self.0 = self.0.wrapping_add(rhs.cast_u64()) + } +} + +impl ops::SubAssign for Address { + fn sub_assign(&mut self, rhs: T) { + self.0 = self.0.wrapping_sub(rhs.cast_u64()) + } +} + +impl From
for u64 { + #[inline(always)] + fn from(value: Address) -> Self { + value.0 + } +} + +impl Debug for Address { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[{:0x}]", self.0) + } +} + +/// Can perform address operations with +pub trait AddressOp { + /// Cast to u64, truncating or extending + fn cast_u64(self) -> u64; +} + +macro_rules! impl_address_ops_u(($($ty:ty),* $(,)?) => { + $(impl AddressOp for $ty { + #[inline(always)] + fn cast_u64(self) -> u64 { self as _ } + })* +}); + +macro_rules! impl_address_ops_i(($($ty:ty),* $(,)?) => { + $(impl AddressOp for $ty { + #[inline(always)] + fn cast_u64(self) -> u64 { self as i64 as u64 } + })* +}); + +impl_address_ops_u!(u8, u16, u32, u64, usize); +impl_address_ops_i!(i8, i16, i32, i64, isize); diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs new file mode 100644 index 0000000..7387abe --- /dev/null +++ b/hbvm/src/mem/mod.rs @@ -0,0 +1,80 @@ +//! Memory implementations + +pub mod softpaging; + +pub(crate) mod addr; + +pub use addr::Address; + +use crate::utils::impl_display; + +/// Load-store memory access +pub trait Memory { + /// Load data from memory on address + /// + /// # Safety + /// - Shall not overrun the buffer + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError>; + + /// Store data to memory on address + /// + /// # Safety + /// - Shall not overrun the buffer + unsafe fn store( + &mut self, + addr: Address, + source: *const u8, + count: usize, + ) -> Result<(), StoreError>; + + /// Read from program memory to execute + /// + /// # Safety + /// - Data read have to be valid + unsafe fn prog_read(&mut self, addr: Address) -> T; +} + +/// Unhandled load access trap +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LoadError(pub Address); +impl_display!(for LoadError => + |LoadError(a)| "Load access error at address {a}", +); + +/// Unhandled store access trap +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StoreError(pub Address); +impl_display!(for StoreError => + |StoreError(a)| "Load access error at address {a}", +); + +/// Reason to access memory +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MemoryAccessReason { + /// Memory was accessed for load (read) + Load, + /// Memory was accessed for store (write) + Store, +} + +impl_display!(for MemoryAccessReason => match { + Self::Load => const "Load"; + Self::Store => const "Store"; +}); + +impl From for crate::VmRunError { + fn from(value: LoadError) -> Self { + Self::LoadAccessEx(value.0) + } +} + +impl From for crate::VmRunError { + fn from(value: StoreError) -> Self { + Self::StoreAccessEx(value.0) + } +} diff --git a/hbvm/src/mem/softpaging/icache.rs b/hbvm/src/mem/softpaging/icache.rs new file mode 100644 index 0000000..963970a --- /dev/null +++ b/hbvm/src/mem/softpaging/icache.rs @@ -0,0 +1,109 @@ +//! Program instruction cache + +use crate::mem::Address; + +use { + super::{lookup::AddrPageLookuper, paging::PageTable, PageSize}, + core::{ + mem::{size_of, MaybeUninit}, + ptr::{copy_nonoverlapping, NonNull}, + }, +}; + +/// Instruction cache +#[derive(Clone, Debug)] +pub struct ICache { + /// Current page address base + base: Address, + /// Curent page pointer + data: Option>, + /// Current page size + size: PageSize, + /// Address mask + mask: u64, +} + +impl Default for ICache { + fn default() -> Self { + Self { + base: Address::NULL, + data: Default::default(), + size: PageSize::Size4K, + mask: Default::default(), + } + } +} + +impl ICache { + /// Fetch instruction from cache + /// + /// # Safety + /// `T` should be valid to read from instruction memory + pub(super) unsafe fn fetch( + &mut self, + addr: Address, + root_pt: *const PageTable, + ) -> Option { + let mut ret = MaybeUninit::::uninit(); + + let pbase = self + .data + .or_else(|| self.fetch_page(self.base + self.size, root_pt))?; + + // Get address base + let base = addr.map(|x| x & self.mask); + + // Base not matching, fetch anew + if base != self.base { + self.fetch_page(base, root_pt)?; + }; + + let offset = addr.get() & !self.mask; + let requ_size = size_of::(); + + // Page overflow + let rem = (offset as usize) + .saturating_add(requ_size) + .saturating_sub(self.size as _); + let first_copy = requ_size.saturating_sub(rem); + + // Copy non-overflowing part + copy_nonoverlapping(pbase.as_ptr(), ret.as_mut_ptr().cast::(), first_copy); + + // Copy overflow + if rem != 0 { + let pbase = self.fetch_page(self.base + self.size, root_pt)?; + + // Unlikely, unsupported scenario + if rem > self.size as _ { + return None; + } + + copy_nonoverlapping( + pbase.as_ptr(), + ret.as_mut_ptr().cast::().add(first_copy), + rem, + ); + } + + Some(ret.assume_init()) + } + + /// Fetch a page + unsafe fn fetch_page(&mut self, addr: Address, pt: *const PageTable) -> Option> { + let res = AddrPageLookuper::new(addr, 0, pt).next()?.ok()?; + if !super::perm_check::executable(res.perm) { + return None; + } + + (self.size, self.mask) = match res.size { + 4096 => (PageSize::Size4K, !((1 << 8) - 1)), + 2097152 => (PageSize::Size2M, !((1 << (8 * 2)) - 1)), + 1073741824 => (PageSize::Size1G, !((1 << (8 * 3)) - 1)), + _ => return None, + }; + self.data = Some(NonNull::new(res.ptr)?); + self.base = addr.map(|x| x & self.mask); + self.data + } +} diff --git a/hbvm/src/mem/softpaging/lookup.rs b/hbvm/src/mem/softpaging/lookup.rs new file mode 100644 index 0000000..04b17d2 --- /dev/null +++ b/hbvm/src/mem/softpaging/lookup.rs @@ -0,0 +1,126 @@ +//! Address lookup + +use crate::mem::addr::Address; + +use super::{ + addr_extract_index, + paging::{PageTable, Permission}, + PageSize, +}; + +/// Good result from address split +pub struct AddrPageLookupOk { + /// Virtual address + pub vaddr: Address, + + /// Pointer to the start for perform operation + pub ptr: *mut u8, + + /// Size to the end of page / end of desired size + pub size: usize, + + /// Page permission + pub perm: Permission, +} + +/// Errornous address split result +pub struct AddrPageLookupError { + /// Address of failure + pub addr: Address, + + /// Requested page size + pub size: PageSize, +} + +/// Address splitter into pages +pub struct AddrPageLookuper { + /// Current address + addr: Address, + + /// Size left + size: usize, + + /// Page table + pagetable: *const PageTable, +} + +impl AddrPageLookuper { + /// Create a new page lookuper + #[inline] + pub const fn new(addr: Address, size: usize, pagetable: *const PageTable) -> Self { + Self { + addr, + size, + pagetable, + } + } + + /// Bump address by size X + pub fn bump(&mut self, page_size: PageSize) { + self.addr += page_size; + self.size = self.size.saturating_sub(page_size as _); + } +} + +impl Iterator for AddrPageLookuper { + type Item = Result; + + fn next(&mut self) -> Option { + // The end, everything is fine + if self.size == 0 { + return None; + } + + let (base, perm, size, offset) = 'a: { + let mut current_pt = self.pagetable; + + // Walk the page table + for lvl in (0..5).rev() { + // Get an entry + unsafe { + let entry = (*current_pt) + .table + .get_unchecked(addr_extract_index(self.addr, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // No page → page fault + Permission::Empty => { + return Some(Err(AddrPageLookupError { + addr: self.addr, + size: PageSize::from_lvl(lvl)?, + })) + } + + // Node → proceed waking + Permission::Node => current_pt = ptr as _, + + // Leaf → return relevant data + perm => { + break 'a ( + // Pointer in host memory + ptr as *mut u8, + perm, + PageSize::from_lvl(lvl)?, + // In-page offset + addr_extract_index(self.addr, lvl), + ); + } + } + } + } + return None; // Reached the end (should not happen) + }; + + // Get available byte count in the selected page with offset + let avail = (size as usize).saturating_sub(offset).clamp(0, self.size); + self.bump(size); + + Some(Ok(AddrPageLookupOk { + vaddr: self.addr, + ptr: unsafe { base.add(offset) }, // Return pointer to the start of region + size: avail, + perm, + })) + } +} diff --git a/hbvm/src/mem/softpaging/mapping.rs b/hbvm/src/mem/softpaging/mapping.rs new file mode 100644 index 0000000..67f65a1 --- /dev/null +++ b/hbvm/src/mem/softpaging/mapping.rs @@ -0,0 +1,166 @@ +//! Automatic memory mapping + +use crate::{mem::addr::Address, utils::impl_display}; + +use { + super::{ + addr_extract_index, + paging::{PageTable, Permission, PtEntry, PtPointedData}, + PageSize, SoftPagedMem, + }, + alloc::boxed::Box, +}; + +impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { + /// Maps host's memory into VM's memory + /// + /// # Safety + /// - Your faith in the gods of UB + /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: + /// - Alright, Miri-sama is also fine with this, who knows why + pub unsafe fn map( + &mut self, + host: *mut u8, + target: Address, + perm: Permission, + pagesize: PageSize, + ) -> Result<(), MapError> { + let mut current_pt = self.root_pt; + + // Decide on what level depth are we going + let lookup_depth = match pagesize { + PageSize::Size4K => 0, + PageSize::Size2M => 1, + PageSize::Size1G => 2, + }; + + // Walk pagetable levels + for lvl in (lookup_depth + 1..5).rev() { + let entry = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // Still not on target and already seeing empty entry? + // No worries! Let's create one (allocates). + Permission::Empty => { + // Increase children count + (*current_pt).childen += 1; + + let table = Box::into_raw(Box::new(PtPointedData { + pt: PageTable::default(), + })); + + core::ptr::write(entry, PtEntry::new(table, Permission::Node)); + current_pt = table as _; + } + // Continue walking + Permission::Node => current_pt = ptr as _, + + // There is some entry on place of node + _ => return Err(MapError::PageOnNode), + } + } + + let node = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lookup_depth)); + + // Check if node is not mapped + if node.permission() != Permission::Empty { + return Err(MapError::AlreadyMapped); + } + + // Write entry + (*current_pt).childen += 1; + core::ptr::write(node, PtEntry::new(host.cast(), perm)); + + Ok(()) + } + + /// Unmaps pages from VM's memory + /// + /// If errors, it only means there is no entry to unmap and in most cases + /// just should be ignored. + pub fn unmap(&mut self, addr: Address) -> Result<(), NothingToUnmap> { + let mut current_pt = self.root_pt; + let mut page_tables = [core::ptr::null_mut(); 5]; + + // Walk page table in reverse + for lvl in (0..5).rev() { + let entry = unsafe { + (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(addr, lvl)) + }; + + let ptr = entry.ptr(); + match entry.permission() { + // Nothing is there, throw an error, not critical! + Permission::Empty => return Err(NothingToUnmap), + // Node – Save to visited pagetables and continue walking + Permission::Node => { + page_tables[lvl as usize] = entry; + current_pt = ptr as _ + } + // Page entry – zero it out! + // Zero page entry is completely valid entry with + // empty permission - no UB here! + _ => unsafe { + core::ptr::write_bytes(entry, 0, 1); + break; + }, + } + } + + // Now walk in order visited page tables + for entry in page_tables.into_iter() { + // Level not visited, skip. + if entry.is_null() { + continue; + } + + unsafe { + let children = &mut (*(*entry).ptr()).pt.childen; + *children -= 1; // Decrease children count + + // If there are no children, deallocate. + if *children == 0 { + let _ = Box::from_raw((*entry).ptr() as *mut PageTable); + + // Zero visited entry + core::ptr::write_bytes(entry, 0, 1); + } else { + break; + } + } + } + + Ok(()) + } +} + +/// Error mapping +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MapError { + /// Entry was already mapped + AlreadyMapped, + /// When walking a page entry was + /// encounterd. + PageOnNode, +} + +impl_display!(for MapError => match { + Self::AlreadyMapped => "There is already a page mapped on specified address"; + Self::PageOnNode => "There was a page mapped on the way instead of node"; +}); + +/// There was no entry in page table to unmap +/// +/// No worry, don't panic, nothing bad has happened, +/// but if you are 120% sure there should be something, +/// double-check your addresses. +#[derive(Clone, Copy, Debug)] +pub struct NothingToUnmap; +impl_display!(for NothingToUnmap => "There is no entry to unmap"); diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs new file mode 100644 index 0000000..3e1f6de --- /dev/null +++ b/hbvm/src/mem/softpaging/mod.rs @@ -0,0 +1,284 @@ +//! Platform independent, software paged memory implementation + +pub mod icache; +pub mod lookup; +pub mod paging; + +#[cfg(feature = "alloc")] +pub mod mapping; + +use { + super::{addr::Address, LoadError, Memory, MemoryAccessReason, StoreError}, + core::mem::size_of, + icache::ICache, + lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}, + paging::{PageTable, Permission}, +}; + +/// HoleyBytes software paged memory +/// +/// - `OUT_PROG_EXEC`: set to `false` to disable executing program +/// not contained in initially provided program, even the pages +/// are executable +#[derive(Clone, Debug)] +pub struct SoftPagedMem<'p, PfH, const OUT_PROG_EXEC: bool = true> { + /// Root page table + pub root_pt: *mut PageTable, + /// Page fault handler + pub pf_handler: PfH, + /// Program memory segment + pub program: &'p [u8], + /// Program instruction cache + pub icache: ICache, +} + +impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory + for SoftPagedMem<'p, PfH, OUT_PROG_EXEC> +{ + /// Load value from an address + /// + /// # Safety + /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError> { + self.memory_access( + MemoryAccessReason::Load, + addr, + target, + count, + perm_check::readable, + |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), + ) + .map_err(LoadError) + } + + /// Store value to an address + /// + /// # Safety + /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] + unsafe fn store( + &mut self, + addr: Address, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + self.memory_access( + MemoryAccessReason::Store, + addr, + source.cast_mut(), + count, + perm_check::writable, + |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), + ) + .map_err(StoreError) + } + + #[inline(always)] + unsafe fn prog_read(&mut self, addr: Address) -> T { + if OUT_PROG_EXEC && addr.truncate_usize() > self.program.len() { + return self + .icache + .fetch::(addr, self.root_pt) + .unwrap_or_else(|| unsafe { core::mem::zeroed() }); + } + + let addr = addr.truncate_usize(); + self.program + .get(addr..addr + size_of::()) + .map(|x| x.as_ptr().cast::().read()) + .unwrap_or_else(|| unsafe { core::mem::zeroed() }) + } +} + +impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, PfH, OUT_PROG_EXEC> { + // Everyone behold, the holy function, the god of HBVM memory accesses! + + /// Split address to pages, check their permissions and feed pointers with offset + /// to a specified function. + /// + /// If page is not found, execute page fault trap handler. + #[allow(clippy::too_many_arguments)] // Silence peasant + fn memory_access( + &mut self, + reason: MemoryAccessReason, + src: Address, + mut dst: *mut u8, + len: usize, + permission_check: fn(Permission) -> bool, + action: fn(*mut u8, *mut u8, usize), + ) -> Result<(), Address> { + // Memory load from program section + let (src, len) = if src.truncate_usize() < self.program.len() as _ { + // Allow only loads + if reason != MemoryAccessReason::Load { + return Err(src); + } + + // Determine how much data to copy from here + let to_copy = len.clamp(0, self.program.len().saturating_sub(src.truncate_usize())); + + // Perform action + action( + unsafe { self.program.as_ptr().add(src.truncate_usize()).cast_mut() }, + dst, + to_copy, + ); + + // Return shifted from what we've already copied + ( + src.saturating_add(to_copy as u64), + len.saturating_sub(to_copy), + ) + } else { + (src, len) // Nothing weird! + }; + + // Nothing to copy? Don't bother doing anything, bail. + if len == 0 { + return Ok(()); + } + + // Create new splitter + let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); + loop { + match pspl.next() { + // Page is found + Some(Ok(AddrPageLookupOk { + vaddr, + ptr, + size, + perm, + })) => { + if !permission_check(perm) { + return Err(vaddr); + } + + // Perform specified memory action and bump destination pointer + action(ptr, dst, size); + dst = unsafe { dst.add(size) }; + } + // No page found + Some(Err(AddrPageLookupError { addr, size })) => { + // Attempt to execute page fault handler + if self.pf_handler.page_fault( + reason, + unsafe { &mut *self.root_pt }, + addr, + size, + dst, + ) { + // Shift the splitter address + pspl.bump(size); + + // Bump dst pointer + dst = unsafe { dst.add(size as _) }; + } else { + return Err(addr); // Unhandleable, VM will yield. + } + } + // No remaining pages, we are done! + None => return Ok(()), + } + } + } +} + +/// Extract index in page table on specified level +/// +/// The level shall not be larger than 4, otherwise +/// the output of the function is unspecified (yes, it can also panic :) +pub fn addr_extract_index(addr: Address, lvl: u8) -> usize { + debug_assert!(lvl <= 4); + let addr = addr.get(); + usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") +} + +/// Page size +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PageSize { + /// 4 KiB page (on level 0) + Size4K = 4096, + + /// 2 MiB page (on level 1) + Size2M = 1024 * 1024 * 2, + + /// 1 GiB page (on level 2) + Size1G = 1024 * 1024 * 1024, +} + +impl PageSize { + /// Convert page table level to size of page + const fn from_lvl(lvl: u8) -> Option { + match lvl { + 0 => Some(PageSize::Size4K), + 1 => Some(PageSize::Size2M), + 2 => Some(PageSize::Size1G), + _ => None, + } + } +} + +impl core::ops::Add for Address { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: PageSize) -> Self::Output { + self + (rhs as u64) + } +} + +impl core::ops::AddAssign for Address { + #[inline(always)] + fn add_assign(&mut self, rhs: PageSize) { + *self = Self::new(self.get().wrapping_add(rhs as u64)); + } +} + +/// Permisison checks +pub mod perm_check { + use super::paging::Permission; + + /// Page is readable + #[inline(always)] + pub const fn readable(perm: Permission) -> bool { + matches!( + perm, + Permission::Readonly | Permission::Write | Permission::Exec + ) + } + + /// Page is writable + #[inline(always)] + pub const fn writable(perm: Permission) -> bool { + matches!(perm, Permission::Write) + } + + /// Page is executable + #[inline(always)] + pub const fn executable(perm: Permission) -> bool { + matches!(perm, Permission::Exec) + } +} + +/// Handle VM traps +pub trait HandlePageFault { + /// Handle page fault + /// + /// Return true if handling was sucessful, + /// otherwise the program will be interrupted and will + /// yield an error. + fn page_fault( + &mut self, + reason: MemoryAccessReason, + pagetable: &mut PageTable, + vaddr: Address, + size: PageSize, + dataptr: *mut u8, + ) -> bool + where + Self: Sized; +} diff --git a/hbvm/src/mem/softpaging/paging.rs b/hbvm/src/mem/softpaging/paging.rs new file mode 100644 index 0000000..c1ab842 --- /dev/null +++ b/hbvm/src/mem/softpaging/paging.rs @@ -0,0 +1,86 @@ +//! Page table and associated structures implementation + +use core::{fmt::Debug, mem::MaybeUninit}; + +/// Page entry permission +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[repr(u8)] +pub enum Permission { + /// No page present + #[default] + Empty, + /// Points to another pagetable + Node, + /// Page is read only + Readonly, + /// Page is readable and writable + Write, + /// Page is readable and executable + Exec, +} + +/// Page table entry +#[derive(Clone, Copy, Default, PartialEq, Eq)] +pub struct PtEntry(u64); +impl PtEntry { + /// Create new + /// + /// # Safety + /// - `ptr` has to point to valid data and shall not be deallocated + /// troughout the entry lifetime + #[inline] + pub unsafe fn new(ptr: *mut PtPointedData, permission: Permission) -> Self { + Self(ptr as u64 | permission as u64) + } + + /// Get permission + #[inline] + pub fn permission(&self) -> Permission { + unsafe { core::mem::transmute(self.0 as u8 & 0b111) } + } + + /// Get pointer to the data (leaf) or next page table (node) + #[inline] + pub fn ptr(&self) -> *mut PtPointedData { + (self.0 & !((1 << 12) - 1)) as _ + } +} + +impl Debug for PtEntry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PtEntry") + .field("ptr", &self.ptr()) + .field("permission", &self.permission()) + .finish() + } +} + +/// Page table +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(align(4096))] +pub struct PageTable { + /// How much entries are in use + pub childen: u8, + /// Entries + pub table: [PtEntry; 256], +} + +impl Default for PageTable { + fn default() -> Self { + // SAFETY: It's fine, zeroed page table entry is valid (= empty) + Self { + childen: 0, + table: unsafe { MaybeUninit::zeroed().assume_init() }, + } + } +} + +/// Data page table entry can possibly point to +#[derive(Clone, Copy)] +#[repr(C, align(4096))] +pub union PtPointedData { + /// Node - next page table + pub pt: PageTable, + /// Leaf + pub page: u8, +} diff --git a/hbvm/src/utils.rs b/hbvm/src/utils.rs new file mode 100644 index 0000000..fbd43af --- /dev/null +++ b/hbvm/src/utils.rs @@ -0,0 +1,53 @@ +macro_rules! impl_display { + (for $ty:ty => $(|$selfty:pat_param|)? $fmt:literal $(, $($param:expr),+)? $(,)?) => { + impl ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + $(let $selfty = self;)? + write!(f, $fmt, $($param),*) + } + } + }; + + (for $ty:ty => $str:literal) => { + impl ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.write_str($str) + } + } + }; + + (for $ty:ty => match {$( + $bind:pat => $($const:ident)? $fmt:literal $(,$($params:tt)*)?; + )*}) => { + impl ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + $( + $bind => $crate::utils::internal::impl_display_match_fragment!($($const,)? f, $fmt $(, $($params)*)?) + ),* + } + } + } + } +} + +#[doc(hidden)] +pub(crate) mod internal { + macro_rules! impl_display_match_fragment { + (const, $f:expr, $lit:literal) => { + $f.write_str($lit) + }; + + ($f:expr, $fmt:literal $(, $($params:tt)*)?) => { + write!($f, $fmt, $($($params)*)?) + }; + } + + pub(crate) use impl_display_match_fragment; +} + +macro_rules! static_assert(($expr:expr $(,)?) => { + const _: [(); !$expr as usize] = []; +}); + +pub(crate) use {impl_display, static_assert}; diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs deleted file mode 100644 index d75306b..0000000 --- a/hbvm/src/validate.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Validate if program is sound to execute - -/// Program validation error kind -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ErrorKind { - /// Unknown opcode - InvalidInstruction, - /// VM doesn't implement this valid opcode - Unimplemented, - /// Attempted to copy over register boundary - RegisterArrayOverflow, -} - -/// Error -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Error { - /// Kind - pub kind: ErrorKind, - /// Location in bytecode - pub index: usize, -} - -/// Perform bytecode validation. If it passes, the program should be -/// sound to execute. -pub fn validate(mut program: &[u8]) -> Result<(), Error> { - use hbbytecode::opcode::*; - - let start = program; - loop { - // Match on instruction types and perform necessary checks - program = match program { - [] => return Ok(()), - [LD..=ST, reg, _, _, _, _, _, _, _, _, _, count, ..] - if usize::from(*reg) * 8 + usize::from(*count) > 2048 => - { - return Err(Error { - kind: ErrorKind::RegisterArrayOverflow, - index: (program.as_ptr() as usize) - (start.as_ptr() as usize), - }) - } - [BRC, src, dst, count, ..] - if src.checked_add(*count).is_none() || dst.checked_add(*count).is_none() => - { - return Err(Error { - kind: ErrorKind::RegisterArrayOverflow, - index: (program.as_ptr() as usize) - (start.as_ptr() as usize), - }) - } - [NOP | ECALL, rest @ ..] - | [DIR | DIRF, _, _, _, _, rest @ ..] - | [ADD..=CMPU | BRC | ADDF..=MULF, _, _, _, rest @ ..] - | [NEG..=NOT | CP..=SWA | NEGF..=FTI, _, _, rest @ ..] - | [LI, _, _, _, _, _, _, _, _, _, rest @ ..] - | [ADDI..=CMPUI | BMC | JAL..=JGTU | ADDFI..=MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..] - | [LD..=ST, _, _, _, _, _, _, _, _, _, _, _, _, rest @ ..] => rest, - _ => { - return Err(Error { - kind: ErrorKind::InvalidInstruction, - index: (program.as_ptr() as usize) - (start.as_ptr() as usize), - }) - } - } - } -} diff --git a/hbvm/src/value.rs b/hbvm/src/value.rs new file mode 100644 index 0000000..b3d24e8 --- /dev/null +++ b/hbvm/src/value.rs @@ -0,0 +1,88 @@ +//! HoleyBytes register value definition + +use crate::utils::static_assert; + +/// Define [`Value`] »union« (it's fake) +/// +/// # Safety +/// Its variants have to be sound to byte-reinterpretate +/// between each other. Otherwise the behaviour is undefined. +macro_rules! value_def { + ($($ty:ident),* $(,)?) => { + /// HBVM register value + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct Value(pub u64); + + $( + impl From<$ty> for Value { + #[inline] + fn from(value: $ty) -> Self { + let mut new = core::mem::MaybeUninit::::zeroed(); + unsafe { + new.as_mut_ptr().cast::<$ty>().write(value); + Self(new.assume_init()) + } + } + } + + static_assert!(core::mem::size_of::<$ty>() <= core::mem::size_of::()); + + impl private::Sealed for $ty {} + unsafe impl ValueVariant for $ty {} + )* + }; +} + +impl Value { + /// Byte reinterpret value to target variant + #[inline] + pub fn cast(self) -> V { + unsafe { core::mem::transmute_copy(&self.0) } + } +} + +/// # Safety +/// - N/A, not to be implemented manually +pub unsafe trait ValueVariant: private::Sealed + Copy + Into {} +impl private::Sealed for Value {} +unsafe impl ValueVariant for Value {} + +mod private { + pub trait Sealed {} +} + +value_def!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); +static_assert!(core::mem::size_of::() == 8); + +impl core::fmt::Debug for Value { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Print formatted as hexadecimal, unsigned integer + write!(f, "{:x}", self.cast::()) + } +} + +pub(crate) trait CheckedDivRem { + fn checked_div(self, other: Self) -> Option + where + Self: Sized; + fn checked_rem(self, other: Self) -> Option + where + Self: Sized; +} + +macro_rules! impl_checked_div_rem { + ($($ty:ty),* $(,)?) => { + $(impl CheckedDivRem for $ty { + #[inline(always)] + fn checked_div(self, another: Self) -> Option + { self.checked_div(another) } + + #[inline(always)] + fn checked_rem(self, another: Self) -> Option + { self.checked_rem(another) } + })* + }; +} + +impl_checked_div_rem!(u8, u16, u32, u64, i8, i16, i32, i64); diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/vm/mem/mod.rs deleted file mode 100644 index 8609515..0000000 --- a/hbvm/src/vm/mem/mod.rs +++ /dev/null @@ -1,453 +0,0 @@ -//! Program memory implementation - -pub mod paging; - -mod pfhandler; - -pub use pfhandler::HandlePageFault; - -use { - self::paging::{PageTable, Permission, PtEntry}, - super::VmRunError, - alloc::boxed::Box, - core::mem::MaybeUninit, - derive_more::Display, -}; - -/// HoleyBytes virtual memory -#[derive(Clone, Debug)] -pub struct Memory { - /// Root page table - root_pt: *mut PageTable, -} - -impl Default for Memory { - fn default() -> Self { - Self { - root_pt: Box::into_raw(Box::default()), - } - } -} - -impl Drop for Memory { - fn drop(&mut self) { - let _ = unsafe { Box::from_raw(self.root_pt) }; - } -} - -impl Memory { - // HACK: Just for allocation testing, will be removed when proper memory interfaces - // implemented. - pub fn insert_test_page(&mut self) { - unsafe { - let mut entry = PtEntry::new( - { - let layout = alloc::alloc::Layout::from_size_align_unchecked(4096, 4096); - let ptr = alloc::alloc::alloc_zeroed(layout); - if ptr.is_null() { - alloc::alloc::handle_alloc_error(layout); - } - - core::ptr::write_bytes(ptr, 69, 10); - ptr.cast() - }, - Permission::Write, - ); - - for _ in 0..4 { - let mut pt = Box::::default(); - pt[0] = entry; - entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node); - } - - (*self.root_pt)[0] = entry; - } - } - - /// Maps host's memory into VM's memory - /// - /// # Safety - /// Who knows. - pub unsafe fn map( - &mut self, - mut host: *mut u8, - target: usize, - pagesize: PageSize, - count: usize, - ) { - todo!() - } - - /// Unmaps pages from VM's memory - pub fn unmap(&mut self, addr: usize, count: usize) { - todo!() - } - - /// Load value from an address - /// - /// # Safety - /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - pub unsafe fn load( - &mut self, - addr: u64, - target: *mut u8, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), LoadError> { - self.memory_access( - MemoryAccessReason::Load, - addr, - target, - count, - |perm| { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - }, - |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), - traph, - ) - .map_err(LoadError) - } - - /// Store value to an address - /// - /// # Safety - /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - pub unsafe fn store( - &mut self, - addr: u64, - source: *const u8, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), StoreError> { - self.memory_access( - MemoryAccessReason::Store, - addr, - source.cast_mut(), - count, - |perm| perm == Permission::Write, - |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), - traph, - ) - .map_err(StoreError) - } - - /// Copy a block of memory - /// - /// # Safety - /// - Same as for [`Self::load`] and [`Self::store`] - /// - Your faith in the gods of UB - /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: - pub unsafe fn block_copy( - &mut self, - src: u64, - dst: u64, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), BlkCopyError> { - // Yea, i know it is possible to do this more efficiently, but I am too lazy. - - const STACK_BUFFER_SIZE: usize = 512; - - // Decide if to use stack-allocated buffer or to heap allocate - // Deallocation is again decided on size at the end of the function - let mut buf = MaybeUninit::<[u8; STACK_BUFFER_SIZE]>::uninit(); - let buf = if count <= STACK_BUFFER_SIZE { - buf.as_mut_ptr().cast() - } else { - unsafe { - let layout = core::alloc::Layout::from_size_align_unchecked(count, 1); - let ptr = alloc::alloc::alloc(layout); - if ptr.is_null() { - alloc::alloc::handle_alloc_error(layout); - } - - ptr - } - }; - - // Perform memory block transfer - let status = (|| { - // Load to buffer - self.memory_access( - MemoryAccessReason::Load, - src, - buf, - count, - |perm| { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - }, - |src, dst, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError { - access_reason: MemoryAccessReason::Load, - addr, - })?; - - // Store from buffer - self.memory_access( - MemoryAccessReason::Store, - dst, - buf, - count, - |perm| perm == Permission::Write, - |dst, src, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError { - access_reason: MemoryAccessReason::Store, - addr, - })?; - - Ok::<_, BlkCopyError>(()) - })(); - - // Deallocate if used heap-allocated array - if count > STACK_BUFFER_SIZE { - alloc::alloc::dealloc( - buf, - core::alloc::Layout::from_size_align_unchecked(count, 1), - ); - } - - status - } - - /// Split address to pages, check their permissions and feed pointers with offset - /// to a specified function. - /// - /// If page is not found, execute page fault trap handler. - #[allow(clippy::too_many_arguments)] // Silence peasant - fn memory_access( - &mut self, - reason: MemoryAccessReason, - src: u64, - mut dst: *mut u8, - len: usize, - permission_check: fn(Permission) -> bool, - action: fn(*mut u8, *mut u8, usize), - traph: &mut impl HandlePageFault, - ) -> Result<(), u64> { - let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); - loop { - match pspl.next() { - // Page found - Some(Ok(AddrPageLookupOk { - vaddr, - ptr, - size, - perm, - })) => { - if !permission_check(perm) { - return Err(vaddr); - } - - // Perform memory action and bump dst pointer - action(ptr, dst, size); - dst = unsafe { dst.add(size) }; - } - Some(Err(AddrPageLookupError { addr, size })) => { - // Execute page fault handler - if traph.page_fault(reason, self, addr, size, dst) { - // Shift the splitter address - pspl.bump(size); - - // Bump dst pointer - dst = unsafe { dst.add(size as _) }; - } else { - return Err(addr); // Unhandleable - } - } - None => return Ok(()), - } - } - } -} - -/// Result from address split -struct AddrPageLookupOk { - /// Virtual address - vaddr: u64, - - /// Pointer to the start for perform operation - ptr: *mut u8, - - /// Size to the end of page / end of desired size - size: usize, - - /// Page permission - perm: Permission, -} - -struct AddrPageLookupError { - /// Address of failure - addr: u64, - - /// Requested page size - size: PageSize, -} - -/// Address splitter into pages -struct AddrPageLookuper { - /// Current address - addr: u64, - - /// Size left - size: usize, - - /// Page table - pagetable: *const PageTable, -} - -impl AddrPageLookuper { - /// Create a new page splitter - pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { - Self { - addr, - size, - pagetable, - } - } - - /// Bump address by size X - fn bump(&mut self, page_size: PageSize) { - self.addr += page_size as u64; - self.size = self.size.saturating_sub(page_size as _); - } -} - -impl Iterator for AddrPageLookuper { - type Item = Result; - - fn next(&mut self) -> Option { - // The end, everything is fine - if self.size == 0 { - return None; - } - - let (base, perm, size, offset) = 'a: { - let mut current_pt = self.pagetable; - - // Walk the page table - for lvl in (0..5).rev() { - // Get an entry - unsafe { - let entry = (*current_pt).get_unchecked( - usize::try_from((self.addr >> (lvl * 9 + 12)) & ((1 << 9) - 1)) - .expect("?conradluget a better CPU"), - ); - - let ptr = entry.ptr(); - match entry.permission() { - // No page → page fault - Permission::Empty => { - return Some(Err(AddrPageLookupError { - addr: self.addr, - size: PageSize::from_lvl(lvl)?, - })) - } - - // Node → proceed waking - Permission::Node => current_pt = ptr as _, - - // Leaft → return relevant data - perm => { - break 'a ( - // Pointer in host memory - ptr as *mut u8, - perm, - PageSize::from_lvl(lvl)?, - // In-page offset - self.addr as usize & ((1 << (lvl * 9 + 12)) - 1), - ); - } - } - } - } - return None; // Reached the end (should not happen) - }; - - // Get available byte count in the selected page with offset - let avail = (size as usize - offset).clamp(0, self.size); - self.bump(size); - - Some(Ok(AddrPageLookupOk { - vaddr: self.addr, - ptr: unsafe { base.add(offset) }, // Return pointer to the start of region - size: avail, - perm, - })) - } -} - -/// Page size -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PageSize { - /// 4 KiB page (on level 0) - Size4K = 4096, - - /// 2 MiB page (on level 1) - Size2M = 1024 * 1024 * 2, - - /// 1 GiB page (on level 2) - Size1G = 1024 * 1024 * 1024, -} - -impl PageSize { - /// Convert page table level to size of page - fn from_lvl(lvl: u8) -> Option { - match lvl { - 0 => Some(PageSize::Size4K), - 1 => Some(PageSize::Size2M), - 2 => Some(PageSize::Size1G), - _ => None, - } - } -} - -/// Unhandled load access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub struct LoadError(u64); - -/// Unhandled store access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub struct StoreError(u64); - -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub enum MemoryAccessReason { - Load, - Store, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct BlkCopyError { - access_reason: MemoryAccessReason, - addr: u64, -} - -impl From for VmRunError { - fn from(value: BlkCopyError) -> Self { - match value.access_reason { - MemoryAccessReason::Load => Self::LoadAccessEx(value.addr), - MemoryAccessReason::Store => Self::StoreAccessEx(value.addr), - } - } -} - -impl From for VmRunError { - fn from(value: LoadError) -> Self { - Self::LoadAccessEx(value.0) - } -} - -impl From for VmRunError { - fn from(value: StoreError) -> Self { - Self::StoreAccessEx(value.0) - } -} diff --git a/hbvm/src/vm/mem/paging.rs b/hbvm/src/vm/mem/paging.rs deleted file mode 100644 index c621ab4..0000000 --- a/hbvm/src/vm/mem/paging.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Page table and associated structures implementation - -use { - core::{ - fmt::Debug, - mem::MaybeUninit, - ops::{Index, IndexMut}, - slice::SliceIndex, - }, - delegate::delegate, -}; - -/// Page entry permission -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -#[repr(u8)] -pub enum Permission { - /// No page present - #[default] - Empty, - /// Points to another pagetable - Node, - /// Page is read only - Readonly, - /// Page is readable and writable - Write, - /// Page is readable and executable - Exec, -} - -/// Page table entry -#[derive(Clone, Copy, Default, PartialEq, Eq)] -pub struct PtEntry(u64); -impl PtEntry { - /// Create new - /// - /// # Safety - /// - `ptr` has to point to valid data and shall not be deallocated - /// troughout the entry lifetime - #[inline] - pub unsafe fn new(ptr: *mut PtPointedData, permission: Permission) -> Self { - Self(ptr as u64 | permission as u64) - } - - /// Get permission - #[inline] - pub fn permission(&self) -> Permission { - unsafe { core::mem::transmute(self.0 as u8 & 0b111) } - } - - /// Get pointer to the data (leaf) or next page table (node) - #[inline] - pub fn ptr(&self) -> *mut PtPointedData { - (self.0 & !((1 << 12) - 1)) as _ - } -} - -impl Debug for PtEntry { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("PtEntry") - .field("ptr", &self.ptr()) - .field("permission", &self.permission()) - .finish() - } -} - -/// Page table -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(align(4096))] -pub struct PageTable([PtEntry; 512]); - -impl PageTable { - delegate!(to self.0 { - /// Returns a reference to an element or subslice depending on the type of - /// index. - /// - /// - If given a position, returns a reference to the element at that - /// position or `None` if out of bounds. - /// - If given a range, returns the subslice corresponding to that range, - /// or `None` if out of bounds. - /// - pub fn get(&self, ix: I) -> Option<&I::Output> - where I: SliceIndex<[PtEntry]>; - - /// Returns a mutable reference to an element or subslice depending on the - /// type of index (see [`get`]) or `None` if the index is out of bounds. - pub fn get_mut(&mut self, ix: I) -> Option<&mut I::Output> - where I: SliceIndex<[PtEntry]>; - - /// Returns a reference to an element or subslice, without doing bounds - /// checking. - /// - /// For a safe alternative see [`get`]. - /// - /// # Safety - /// - /// Calling this method with an out-of-bounds index is *[undefined behavior]* - /// even if the resulting reference is not used. - pub unsafe fn get_unchecked(&self, index: I) -> &I::Output - where I: SliceIndex<[PtEntry]>; - - /// Returns a mutable reference to an element or subslice, without doing - /// bounds checking. - /// - /// For a safe alternative see [`get_mut`]. - /// - /// # Safety - /// - /// Calling this method with an out-of-bounds index is *[undefined behavior]* - /// even if the resulting reference is not used. - pub unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut I::Output - where I: SliceIndex<[PtEntry]>; - }); -} - -impl Index for PageTable -where - Idx: SliceIndex<[PtEntry]>, -{ - type Output = Idx::Output; - - #[inline(always)] - fn index(&self, index: Idx) -> &Self::Output { - &self.0[index] - } -} - -impl IndexMut for PageTable -where - Idx: SliceIndex<[PtEntry]>, -{ - #[inline(always)] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - &mut self.0[index] - } -} - -impl Default for PageTable { - fn default() -> Self { - // SAFETY: It's fine, zeroed page table entry is valid (= empty) - Self(unsafe { MaybeUninit::zeroed().assume_init() }) - } -} - -/// Data page table entry can possibly point to -#[derive(Clone, Copy)] -#[repr(C, align(4096))] -pub union PtPointedData { - /// Node - next page table - pub pt: PageTable, - /// Leaf - pub page: u8, -} diff --git a/hbvm/src/vm/mem/pfhandler.rs b/hbvm/src/vm/mem/pfhandler.rs deleted file mode 100644 index 580e44e..0000000 --- a/hbvm/src/vm/mem/pfhandler.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Program trap handling interfaces - -use super::{Memory, MemoryAccessReason, PageSize}; - -/// Handle VM traps -pub trait HandlePageFault { - /// Handle page fault - fn page_fault( - &mut self, - reason: MemoryAccessReason, - memory: &mut Memory, - vaddr: u64, - size: PageSize, - dataptr: *mut u8, - ) -> bool; -} diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs deleted file mode 100644 index 1fcee80..0000000 --- a/hbvm/src/vm/mod.rs +++ /dev/null @@ -1,368 +0,0 @@ -//! HoleyBytes Virtual Machine -//! -//! All unsafe code here should be sound, if input bytecode passes validation. - -// # General safety notice: -// - Validation has to assure there is 256 registers (r0 - r255) -// - Instructions have to be valid as specified (values and sizes) -// - Mapped pages should be at least 4 KiB -// - Yes, I am aware of the UB when jumping in-mid of instruction where -// the read byte corresponds to an instruction whose lenght exceets the -// program size. If you are (rightfully) worried about the UB, for now just -// append your program with 11 zeroes. - -use self::mem::HandlePageFault; - -pub mod mem; -pub mod value; - -use { - crate::validate, - core::ops, - hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBD}, - mem::Memory, - static_assertions::assert_impl_one, - value::Value, -}; - -/// Extract a parameter from program -macro_rules! param { - ($self:expr, $ty:ty) => {{ - assert_impl_one!($ty: OpParam); - let data = $self - .program - .as_ptr() - .add($self.pc + 1) - .cast::<$ty>() - .read(); - $self.pc += 1 + core::mem::size_of::<$ty>(); - data - }}; -} - -/// Perform binary operation `#0 ← #1 OP #2` -macro_rules! binary_op { - ($self:expr, $ty:ident, $handler:expr) => {{ - let ParamBBB(tg, a0, a1) = param!($self, ParamBBB); - $self.write_reg( - tg, - $handler( - Value::$ty(&$self.read_reg(a0)), - Value::$ty(&$self.read_reg(a1)), - ), - ); - }}; -} - -/// Perform binary operation with immediate `#0 ← #1 OP imm #2` -macro_rules! binary_op_imm { - ($self:expr, $ty:ident, $handler:expr) => {{ - let ParamBBD(tg, a0, imm) = param!($self, ParamBBD); - $self.write_reg( - tg, - $handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into())), - ); - }}; -} - -/// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected -macro_rules! cond_jump { - ($self:expr, $ty:ident, $expected:ident) => {{ - let ParamBBD(a0, a1, jt) = param!($self, ParamBBD); - if core::cmp::Ord::cmp(&$self.read_reg(a0).as_u64(), &$self.read_reg(a1).as_u64()) - == core::cmp::Ordering::$expected - { - $self.pc = jt as usize; - } - }}; -} - -/// HoleyBytes Virtual Machine -pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { - /// Holds 256 registers - /// - /// Writing to register 0 is considered undefined behaviour - /// in terms of HoleyBytes program execution - pub registers: [Value; 256], - - /// Memory implementation - pub memory: Memory, - - /// Trap handler - pub pfhandler: PfHandler, - - // Program counter - pc: usize, - - /// Program - program: &'a [u8], - - /// Program timer - timer: usize, -} - -impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> - Vm<'a, PfHandler, TIMER_QUOTIENT> -{ - /// Create a new VM with program and trap handler - /// - /// # Safety - /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], traph: PfHandler) -> Self { - Self { - registers: [Value::from(0_u64); 256], - memory: Default::default(), - pfhandler: traph, - pc: 0, - program, - timer: 0, - } - } - - /// Create a new VM with program and trap handler only if it passes validation - pub fn new_validated(program: &'a [u8], traph: PfHandler) -> Result { - validate::validate(program)?; - Ok(unsafe { Self::new_unchecked(program, traph) }) - } - - /// Execute program - /// - /// Program can return [`VmRunError`] if a trap handling failed - pub fn run(&mut self) -> Result { - use hbbytecode::opcode::*; - loop { - // Fetch instruction - let Some(&opcode) = self.program.get(self.pc) - else { return Ok(VmRunOk::End) }; - - // Big match - unsafe { - match opcode { - NOP => param!(self, ()), - ADD => binary_op!(self, as_u64, u64::wrapping_add), - SUB => binary_op!(self, as_u64, u64::wrapping_sub), - MUL => binary_op!(self, as_u64, u64::wrapping_mul), - AND => binary_op!(self, as_u64, ops::BitAnd::bitand), - OR => binary_op!(self, as_u64, ops::BitOr::bitor), - XOR => binary_op!(self, as_u64, ops::BitXor::bitxor), - SL => binary_op!(self, as_u64, ops::Shl::shl), - SR => binary_op!(self, as_u64, ops::Shr::shr), - SRS => binary_op!(self, as_i64, ops::Shr::shr), - CMP => { - let ParamBBB(tg, a0, a1) = param!(self, ParamBBB); - self.write_reg( - tg, - self.read_reg(a0).as_i64().cmp(&self.read_reg(a1).as_i64()) as i64, - ); - } - CMPU => { - let ParamBBB(tg, a0, a1) = param!(self, ParamBBB); - self.write_reg( - tg, - self.read_reg(a0).as_u64().cmp(&self.read_reg(a1).as_u64()) as i64, - ); - } - NOT => { - let param = param!(self, ParamBB); - self.write_reg(param.0, !self.read_reg(param.1).as_u64()); - } - NEG => { - let param = param!(self, ParamBB); - self.write_reg( - param.0, - match self.read_reg(param.1).as_u64() { - 0 => 1_u64, - _ => 0, - }, - ); - } - DIR => { - let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB); - let a0 = self.read_reg(a0).as_u64(); - let a1 = self.read_reg(a1).as_u64(); - self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); - self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); - } - ADDI => binary_op_imm!(self, as_u64, ops::Add::add), - MULI => binary_op_imm!(self, as_u64, ops::Mul::mul), - ANDI => binary_op_imm!(self, as_u64, ops::BitAnd::bitand), - ORI => binary_op_imm!(self, as_u64, ops::BitOr::bitor), - XORI => binary_op_imm!(self, as_u64, ops::BitXor::bitxor), - SLI => binary_op_imm!(self, as_u64, ops::Shl::shl), - SRI => binary_op_imm!(self, as_u64, ops::Shr::shr), - SRSI => binary_op_imm!(self, as_i64, ops::Shr::shr), - CMPI => { - let ParamBBD(tg, a0, imm) = param!(self, ParamBBD); - self.write_reg( - tg, - self.read_reg(a0).as_i64().cmp(&Value::from(imm).as_i64()) as i64, - ); - } - CMPUI => { - let ParamBBD(tg, a0, imm) = param!(self, ParamBBD); - self.write_reg(tg, self.read_reg(a0).as_u64().cmp(&imm) as i64); - } - CP => { - let param = param!(self, ParamBB); - self.write_reg(param.0, self.read_reg(param.1)); - } - SWA => { - let ParamBB(src, dst) = param!(self, ParamBB); - if src + dst != 0 { - core::ptr::swap( - self.registers.get_unchecked_mut(usize::from(src)), - self.registers.get_unchecked_mut(usize::from(dst)), - ); - } - } - LI => { - let param = param!(self, ParamBD); - self.write_reg(param.0, param.1); - } - LD => { - let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH); - let n: usize = match dst { - 0 => 1, - _ => 0, - }; - - self.memory.load( - self.read_reg(base).as_u64() + off + n as u64, - self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(), - usize::from(count).saturating_sub(n), - &mut self.pfhandler, - )?; - } - ST => { - let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH); - self.memory.store( - self.read_reg(base).as_u64() + off, - self.registers.as_ptr().add(usize::from(dst)).cast(), - count.into(), - &mut self.pfhandler, - )?; - } - BMC => { - let ParamBBD(src, dst, count) = param!(self, ParamBBD); - self.memory.block_copy( - self.read_reg(src).as_u64(), - self.read_reg(dst).as_u64(), - count as _, - &mut self.pfhandler, - )?; - } - BRC => { - let ParamBBB(src, dst, count) = param!(self, ParamBBB); - core::ptr::copy( - self.registers.get_unchecked(usize::from(src)), - self.registers.get_unchecked_mut(usize::from(dst)), - usize::from(count), - ); - } - JAL => { - let ParamBBD(save, reg, offset) = param!(self, ParamBBD); - self.write_reg(save, self.pc as u64); - self.pc = (self.read_reg(reg).as_u64() + offset) as usize; - } - JEQ => cond_jump!(self, int, Equal), - JNE => { - let ParamBBD(a0, a1, jt) = param!(self, ParamBBD); - if self.read_reg(a0).as_u64() != self.read_reg(a1).as_u64() { - self.pc = jt as usize; - } - } - JLT => cond_jump!(self, int, Less), - JGT => cond_jump!(self, int, Greater), - JLTU => cond_jump!(self, sint, Less), - JGTU => cond_jump!(self, sint, Greater), - ECALL => { - param!(self, ()); - return Ok(VmRunOk::Ecall); - } - ADDF => binary_op!(self, as_f64, ops::Add::add), - SUBF => binary_op!(self, as_f64, ops::Sub::sub), - MULF => binary_op!(self, as_f64, ops::Mul::mul), - DIRF => { - let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB); - let a0 = self.read_reg(a0).as_f64(); - let a1 = self.read_reg(a1).as_f64(); - self.write_reg(dt, a0 / a1); - self.write_reg(rt, a0 % a1); - } - FMAF => { - let ParamBBBB(dt, a0, a1, a2) = param!(self, ParamBBBB); - self.write_reg( - dt, - self.read_reg(a0).as_f64() * self.read_reg(a1).as_f64() - + self.read_reg(a2).as_f64(), - ); - } - NEGF => { - let ParamBB(dt, a0) = param!(self, ParamBB); - self.write_reg(dt, -self.read_reg(a0).as_f64()); - } - ITF => { - let ParamBB(dt, a0) = param!(self, ParamBB); - self.write_reg(dt, self.read_reg(a0).as_i64() as f64); - } - FTI => { - let ParamBB(dt, a0) = param!(self, ParamBB); - self.write_reg(dt, self.read_reg(a0).as_f64() as i64); - } - ADDFI => binary_op_imm!(self, as_f64, ops::Add::add), - MULFI => binary_op_imm!(self, as_f64, ops::Mul::mul), - op => return Err(VmRunError::InvalidOpcode(op)), - } - } - - if TIMER_QUOTIENT != 0 { - self.timer = self.timer.wrapping_add(1); - if self.timer % TIMER_QUOTIENT == 0 { - return Ok(VmRunOk::Timer); - } - } - } - } - - /// Read register - #[inline] - unsafe fn read_reg(&self, n: u8) -> Value { - *self.registers.get_unchecked(n as usize) - } - - /// Write a register. - /// Writing to register 0 is no-op. - #[inline] - unsafe fn write_reg(&mut self, n: u8, value: impl Into) { - if n != 0 { - *self.registers.get_unchecked_mut(n as usize) = value.into(); - } - } -} - -/// Virtual machine halt error -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u8)] -pub enum VmRunError { - /// Tried to execute invalid instruction - InvalidOpcode(u8), - - /// Unhandled load access exception - LoadAccessEx(u64), - - /// Unhandled store access exception - StoreAccessEx(u64), -} - -/// Virtual machine halt ok -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum VmRunOk { - /// Program has eached its end - End, - - /// Program was interrupted by a timer - Timer, - - /// Environment call - Ecall, -} diff --git a/hbvm/src/vm/value.rs b/hbvm/src/vm/value.rs deleted file mode 100644 index ffc069c..0000000 --- a/hbvm/src/vm/value.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! HoleyBytes register value definition - -use core::fmt::Debug; - -/// Define [`Value`] union -/// -/// # Safety -/// Union variants have to be sound to byte-reinterpretate -/// between each other. Otherwise the behaviour is undefined. -macro_rules! value_def { - ($($ty:ident),* $(,)?) => { - /// HBVM register value - #[derive(Copy, Clone)] - #[repr(packed)] - pub union Value { - $(pub $ty: $ty),* - } - - paste::paste! { - impl Value {$( - #[doc = "Byte-reinterpret [`Value`] as [`" $ty "`]"] - #[inline] - pub fn [](&self) -> $ty { - unsafe { self.$ty } - } - )*} - } - - $( - impl From<$ty> for Value { - #[inline] - fn from(value: $ty) -> Self { - Self { $ty: value } - } - } - )* - }; -} - -value_def!(u64, i64, f64); -static_assertions::const_assert_eq!(core::mem::size_of::(), 8); - -impl Debug for Value { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // Print formatted as hexadecimal, unsigned integer - write!(f, "{:x}", self.as_u64()) - } -} diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs new file mode 100644 index 0000000..56e7f42 --- /dev/null +++ b/hbvm/src/vmrun.rs @@ -0,0 +1,570 @@ +//! Welcome to the land of The Great Dispatch Loop +//! +//! Have fun + +use { + super::{bmc::BlockCopier, mem::Memory, value::ValueVariant, Vm, VmRunError, VmRunOk}, + crate::{ + mem::{addr::AddressOp, Address}, + value::CheckedDivRem, + }, + core::{cmp::Ordering, ops}, + hbbytecode::{ + OpsN, OpsO, OpsP, OpsRB, OpsRD, OpsRH, OpsRR, OpsRRA, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, + OpsRRO, OpsRROH, OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, OpsRW, RoundingMode, + }, +}; + +macro_rules! handler { + ($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{ + let $ty($($ident),*) = $self.decode::<$ty>(); + #[allow(clippy::no_effect)] let e = $expr; + $self.bump_pc::<$ty>(); + e + }}; +} + +impl Vm +where + Mem: Memory, +{ + /// Execute program + /// + /// Program can return [`VmRunError`] if a trap handling failed + #[cfg_attr(feature = "nightly", repr(align(4096)))] + pub fn run(&mut self) -> Result { + use hbbytecode::opcode::*; + loop { + // Big match + // + // Contribution guide: + // - Zero register shall never be overwitten. It's value has to always be 0. + // - Prefer `Self::read_reg` and `Self::write_reg` functions + // - Extract parameters using `param!` macro + // - Prioritise speed over code size + // - Memory is cheap, CPUs not that much + // - Do not heap allocate at any cost + // - Yes, user-provided trap handler may allocate, + // but that is not our »fault«. + // - Unsafe is kinda must, but be sure you have validated everything + // - Your contributions have to pass sanitizers and Miri + // - Strictly follow the spec + // - The spec does not specify how you perform actions, in what order, + // just that the observable effects have to be performed in order and + // correctly. + // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU + // sorry 8 bit fans, HBVM won't run on your Speccy :( + unsafe { + match self.memory.prog_read::(self.pc as _) { + UN => { + self.bump_pc::(); + return Err(VmRunError::Unreachable); + } + TX => { + self.bump_pc::(); + return Ok(VmRunOk::End); + } + NOP => handler!(self, |OpsN()| ()), + ADD8 => self.binary_op(u8::wrapping_add), + ADD16 => self.binary_op(u16::wrapping_add), + ADD32 => self.binary_op(u32::wrapping_add), + ADD64 => self.binary_op(u64::wrapping_add), + SUB8 => self.binary_op(u8::wrapping_sub), + SUB16 => self.binary_op(u16::wrapping_sub), + SUB32 => self.binary_op(u32::wrapping_sub), + SUB64 => self.binary_op(u64::wrapping_sub), + MUL8 => self.binary_op(u8::wrapping_mul), + MUL16 => self.binary_op(u16::wrapping_mul), + MUL32 => self.binary_op(u32::wrapping_mul), + MUL64 => self.binary_op(u64::wrapping_mul), + AND => self.binary_op::(ops::BitAnd::bitand), + OR => self.binary_op::(ops::BitOr::bitor), + XOR => self.binary_op::(ops::BitXor::bitxor), + SLU8 => self.binary_op_shift::(u8::wrapping_shl), + SLU16 => self.binary_op_shift::(u16::wrapping_shl), + SLU32 => self.binary_op_shift::(u32::wrapping_shl), + SLU64 => self.binary_op_shift::(u64::wrapping_shl), + SRU8 => self.binary_op_shift::(u8::wrapping_shr), + SRU16 => self.binary_op_shift::(u16::wrapping_shr), + SRU32 => self.binary_op_shift::(u32::wrapping_shr), + SRU64 => self.binary_op_shift::(u64::wrapping_shr), + SRS8 => self.binary_op_shift::(i8::wrapping_shr), + SRS16 => self.binary_op_shift::(i16::wrapping_shr), + SRS32 => self.binary_op_shift::(i32::wrapping_shr), + SRS64 => self.binary_op_shift::(i64::wrapping_shr), + CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp( + tg, + a0, + self.read_reg(a1).cast::() + )), + CMPS => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp( + tg, + a0, + self.read_reg(a1).cast::() + )), + DIRU8 => self.dir::(), + DIRU16 => self.dir::(), + DIRU32 => self.dir::(), + DIRU64 => self.dir::(), + DIRS8 => self.dir::(), + DIRS16 => self.dir::(), + DIRS32 => self.dir::(), + DIRS64 => self.dir::(), + NEG => handler!(self, |OpsRR(tg, a0)| { + // Bit negation + self.write_reg(tg, !self.read_reg(a0).cast::()) + }), + NOT => handler!(self, |OpsRR(tg, a0)| { + // Logical negation + self.write_reg(tg, u64::from(self.read_reg(a0).cast::() == 0)); + }), + SXT8 => handler!(self, |OpsRR(tg, a0)| { + self.write_reg(tg, self.read_reg(a0).cast::() as i64) + }), + SXT16 => handler!(self, |OpsRR(tg, a0)| { + self.write_reg(tg, self.read_reg(a0).cast::() as i64) + }), + SXT32 => handler!(self, |OpsRR(tg, a0)| { + self.write_reg(tg, self.read_reg(a0).cast::() as i64) + }), + ADDI8 => self.binary_op_imm(u8::wrapping_add), + ADDI16 => self.binary_op_imm(u16::wrapping_add), + ADDI32 => self.binary_op_imm(u32::wrapping_add), + ADDI64 => self.binary_op_imm(u64::wrapping_add), + MULI8 => self.binary_op_imm(u8::wrapping_sub), + MULI16 => self.binary_op_imm(u16::wrapping_sub), + MULI32 => self.binary_op_imm(u32::wrapping_sub), + MULI64 => self.binary_op_imm(u64::wrapping_sub), + ANDI => self.binary_op_imm::(ops::BitAnd::bitand), + ORI => self.binary_op_imm::(ops::BitOr::bitor), + XORI => self.binary_op_imm::(ops::BitXor::bitxor), + SLUI8 => self.binary_op_ims::(u8::wrapping_shl), + SLUI16 => self.binary_op_ims::(u16::wrapping_shl), + SLUI32 => self.binary_op_ims::(u32::wrapping_shl), + SLUI64 => self.binary_op_ims::(u64::wrapping_shl), + SRUI8 => self.binary_op_ims::(u8::wrapping_shr), + SRUI16 => self.binary_op_ims::(u16::wrapping_shr), + SRUI32 => self.binary_op_ims::(u32::wrapping_shr), + SRUI64 => self.binary_op_ims::(u64::wrapping_shr), + SRSI8 => self.binary_op_ims::(i8::wrapping_shr), + SRSI16 => self.binary_op_ims::(i16::wrapping_shr), + SRSI32 => self.binary_op_ims::(i32::wrapping_shr), + SRSI64 => self.binary_op_ims::(i64::wrapping_shr), + CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm) }), + CMPSI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm as i64) }), + CP => handler!(self, |OpsRR(tg, a0)| self.write_reg(tg, self.read_reg(a0))), + SWA => handler!(self, |OpsRR(r0, r1)| { + // Swap registers + match (r0, r1) { + (0, 0) => (), + (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), + (r0, r1) => { + core::ptr::swap( + self.registers.get_unchecked_mut(usize::from(r0)), + self.registers.get_unchecked_mut(usize::from(r1)), + ); + } + } + }), + LI8 => handler!(self, |OpsRB(tg, imm)| self.write_reg(tg, imm)), + LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)), + LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)), + LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)), + LRA => handler!(self, |OpsRRO(tg, reg, off)| self.write_reg( + tg, + self.pcrel(off, 3) + .wrapping_add(self.read_reg(reg).cast::()) + .get(), + )), + // Load. If loading more than register size, continue on adjecent registers + LD => handler!(self, |OpsRRAH(dst, base, off, count)| self + .load(dst, base, off, count)?), + // Store. Same rules apply as to LD + ST => handler!(self, |OpsRRAH(dst, base, off, count)| self + .store(dst, base, off, count)?), + LDR => handler!(self, |OpsRROH(dst, base, off, count)| self.load( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), + STR => handler!(self, |OpsRROH(dst, base, off, count)| self.store( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), + BMC => { + // Block memory copy + match if let Some(copier) = &mut self.copier { + // There is some copier, poll. + copier.poll(&mut self.memory) + } else { + // There is none, make one! + let OpsRRH(src, dst, count) = self.decode(); + + self.copier = Some(BlockCopier::new( + Address::new(self.read_reg(src).cast()), + Address::new(self.read_reg(dst).cast()), + count as _, + )); + + self.copier + .as_mut() + .unwrap_unchecked() // SAFETY: We just assigned there + .poll(&mut self.memory) + } { + // We are done, shift program counter + core::task::Poll::Ready(Ok(())) => { + self.copier = None; + self.bump_pc::(); + } + // Error, shift program counter (for consistency) + // and yield error + core::task::Poll::Ready(Err(e)) => { + return Err(e.into()); + } + // Not done yet, proceed to next cycle + core::task::Poll::Pending => (), + } + } + BRC => handler!(self, |OpsRRB(src, dst, count)| { + // Block register copy + if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { + return Err(VmRunError::RegOutOfBounds); + } + + core::ptr::copy( + self.registers.get_unchecked(usize::from(src)), + self.registers.get_unchecked_mut(usize::from(dst)), + usize::from(count), + ); + }), + JMP => { + let OpsO(off) = self.decode(); + self.pc = self.pc.wrapping_add(off).wrapping_add(1); + } + JAL => { + // Jump and link. Save PC after this instruction to + // specified register and jump to reg + relative offset. + let OpsRRO(save, reg, offset) = self.decode(); + + self.write_reg(save, self.pc.get()); + self.pc = self + .pcrel(offset, 3) + .wrapping_add(self.read_reg(reg).cast::()); + } + JALA => { + // Jump and link. Save PC after this instruction to + // specified register and jump to reg + let OpsRRA(save, reg, offset) = self.decode(); + + self.write_reg(save, self.pc.get()); + self.pc = + Address::new(self.read_reg(reg).cast::().wrapping_add(offset)); + } + // Conditional jumps, jump only to immediates + JEQ => self.cond_jmp::(Ordering::Equal), + JNE => { + let OpsRRP(a0, a1, ja) = self.decode(); + if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { + self.pc = self.pcrel(ja, 3); + } else { + self.bump_pc::(); + } + } + JLTS => self.cond_jmp::(Ordering::Less), + JGTS => self.cond_jmp::(Ordering::Greater), + JLTU => self.cond_jmp::(Ordering::Less), + JGTU => self.cond_jmp::(Ordering::Greater), + ECA => { + // So we don't get timer interrupt after ECALL + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + } + + self.bump_pc::(); + return Ok(VmRunOk::Ecall); + } + EBP => { + self.bump_pc::(); + return Ok(VmRunOk::Breakpoint); + } + FADD32 => self.binary_op::(ops::Add::add), + FADD64 => self.binary_op::(ops::Add::add), + FSUB32 => self.binary_op::(ops::Sub::sub), + FSUB64 => self.binary_op::(ops::Sub::sub), + FMUL32 => self.binary_op::(ops::Mul::mul), + FMUL64 => self.binary_op::(ops::Mul::mul), + FDIV32 => self.binary_op::(ops::Div::div), + FDIV64 => self.binary_op::(ops::Div::div), + FMA32 => self.fma::(), + FMA64 => self.fma::(), + FINV32 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, 1. / self.read_reg(reg).cast::())), + FINV64 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, 1. / self.read_reg(reg).cast::())), + FCMPLT32 => self.fcmp::(Ordering::Less), + FCMPLT64 => self.fcmp::(Ordering::Less), + FCMPGT32 => self.fcmp::(Ordering::Greater), + FCMPGT64 => self.fcmp::(Ordering::Greater), + ITF32 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, self.read_reg(reg).cast::() as f32)), + ITF64 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, self.read_reg(reg).cast::() as f64)), + FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg( + tg, + crate::float::f32toint( + self.read_reg(reg).cast::(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + )), + FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg( + tg, + crate::float::f64toint( + self.read_reg(reg).cast::(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + )), + FC32T64 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, self.read_reg(reg).cast::() as f64)), + FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg( + tg, + crate::float::conv64to32( + self.read_reg(reg).cast(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + )), + LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| self.write_reg( + tg, + (self.pc + self.read_reg(reg).cast::() + imm + 3_u16).get(), + )), + LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.load( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), + STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.store( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), + JMP16 => { + let OpsP(off) = self.decode(); + self.pc = self.pcrel(off, 1); + } + op => return Err(VmRunError::InvalidOpcode(op)), + } + } + + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + if self.timer % TIMER_QUOTIENT == 0 { + return Ok(VmRunOk::Timer); + } + } + } + } + + /// Bump instruction pointer + #[inline(always)] + fn bump_pc(&mut self) { + self.pc = self.pc.wrapping_add(core::mem::size_of::()); + } + + /// Decode instruction operands + #[inline(always)] + unsafe fn decode(&mut self) -> T { + self.memory.prog_read::(self.pc + 1_u64) + } + + /// Load + #[inline(always)] + unsafe fn load( + &mut self, + dst: u8, + base: u8, + offset: u64, + count: u16, + ) -> Result<(), VmRunError> { + let n: u8 = match dst { + 0 => 1, + _ => 0, + }; + + self.memory.load( + self.ldst_addr_uber(dst, base, offset, count, n)?, + self.registers + .as_mut_ptr() + .add(usize::from(dst) + usize::from(n)) + .cast(), + usize::from(count).saturating_sub(n.into()), + )?; + + Ok(()) + } + + /// Store + #[inline(always)] + unsafe fn store( + &mut self, + dst: u8, + base: u8, + offset: u64, + count: u16, + ) -> Result<(), VmRunError> { + self.memory.store( + self.ldst_addr_uber(dst, base, offset, count, 0)?, + self.registers.as_ptr().add(usize::from(dst)).cast(), + count.into(), + )?; + Ok(()) + } + + /// Three-way comparsion + #[inline(always)] + unsafe fn cmp(&mut self, to: u8, reg: u8, val: T) { + self.write_reg(to, self.read_reg(reg).cast::().cmp(&val) as i64); + } + + /// Perform binary operating over two registers + #[inline(always)] + unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { + let OpsRRR(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), + ); + self.bump_pc::(); + } + + /// Perform binary operation over register and immediate + #[inline(always)] + unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { + #[derive(Clone, Copy)] + #[repr(packed)] + struct OpsRRImm(OpsRR, I); + + let OpsRRImm::(OpsRR(tg, reg), imm) = self.decode(); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); + self.bump_pc::>(); + } + + /// Perform binary operation over register and shift immediate + #[inline(always)] + unsafe fn binary_op_shift(&mut self, op: impl Fn(T, u32) -> T) { + let OpsRRR(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + op( + self.read_reg(a0).cast::(), + self.read_reg(a1).cast::(), + ), + ); + self.bump_pc::(); + } + + /// Perform binary operation over register and shift immediate + #[inline(always)] + unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { + let OpsRRB(tg, reg, imm) = self.decode(); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm.into())); + self.bump_pc::(); + } + + /// Fused division-remainder + #[inline(always)] + unsafe fn dir(&mut self) { + handler!(self, |OpsRRRR(td, tr, a0, a1)| { + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + + if let Some(div) = a0.checked_div(a1) { + self.write_reg(td, div); + } else { + self.write_reg(td, -1_i64); + } + + if let Some(rem) = a0.checked_rem(a1) { + self.write_reg(tr, rem); + } else { + self.write_reg(tr, a0); + } + }); + } + + /// Fused multiply-add + #[inline(always)] + unsafe fn fma(&mut self) + where + T: ValueVariant + core::ops::Mul + core::ops::Add, + { + handler!(self, |OpsRRRR(tg, a0, a1, a2)| { + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + let a2 = self.read_reg(a2).cast::(); + self.write_reg(tg, a0 * a1 + a2) + }); + } + + /// Float comparsion + #[inline(always)] + unsafe fn fcmp(&mut self, nan: Ordering) { + handler!(self, |OpsRRR(tg, a0, a1)| { + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + self.write_reg(tg, (a0.partial_cmp(&a1).unwrap_or(nan) as i8 + 1) as u8) + }); + } + + /// Calculate pc-relative address + #[inline(always)] + fn pcrel(&self, offset: impl AddressOp, pos: u8) -> Address { + self.pc.wrapping_add(pos).wrapping_add(offset) + } + + /// Jump at `PC + #3` if ordering on `#0 <=> #1` is equal to expected + #[inline(always)] + unsafe fn cond_jmp(&mut self, expected: Ordering) { + let OpsRRP(a0, a1, ja) = self.decode(); + if self + .read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + == expected + { + self.pc = self.pcrel(ja, 3); + } else { + self.bump_pc::(); + } + } + + /// Load / Store Address check-computation überfunction + #[inline(always)] + unsafe fn ldst_addr_uber( + &self, + dst: u8, + base: u8, + offset: u64, + size: u16, + adder: u8, + ) -> Result { + let reg = dst.checked_add(adder).ok_or(VmRunError::RegOutOfBounds)?; + + if usize::from(reg) * 8 + usize::from(size) > 2048 { + Err(VmRunError::RegOutOfBounds) + } else { + self.read_reg(base) + .cast::() + .checked_add(offset) + .and_then(|x| x.checked_add(adder.into())) + .ok_or(VmRunError::AddrOutOfBounds) + .map(Address::new) + } + } +} diff --git a/hbvm_aos_on_linux/Cargo.toml b/hbvm_aos_on_linux/Cargo.toml new file mode 100644 index 0000000..a82122d --- /dev/null +++ b/hbvm_aos_on_linux/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hbvm_aos_on_linux" +version = "0.1.0" +edition = "2021" +default-run = "hbvm_aos_on_linux" + +[dependencies] +hbvm.path = "../hbvm" +nix = { version = "0.27", features = ["mman", "signal"] } diff --git a/hbvm_aos_on_linux/readme.md b/hbvm_aos_on_linux/readme.md new file mode 100644 index 0000000..24d919f --- /dev/null +++ b/hbvm_aos_on_linux/readme.md @@ -0,0 +1,3 @@ +As close to the AbleOS runtime as possible + +useful for me to spec out things on my laptop \ No newline at end of file diff --git a/hbvm_aos_on_linux/src/main.rs b/hbvm_aos_on_linux/src/main.rs new file mode 100644 index 0000000..38dea08 --- /dev/null +++ b/hbvm_aos_on_linux/src/main.rs @@ -0,0 +1,96 @@ +//! Holey Bytes Experimental Runtime +mod mem; + +use { + hbvm::{mem::Address, Vm, VmRunOk}, + nix::sys::mman::{mmap, MapFlags, ProtFlags}, + std::{env::args, fs::File, num::NonZeroUsize, process::exit}, +}; + +fn main() -> Result<(), Box> { + eprintln!("== HB×RT (Holey Bytes Linux Runtime) v0.1 =="); + eprintln!("[W] Currently supporting only flat images"); + + let Some(image_path) = args().nth(1) else { + eprintln!("[E] Missing image path"); + exit(1); + }; + + // Load program + eprintln!("[I] Loading image from \"{image_path}\""); + let file = File::open(image_path)?; + let ptr = unsafe { + mmap( + None, + NonZeroUsize::new(file.metadata()?.len() as usize).ok_or("File is empty")?, + ProtFlags::PROT_READ, + MapFlags::MAP_PRIVATE, + Some(&file), + 0, + )? + }; + + eprintln!("[I] Image loaded at {ptr:p}"); + + // Execute program + let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(ptr as u64)) }; + + // Memory access fault handling + unsafe { + use nix::sys::signal; + + extern "C" fn action( + _: std::ffi::c_int, + info: *mut nix::libc::siginfo_t, + _: *mut std::ffi::c_void, + ) { + unsafe { + eprintln!("[E] Memory access fault at {:p}", (*info).si_addr()); + } + } + + signal::sigaction( + signal::Signal::SIGSEGV, + &nix::sys::signal::SigAction::new( + signal::SigHandler::SigAction(action), + signal::SaFlags::SA_NODEFER, + nix::sys::signalfd::SigSet::empty(), + ), + )?; + } + + let stat = loop { + match vm.run() { + Ok(VmRunOk::Breakpoint) => eprintln!( + "[I] Hit breakpoint\nIP: {}\n== Registers ==\n{:?}", + vm.pc, vm.registers + ), + Ok(VmRunOk::Timer) => (), + Ok(VmRunOk::Ecall) => { + + // unsafe { + // std::arch::asm!( + // "syscall", + // inlateout("rax") vm.registers[1].0, + // in("rdi") vm.registers[2].0, + // in("rsi") vm.registers[3].0, + // in("rdx") vm.registers[4].0, + // in("r10") vm.registers[5].0, + // in("r8") vm.registers[6].0, + // in("r9") vm.registers[7].0, + // ) + // } + } + Ok(VmRunOk::End) => break Ok(()), + Err(e) => break Err(e), + } + }; + + eprintln!("\n== Registers ==\n{:?}", vm.registers); + if let Err(e) = stat { + eprintln!("\n[E] Runtime error: {e:?}"); + exit(2); + } + + Ok(()) +} diff --git a/hbvm_aos_on_linux/src/mem.rs b/hbvm_aos_on_linux/src/mem.rs new file mode 100644 index 0000000..e1687d3 --- /dev/null +++ b/hbvm_aos_on_linux/src/mem.rs @@ -0,0 +1,31 @@ +use hbvm::mem::{Address, LoadError, Memory, StoreError}; + +pub struct HostMemory; +impl Memory for HostMemory { + #[inline] + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError> { + unsafe { core::ptr::copy(addr.get() as *const u8, target, count) } + Ok(()) + } + + #[inline] + unsafe fn store( + &mut self, + addr: Address, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) } + Ok(()) + } + + #[inline] + unsafe fn prog_read(&mut self, addr: Address) -> T { + core::ptr::read(addr.get() as *const T) + } +} diff --git a/hbxrt/Cargo.toml b/hbxrt/Cargo.toml new file mode 100644 index 0000000..2fd08a9 --- /dev/null +++ b/hbxrt/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hbxrt" +version = "0.1.0" +edition = "2021" +default-run = "hbxrt" + +[dependencies] +hbvm.path = "../hbvm" +nix = { version = "0.27", features = ["mman", "signal"] } diff --git a/hbxrt/src/main.rs b/hbxrt/src/main.rs new file mode 100644 index 0000000..0afb807 --- /dev/null +++ b/hbxrt/src/main.rs @@ -0,0 +1,94 @@ +//! Holey Bytes Experimental Runtime +mod mem; + +use { + hbvm::{mem::Address, Vm, VmRunOk}, + nix::sys::mman::{mmap, MapFlags, ProtFlags}, + std::{env::args, fs::File, num::NonZeroUsize, process::exit}, +}; + +fn main() -> Result<(), Box> { + eprintln!("== HB×RT (Holey Bytes Linux Runtime) v0.1 =="); + eprintln!("[W] Currently supporting only flat images"); + + let Some(image_path) = args().nth(1) else { + eprintln!("[E] Missing image path"); + exit(1); + }; + + // Load program + eprintln!("[I] Loading image from \"{image_path}\""); + let file = File::open(image_path)?; + let ptr = unsafe { + mmap( + None, + NonZeroUsize::new(file.metadata()?.len() as usize).ok_or("File is empty")?, + ProtFlags::PROT_READ, + MapFlags::MAP_PRIVATE, + Some(&file), + 0, + )? + }; + + eprintln!("[I] Image loaded at {ptr:p}"); + + // Execute program + let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(ptr as u64)) }; + + // Memory access fault handling + unsafe { + use nix::sys::signal; + + extern "C" fn action( + _: std::ffi::c_int, + info: *mut nix::libc::siginfo_t, + _: *mut std::ffi::c_void, + ) { + unsafe { + eprintln!("[E] Memory access fault at {:p}", (*info).si_addr()); + exit(2); + } + } + + signal::sigaction( + signal::Signal::SIGSEGV, + &nix::sys::signal::SigAction::new( + signal::SigHandler::SigAction(action), + signal::SaFlags::SA_NODEFER, + nix::sys::signalfd::SigSet::empty(), + ), + )?; + } + + let stat = loop { + match vm.run() { + Ok(VmRunOk::Breakpoint) => eprintln!( + "[I] Hit breakpoint\nIP: {}\n== Registers ==\n{:?}", + vm.pc, vm.registers + ), + Ok(VmRunOk::Timer) => (), + Ok(VmRunOk::Ecall) => unsafe { + std::arch::asm!( + "syscall", + inlateout("rax") vm.registers[1].0, + in("rdi") vm.registers[2].0, + in("rsi") vm.registers[3].0, + in("rdx") vm.registers[4].0, + in("r10") vm.registers[5].0, + in("r8") vm.registers[6].0, + in("r9") vm.registers[7].0, + ) + }, + Ok(VmRunOk::End) => break Ok(()), + Err(e) => break Err(e), + } + }; + + eprintln!("\n== Registers ==\n{:?}", vm.registers); + if let Err(e) = stat { + eprintln!("\n[E] Runtime error: {e:?}"); + exit(2); + } + + Ok(()) +} diff --git a/hbxrt/src/mem.rs b/hbxrt/src/mem.rs new file mode 100644 index 0000000..e1687d3 --- /dev/null +++ b/hbxrt/src/mem.rs @@ -0,0 +1,31 @@ +use hbvm::mem::{Address, LoadError, Memory, StoreError}; + +pub struct HostMemory; +impl Memory for HostMemory { + #[inline] + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError> { + unsafe { core::ptr::copy(addr.get() as *const u8, target, count) } + Ok(()) + } + + #[inline] + unsafe fn store( + &mut self, + addr: Address, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) } + Ok(()) + } + + #[inline] + unsafe fn prog_read(&mut self, addr: Address) -> T { + core::ptr::read(addr.get() as *const T) + } +} diff --git a/rustfmt.toml b/rustfmt.toml index 90a36c7..907ba34 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ hex_literal_case = "Upper" imports_granularity = "One" -struct_field_align_threshold = 5 -enum_discrim_align_threshold = 5 \ No newline at end of file +struct_field_align_threshold = 8 +enum_discrim_align_threshold = 8 \ No newline at end of file diff --git a/spec.md b/spec.md index 55c3aaa..a30e50e 100644 --- a/spec.md +++ b/spec.md @@ -1,295 +1,497 @@ # HoleyBytes ISA Specification # Bytecode format +- Image format is not specified, though ELF is recommended - All numbers are encoded little-endian - There is 256 registers, they are represented by a byte -- Immediate values are 64 bit +- Immediate values are 8, 16, 32 or 64 bit -### Instruction encoding -- Instruction parameters are packed (no alignment) -- [opcode, …parameters…] +## Instruction encoding +- Instruction operands are packed (no alignment) +- [opcode, operand 0, operand 1, …] -### Instruction parameter types -- B = Byte -- D = Doubleword (64 bits) -- H = Halfword (16 bits) +## Instruction parameter types +- `R`: Register (8 bits) +- Relative program-counter offset immediates: + - `O`: 32 bit (Si32) + - `P`: 16 bit (Si16) +- Immediates: + - `B`: Byte, 8 bit (Xi8) + - `H`: Half-word, 16 bit (Xi16) + - `W`: Word, 32 bit (Xi32) + - `D`: Double-word, 64 bit (Xi64) +- `A`: Absolute address immediate, 64 bit (Ui64) -| Name | Size | -|:----:|:--------| -| BBBB | 32 bits | -| BBB | 24 bits | -| BBDH | 96 bits | -| BBD | 80 bits | -| BB | 16 bits | -| BD | 72 bits | -| D | 64 bits | -| N | 0 bits | +## Types +- Si*n*: Signed integer of size *n* bits (Si8, Si16, Si32, Si64) +- Ui*n*: Unsigned integer of size *n* bits (Ui8, Ui16, Ui32, Ui64) +- Xi*n*: Sign-agnostic integer of size *n* bits (Xi8, Xi16, Xi32, Xi64) +- Fl*n*: Floating point number of size *n* bits (Fl32, Fl64) + +# Behaviour +- There is only one type of register, a general-purpose one. + Used for both integers and floats. +- Integer operations are wrapping, including signed numbers + - Bitshifts are truncating +- Two's complement +- Floats as specified by IEEE 754 +- Execution model is implementation defined as long all observable + effects are performed in correct order + +## Relative addressing +Relative addresses are computed from address of the first byte +of offset in the code. Not from the beginning of current or following instruction. + +## Zero register +- Register 0 +- Cannot be clobbered + - Write is no-op +- Load always yields 0 + +## Rounding modes +| Rounding mode | Value | +|:-------------------------|:------| +| To nearest, ties to even | 0b00 | +| Towards 0 (truncate) | 0b01 | +| Towards +∞ (up) | 0b10 | +| Towards -∞ (down) | 0b11 | + +- Remaining values in the byte traps with invalid operand exception + +# Memory +- Memory implementation is implementation-defined +- Zero address (`0x0`) is considered invalid + +# Traps +- Environment call +- Environment breakpoint + +Program counter goes to the following instruction + +## Exceptions +- Memory access fault +- Invalid operand +- Unknown opcode + +Program counter stays on the currently executed instruction # Instructions - `#n`: register in parameter *n* -- `imm #n`: for immediate in parameter *n* -- `P ← V`: Set register P to value V +- `$n`: for immediate in parameter *n* +- `#P ← V`: Set register P to value V - `[x]`: Address x +- `XY`: X bytes from location Y +- `pc`: Program counter +- ``: Placeholder +- `Type(X)`: Cast -## No-op -- N type +## Program execution control +- Type `N` -| Opcode | Name | Action | -|:------:|:----:|:----------:| -| 0 | NOP | Do nothing | +| Opcode | Mnemonic | Action | +|:-------|:---------|:--------------------------------------------| +| 0x00 | UN | Throw unreachable code exception | +| 0x01 | TX | Terminate execution (eg. on end of program) | +| 0x02 | NOP | Do nothing | -## Integer binary ops. -- BBB type -- `#0 ← #1 #2` +## Binary register-immediate ops +- Type `RR` +- Action: `#0 ← #1 #2` -| Opcode | Name | Action | -|:------:|:----:|:-----------------------:| -| 1 | ADD | Wrapping addition | -| 2 | SUB | Wrapping subtraction | -| 3 | MUL | Wrapping multiplication | -| 4 | AND | Bitand | -| 5 | OR | Bitor | -| 6 | XOR | Bitxor | -| 7 | SL | Unsigned left bitshift | -| 8 | SR | Unsigned right bitshift | -| 9 | SRS | Signed right bitshift | +## Addition (`+`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x03 | ADD8 | Xi8 | +| 0x04 | ADD16 | Xi16 | +| 0x05 | ADD32 | Xi32 | +| 0x06 | ADD64 | Xi64 | -### Comparsion -| Opcode | Name | Action | -|:------:|:----:|:-------------------:| -| 10 | CMP | Signed comparsion | -| 11 | CMPU | Unsigned comparsion | +## Subtraction (`-`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x07 | SUB8 | Xi8 | +| 0x08 | SUB16 | Xi16 | +| 0x09 | SUB32 | Xi32 | +| 0x0A | SUB64 | Xi64 | -#### Comparsion table -| #1 *op* #2 | Result | -|:----------:|:------:| -| < | -1 | -| = | 0 | -| > | 1 | +## Multiplication (`*`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x0B | MUL8 | Xi8 | +| 0x0C | MUL16 | Xi16 | +| 0x0D | MUL32 | Xi32 | +| 0x0E | MUL64 | Xi64 | -### Division-remainder -- Type BBBB -- In case of `#3` is zero, the resulting value is all-ones -- `#0 ← #2 ÷ #3` -- `#1 ← #2 % #3` +## Bitwise ops (type: Xi64) +| Opcode | Mnemonic | Operation | +|:-------|:---------|:--------------------| +| 0x0F | AND | Conjunction (&) | +| 0x10 | OR | Disjunction (\|) | +| 0x11 | XOR | Non-equivalence (^) | -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------:| -| 12 | DIR | Divide and remainder combinated | +## Unsigned left bitshift (`<<`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x12 | SLU8 | Ui8 | +| 0x13 | SLU16 | Ui16 | +| 0x14 | SLU32 | Ui32 | +| 0x15 | SLU64 | Ui64 | -### Negations -- Type BB -- `#0 ← #1 #2` +## Unsigned right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x16 | SRU8 | Ui8 | +| 0x17 | SRU16 | Ui16 | +| 0x18 | SRU32 | Ui32 | +| 0x19 | SRU64 | Ui64 | -| Opcode | Name | Action | -|:------:|:----:|:----------------:| -| 13 | NEG | Bit negation | -| 14 | NOT | Logical negation | +## Signed right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x1A | SRS8 | Si8 | +| 0x1B | SRS16 | Si16 | +| 0x1C | SRS32 | Si32 | +| 0x1D | SRS64 | Si64 | -## Integer immediate binary ops. -- Type BBD -- `#0 ← #1 imm #2` +## Comparsion +- Compares two numbers, saves result to register +- Operation: `#0 ← #1 <=> #2` -| Opcode | Name | Action | -|:------:|:----:|:-----------------------:| -| 15 | ADDI | Wrapping addition | -| 16 | MULI | Wrapping subtraction | -| 17 | ANDI | Bitand | -| 18 | ORI | Bitor | -| 19 | XORI | Bitxor | -| 20 | SLI | Unsigned left bitshift | -| 21 | SRI | Unsigned right bitshift | -| 22 | SRSI | Signed right bitshift | +| Ordering | Number | +|:---------|:-------| +| < | -1 | +| = | 0 | +| > | 1 | -### Comparsion -- Comparsion is the same as when RRR type +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x1E | CMPU | Ui64 | +| 0x1F | CMPS | Si64 | -| Opcode | Name | Action | -|:------:|:-----:|:-------------------:| -| 23 | CMPI | Signed comparsion | -| 24 | CMPUI | Unsigned comparsion | +# Merged divide-remainder +- Type `RRRR` +- Operation: + - `#0 ← #2 / #3` + - `#1 ← #2 % #3` -## Register value set / copy +- If dividing by zero: + - `#0 ← Ui64(-1)` + - `#1 ← #2` -### Copy -- Type BB -- `#0 ← #1` +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x20 | DIRU8 | Ui8 | +| 0x21 | DIRU16 | Ui16 | +| 0x22 | DIRU32 | Ui32 | +| 0x23 | DIRU64 | Ui64 | +| 0x24 | DIRS8 | Si8 | +| 0x25 | DIRS16 | Si16 | +| 0x26 | DIRS32 | Si32 | +| 0x27 | DIRS64 | Si64 | -| Opcode | Name | Action | -|:------:|:----:|:------:| -| 25 | CP | Copy | +# Unary register operations (type: Xi64) +- Type: `RR` +- Operation: `#0 ← #1` -### Swap -- Type BB -- Swap #0 and #1 +| Opcode | Mnemonic | Operation | +|:-------|:---------|:-------------------------| +| 0x28 | NEG | Bitwise complement (`~`) | +| 0x29 | NOT | Logical negation (`!`) | -| Opcode | Name | Action | -|:------:|:----:|:------:| -| 26 | SWA | Swap | +## Sign extensions +- Operation: `#0 ← Si64(#1)` -### Load immediate -- Type BD -- `#0 ← #1` +| Opcode | Mnemonic | Source type | +|:-------|:---------|:------------| +| 0x2A | SXT8 | Si8 | +| 0x2B | SXT16 | Si16 | +| 0x2C | SXT32 | Si32 | -| Opcode | Name | Action | -|:------:|:----:|:--------------:| -| 27 | LI | Load immediate | +# Binary register-immediate operations +- Type: `RR` +- Operation: `#0 ← #1 $2` -## Memory operations -- Type BBDH -- If loaded/store value exceeds one register size, continue accessing following registers +## Addition (`+`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x2D | ADDI8 | Xi8 | +| 0x2E | ADDI16 | Xi16 | +| 0x2F | ADDI32 | Xi32 | +| 0x30 | ADDI64 | Xi64 | -### Load / Store -| Opcode | Name | Action | -|:------:|:----:|:---------------------------------------:| -| 28 | LD | `#0 ← [#1 + imm #3], copy imm #4 bytes` | -| 29 | ST | `[#1 + imm #3] ← #0, copy imm #4 bytes` | +## Multiplication (`*`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x31 | MULI8 | Xi8 | +| 0x32 | MULI16 | Xi16 | +| 0x33 | MULI32 | Xi32 | +| 0x34 | MULI64 | Xi64 | -## Block copy -- Block copy source and target can overlap +## Bitwise ops (type: Xi64) +| Opcode | Mnemonic | Operation | +|:-------|:---------|:--------------------| +| 0x35 | ANDI | Conjunction (&) | +| 0x36 | ORI | Disjunction (\|) | +| 0x37 | XORI | Non-equivalence (^) | -### Memory copy -- Type BBD +# Register-immediate bitshifts +- Type: `RRB` +- Operation: `#0 ← #1 $2` -| Opcode | Name | Action | -|:------:|:----:|:--------------------------------:| -| 30 | BMC | `[#1] ← [#0], copy imm #2 bytes` | +## Unsigned left bitshift (`<<`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x38 | SLUI8 | Ui8 | +| 0x39 | SLUI16 | Ui16 | +| 0x3A | SLUI32 | Ui32 | +| 0x3B | SLUI64 | Ui64 | -### Register copy -- Type BBB -- Copy a block a register to another location (again, overflowing to following registers) +## Unsigned right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x3C | SRUI8 | Ui8 | +| 0x3D | SRUI16 | Ui16 | +| 0x3E | SRUI32 | Ui32 | +| 0x3F | SRUI64 | Ui64 | -| Opcode | Name | Action | -|:------:|:----:|:--------------------------------:| -| 31 | BRC | `#1 ← #0, copy imm #2 registers` | +## Signed right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x40 | SRSI8 | Si8 | +| 0x41 | SRSI16 | Si16 | +| 0x42 | SRSI32 | Si32 | +| 0x43 | SRSI64 | Si64 | -## Control flow +## Comparsion +- Compares two numbers, saves result to register +- Operation: `#0 ← #1 <=> $2` +- Comparsion table same for register-register one -### Unconditional jump -- Type BBD +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x44 | CMPUI | Ui64 | +| 0x45 | CMPSI | Si64 | -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------------------------:| -| 32 | JAL | Save current PC to `#0` and jump at `#1 + imm #2` | +# Register copies +- Type: `RR` -### Conditional jumps -- Type BBD -- Jump at `imm #2` if `#0 #1` +| Opcode | Mnemonic | Operation | +|:-------|:---------|:---------------------------------| +| 0x46 | CP | Copy register value (`#0 ← #1`) | +| 0x47 | SWA | Swap register values (`#0 ⇆ #1`) | -| Opcode | Name | Comparsion | -|:------:|:----:|:------------:| -| 33 | JEQ | = | -| 34 | JNE | ≠ | -| 35 | JLT | < (signed) | -| 36 | JGT | > (signed) | -| 37 | JLTU | < (unsigned) | -| 38 | JGTU | > (unsigned) | +# Load immediate +- Load immediate value from code to register +- Type: `R` +- Operation: `#0 ← $1` -### Environment call -- Type N +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x48 | LI8 | Xi8 | +| 0x49 | LI16 | Xi16 | +| 0x4A | Li32 | Xi32 | +| 0x4B | Li64 | Xi64 | -| Opcode | Name | Action | -|:------:|:-----:|:-------------------------------------:| -| 39 | ECALL | Cause an trap to the host environment | +# Load relative address +- Compute value from program counter, register value and offset +- Type: `RRO` +- Operation: `#0 ← pc + #1 + $2` -## Floating point operations -- Type BBB -- `#0 ← #1 #2` +| Opcode | Mnemonic | +|:-------|:---------| +| 0x4C | LRA | -| Opcode | Name | Action | -|:------:|:----:|:--------------:| -| 40 | ADDF | Addition | -| 41 | SUBF | Subtraction | -| 42 | MULF | Multiplication | +# Memory access operations +- Immediate `$3` specifies size +- If size is greater than register size, + it overflows to adjecent register + (eg. copying 16 bytes to register `r1` copies first 8 bytes to it + and the remaining to `r2`) -### Division-remainder -- Type BBBB +## Absolute addressing +- Type: `RRAH` +- Computes address from base register and absolute offset -| Opcode | Name | Action | -|:------:|:----:|:-------------------------:| -| 43 | DIRF | Same as for integer `DIR` | +| Opcode | Mnemonic | Operation | +|:-------|:---------|:-------------------| +| 0x4D | LD | `#0 ← $3[#1 + $2]` | +| 0x4E | ST | `$3[#1 + $2] ← #0` | -### Fused Multiply-Add -- Type BBBB +## Relative addressing +- Type: `RROH` +- Computes address from register and offset from program counter -| Opcode | Name | Action | -|:------:|:----:|:---------------------:| -| 44 | FMAF | `#0 ← (#1 * #2) + #3` | +| Opcode | Mnemonic | Operation | +|:-------|:---------|:------------------------| +| 0x4F | LDR | `#0 ← $3[pc + #1 + $2]` | +| 0x50 | STR | `$3[pc + #1 + $2] ← #0` | -### Negation -- Type BB -| Opcode | Name | Action | -|:------:|:----:|:----------:| -| 45 | NEGF | `#0 ← -#1` | +# Block memory copy +- Type: `RRH` +- Copies block of `$3` bytes from memory location on address on `#0` to `#1` -### Conversion -- Type BB -- Signed -- `#0 ← #1 as _` +| Opcode | Mnemonic | Operation | +|:-------|:---------|:------------------| +| 0x51 | BMC | `$3[#1] ← $3[x0]` | -| Opcode | Name | Action | -|:------:|:----:|:------------:| -| 46 | ITF | Int to Float | -| 47 | FTI | Float to Int | +# Block register copy +- Type: `RRB` +- Copy block of `$3` registers starting with `#0` to `#1` +- Copying over the 256 registers causes an exception -## Floating point immediate operations -- Type BBD -- `#0 ← #1 imm #2` +| Opcode | Mnemonic | Operation | +|:-------|:---------|:--------------| +| 0x52 | BRC | `$3#1 ← $3#0` | -| Opcode | Name | Action | -|:------:|:-----:|:--------------:| -| 48 | ADDFI | Addition | -| 49 | MULFI | Multiplication | +# Relative jump +- Type: `O` -# Registers -- There is 255 registers + one zero register (with index 0) -- Reading from zero register yields zero -- Writing to zero register is a no-op +| Opcode | Mnemonic | Operation | +|:-------|:---------|:---------------| +| 0x53 | JMP | `pc ← pc + $0` | -# Memory -- Addresses are 64 bit -- Memory implementation is arbitrary -- In case of accessing invalid address: - - Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address - - Value of register when trapped is undefined +# Linking jump +- Operation: + - Save address of following instruction to `#0` + - `#0 ← pc+` + - Jump to specified address -## Recommendations -- Leave address `0x0` as invalid -- If paging used: - - Leave first page invalid - - Pages should be at least 4 KiB +| Opcode | Mnemonic | Instruction type | Address | +|:-------|:---------|:------------------|:-------------------------| +| 0x54 | JAL | RRO (size = 6 B) | Relative, `pc + #1 + $2` | +| 0x55 | JALA | RRA (size = 10 B) | Absolute, `#1 + $2` | -# Program execution -- The way of program execution is implementation defined -- The order of instruction is arbitrary, as long all observable - effects are applied in the program's order +# Conditional jump +- Perform comparsion, if operation met, jump to relative address +- Type: `RRP` +- Operation: `if #0 #1 { pc ← pc + $2 }` -# Program validation -- Invalid program should cause runtime error: - - The form of error is arbitrary. Can be a trap or an interpreter-specified error - - It shall not be handleable from within the program -- Executing invalid opcode should trap -- Program can be validaded either before execution or when executing +| Opcode | Mnemonic | Condition | Type | +|:-------|:---------|:-------------------|:-----| +| 0x56 | JEQ | Equals (`=`) | Xi64 | +| 0x57 | JNE | Not-equals (`≠`) | Xi64 | +| 0x58 | JLTU | Less-than (`<`) | Ui64 | +| 0x59 | JGTU | Greater-than (`>`) | Ui64 | +| 0x5A | JLTS | Less-than (`<`) | Si64 | +| 0x5B | JGTS | Greater-than (`>`) | Si64 | -# Traps -Program should at least implement these traps: -- Environment call -- Invalid instruction exception -- Load address exception -- Store address exception +# Environment traps +- Traps to the environment +- Type: `N` -and executing environment should be able to get information about them, -like the opcode of invalid instruction or attempted address to load/store. -Details about these are left as an implementation detail. +| Opcode | Mnemonic | Trap type | +|:-------|:---------|:-----------------| +| 0x5C | ECA | Environment call | +| 0x5D | EBP | Breakpoint | -# Assembly -HoleyBytes assembly format is not defined, this is just a weak description -of `hbasm` syntax. +# Floating point binary operations +- Type: `RRR` +- Operation: `#0 ← #1 #2` -- Opcode names correspond to specified opcode names, lowercase (`nop`) -- Parameters are separated by comma (`addi r0, r0, 1`) -- Instructions are separated by either line feed or semicolon -- Registers are represented by `r` followed by the number (`r10`) -- Labels are defined by label name followed with colon (`loop:`) -- Labels are references simply by their name (`print`) -- Immediates are entered plainly. Negative numbers supported. \ No newline at end of file +| Opcode | Mnemonic | Operation | Type | +|:-------|:---------|:---------------------|:-----| +| 0x5E | FADD32 | Addition (`+`) | Fl32 | +| 0x5F | FADD64 | Addition (`+`) | Fl64 | +| 0x60 | FSUB32 | Subtraction (`-`) | Fl32 | +| 0x61 | FSUB64 | Subtraction (`-`) | Fl64 | +| 0x62 | FMUL32 | Multiplication (`*`) | Fl32 | +| 0x63 | FMUL64 | Multiplication (`*`) | Fl64 | +| 0x64 | FDIV32 | Division (`/`) | Fl32 | +| 0x65 | FDIV64 | Division (`/`) | Fl64 | + +# Fused multiply-add +- Type: `RRRR` +- Operation: `#0 ← (#1 * #2) + #3` + +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x66 | FMA32 | Fl32 | +| 0x67 | FMA64 | Fl64 | + +# Comparsions +- Type: `RRR` +- Operation: `#0 ← #1 <=> #2` +- Comparsion table same as for `CMPx`/`CMPxI` +- NaN is less-than/greater-than depends on variant + +| Opcode | Mnemonic | Type | NaN is | +|:-------|:---------|:-----|:-------| +| 0x6A | FCMPLT32 | Fl32 | < | +| 0x6B | FCMPLT64 | Fl64 | < | +| 0x6C | FCMPGT32 | Fl32 | > | +| 0x6D | FCMPGT64 | Fl64 | > | + +# Int to float +- Type: `RR` +- Converts from `Si64` +- Operation: `#0 ← Fl(#1)` + +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x6E | ITF32 | Fl32 | +| 0x6F | ITF64 | Fl64 | + +# Float to int +- Type: `RRB` +- Operation: `#0 ← Si64(#1)` +- Immediate `$2` specifies rounding mode + +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x70 | FTI32 | Fl32 | +| 0x71 | FTI64 | Fl64 | + +# Fl32 to Fl64 +- Type: `RR` +- Operation: `#0 ← Fl64(#1)` + +| Opcode | Mnemonic | +|:-------|:---------| +| 0x72 | FC32T64 | + +# Fl64 to Fl32 +- Type: `RRB` +- Operation: `#0 ← Fl32(#1)` +- Immediate `$2` specified rounding mode + +| Opcode | Mnemonic | +|:-------|:---------| +| 0x73 | FC64T32 | + +# 16-bit relative address instruction variants + +| Opcode | Mnemonic | Type | Variant of | +|:-------|:---------|:-----|:-----------| +| 0x74 | LRA16 | RRP | LRA | +| 0x75 | LDR16 | RRPH | LDR | +| 0x76 | STR16 | RRPH | STR | +| 0x77 | JMP16 | P | JMP | + +# psABI +## C datatypes and alignment +- One byte is 8 bits + +| C Type | Description | Byte sizes | +|:------------|:-------------------------|:-----------| +| char | Character / byte | 1 | +| short | Short integer | 2 | +| int | Integer | 4 | +| long | Long integer | 8 | +| long long | Long long integer | 8 | +| float | Single-precision float | 4 | +| double | Double-precision float | 8 | +| long double | Extended-precision float | TBD | + +## Call convention +- Registers r1 – r30 are caller saved +- Registers r31 – r255 are callee saved + +| Register | Description | Saver | +|:---------|:--------------------|:-------| +| r0 | Hard-wired zero | N/A | +| r1 - r2 | Return values | Caller | +| r2 - r11 | Function parameters | Caller | +| r30 | Return address | Caller | + +If return value is too big to fit one register, r2 is also used. +TODO: Stack pointer, Thread pointer, ... diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..cde8bef --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +argh = "0.1" +color-eyre = "0.6" +once_cell = "1.18" diff --git a/xtask/src/fmt.rs b/xtask/src/fmt.rs new file mode 100644 index 0000000..4cce8d7 --- /dev/null +++ b/xtask/src/fmt.rs @@ -0,0 +1,93 @@ +use { + crate::{utils::IterExt, ROOT}, + argh::FromArgs, + color_eyre::{eyre::eyre, Result}, + std::{ + fs::File, + io::{BufRead, BufReader, BufWriter, Seek, Write}, + }, +}; + +/// Format `instructions.in` +#[derive(Debug, FromArgs, PartialEq)] +#[argh(subcommand, name = "fmt")] +pub struct Command { + /// renumber instructions in their definition order + #[argh(switch, short = 'r')] + renumber: bool, +} + +pub fn command(args: Command) -> Result<()> { + let mut file = File::options() + .read(true) + .write(true) + .open(ROOT.join("hbbytecode/instructions.in"))?; + + // Extract records + let reader = BufReader::new(&file); + let mut recs = vec![]; + let mut lens = [0_usize; 4]; + + for rec in reader.split(b';').filter_map(|r| { + r.map(|ln| { + let s = String::from_utf8_lossy(&ln); + let s = s.trim_matches('\n'); + if s.is_empty() { + return None; + } + + s.split(',') + .map(|s| Box::::from(s.trim())) + .collect_array::<4>() + .map(Ok::<_, ()>) + }) + .transpose() + }) { + let rec = rec?.map_err(|_| eyre!("Invalid record format"))?; + for (current, next) in lens.iter_mut().zip(rec.iter()) { + *current = (*current).max(next.len()); + } + + recs.push(rec); + } + + // Clear file! + file.set_len(0)?; + file.seek(std::io::SeekFrom::Start(0))?; + + let mut writer = BufWriter::new(file); + + let ord_opco_len = digit_count(recs.len()) as usize; + for (n, rec) in recs.iter().enumerate() { + // Write opcode number + if args.renumber { + let n = format!("{n:#04X}"); + write!(writer, "{n}, {}", padding(ord_opco_len, &n))?; + } else { + write!(writer, "{}, {}", rec[0], padding(lens[0], &rec[0]))?; + } + + // Write other fields + writeln!( + writer, + "{}, {}{},{} {}{};", + rec[1], + padding(lens[1], &rec[1]), + rec[2], + padding(lens[2], &rec[2]), + rec[3], + padding(lens[3], &rec[3]), + )?; + } + + Ok(()) +} + +fn padding(req: usize, s: &str) -> Box { + " ".repeat(req.saturating_sub(s.len())).into() +} + +#[inline] +fn digit_count(n: usize) -> u32 { + n.checked_ilog10().unwrap_or(0) + 1 +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..8468a8f --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,25 @@ +mod fmt; +mod utils; + +use {argh::FromArgs, color_eyre::Result, once_cell::sync::Lazy, std::path::Path}; + +static ROOT: Lazy<&Path> = Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap()); + +/// xTask for Holey Bytes project +#[derive(FromArgs)] +struct Command { + #[argh(subcommand)] + subcom: Subcommands, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +enum Subcommands { + Format(fmt::Command), +} + +fn main() -> Result<()> { + match argh::from_env::().subcom { + Subcommands::Format(com) => fmt::command(com), + } +} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs new file mode 100644 index 0000000..8ad0081 --- /dev/null +++ b/xtask/src/utils.rs @@ -0,0 +1,19 @@ +use std::mem::MaybeUninit; + +pub trait IterExt: Iterator { + fn collect_array(&mut self) -> Option<[Self::Item; N]> + where + Self: Sized, + { + let mut array: [MaybeUninit; N] = + unsafe { MaybeUninit::uninit().assume_init() }; + + for item in &mut array { + item.write(self.next()?); + } + + Some(array.map(|item| unsafe { item.assume_init() })) + } +} + +impl IterExt for T {}