From ee4d767757871129bc7b51342e36949ee8f00a8d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Sat, 13 Jan 2024 01:27:46 +0100 Subject: [PATCH] Add support for cross-compilation This adds proper support for cross-compiling Inko programs from one target to another. The Rust runtime is compiled ahead of time as part of the release process. These runtimes are managed using the `inko runtime` command. For musl we also include libunwind.a as this is needed to resolve linker symbol errors. The library is taken from the rustup musl toolchain to ensure it's the same version as Rust expects. This also saves us having to compile it in the first place. By default the compiler tries to detect what compiler/linker driver to use based on the target you're compiling for. For example, when targeting musl, the linker tries to use musl-gcc or musl-clang if it's installed. A custom linker can be specified using the --linker option. We also support linking using Zig, which makes it _much_ easier to target macOS and potentially other targets, provided you have Zig installed of course. This commit also contains some dependency version updates and the necessary changes, in an effort to reduce the dependency tree size a bit after the addition of the "ureq" crate. Finally, this commit replaces the use of blake2 with blake3. The blake3 crate has a simple API and is supposed to perform much better than blake2. Also, the blake2 crate relies on a pile of generics and dependencies such that rust-analyzer isn't able to provide meaningful help (i.e showing types on hover doesn't work), while this works fine with blake3. This fixes https://github.com/inko-lang/inko/issues/524. Changelog: added --- .github/workflows/release.yml | 20 ++ Cargo.lock | 504 ++++++++++++++++++----------- Makefile | 14 +- compiler/Cargo.toml | 12 +- compiler/src/config.rs | 46 ++- compiler/src/hir.rs | 1 - compiler/src/linker.rs | 309 ++++++++++++++---- compiler/src/llvm/builder.rs | 181 ++++++----- compiler/src/llvm/passes.rs | 19 +- compiler/src/pkg/manifest.rs | 16 +- compiler/src/target.rs | 173 +++++++--- deny.toml | 19 +- inko/Cargo.toml | 3 + inko/src/command.rs | 2 + inko/src/command/build.rs | 26 +- inko/src/command/check.rs | 2 +- inko/src/command/main.rs | 20 +- inko/src/command/pkg.rs | 2 +- inko/src/command/pkg/add.rs | 13 +- inko/src/command/pkg/update.rs | 4 +- inko/src/command/print.rs | 20 +- inko/src/command/run.rs | 6 +- inko/src/command/runtime.rs | 64 ++++ inko/src/command/runtime/add.rs | 180 +++++++++++ inko/src/command/runtime/list.rs | 51 +++ inko/src/command/runtime/remove.rs | 51 +++ inko/src/command/targets.rs | 44 +++ inko/src/command/test.rs | 10 +- inko/src/error.rs | 20 +- inko/src/http.rs | 30 ++ inko/src/main.rs | 8 +- inko/src/pkg/util.rs | 18 +- rt/Cargo.toml | 6 +- rt/src/lib.rs | 3 + rt/src/network_poller.rs | 31 +- scripts/runtimes.sh | 85 +++++ std/src/std/fs/path.inko | 9 + std/test/helpers.inko | 5 +- 38 files changed, 1529 insertions(+), 498 deletions(-) create mode 100644 inko/src/command/runtime.rs create mode 100644 inko/src/command/runtime/add.rs create mode 100644 inko/src/command/runtime/list.rs create mode 100644 inko/src/command/runtime/remove.rs create mode 100644 inko/src/command/targets.rs create mode 100644 inko/src/http.rs create mode 100755 scripts/runtimes.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c9a337f5..b2925d4ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,6 +66,26 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + runtimes: + runs-on: ubuntu-latest + needs: + - lints + - linux + - mac + - freebsd + env: + AWS_REGION: eu-west-1 + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + steps: + - uses: actions/checkout@v4 + - name: Installing dependencies + run: sudo ./scripts/deps.sh ubuntu:latest + - name: Installing Rust + run: ./scripts/rust.sh 1.70 + - name: Generating runtimes + run: make runtimes + docs: runs-on: ubuntu-latest needs: diff --git a/Cargo.lock b/Cargo.lock index 25f0bfd26..af95a60d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "ast" version = "0.13.2" @@ -35,12 +47,6 @@ dependencies = [ "unicode-segmentation", ] -[[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" @@ -56,6 +62,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bitflags" version = "1.3.2" @@ -69,21 +81,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" +name = "blake3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ - "generic-array", + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", ] [[package]] @@ -117,7 +124,7 @@ name = "compiler" version = "0.13.2" dependencies = [ "ast", - "blake2", + "blake3", "fnv", "getopts", "inkwell", @@ -129,64 +136,54 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] -name = "crossbeam-queue" -version = "0.3.8" +name = "constant_time_eq" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "crossbeam-queue" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "generic-array", - "typenum", + "crossbeam-utils", ] [[package]] -name = "digest" -version = "0.10.7" +name = "crossbeam-utils" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "either" @@ -202,12 +199,34 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", ] [[package]] @@ -217,13 +236,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "generic-array" -version = "0.14.7" +name = "form_urlencoded" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "typenum", - "version_check", + "percent-encoding", ] [[package]] @@ -237,9 +255,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -248,38 +266,49 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "idna" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] [[package]] name = "inko" version = "0.13.2" dependencies = [ "compiler", + "flate2", "getopts", + "tar", "types", + "ureq", ] [[package]] name = "inkwell" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbac11e485159a525867fb7e6aa61981453e6a72f625fde6a4ab3047b0c6dec9" +version = "0.2.0" +source = "git+https://github.com/TheDan64/inkwell.git?rev=c18e3e8#c18e3e8f2bb408c3ec323dd80d08b3a24e7c5152" dependencies = [ "either", "inkwell_internals", "libc", "llvm-sys", "once_cell", - "parking_lot", + "thiserror", ] [[package]] name = "inkwell_internals" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87d00c17e264ce02be5bc23d7bff959188ec7137beddd06b8b6b05a7c680ea85" +version = "0.8.0" +source = "git+https://github.com/TheDan64/inkwell.git?rev=c18e3e8#c18e3e8f2bb408c3ec323dd80d08b3a24e7c5152" dependencies = [ "proc-macro2", "quote", @@ -294,21 +323,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "llvm-sys" -version = "150.1.2" +version = "150.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417dbaef2fece3b186fe15704e010849279de5f7eea1caa8845558130867bdd2" +checksum = "bfd60e740af945d99c2446a52e3ab8cdba2f740a40a16c51f6871bdea2abc687" dependencies = [ "cc", "lazy_static", @@ -317,16 +346,6 @@ dependencies = [ "semver", ] -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.20" @@ -335,9 +354,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" @@ -350,41 +369,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "parking_lot" -version = "0.12.1" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", -] +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -394,18 +396,16 @@ checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "polling" -version = "2.8.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" dependencies = [ - "autocfg", - "bitflags 1.3.2", "cfg-if", "concurrent-queue", - "libc", - "log", "pin-project-lite", - "windows-sys 0.48.0", + "rustix", + "tracing", + "windows-sys 0.52.0", ] [[package]] @@ -416,18 +416,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -506,6 +506,20 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rt" version = "0.13.2" @@ -529,34 +543,60 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.24" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "similar" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" dependencies = [ "bstr", "unicode-segmentation", @@ -572,12 +612,6 @@ dependencies = [ "similar", ] -[[package]] -name = "smallvec" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - [[package]] name = "socket2" version = "0.5.5" @@ -589,16 +623,16 @@ dependencies = [ ] [[package]] -name = "subtle" -version = "2.5.0" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -606,21 +640,91 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.17.0" +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[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 = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" [[package]] name = "types" version = "0.13.2" +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.10.1" @@ -634,10 +738,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] -name = "version_check" -version = "0.9.4" +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" +dependencies = [ + "base64", + "log", + "once_cell", + "rustls", + "rustls-webpki", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] [[package]] name = "wasi" @@ -646,13 +776,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "windows-sys" -version = "0.45.0" +name = "webpki-roots" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "windows-sys" @@ -664,18 +791,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.0", ] [[package]] @@ -694,10 +815,19 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] [[package]] name = "windows_aarch64_gnullvm" @@ -706,10 +836,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +name = "windows_aarch64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -718,10 +848,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "windows_i686_gnu" -version = "0.42.2" +name = "windows_aarch64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -730,10 +860,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -742,10 +872,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +name = "windows_i686_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -754,10 +884,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +name = "windows_x86_64_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -766,13 +896,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "windows_x86_64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Makefile b/Makefile index fe1b34f58..248ee1f0a 100644 --- a/Makefile +++ b/Makefile @@ -165,18 +165,10 @@ docs/versions: --distribution-id ${DOCS_CLOUDFRONT_ID} --paths "/manual/versions.json" rm versions.json -clippy: - touch */src/lib.rs */src/main.rs - cargo clippy -- -D warnings - -rustfmt-check: - rustfmt --check */src/lib.rs */src/main.rs - -rustfmt: - rustfmt --emit files */src/lib.rs */src/main.rs +runtimes: + bash scripts/runtimes.sh ${VERSION} .PHONY: release/source release/manifest release/changelog release/versions .PHONY: release/commit release/publish release/tag -.PHONY: build install clean -.PHONY: rustfmt rustfmt-check clippy +.PHONY: build install clean runtimes .PHONY: docs/install docs/build docs/server docs/publish docs/versions diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 6c11f320b..86c4ffa44 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -10,14 +10,18 @@ build = "build.rs" doctest = false [dependencies] -unicode-segmentation = "^1.8" +unicode-segmentation = "^1.10" getopts = "^0.2" ast = { path = "../ast" } types = { path = "../types" } fnv = "^1.0" -inkwell = { version = "^0.1", features = ["llvm15-0"] } -llvm-sys-150 = { package = "llvm-sys", version = "150.1.2", features = ["prefer-static"] } -blake2 = "^0.10" + +# Inkwell only sees a release every now and then. The pinned commit removes the +# parking_lot dependency, reducing the number of crates that need to be +# compiled. We'll revert back to a crates.io version once this is released. +inkwell = { git = "https://github.com/TheDan64/inkwell.git", rev = "c18e3e8", features = ["llvm15-0"] } +llvm-sys-150 = { package = "llvm-sys", version = "^150.1", features = ["prefer-static"] } +blake3 = "^1.5" [dev-dependencies] similar-asserts = "^1.1" diff --git a/compiler/src/config.rs b/compiler/src/config.rs index a2b0f0e8f..5d4915efa 100644 --- a/compiler/src/config.rs +++ b/compiler/src/config.rs @@ -29,6 +29,29 @@ const MAIN_TEST_MODULE: &str = "inko-tests"; /// The name of the directory to store build files in. const BUILD: &str = "build"; +fn home_dir() -> Option { + env::var_os("HOME").filter(|v| !v.is_empty()).map(PathBuf::from) +} + +pub fn data_directory() -> Option { + let base = if cfg!(target_os = "macos") { + home_dir().map(|h| h.join("Library").join("Application Support")) + } else { + env::var_os("XDG_DATA_HOME") + .filter(|v| !v.is_empty()) + .map(PathBuf::from) + .or_else(|| home_dir().map(|h| h.join(".local").join("share"))) + }; + + base.map(|p| p.join("inko")) +} + +pub fn local_runtimes_directory() -> Option { + // The Inko ABI isn't stable, so runtimes are scoped to the Inko version + // they were compiled for. + data_directory().map(|p| p.join("runtimes").join(env!("CARGO_PKG_VERSION"))) +} + fn create_directory(path: &Path) -> Result<(), String> { if path.is_dir() { return Ok(()); @@ -134,7 +157,7 @@ pub enum Output { } /// A type describing which linker to use. -#[derive(Copy, Clone)] +#[derive(Clone)] pub enum Linker { /// Detect which linker to use. Detect, @@ -147,6 +170,12 @@ pub enum Linker { /// Always use Mold. Mold, + + /// Always use Zig. + Zig, + + /// Use a custom linker with any extra arguments. + Custom(String), } impl Linker { @@ -155,9 +184,15 @@ impl Linker { "system" => Some(Linker::System), "lld" => Some(Linker::Lld), "mold" => Some(Linker::Mold), + "zig" => Some(Linker::Zig), + _ if !value.is_empty() => Some(Linker::Custom(value.to_string())), _ => None, } } + + pub(crate) fn is_zig(&self) -> bool { + matches!(self, Linker::Zig) + } } /// A type for storing compiler configuration, such as the source directories to @@ -166,8 +201,7 @@ pub struct Config { /// The directory containing the Inko's standard library. pub(crate) std: PathBuf, - /// The directory containing runtime library files to link to the generated - /// code. + /// The path to the global runtime directory. pub runtime: PathBuf, /// The directory containing the project's source code. @@ -219,6 +253,9 @@ pub struct Config { /// The linker to use. pub linker: Linker, + /// Extra arguments to pass to the linker. + pub linker_arguments: Vec, + /// If incremental compilation is enabled or not. pub incremental: bool, @@ -259,6 +296,7 @@ impl Config { static_linking: false, threads: available_parallelism().map(|v| v.get()).unwrap_or(1), linker: Linker::Detect, + linker_arguments: Vec::new(), incremental: true, compiled_at, } @@ -286,7 +324,7 @@ impl Config { } pub fn set_target(&mut self, name: &str) -> Result<(), String> { - if let Some(val) = Target::from_str(name) { + if let Some(val) = Target::parse(name) { self.target = val; Ok(()) } else { diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 05e666f9e..7a6f31e2d 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -2979,7 +2979,6 @@ mod tests { use crate::test::cols; use ::ast::parser::Parser; use similar_asserts::assert_eq; - use types::module_name::ModuleName; #[track_caller] diff --git a/compiler/src/linker.rs b/compiler/src/linker.rs index e308c2508..39d20ceca 100644 --- a/compiler/src/linker.rs +++ b/compiler/src/linker.rs @@ -1,33 +1,15 @@ -use crate::config::{Config, Linker}; +use crate::config::{local_runtimes_directory, Linker}; use crate::state::State; -use crate::target::OperatingSystem; +use crate::target::{OperatingSystem, Target}; +use std::io::Read as _; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -fn runtime_library(config: &Config) -> Option { - let mut files = vec![format!("libinko-{}.a", &config.target)]; - - // When compiling for the native target we also support DIR/libinko.a, as - // this makes development of Inko easier by just using e.g. `./target/debug` - // as the search directory. - if config.target.is_native() { - files.push("libinko.a".to_string()); - } - - files.iter().find_map(|file| { - let path = config.runtime.join(file); - - if path.is_file() { - Some(path) - } else { - None - } - }) -} - -fn linker_is_available(linker: &str) -> bool { - Command::new(linker) - .arg("--version") +fn command_is_available(name: &str) -> bool { + // We use --help here instead of --version, as not all commands may have a + // --version flag (e.g. "zig" for some reason). + Command::new(name) + .arg("--help") .stdout(Stdio::null()) .stderr(Stdio::null()) .stdin(Stdio::null()) @@ -36,12 +18,169 @@ fn linker_is_available(linker: &str) -> bool { .map_or(false, |status| status.success()) } +fn cc_is_clang() -> bool { + let Ok(mut child) = Command::new("cc") + .arg("--version") + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + else { + return false; + }; + + let mut stdout = child.stdout.take().unwrap(); + let Ok(status) = child.wait() else { return false }; + let mut output = String::new(); + let _ = stdout.read_to_string(&mut output); + + status.success() && output.contains("clang version") +} + fn lld_is_available() -> bool { - linker_is_available("ld.lld") + command_is_available("ld.lld") } fn mold_is_available() -> bool { - linker_is_available("ld.mold") + command_is_available("ld.mold") +} + +fn musl_linker(target: &Target) -> Option<&'static str> { + if !target.abi.is_musl() { + return None; + } + + let gcc = "musl-gcc"; + let clang = "musl-clang"; + + if command_is_available(gcc) { + Some(gcc) + } else if command_is_available(clang) { + Some(clang) + } else { + None + } +} + +fn zig_cc(target: &Target) -> Command { + let mut cmd = Command::new("zig"); + + // To make using Zig as a linker a bit easier, we translate our target + // triples into those used by Zig (which in turn are a bit different + // from the ones used by LLVM). + cmd.arg("cc"); + cmd.arg(&format!("--target={}", target.zig_triple())); + cmd +} + +fn driver(state: &State) -> Result { + let target = &state.config.target; + let triple = target.llvm_triple(); + let cmd = if let Linker::Custom(name) = &state.config.linker { + Command::new(name) + } else if state.config.linker.is_zig() { + zig_cc(target) + } else { + let gcc_exe = format!("{}-gcc", triple); + let mut linker = state.config.linker.clone(); + let mut cmd = if target.is_native() { + Command::new("cc") + } else if target.os.is_mac() && command_is_available("zig") { + // Cross-compiling from a non-mac host to macOS is a pain due to the + // licensing of the various dependencies needed for this. Zig makes + // this much easier, so we'll use it if it's available. + linker = Linker::System; + zig_cc(target) + } else if command_is_available(&gcc_exe) { + // GCC cross compilers don't support the `-fuse-ld` flag, so in this + // case we force the use of the system linker. + linker = Linker::System; + Command::new(gcc_exe) + } else if let Some(name) = musl_linker(target) { + // For musl we want to use the musl-gcc/musl-clang wrappers so we + // don't have to figure out all the necessary parameters ourselves. + linker = Linker::System; + Command::new(name) + } else if cc_is_clang() || command_is_available("clang") { + // We check for clang _after_ GCC, because if a dedicated GCC + // executable for the target is available, using it is less prone to + // error as we don't have to bother finding the right sysroot. + Command::new("clang") + } else { + return Err(format!( + "you are cross-compiling to {}, but the linker used (cc) \ + doesn't support cross-compilation. You can specify a custom \ + linker using the --linker=LINKER option", + triple + )); + }; + + if cmd.get_program() == "clang" { + // clang tends to pick the host version of any necessary libraries + // (including crt1.o and the likes) when cross-compiling. We try to + // fix this here by automatically setting the correct sysroot. + // + // Linux distributions (Arch Linux, Fedora, Ubuntu, etc) typically + // install the toolchains in /usr, e.g. /usr/aarch64-linux-gnu. + // + // For other platforms we don't bother trying to find the sysroot, + // as they don't reside in a consistent location. + if cfg!(target_os = "linux") { + let path = format!("/usr/{}", triple); + + if Path::new(&path).is_dir() { + cmd.arg(format!("--sysroot={}", path)); + } + } + + cmd.arg(&format!("--target={}", triple)); + } + + if let Linker::Detect = linker { + // Mold doesn't support macOS, so we don't enable it for macOS + // targets. + if mold_is_available() && !target.os.is_mac() { + linker = Linker::Mold; + } else if lld_is_available() { + linker = Linker::Lld; + } + } + + match linker { + Linker::Lld => { + cmd.arg("-fuse-ld=lld"); + } + Linker::Mold => { + cmd.arg("-fuse-ld=mold"); + } + _ => {} + } + + cmd + }; + + let name = cmd.get_program(); + + // While it's possible the user has specified the many flags needed to get + // regular clang/gcc to correctly use musl, it's rather unlikely, so we + // instead just direct users to use musl-clang, musl-gcc, or zig and be done + // with it. + if cfg!(target_env = "gnu") + && target.abi.is_musl() + && name != "musl-clang" + && name != "musl-gcc" + && name != "zig" + { + return Err( + "targeting musl on a GNU host using clang or gcc is likely to \ + result in the executable still linking against glibc. To resolve \ + this, use --linker=zig, or --linker=musl-clang/--linker=musl-gcc \ + (provided musl-clang/musl-gcc is in your PATH)" + .to_string(), + ); + } + + Ok(cmd) } pub(crate) fn link( @@ -49,14 +188,11 @@ pub(crate) fn link( output: &Path, paths: &[PathBuf], ) -> Result<(), String> { - // On Unix systems the necessary libraries/object files are all over the - // place. Instead of re-implementing the logic necessary to find these - // files, we rely on the system's compiler to do this for us. - // - // As we only use this executable for linking it doesn't really matter - // if this ends up using gcc, clang or something else, because we only - // use it as a wrapper around the linker executable. - let mut cmd = Command::new("cc"); + let mut cmd = driver(state)?; + + for arg in &state.config.linker_arguments { + cmd.arg(arg); + } // Object files must come before any of the libraries to link against, as // certain linkers are very particular about the order of flags such as @@ -65,11 +201,33 @@ pub(crate) fn link( cmd.arg(path); } - let rt_path = runtime_library(&state.config).ok_or_else(|| { - format!("No runtime is available for target '{}'", state.config.target) - })?; + if state.config.target.is_native() { + cmd.arg(state.config.runtime.join("libinko.a")); + } else if let Some(runtimes) = local_runtimes_directory() { + let dir = runtimes.join(state.config.target.to_string()); + let inko = dir.join("libinko.a"); + let unwind = dir.join("libunwind.a"); - cmd.arg(&rt_path); + if !inko.is_file() { + return Err(format!( + "no runtime is available for target '{}'", + state.config.target + )); + } + + cmd.arg(inko); + + // On musl hosts we just rely on whatever the system unwinder is. This + // way distributions such as Alpine don't need to patch things out to + // achieve that. On other hosts we use the bundled libunwind, otherwise + // we get _Unwind_XXX linker errors. + if !cfg!(target_env = "musl") + && state.config.target.abi.is_musl() + && unwind.is_file() + { + cmd.arg(unwind); + } + } // Include any extra platform specific libraries, such as libm on the // various Unix platforms. These must come _after_ any object files and @@ -87,7 +245,7 @@ pub(crate) fn link( OperatingSystem::Linux => { // Certain versions of Linux (e.g. Debian 11) also need libdl and // libpthread to be linked in explicitly. We use the --as-needed - // flag here (supported by both gcc and clang) to only link these + // flag here (supported by both GCC and clang) to only link these // libraries if actually needed. cmd.arg("-Wl,--as-needed"); cmd.arg("-ldl"); @@ -103,6 +261,19 @@ pub(crate) fn link( let mut static_linking = state.config.static_linking; + if !cfg!(target_env = "musl") && state.config.target.abi.is_musl() { + // If a non-musl hosts targets musl, we statically link everything. The + // reason for this is that when using musl one might believe the + // resulting executable to be portable, but that's only the case if it's + // indeed a statically linked executable. + // + // This does mean any C dependencies need to be available in their + // static form, but if that's not the case then targeting musl on + // non-musl hosts isn't going to work well anyway (e.g. because the + // dynamic libraries are likely to link to glibc). + static_linking = true; + } + match state.config.target.os { OperatingSystem::Mac if static_linking => { // On macOS there's no equivalent of -l:libX.a as there is for GNU @@ -118,10 +289,6 @@ pub(crate) fn link( _ => (), } - if static_linking { - cmd.arg("-Wl,-Bstatic"); - } - for lib in &state.libraries { // These libraries are already included if needed, and we can't // statically link against them (if static linking is desired), so we @@ -130,41 +297,41 @@ pub(crate) fn link( continue; } - cmd.arg(&(format!("-l{}", lib))); - } + // We don't use the pattern `-Wl,-Bstatic -lX -Wl,-Bdynamic` as the + // "closing" `-Bdynamic` also affects any linker flags that come after + // it, which can prevent us from static linking against e.g. libc for + // musl targets. + let flag = if static_linking { + format!("-l:lib{}.a", lib) + } else { + format!("-l{}", lib) + }; - if static_linking { - cmd.arg("-Wl,-Bdynamic"); + cmd.arg(&flag); } - cmd.arg("-o"); - cmd.arg(output); + if state.config.target.os.is_linux() { + // For these targets we need to ensure this flag is set, which isn't + // always passed by GCC (and possibly other) compilers. + cmd.arg("-Wl,--eh-frame-hdr"); - if let OperatingSystem::Linux = state.config.target.os { // This removes the need for installing libgcc in deployment // environments. cmd.arg("-static-libgcc"); } - let mut linker = state.config.linker; - - if let Linker::Detect = linker { - if mold_is_available() { - linker = Linker::Mold; - } else if lld_is_available() { - linker = Linker::Lld; - } + // In case we're targeting musl we also want to statically link musl's libc. + // This isn't done for GNU targets because glibc makes use of dlopen(), so + // static linking glibc is basically a lie (and generally recommended + // against). This also ensures all linkers behave the same, as e.g. Zig + // defaults to static linking (https://github.com/ziglang/zig/issues/11909) + // but musl-clang and musl-gcc default to dynamic linking. + if static_linking && state.config.target.abi.is_musl() { + cmd.arg("-static"); } - match linker { - Linker::Lld => { - cmd.arg("-fuse-ld=lld"); - } - Linker::Mold => { - cmd.arg("-fuse-ld=mold"); - } - _ => {} - } + cmd.arg("-o"); + cmd.arg(output); cmd.stdin(Stdio::null()); cmd.stderr(Stdio::piped()); @@ -182,9 +349,9 @@ pub(crate) fn link( Ok(()) } else { Err(format!( - "The linker exited with status code {}:\n{}", + "the linker exited with status code {}:\n\n{}", output.status.code().unwrap_or(0), - String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stderr).trim(), )) } } diff --git a/compiler/src/llvm/builder.rs b/compiler/src/llvm/builder.rs index ac358c665..2cae2246c 100644 --- a/compiler/src/llvm/builder.rs +++ b/compiler/src/llvm/builder.rs @@ -60,7 +60,7 @@ impl<'ctx> Builder<'ctx> { let vtype = receiver_type.get_field_type_at_index(index).unwrap(); let field_ptr = self.field_address(receiver_type, receiver, index); - self.inner.build_load(vtype, field_ptr, "") + self.inner.build_load(vtype, field_ptr, "").unwrap() } pub(crate) fn field_address( @@ -90,12 +90,14 @@ impl<'ctx> Builder<'ctx> { } unsafe { - self.inner.build_gep( - receiver_type, - receiver, - &[self.u32_literal(0), self.u32_literal(field), index], - "", - ) + self.inner + .build_gep( + receiver_type, + receiver, + &[self.u32_literal(0), self.u32_literal(field), index], + "", + ) + .unwrap() } } @@ -106,18 +108,20 @@ impl<'ctx> Builder<'ctx> { index: usize, ) -> BasicValueEnum<'ctx> { let ptr = unsafe { - self.inner.build_gep( - array_type, - array, - &[ - self.context.i32_type().const_int(0, false), - self.context.i32_type().const_int(index as _, false), - ], - "", - ) + self.inner + .build_gep( + array_type, + array, + &[ + self.context.i32_type().const_int(0, false), + self.context.i32_type().const_int(index as _, false), + ], + "", + ) + .unwrap() }; - self.inner.build_load(array_type.get_element_type(), ptr, "") + self.inner.build_load(array_type.get_element_type(), ptr, "").unwrap() } pub(crate) fn store_array_field>( @@ -128,15 +132,17 @@ impl<'ctx> Builder<'ctx> { value: V, ) { let ptr = unsafe { - self.inner.build_gep( - array_type, - array, - &[ - self.context.i32_type().const_int(0, false), - self.context.i32_type().const_int(index as _, false), - ], - "", - ) + self.inner + .build_gep( + array_type, + array, + &[ + self.context.i32_type().const_int(0, false), + self.context.i32_type().const_int(index as _, false), + ], + "", + ) + .unwrap() }; self.store(ptr, value); @@ -159,7 +165,7 @@ impl<'ctx> Builder<'ctx> { variable: PointerValue<'ctx>, value: V, ) { - self.inner.build_store(variable, value); + self.inner.build_store(variable, value).unwrap(); } pub(crate) fn load>( @@ -167,7 +173,7 @@ impl<'ctx> Builder<'ctx> { typ: T, variable: PointerValue<'ctx>, ) -> BasicValueEnum<'ctx> { - self.inner.build_load(typ, variable, "") + self.inner.build_load(typ, variable, "").unwrap() } pub(crate) fn load_int( @@ -176,6 +182,7 @@ impl<'ctx> Builder<'ctx> { ) -> IntValue<'ctx> { self.inner .build_load(self.context.i64_type(), variable, "") + .unwrap() .into_int_value() } @@ -185,6 +192,7 @@ impl<'ctx> Builder<'ctx> { ) -> FloatValue<'ctx> { self.inner .build_load(self.context.f64_type(), variable, "") + .unwrap() .into_float_value() } @@ -227,6 +235,7 @@ impl<'ctx> Builder<'ctx> { ) -> BasicValueEnum<'ctx> { self.inner .build_call(function, arguments, "") + .unwrap() .try_as_basic_value() .left() .unwrap() @@ -238,7 +247,7 @@ impl<'ctx> Builder<'ctx> { func: PointerValue<'ctx>, args: &[BasicMetadataValueEnum<'ctx>], ) -> CallSiteValue<'ctx> { - self.inner.build_indirect_call(typ, func, args, "") + self.inner.build_indirect_call(typ, func, args, "").unwrap() } pub(crate) fn call_void( @@ -246,14 +255,14 @@ impl<'ctx> Builder<'ctx> { function: FunctionValue<'ctx>, arguments: &[BasicMetadataValueEnum<'ctx>], ) { - self.inner.build_call(function, arguments, ""); + self.inner.build_call(function, arguments, "").unwrap(); } pub(crate) fn pointer_to_int( &self, value: PointerValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_ptr_to_int(value, self.context.i64_type(), "") + self.inner.build_ptr_to_int(value, self.context.i64_type(), "").unwrap() } pub(crate) fn u8_literal(&self, value: u8) -> IntValue<'ctx> { @@ -284,8 +293,12 @@ impl<'ctx> Builder<'ctx> { &self, value: &str, ) -> (PointerValue<'ctx>, IntValue<'ctx>) { - let string = - self.inner.build_global_string_ptr(value, "").as_pointer_value(); + let string = self + .inner + .build_global_string_ptr(value, "") + .unwrap() + .as_pointer_value(); + let len = self.u64_literal(value.len() as _); (string, len) @@ -325,7 +338,10 @@ impl<'ctx> Builder<'ctx> { &self, variable: PointerValue<'ctx>, ) -> IntValue<'ctx> { - let res = self.inner.build_load(self.context.i32_type(), variable, ""); + let res = self + .inner + .build_load(self.context.i32_type(), variable, "") + .unwrap(); let ins = res.as_instruction_value().unwrap(); // If the alignment doesn't match the value size, LLVM compiles this to @@ -342,7 +358,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_compare(IntPredicate::EQ, lhs, rhs, "") + self.inner.build_int_compare(IntPredicate::EQ, lhs, rhs, "").unwrap() } pub(crate) fn int_gt( @@ -350,7 +366,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_compare(IntPredicate::SGT, lhs, rhs, "") + self.inner.build_int_compare(IntPredicate::SGT, lhs, rhs, "").unwrap() } pub(crate) fn int_ge( @@ -358,7 +374,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_compare(IntPredicate::SGE, lhs, rhs, "") + self.inner.build_int_compare(IntPredicate::SGE, lhs, rhs, "").unwrap() } pub(crate) fn int_lt( @@ -366,7 +382,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_compare(IntPredicate::SLT, lhs, rhs, "") + self.inner.build_int_compare(IntPredicate::SLT, lhs, rhs, "").unwrap() } pub(crate) fn int_le( @@ -374,7 +390,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_compare(IntPredicate::SLE, lhs, rhs, "") + self.inner.build_int_compare(IntPredicate::SLE, lhs, rhs, "").unwrap() } pub(crate) fn int_sub( @@ -382,7 +398,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_sub(lhs, rhs, "") + self.inner.build_int_sub(lhs, rhs, "").unwrap() } pub(crate) fn int_add( @@ -390,7 +406,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_add(lhs, rhs, "") + self.inner.build_int_add(lhs, rhs, "").unwrap() } pub(crate) fn int_mul( @@ -398,7 +414,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_mul(lhs, rhs, "") + self.inner.build_int_mul(lhs, rhs, "").unwrap() } pub(crate) fn int_div( @@ -406,7 +422,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_signed_div(lhs, rhs, "") + self.inner.build_int_signed_div(lhs, rhs, "").unwrap() } pub(crate) fn int_rem( @@ -414,7 +430,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_int_signed_rem(lhs, rhs, "") + self.inner.build_int_signed_rem(lhs, rhs, "").unwrap() } pub(crate) fn bit_and( @@ -422,7 +438,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_and(lhs, rhs, "") + self.inner.build_and(lhs, rhs, "").unwrap() } pub(crate) fn bit_or( @@ -430,7 +446,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_or(lhs, rhs, "") + self.inner.build_or(lhs, rhs, "").unwrap() } pub(crate) fn bit_xor( @@ -438,11 +454,11 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_xor(lhs, rhs, "") + self.inner.build_xor(lhs, rhs, "").unwrap() } pub(crate) fn bit_not(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { - self.inner.build_not(value, "") + self.inner.build_not(value, "").unwrap() } pub(crate) fn left_shift( @@ -450,7 +466,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_left_shift(lhs, rhs, "") + self.inner.build_left_shift(lhs, rhs, "").unwrap() } pub(crate) fn right_shift( @@ -458,7 +474,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_right_shift(lhs, rhs, false, "") + self.inner.build_right_shift(lhs, rhs, false, "").unwrap() } pub(crate) fn signed_right_shift( @@ -466,7 +482,7 @@ impl<'ctx> Builder<'ctx> { lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_right_shift(lhs, rhs, true, "") + self.inner.build_right_shift(lhs, rhs, true, "").unwrap() } pub(crate) fn int_to_float( @@ -482,6 +498,7 @@ impl<'ctx> Builder<'ctx> { self.inner .build_cast(InstructionOpcode::SIToFP, value, typ, "") + .unwrap() .into_float_value() } @@ -499,19 +516,19 @@ impl<'ctx> Builder<'ctx> { _ => self.context.i64_type(), }; - self.inner.build_int_cast_sign_flag(value, target, signed, "") + self.inner.build_int_cast_sign_flag(value, target, signed, "").unwrap() } pub(crate) fn bool_to_int(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { let typ = self.context.i64_type(); - self.inner.build_int_cast_sign_flag(value, typ, false, "") + self.inner.build_int_cast_sign_flag(value, typ, false, "").unwrap() } pub(crate) fn int_to_bool(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { let typ = self.context.bool_type(); - self.inner.build_int_cast_sign_flag(value, typ, true, "") + self.inner.build_int_cast_sign_flag(value, typ, true, "").unwrap() } pub(crate) fn float_to_float( @@ -524,14 +541,16 @@ impl<'ctx> Builder<'ctx> { _ => self.context.f64_type(), }; - self.inner.build_float_cast(value, target, "") + self.inner.build_float_cast(value, target, "").unwrap() } pub(crate) fn int_to_pointer( &self, value: IntValue<'ctx>, ) -> PointerValue<'ctx> { - self.inner.build_int_to_ptr(value, self.context.pointer_type(), "") + self.inner + .build_int_to_ptr(value, self.context.pointer_type(), "") + .unwrap() } pub(crate) fn float_add( @@ -539,7 +558,7 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> FloatValue<'ctx> { - self.inner.build_float_add(lhs, rhs, "") + self.inner.build_float_add(lhs, rhs, "").unwrap() } pub(crate) fn float_sub( @@ -547,7 +566,7 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> FloatValue<'ctx> { - self.inner.build_float_sub(lhs, rhs, "") + self.inner.build_float_sub(lhs, rhs, "").unwrap() } pub(crate) fn float_div( @@ -555,7 +574,7 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> FloatValue<'ctx> { - self.inner.build_float_div(lhs, rhs, "") + self.inner.build_float_div(lhs, rhs, "").unwrap() } pub(crate) fn float_mul( @@ -563,7 +582,7 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> FloatValue<'ctx> { - self.inner.build_float_mul(lhs, rhs, "") + self.inner.build_float_mul(lhs, rhs, "").unwrap() } pub(crate) fn float_rem( @@ -571,7 +590,7 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> FloatValue<'ctx> { - self.inner.build_float_rem(lhs, rhs, "") + self.inner.build_float_rem(lhs, rhs, "").unwrap() } pub(crate) fn float_eq( @@ -579,7 +598,9 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_float_compare(FloatPredicate::OEQ, lhs, rhs, "") + self.inner + .build_float_compare(FloatPredicate::OEQ, lhs, rhs, "") + .unwrap() } pub(crate) fn float_lt( @@ -587,7 +608,9 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_float_compare(FloatPredicate::OLT, lhs, rhs, "") + self.inner + .build_float_compare(FloatPredicate::OLT, lhs, rhs, "") + .unwrap() } pub(crate) fn float_le( @@ -595,7 +618,9 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_float_compare(FloatPredicate::OLE, lhs, rhs, "") + self.inner + .build_float_compare(FloatPredicate::OLE, lhs, rhs, "") + .unwrap() } pub(crate) fn float_gt( @@ -603,7 +628,9 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_float_compare(FloatPredicate::OGT, lhs, rhs, "") + self.inner + .build_float_compare(FloatPredicate::OGT, lhs, rhs, "") + .unwrap() } pub(crate) fn float_ge( @@ -611,14 +638,18 @@ impl<'ctx> Builder<'ctx> { lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_float_compare(FloatPredicate::OGE, lhs, rhs, "") + self.inner + .build_float_compare(FloatPredicate::OGE, lhs, rhs, "") + .unwrap() } pub(crate) fn float_is_nan( &self, value: FloatValue<'ctx>, ) -> IntValue<'ctx> { - self.inner.build_float_compare(FloatPredicate::UNO, value, value, "") + self.inner + .build_float_compare(FloatPredicate::UNO, value, value, "") + .unwrap() } pub(crate) fn bitcast, T: BasicType<'ctx>>( @@ -626,7 +657,7 @@ impl<'ctx> Builder<'ctx> { value: V, typ: T, ) -> BasicValueEnum<'ctx> { - self.inner.build_bitcast(value, typ, "") + self.inner.build_bitcast(value, typ, "").unwrap() } pub(crate) fn first_block(&self) -> BasicBlock<'ctx> { @@ -645,15 +676,15 @@ impl<'ctx> Builder<'ctx> { &self, typ: T, ) -> PointerValue<'ctx> { - self.inner.build_alloca(typ, "") + self.inner.build_alloca(typ, "").unwrap() } pub(crate) fn jump(&self, block: BasicBlock<'ctx>) { - self.inner.build_unconditional_branch(block); + self.inner.build_unconditional_branch(block).unwrap(); } pub(crate) fn return_value(&self, val: Option<&dyn BasicValue<'ctx>>) { - self.inner.build_return(val); + self.inner.build_return(val).unwrap(); } pub(crate) fn branch( @@ -662,7 +693,9 @@ impl<'ctx> Builder<'ctx> { true_block: BasicBlock<'ctx>, false_block: BasicBlock<'ctx>, ) { - self.inner.build_conditional_branch(condition, true_block, false_block); + self.inner + .build_conditional_branch(condition, true_block, false_block) + .unwrap(); } pub(crate) fn switch( @@ -671,7 +704,7 @@ impl<'ctx> Builder<'ctx> { cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], fallback: BasicBlock<'ctx>, ) { - self.inner.build_switch(value, fallback, cases); + self.inner.build_switch(value, fallback, cases).unwrap(); } pub(crate) fn exhaustive_switch( @@ -683,7 +716,7 @@ impl<'ctx> Builder<'ctx> { } pub(crate) fn unreachable(&self) { - self.inner.build_unreachable(); + self.inner.build_unreachable().unwrap(); } pub(crate) fn string_bytes(&self, value: &str) -> ArrayValue<'ctx> { diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index 0af6b9f91..08630af49 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -20,8 +20,7 @@ use crate::mir::{ use crate::state::State; use crate::symbol_names::{shapes, SymbolNames}; use crate::target::Architecture; -use blake2::digest::consts::U16; -use blake2::{Blake2b, Digest as _}; +use blake3::{hash, Hasher}; use inkwell::basic_block::BasicBlock; use inkwell::module::Linkage; use inkwell::passes::{PassManager, PassManagerBuilder}; @@ -52,9 +51,9 @@ use types::{ }; fn object_path(directories: &BuildDirectories, name: &ModuleName) -> PathBuf { - let digest = Blake2b::::digest(name.as_str()); + let hash = hash(name.as_str().as_bytes()).to_string(); - directories.objects.join(format!("{:x}.o", digest)) + directories.objects.join(format!("{}.o", hash)) } fn check_object_cache( @@ -167,10 +166,10 @@ fn check_object_cache( names.sort(); - let mut hasher: Blake2b = Blake2b::new(); + let mut hasher = Hasher::new(); for name in names { - hasher.update(name); + hasher.update(name.as_bytes()); } // The module may contain dynamic dispatch call sites. If the need for @@ -178,21 +177,21 @@ fn check_object_cache( // do this by hashing the collision states of all dynamic calls in the // current module, such that if any of them change, so does the hash. for &mid in &module.methods { - hasher.update(methods.info[mid.0 as usize].hash.to_le_bytes()); + hasher.update(&methods.info[mid.0 as usize].hash.to_le_bytes()); for block in &mir.methods[&mid].body.blocks { for ins in &block.instructions { if let Instruction::CallDynamic(op) = ins { let val = methods.info[op.method.0 as usize].collision; - hasher.update([val as u8]); + hasher.update(&[val as u8]); } } } } - let new_hash = format!("{:x}", hasher.finalize()); - let hash_path = obj_path.with_extension("o.blake2"); + let new_hash = format!("{}", hasher.finalize()); + let hash_path = obj_path.with_extension("o.blake3"); // We don't need to perform this check if another check already // determined the object file needs to be refreshed. diff --git a/compiler/src/pkg/manifest.rs b/compiler/src/pkg/manifest.rs index 3c7b3f6e2..1c17b46ec 100644 --- a/compiler/src/pkg/manifest.rs +++ b/compiler/src/pkg/manifest.rs @@ -1,5 +1,5 @@ use crate::pkg::version::Version; -use blake2::{digest::consts::U16, Blake2b, Digest}; +use blake3; use std::fmt; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, Read, Write}; @@ -38,13 +38,7 @@ impl Url { } pub fn directory_name(&self) -> String { - // We don't need ultra long hashes, as all we care about is being able - // to generate a directory name from a URL _without_ it colliding with - // literally everything. - let mut hasher: Blake2b = Blake2b::new(); - - hasher.update(&self.value); - format!("{:x}", hasher.finalize()) + blake3::hash(self.value.as_bytes()).to_string() } pub fn import_name(&self) -> String { @@ -309,11 +303,13 @@ mod tests { fn test_url_directory_name() { assert_eq!( Url::new("https://gitlab.com/foo/bar").directory_name(), - "4efb5ddfa8b68f5e1885fc8b75838f43".to_string() + "6c95f3810d546c9b4137d8291af2abe47019f97b8643ae0800db9da680ce811e" + .to_string() ); assert_eq!( Url::new("http://gitlab.com/foo/bar").directory_name(), - "2334066f1e6f5fea14ebf3fb71f714ca".to_string() + "6100c5254dd22a9f5577da816f5df90cff0e535e2d7c9fa7356e945d4c364107" + .to_string() ); } diff --git a/compiler/src/target.rs b/compiler/src/target.rs index c4443da22..279a70923 100644 --- a/compiler/src/target.rs +++ b/compiler/src/target.rs @@ -56,6 +56,14 @@ impl OperatingSystem { panic!("The host operating system isn't supported"); } } + + pub(crate) fn is_mac(&self) -> bool { + matches!(self, OperatingSystem::Mac) + } + + pub(crate) fn is_linux(&self) -> bool { + matches!(self, OperatingSystem::Linux) + } } /// The ABI to target. @@ -85,6 +93,10 @@ impl Abi { Abi::Native } } + + pub(crate) fn is_musl(&self) -> bool { + matches!(self, Abi::Musl) + } } /// A type describing the compile target, such as the operating system and @@ -97,10 +109,35 @@ pub struct Target { } impl Target { + /// Returns a list of all the targets we officially support. + pub fn supported() -> Vec { + use Abi::*; + use Architecture::*; + use OperatingSystem::*; + + vec![ + Target::new(Amd64, Freebsd, Native), + Target::new(Amd64, Linux, Gnu), + Target::new(Amd64, Linux, Musl), + Target::new(Arm64, Linux, Gnu), + Target::new(Arm64, Linux, Musl), + Target::new(Amd64, Mac, Native), + Target::new(Arm64, Mac, Native), + ] + } + + pub(crate) fn new( + arch: Architecture, + os: OperatingSystem, + abi: Abi, + ) -> Target { + Target { arch, os, abi } + } + /// Parses a target from a string. /// /// If the target is invalid, a None is returned. - pub(crate) fn from_str(input: &str) -> Option { + pub fn parse(input: &str) -> Option { let mut iter = input.split('-'); let arch = iter.next().and_then(Architecture::from_str)?; let os = iter.next().and_then(OperatingSystem::from_str)?; @@ -118,6 +155,10 @@ impl Target { } } + pub fn runtime_file_name(&self) -> String { + format!("libinko-{}.a", self) + } + /// Returns a String describing the target using the LLVM triple format. pub(crate) fn llvm_triple(&self) -> String { let arch = match self.arch { @@ -128,13 +169,25 @@ impl Target { let os = match self.os { OperatingSystem::Freebsd => "unknown-freebsd", OperatingSystem::Mac => "apple-darwin", - OperatingSystem::Linux => { - if let Abi::Musl = self.abi { - "unknown-linux-musl" - } else { - "unknown-linux-gnu" - } - } + OperatingSystem::Linux if self.abi.is_musl() => "linux-musl", + OperatingSystem::Linux => "linux-gnu", + }; + + format!("{}-{}", arch, os) + } + + /// Returns a String describing the target using Zig's triple format. + pub(crate) fn zig_triple(&self) -> String { + let arch = match self.arch { + Architecture::Amd64 => "x86_64", + Architecture::Arm64 => "aarch64", + }; + + let os = match self.os { + OperatingSystem::Freebsd => "freebsd-none", + OperatingSystem::Mac => "macos-none", + OperatingSystem::Linux if self.abi.is_musl() => "linux-musl", + OperatingSystem::Linux => "linux-gnu", }; format!("{}-{}", arch, os) @@ -172,7 +225,7 @@ impl Target { } } - pub(crate) fn is_native(&self) -> bool { + pub fn is_native(&self) -> bool { self == &Target::native() } @@ -204,10 +257,6 @@ impl fmt::Display for Target { mod tests { use super::*; - fn target(arch: Architecture, os: OperatingSystem, abi: Abi) -> Target { - Target { arch, os, abi } - } - #[test] fn test_operating_system_from_str() { assert_eq!( @@ -235,29 +284,33 @@ mod tests { #[test] fn test_target_from_str() { assert_eq!( - Target::from_str("amd64-freebsd-native"), - Some(target( + Target::parse("amd64-freebsd-native"), + Some(Target::new( Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native )) ); assert_eq!( - Target::from_str("arm64-linux-gnu"), - Some(target(Architecture::Arm64, OperatingSystem::Linux, Abi::Gnu)) + Target::parse("arm64-linux-gnu"), + Some(Target::new( + Architecture::Arm64, + OperatingSystem::Linux, + Abi::Gnu + )) ); assert_eq!( - Target::from_str("arm64-linux-musl"), - Some(target( + Target::parse("arm64-linux-musl"), + Some(Target::new( Architecture::Arm64, OperatingSystem::Linux, Abi::Musl )) ); - assert_eq!(Target::from_str("bla-linux-native"), None); - assert_eq!(Target::from_str("amd64-bla-native"), None); - assert_eq!(Target::from_str("amd64-linux"), None); + assert_eq!(Target::parse("bla-linux-native"), None); + assert_eq!(Target::parse("amd64-bla-native"), None); + assert_eq!(Target::parse("amd64-linux"), None); } #[test] @@ -271,46 +324,94 @@ mod tests { #[test] fn test_target_llvm_triple() { assert_eq!( - target(Architecture::Amd64, OperatingSystem::Linux, Abi::Native) - .llvm_triple(), - "x86_64-unknown-linux-gnu" + Target::new( + Architecture::Amd64, + OperatingSystem::Linux, + Abi::Native + ) + .llvm_triple(), + "x86_64-linux-gnu" ); assert_eq!( - target(Architecture::Amd64, OperatingSystem::Linux, Abi::Musl) + Target::new(Architecture::Amd64, OperatingSystem::Linux, Abi::Musl) .llvm_triple(), - "x86_64-unknown-linux-musl" + "x86_64-linux-musl" ); assert_eq!( - target(Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native) - .llvm_triple(), + Target::new( + Architecture::Amd64, + OperatingSystem::Freebsd, + Abi::Native + ) + .llvm_triple(), "x86_64-unknown-freebsd" ); assert_eq!( - target(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + Target::new(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) .llvm_triple(), "aarch64-apple-darwin" ); } + #[test] + fn test_target_zig_triple() { + assert_eq!( + Target::new( + Architecture::Amd64, + OperatingSystem::Linux, + Abi::Native + ) + .zig_triple(), + "x86_64-linux-gnu" + ); + assert_eq!( + Target::new(Architecture::Amd64, OperatingSystem::Linux, Abi::Musl) + .zig_triple(), + "x86_64-linux-musl" + ); + assert_eq!( + Target::new( + Architecture::Amd64, + OperatingSystem::Freebsd, + Abi::Native + ) + .zig_triple(), + "x86_64-freebsd-none" + ); + assert_eq!( + Target::new(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + .zig_triple(), + "aarch64-macos-none" + ); + } + #[test] fn test_target_to_string() { assert_eq!( - target(Architecture::Amd64, OperatingSystem::Linux, Abi::Native) - .to_string(), + Target::new( + Architecture::Amd64, + OperatingSystem::Linux, + Abi::Native + ) + .to_string(), "amd64-linux-gnu" ); assert_eq!( - target(Architecture::Amd64, OperatingSystem::Linux, Abi::Musl) + Target::new(Architecture::Amd64, OperatingSystem::Linux, Abi::Musl) .to_string(), "amd64-linux-musl" ); assert_eq!( - target(Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native) - .to_string(), + Target::new( + Architecture::Amd64, + OperatingSystem::Freebsd, + Abi::Native + ) + .to_string(), "amd64-freebsd-native" ); assert_eq!( - target(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + Target::new(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) .to_string(), "arm64-mac-native" ); diff --git a/deny.toml b/deny.toml index 994570708..3006aae4c 100644 --- a/deny.toml +++ b/deny.toml @@ -11,10 +11,12 @@ ignore = [] [licenses] unlicensed = "deny" allow = [ - "MIT", "Apache-2.0", + "BSD-2-Clause", + "ISC", + "MIT", "MPL-2.0", - "BSD-3-Clause", + "OpenSSL", "Unicode-DFS-2016", ] copyleft = "deny" @@ -22,14 +24,23 @@ allow-osi-fsf-free = "neither" default = "deny" confidence-threshold = 0.8 +[[licenses.clarify]] +name = "ring" +expression = "OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + [bans] multiple-versions = "warn" wildcards = "allow" highlight = "all" -skip = [] +skip = [{ name = "bitflags" }] +skip-tree = [ + { name = "windows-sys", depth = 5 }, +] +allow = [] [sources] unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = [] +allow-git = ["https://github.com/TheDan64/inkwell.git"] diff --git a/inko/Cargo.toml b/inko/Cargo.toml index eab34d378..cb6590320 100644 --- a/inko/Cargo.toml +++ b/inko/Cargo.toml @@ -9,3 +9,6 @@ license = "MPL-2.0" getopts = "^0.2" compiler = { path = "../compiler" } types = { path = "../types" } +ureq = { version = "^2.9", default-features = false, features = ["tls"] } +flate2 = "^1.0" +tar = { version = "^0.4", default-features = false } diff --git a/inko/src/command.rs b/inko/src/command.rs index 6f4f79108..4a7a35e3a 100644 --- a/inko/src/command.rs +++ b/inko/src/command.rs @@ -4,4 +4,6 @@ pub(crate) mod main; pub(crate) mod pkg; pub(crate) mod print; pub(crate) mod run; +pub(crate) mod runtime; +pub(crate) mod targets; pub(crate) mod test; diff --git a/inko/src/command/build.rs b/inko/src/command/build.rs index 3a4f8c951..79c459d91 100644 --- a/inko/src/command/build.rs +++ b/inko/src/command/build.rs @@ -90,8 +90,15 @@ pub(crate) fn run(arguments: &[String]) -> Result { options.optopt( "", "linker", - "The specific linker to use, instead of detecting the linker automatically", - "system,lld,mold", + "A custom linker to use, instead of detecting the linker automatically", + "LINKER", + ); + + options.optmulti( + "", + "linker-arg", + "An extra argument to pass to the linker", + "ARG", ); options.optflag( @@ -152,7 +159,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { if let Some(val) = matches.opt_str("threads") { match val.parse::() { Ok(0) | Err(_) => { - return Err(Error::generic(format!( + return Err(Error::from(format!( "'{}' isn't a valid number of threads", val ))); @@ -163,16 +170,17 @@ pub(crate) fn run(arguments: &[String]) -> Result { if let Some(val) = matches.opt_str("linker") { config.linker = Linker::parse(&val).ok_or_else(|| { - Error::generic(format!("'{}' isn't a valid linker", val)) + Error::from(format!("'{}' isn't a valid linker", val)) })?; } + for arg in matches.opt_strs("linker-arg") { + config.linker_arguments.push(arg); + } + let timings = match matches.opt_str("timings") { Some(val) => Timings::parse(&val).ok_or_else(|| { - Error::generic(format!( - "'{}' is an invalid --timings argument", - val - )) + Error::from(format!("'{}' is an invalid --timings argument", val)) })?, _ if matches.opt_present("timings") => Timings::Basic, _ => Timings::None, @@ -193,6 +201,6 @@ pub(crate) fn run(arguments: &[String]) -> Result { match result { Ok(_) => Ok(0), Err(CompileError::Invalid) => Ok(1), - Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), + Err(CompileError::Internal(msg)) => Err(Error::from(msg)), } } diff --git a/inko/src/command/check.rs b/inko/src/command/check.rs index ce2d4a943..af9f140b7 100644 --- a/inko/src/command/check.rs +++ b/inko/src/command/check.rs @@ -63,6 +63,6 @@ pub(crate) fn run(arguments: &[String]) -> Result { match result { Ok(_) => Ok(0), Err(CompileError::Invalid) => Ok(1), - Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), + Err(CompileError::Internal(msg)) => Err(Error::from(msg)), } } diff --git a/inko/src/command/main.rs b/inko/src/command/main.rs index 6af43382b..92d6b04da 100644 --- a/inko/src/command/main.rs +++ b/inko/src/command/main.rs @@ -3,6 +3,8 @@ use crate::command::check; use crate::command::pkg; use crate::command::print; use crate::command::run; +use crate::command::runtime; +use crate::command::targets; use crate::command::test; use crate::error::Error; use crate::options::print_usage; @@ -13,12 +15,14 @@ const USAGE: &str = "Usage: inko [OPTIONS] [COMMAND | FILE] Commands: - build Compile Inko source code - check Check a project or single file for correctness - pkg Manage Inko packages - print Print compiler details to STDOUT - run Compile and run source code directly - test Run Inko unit tests + build Compile Inko source code + check Check a project or single file for correctness + pkg Manage Inko packages + print Print compiler details to STDOUT + run Compile and run source code directly + runtime Manage runtimes for the available targets + targets List the supported target triples + test Run Inko unit tests Examples: @@ -54,8 +58,10 @@ pub(crate) fn run() -> Result { Some("test") => test::run(&matches.free[1..]), Some("print") => print::run(&matches.free[1..]), Some("pkg") => pkg::run(&matches.free[1..]), + Some("runtime") => runtime::run(&matches.free[1..]), + Some("targets") => targets::run(&matches.free[1..]), Some(cmd) => { - Err(Error::generic(format!("The command '{}' is invalid", cmd))) + Err(Error::from(format!("The command '{}' is invalid", cmd))) } None => { print_usage(&options, USAGE); diff --git a/inko/src/command/pkg.rs b/inko/src/command/pkg.rs index dfd15ce11..b92cbe8da 100644 --- a/inko/src/command/pkg.rs +++ b/inko/src/command/pkg.rs @@ -42,7 +42,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { Some("sync") => sync::run(&matches.free[1..]), Some("update") => update::run(&matches.free[1..]), Some(cmd) => { - Err(Error::generic(format!("The command {:?} is invalid", cmd))) + Err(Error::from(format!("The command {:?} is invalid", cmd))) } None => { print_usage(&options, USAGE); diff --git a/inko/src/command/pkg/add.rs b/inko/src/command/pkg/add.rs index 1e9eb2cb9..910b56c56 100644 --- a/inko/src/command/pkg/add.rs +++ b/inko/src/command/pkg/add.rs @@ -30,19 +30,20 @@ pub(crate) fn run(args: &[String]) -> Result { } if matches.free.len() != 2 { - return Err(Error::generic( + return Err(Error::from( "You must specify a package and version to add".to_string(), )); } - let url = matches.free.first().and_then(|uri| Url::parse(uri)).ok_or_else( - || Error::generic("The package URL is invalid".to_string()), - )?; + let url = + matches.free.first().and_then(|uri| Url::parse(uri)).ok_or_else( + || Error::from("The package URL is invalid".to_string()), + )?; let name = url.import_name(); let version = matches.free.get(1).and_then(|uri| Version::parse(uri)).ok_or_else( - || Error::generic("The package version is invalid".to_string()), + || Error::from("The package version is invalid".to_string()), )?; let dir = data_dir()?.join(url.directory_name()); @@ -68,7 +69,7 @@ pub(crate) fn run(args: &[String]) -> Result { }; let hash = tag.map(|t| t.target).ok_or_else(|| { - Error::generic(format!("Version {} doesn't exist", version)) + Error::from(format!("Version {} doesn't exist", version)) })?; let checksum = Checksum::new(hash); diff --git a/inko/src/command/pkg/update.rs b/inko/src/command/pkg/update.rs index 7be4a8f2e..2b0bfdf9f 100644 --- a/inko/src/command/pkg/update.rs +++ b/inko/src/command/pkg/update.rs @@ -43,7 +43,7 @@ pub(crate) fn run(args: &[String]) -> Result { if let Some(dep) = manifest.find_dependency(&url) { vec![dep] } else { - return Err(Error::generic(format!( + return Err(Error::from(format!( "The package {} isn't listed in {}", url, MANIFEST_FILE ))); @@ -66,7 +66,7 @@ pub(crate) fn run(args: &[String]) -> Result { let tag_names = repo.version_tag_names(); if tag_names.is_empty() { - return Err(Error::generic(format!( + return Err(Error::from(format!( "The package {} doesn't have any versions", dep.url ))); diff --git a/inko/src/command/print.rs b/inko/src/command/print.rs index f94fb1a6a..b817cf35b 100644 --- a/inko/src/command/print.rs +++ b/inko/src/command/print.rs @@ -1,6 +1,6 @@ use crate::error::Error; use crate::options::print_usage; -use compiler::config::Config; +use compiler::config::{local_runtimes_directory, Config}; use compiler::target::Target; use getopts::Options; @@ -11,7 +11,7 @@ Print compiler details, such as the target, to STDOUT. Available values: target # Print the host's target triple (e.g. amd64-linux-gnu) - runtime # Print the path to the static runtime library + runtimes # Print the paths to search for the runtime library Examples: @@ -34,15 +34,19 @@ pub(crate) fn run(arguments: &[String]) -> Result { println!("{}", Target::native()); Ok(0) } - Some("runtime") => { + Some("runtimes") => { + if let Some(dir) = local_runtimes_directory() { + println!("{}", dir.display()); + } + println!("{}", Config::default().runtime.display()); + Ok(0) } - Some(val) => Err(Error::generic(format!( - "'{}' isn't a valid value to print", - val - ))), - None => Err(Error::generic( + Some(val) => { + Err(Error::from(format!("'{}' isn't a valid value to print", val))) + } + None => Err(Error::from( "You must specify a type of value to print".to_string(), )), } diff --git a/inko/src/command/run.rs b/inko/src/command/run.rs index ae272d0a3..4edd1d8a0 100644 --- a/inko/src/command/run.rs +++ b/inko/src/command/run.rs @@ -77,7 +77,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { if !build_dir.is_dir() { create_dir(&build_dir).map_err(|err| { - Error::generic(format!( + Error::from(format!( "Failed to create {}: {}", build_dir.display(), err @@ -100,7 +100,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { .spawn() .and_then(|mut child| child.wait()) .map_err(|err| { - Error::generic(format!( + Error::from(format!( "Failed to run the executable: {}", err )) @@ -116,6 +116,6 @@ pub(crate) fn run(arguments: &[String]) -> Result { status } Err(CompileError::Invalid) => Ok(1), - Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), + Err(CompileError::Internal(msg)) => Err(Error::from(msg)), } } diff --git a/inko/src/command/runtime.rs b/inko/src/command/runtime.rs new file mode 100644 index 000000000..7a7793cf8 --- /dev/null +++ b/inko/src/command/runtime.rs @@ -0,0 +1,64 @@ +mod add; +mod list; +mod remove; + +use crate::error::Error; +use crate::options::print_usage; +use compiler::config::local_runtimes_directory; +use getopts::{Options, ParsingStyle}; +use std::fs::create_dir_all; + +const USAGE: &str = "inko runtime [OPTIONS] [COMMAND] + +Manage runtimes for the available targets. + +Commands: + + add Install a new runtime + remove Remove an installed runtime + list List the available and installed runtimes + +Examples: + + inko runtime add arm64-linux-gnu + inko runtime remove arm64-linux-gnu"; + +pub(crate) fn run(arguments: &[String]) -> Result { + let mut options = Options::new(); + + options.parsing_style(ParsingStyle::StopAtFirstFree); + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + // Instead of each command having to check if the runtimes directory exists, + // we ensure it does here. + let runtimes = local_runtimes_directory().ok_or_else(|| { + Error::from("failed to determine the runtimes directory".to_string()) + })?; + + if let Err(e) = create_dir_all(&runtimes) { + return Err(Error::from(format!( + "failed to create the runtimes directory: {}", + e + ))); + } + + match matches.free.first().map(|s| s.as_str()) { + Some("add") => add::run(runtimes, &matches.free[1..]), + Some("remove") => remove::run(runtimes, &matches.free[1..]), + Some("list") => list::run(runtimes, &matches.free[1..]), + Some(cmd) => { + Err(Error::from(format!("The command {:?} is invalid", cmd))) + } + None => { + print_usage(&options, USAGE); + Ok(0) + } + } +} diff --git a/inko/src/command/runtime/add.rs b/inko/src/command/runtime/add.rs new file mode 100644 index 000000000..e609a538c --- /dev/null +++ b/inko/src/command/runtime/add.rs @@ -0,0 +1,180 @@ +use crate::error::Error; +use crate::http; +use crate::options::print_usage; +use compiler::target::Target; +use flate2::read::GzDecoder; +use getopts::{Options, ParsingStyle}; +use std::env::temp_dir; +use std::fs::{remove_dir_all, remove_file, File}; +use std::io::{stdout, IsTerminal as _}; +use std::io::{Read as _, Write as _}; +use std::path::{Path, PathBuf}; +use tar::Archive; + +/// The base URL from which to download the runtime files. +pub(crate) const URL: &str = "https://releases.inko-lang.org"; + +const USAGE: &str = "inko runtime add [OPTIONS] TARGET + +Add a new runtime for a given target. + +Examples: + + inko runtime add arm64-linux-gnu"; + +struct ProgressBar { + current: usize, + total: usize, + last_percentage: usize, +} + +impl ProgressBar { + fn new(total: usize) -> ProgressBar { + ProgressBar { current: 0, total, last_percentage: 0 } + } + + fn add(&mut self, amount: usize) { + self.current += amount; + + let percent = + (((self.current as f64) / (self.total as f64)) * 100.0) as usize; + + if percent != self.last_percentage { + let done_mb = (self.current as f64 / 1024.0 / 1024.0) as usize; + let total_mb = (self.total as f64 / 1024.0 / 1024.0) as usize; + + self.last_percentage = percent; + print!("\r {} MiB / {} MiB ({}%)", done_mb, total_mb, percent); + + let _ = stdout().flush(); + } + } +} + +impl Drop for ProgressBar { + fn drop(&mut self) { + // This ensures that we always produce a new line after the progress + // line, even in the event of an error, ensuring future output isn't + // placed on the same line. + if self.last_percentage > 0 { + println!(); + } + } +} + +fn download(target: &Target) -> Result { + let archive_name = format!("{}.tar.gz", target); + let url = format!( + "{}/runtimes/{}/{}", + URL, + env!("CARGO_PKG_VERSION"), + archive_name, + ); + + let response = http::get(&url)?; + let total = response + .header("Content-Length") + .and_then(|v| v.parse::().ok()) + .unwrap_or(0); + + // We don't decompress here right away as that prevents us from reporting + // progress correctly (due to the total read size being different from the + // Content-Length value). + let mut reader = response.into_reader(); + let path = temp_dir().join(archive_name); + let mut file = File::create(&path).map_err(|e| { + Error::from(format!("failed to open {}: {}", path.display(), e)) + })?; + + let mut run = true; + let mut progress = ProgressBar::new(total); + let is_term = stdout().is_terminal(); + + while run { + let mut buff = [0_u8; 8096]; + let result = reader + .read(&mut buff) + .and_then(|len| file.write_all(&buff[0..len]).map(|_| len)); + + let read = match result { + Ok(0) => { + run = false; + 0 + } + Ok(n) => n, + Err(err) => { + return Err(Error::from(format!( + "failed to download the runtime: {}", + err + ))); + } + }; + + if is_term { + progress.add(read) + } + } + + Ok(path) +} + +fn unpack(path: &Path, into: &Path) -> Result<(), String> { + let archive = File::open(path).map_err(|e| e.to_string())?; + let res = Archive::new(GzDecoder::new(archive)) + .entries() + .and_then(|entries| { + for entry in entries { + entry.and_then(|mut entry| entry.unpack_in(into))?; + } + + Ok(()) + }) + .map_err(|e| format!("failed to unpack the archive: {}", e)) + .and_then(|_| remove_file(path).map_err(|e| e.to_string())) + .map(|_| ()); + + // This ensures that in the event of an error, we don't leave behind a + // partially decompressed archive in the runtimes directory. + if res.is_err() && into.is_dir() { + let _ = remove_dir_all(into); + } + + res +} + +pub(crate) fn run( + runtimes: PathBuf, + arguments: &[String], +) -> Result { + let mut options = Options::new(); + + options.parsing_style(ParsingStyle::StopAtFirstFree); + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + let target = + matches.free.first().and_then(|v| Target::parse(v)).ok_or_else( + || Error::from("a valid target triple is required".to_string()), + )?; + + if runtimes.join(target.to_string()).is_dir() { + return Err(Error::from(format!( + "the runtime for the target '{}' is already installed", + target + ))); + } + + println!("Downloading runtime for target '{}'...", target); + + let tmp_path = download(&target)?; + + unpack(&tmp_path, &runtimes).map(|_| 0).map_err(|e| { + Error::from(format!("failed to decompress the runtime: {}", e)) + }) +} diff --git a/inko/src/command/runtime/list.rs b/inko/src/command/runtime/list.rs new file mode 100644 index 000000000..f06827445 --- /dev/null +++ b/inko/src/command/runtime/list.rs @@ -0,0 +1,51 @@ +use crate::error::Error; +use crate::options::print_usage; +use compiler::target::Target; +use getopts::{Options, ParsingStyle}; +use std::io::{stdout, IsTerminal as _}; +use std::path::PathBuf; + +const USAGE: &str = "inko runtime list [OPTIONS] + +List the targets for which a runtime is available, and highlights those for +which a runtime is installed. + +Examples: + + inko runtime list"; + +pub(crate) fn run( + runtimes: PathBuf, + arguments: &[String], +) -> Result { + let mut options = Options::new(); + + options.parsing_style(ParsingStyle::StopAtFirstFree); + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + let is_term = stdout().is_terminal(); + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + for target in Target::supported() { + let dir = runtimes.join(target.to_string()); + + if dir.is_dir() { + let line = format!("{} (installed)", target); + + if is_term { + println!("\x1b[1m{}\x1b[0m", line); + } else { + println!("{}", line); + } + } else { + println!("{}", target); + } + } + + Ok(0) +} diff --git a/inko/src/command/runtime/remove.rs b/inko/src/command/runtime/remove.rs new file mode 100644 index 000000000..16384d863 --- /dev/null +++ b/inko/src/command/runtime/remove.rs @@ -0,0 +1,51 @@ +use crate::error::Error; +use crate::options::print_usage; +use compiler::target::Target; +use getopts::{Options, ParsingStyle}; +use std::fs::remove_dir_all; +use std::path::PathBuf; + +const USAGE: &str = "inko runtime remove [OPTIONS] TARGET + +Remove an existing runtime for a given target. + +Examples: + + inko runtime remove arm64-linux-gnu"; + +pub(crate) fn run( + runtimes: PathBuf, + arguments: &[String], +) -> Result { + let mut options = Options::new(); + + options.parsing_style(ParsingStyle::StopAtFirstFree); + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + let target = + matches.free.first().and_then(|v| Target::parse(v)).ok_or_else( + || Error::from("a valid target triple is required".to_string()), + )?; + + let dir = runtimes.join(target.to_string()); + + if !dir.is_dir() { + return Err(Error::from(format!( + "no runtime for the target '{}' is installed", + target + ))); + } + + remove_dir_all(&dir) + .map_err(|e| { + Error::from(format!("failed to remove {}: {}", dir.display(), e)) + }) + .map(|_| 0) +} diff --git a/inko/src/command/targets.rs b/inko/src/command/targets.rs new file mode 100644 index 000000000..f3ad0ca2d --- /dev/null +++ b/inko/src/command/targets.rs @@ -0,0 +1,44 @@ +use crate::error::Error; +use crate::options::print_usage; +use compiler::target::Target; +use getopts::{Options, ParsingStyle}; +use std::io::{stdout, IsTerminal as _}; + +const USAGE: &str = "inko targets [OPTIONS] + +List the supported target triples. + +Examples: + + inko targets"; + +pub(crate) fn run(arguments: &[String]) -> Result { + let mut options = Options::new(); + + options.parsing_style(ParsingStyle::StopAtFirstFree); + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + let is_term = stdout().is_terminal(); + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + for target in Target::supported() { + if target.is_native() { + let line = format!("{} (native)", target); + + if is_term { + println!("\x1b[1m{}\x1b[0m", line); + } else { + println!("{}", line); + } + } else { + println!("{}", target); + } + } + + Ok(0) +} diff --git a/inko/src/command/test.rs b/inko/src/command/test.rs index 5fa0df5ca..3a1e02bd6 100644 --- a/inko/src/command/test.rs +++ b/inko/src/command/test.rs @@ -36,7 +36,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { let input = config.main_test_module(); if !config.tests.is_dir() { - return Err(Error::generic(format!( + return Err(Error::from(format!( "The tests directory {:?} doesn't exist", config.tests ))); @@ -46,7 +46,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { config.output = Output::File("inko-tests".to_string()); let tests = test_module_names(&config.tests).map_err(|err| { - Error::generic(format!("Failed to find test modules: {}", err)) + Error::from(format!("Failed to find test modules: {}", err)) })?; let mut compiler = Compiler::new(config); @@ -56,7 +56,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { compiler.create_build_directory()?; write(&input, generate_main_test_module(tests)).map_err(|err| { - Error::generic(format!("Failed to write {}: {}", input.display(), err)) + Error::from(format!("Failed to write {}: {}", input.display(), err)) })?; let result = compiler.build(Some(input)); @@ -69,11 +69,11 @@ pub(crate) fn run(arguments: &[String]) -> Result { .spawn() .and_then(|mut child| child.wait()) .map_err(|err| { - Error::generic(format!("Failed to run the tests: {}", err)) + Error::from(format!("Failed to run the tests: {}", err)) }) .map(|status| status.code().unwrap_or(0)), Err(CompileError::Invalid) => Ok(1), - Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), + Err(CompileError::Internal(msg)) => Err(Error::from(msg)), } } diff --git a/inko/src/error.rs b/inko/src/error.rs index 1fc44e78c..b4592fa7b 100644 --- a/inko/src/error.rs +++ b/inko/src/error.rs @@ -11,26 +11,20 @@ pub(crate) struct Error { pub(crate) message: Option, } -impl Error { - pub(crate) fn generic(message: String) -> Self { - Error { status: 1, message: Some(message) } - } -} - impl From for Error { fn from(fail: Fail) -> Self { - Self::generic(fail.to_string()) + Self::from(fail.to_string()) } } -impl From for Error { - fn from(error: io::Error) -> Self { - Self::generic(error.to_string()) +impl From for Error { + fn from(message: String) -> Self { + Error { status: 1, message: Some(message) } } } -impl From for Error { - fn from(message: String) -> Self { - Self::generic(message) +impl From for Error { + fn from(error: io::Error) -> Self { + Error { status: 1, message: Some(error.to_string()) } } } diff --git a/inko/src/http.rs b/inko/src/http.rs new file mode 100644 index 000000000..84c64f019 --- /dev/null +++ b/inko/src/http.rs @@ -0,0 +1,30 @@ +use crate::error::Error; +use std::time::Duration; +use ureq::{self, Agent, Error as HttpError, Response}; + +const TIMEOUT: u64 = 10; + +pub(crate) fn get(url: &str) -> Result { + let agent = agent(); + + match agent.get(url).call() { + Ok(response) => Ok(response), + Err(HttpError::Status(code, response)) => Err(Error::from(format!( + "GET {} failed: HTTP {} {}", + url, + code, + response.status_text() + ))), + Err(HttpError::Transport(err)) => { + Err(Error::from(format!("GET {} failed: {}", url, err))) + } + } +} + +fn agent() -> Agent { + ureq::builder() + .timeout_connect(Duration::from_secs(TIMEOUT)) + .timeout_read(Duration::from_secs(TIMEOUT)) + .user_agent(&format!("inko {}", env!("CARGO_PKG_VERSION"))) + .build() +} diff --git a/inko/src/main.rs b/inko/src/main.rs index beeb199f2..27d90d602 100644 --- a/inko/src/main.rs +++ b/inko/src/main.rs @@ -1,9 +1,11 @@ mod command; mod error; +mod http; mod options; mod pkg; use crate::command::main; +use std::io::{stdout, IsTerminal as _}; use std::process::exit; fn main() { @@ -11,7 +13,11 @@ fn main() { Ok(status) => exit(status), Err(err) => { if let Some(message) = err.message { - eprintln!("{}", message); + if stdout().is_terminal() { + eprintln!("\x1b[31;1merror:\x1b[0m {}", message); + } else { + eprintln!("error: {}", message); + } } exit(err.status); diff --git a/inko/src/pkg/util.rs b/inko/src/pkg/util.rs index 6851498ef..629281a64 100644 --- a/inko/src/pkg/util.rs +++ b/inko/src/pkg/util.rs @@ -1,22 +1,10 @@ -use std::env; +use compiler::config; use std::fs::{copy, create_dir_all, read_dir}; use std::path::{Path, PathBuf}; -fn home_dir() -> Option { - env::var_os("HOME").filter(|v| !v.is_empty()).map(PathBuf::from) -} - pub(crate) fn data_dir() -> Result { - let base = if cfg!(target_os = "macos") { - home_dir().map(|h| h.join("Library").join("Application Support")) - } else { - env::var_os("XDG_DATA_HOME") - .filter(|v| !v.is_empty()) - .map(PathBuf::from) - .or_else(|| home_dir().map(|h| h.join(".local").join("share"))) - }; - - base.map(|p| p.join("inko").join("packages")) + config::data_directory() + .map(|p| p.join("packages")) .ok_or_else(|| "No data directory could be determined".to_string()) } diff --git a/rt/Cargo.toml b/rt/Cargo.toml index b730832c8..24c4d6ecb 100644 --- a/rt/Cargo.toml +++ b/rt/Cargo.toml @@ -16,10 +16,10 @@ crossbeam-utils = "^0.8" crossbeam-queue = "^0.3" libc = "^0.2" rand = { version = "^0.8", features = ["default", "small_rng"] } -polling = "^2.8" -unicode-segmentation = "^1.8" +polling = "^3.3" +unicode-segmentation = "^1.10" backtrace = "^0.3" -rustix = { version = "^0.38.24", features = ["fs", "mm", "param", "process", "net", "std", "time"], default-features = false } +rustix = { version = "^0.38", features = ["fs", "mm", "param", "process", "net", "std", "time"], default-features = false } [dependencies.socket2] version = "^0.5" diff --git a/rt/src/lib.rs b/rt/src/lib.rs index cdc264fb0..78a2d8256 100644 --- a/rt/src/lib.rs +++ b/rt/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::missing_safety_doc))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] +#[cfg(feature = "unwinding")] +extern crate unwinding; + pub mod macros; pub mod arc_without_weak; diff --git a/rt/src/network_poller.rs b/rt/src/network_poller.rs index e0eb95f64..8b5156b83 100644 --- a/rt/src/network_poller.rs +++ b/rt/src/network_poller.rs @@ -1,7 +1,7 @@ //! Polling of non-blocking sockets using the system's polling mechanism. use crate::process::{ProcessPointer, RescheduleRights}; use crate::state::RcState; -use polling::{Event, Poller, Source}; +use polling::{AsRawSource, AsSource, Event, Events, Poller}; use std::io; /// The type of event a poller should wait for. @@ -25,29 +25,29 @@ impl NetworkPoller { } } - pub(crate) fn poll(&self, events: &mut Vec) -> io::Result { + pub(crate) fn poll(&self, events: &mut Events) -> io::Result { self.poller.wait(events, None) } pub(crate) fn add( &self, process: ProcessPointer, - source: impl Source, + source: impl AsRawSource, interest: Interest, ) -> io::Result<()> { - self.poller.add(source, self.event(process, interest)) + unsafe { self.poller.add(source, self.event(process, interest)) } } pub(crate) fn modify( &self, process: ProcessPointer, - source: impl Source, + source: impl AsSource, interest: Interest, ) -> io::Result<()> { self.poller.modify(source, self.event(process, interest)) } - pub(crate) fn delete(&self, source: impl Source) -> io::Result<()> { + pub(crate) fn delete(&self, source: impl AsSource) -> io::Result<()> { self.poller.delete(source) } @@ -73,7 +73,7 @@ impl Worker { } pub(crate) fn run(&self) { - let mut events = Vec::new(); + let mut events = Events::new(); let poller = &self.state.network_pollers[self.id]; loop { @@ -122,6 +122,7 @@ mod tests { use super::*; use crate::test::{empty_process_class, new_process}; use std::net::UdpSocket; + use std::num::NonZeroUsize; use std::time::Duration; #[test] @@ -151,7 +152,7 @@ mod tests { let poller = NetworkPoller::new(); let class = empty_process_class("A"); let process = new_process(*class); - let mut events = Vec::with_capacity(1); + let mut events = Events::with_capacity(NonZeroUsize::new(1).unwrap()); assert!(poller.add(*process, &output, Interest::Write).is_ok()); assert!(poller.delete(&output).is_ok()); @@ -171,14 +172,14 @@ mod tests { let poller = NetworkPoller::new(); let class = empty_process_class("A"); let process = new_process(*class); - let mut events = Vec::with_capacity(1); + let mut events = Events::with_capacity(NonZeroUsize::new(1).unwrap()); poller.add(*process, &output, Interest::Write).unwrap(); assert!(poller.poll(&mut events).is_ok()); - assert_eq!(events.capacity(), 1); + assert_eq!(events.capacity().get(), 1); assert_eq!(events.len(), 1); - assert_eq!(events[0].key, process.identifier()); + assert_eq!(events.iter().next().unwrap().key, process.identifier()); } #[test] @@ -188,13 +189,15 @@ mod tests { let poller = NetworkPoller::new(); let class = empty_process_class("A"); let process = new_process(*class); - let mut events = Vec::with_capacity(1); + let mut events = Events::with_capacity(NonZeroUsize::new(1).unwrap()); poller.add(*process, &sock1, Interest::Write).unwrap(); poller.add(*process, &sock2, Interest::Write).unwrap(); assert!(poller.poll(&mut events).is_ok()); - assert!(events.capacity() >= 2); - assert_eq!(events.len(), 2); + assert_eq!(events.len(), 1); + + assert!(poller.poll(&mut events).is_ok()); + assert_eq!(events.len(), 1); } } diff --git a/scripts/runtimes.sh b/scripts/runtimes.sh new file mode 100755 index 000000000..cc6c7686f --- /dev/null +++ b/scripts/runtimes.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +set -e + +# We use a nightly version as close as possible to the minimum Rust version that +# we require. This version is obtained by going through the releases at +# https://releases.rs/ and picking a nightly close to the branch date of the +# desired release. +# +# This is needed so we can use the unwinding crate while still having a somewhat +# stable version of Rust, instead of relying on the latest nightly version that +# may randomly change. + +# The Inko version we're building the runtimes for. +VERSION="${1}" + +# The directory to place the runtimes in. +DIR="tmp/runtimes/${VERSION}" + +# The S3 bucket to store the runtime files in. +S3_BUCKET="releases.inko-lang.org" + +function rustup_lib { + local home + local toolchain + home="$(rustup show home)" + toolchain="$(rustup show active-toolchain | awk '{print $1}')" + + echo "${home}/toolchains/${toolchain}/lib/rustlib/${1}/lib/" +} + +function build { + local rust_target + local inko_target + local out + local target_dir + rust_target="${1}" + inko_target="${2}" + out="${DIR}/${inko_target}.tar.gz" + target_dir="${DIR}/${inko_target}" + + if [[ -f "${out}" ]] + then + return 0 + fi + + rustup target add "${rust_target}" + cargo build -p rt --release --target="${rust_target}" + mkdir -p "${target_dir}" + cp "target/${rust_target}/release/libinko.a" "${target_dir}" + + if [[ "${rust_target}" == *-musl ]] + then + cp "$(rustup_lib "${rust_target}")/self-contained/libunwind.a" \ + "${target_dir}" + fi + + tar --directory "${DIR}" --create --gzip --file "${out}" "${inko_target}" + rm -rf "${target_dir}" +} + +if [[ "${VERSION}" = "" ]] +then + echo 'You must specify the Inko version to build the runtimes for' + exit 1 +fi + +mkdir -p "${DIR}" + +# FreeBSD +build "x86_64-unknown-freebsd" "amd64-freebsd-native" + +# Linux +build "x86_64-unknown-linux-gnu" "amd64-linux-gnu" +build "x86_64-unknown-linux-musl" "amd64-linux-musl" +build "aarch64-unknown-linux-gnu" "arm64-linux-gnu" +build "aarch64-unknown-linux-musl" "arm64-linux-musl" + +# macOS +build "x86_64-apple-darwin" "amd64-mac-native" +build "aarch64-apple-darwin" "arm64-mac-native" + +# Upload the results to the S3 bucket. +aws s3 sync --no-progress --acl=public-read --cache-control max-age=86400 \ + "${DIR}" "s3://${S3_BUCKET}/runtimes/${VERSION}" diff --git a/std/src/std/fs/path.inko b/std/src/std/fs/path.inko index 1b23f284f..ab24902c1 100644 --- a/std/src/std/fs/path.inko +++ b/std/src/std/fs/path.inko @@ -211,6 +211,9 @@ class pub Path { # Returns the creation time of `self`. # + # The target platform may not supported getting the creation time, in which + # case an `Error` is returned. musl targets are an example of such a platform. + # # # Examples # # Obtaining the creation time of a `Path`: @@ -233,6 +236,9 @@ class pub Path { # Returns the modification time of `self`. # + # The target platform may not supported getting the creation time, in which + # case an `Error` is returned. + # # # Examples # # Obtaining the modification time of a `Path`: @@ -255,6 +261,9 @@ class pub Path { # Returns the access time of `self`. # + # The target platform may not supported getting the creation time, in which + # case an `Error` is returned. + # # # Examples # # Obtaining the access time of a `Path`: diff --git a/std/test/helpers.inko b/std/test/helpers.inko index 7c75e93e1..72fe7c2fa 100644 --- a/std/test/helpers.inko +++ b/std/test/helpers.inko @@ -5,6 +5,9 @@ import std.fs.path.Path import std.hash.(Hash, Hasher) import std.hash.siphash.SipHasher13 +import std.fmt.(fmt) +import std.stdio.STDOUT + fn pub hash[T: Hash](value: ref T) -> Int { let hasher = SipHasher13.default @@ -36,7 +39,7 @@ fn pub compiler_path -> Path { let debug = target.join('debug').join('inko') let release = target.join('release').join('inko') - match (debug.created_at, release.created_at) { + match (debug.modified_at, release.modified_at) { case (Ok(deb), Ok(rel)) -> if deb >= rel { debug } else { release } case (Ok(_), Error(_)) -> debug case (Error(_), Ok(_)) -> release