diff --git a/.cargo/config b/.cargo/config index 1c4ab2c16..e7a82400e 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,5 @@ -[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'] -rustflags = ['-C', 'target-feature=+aes'] +# This is needed for stack traces to work when `panic=abort` is used. See +# https://github.com/rust-lang/rust/issues/94815 and +# https://github.com/rust-lang/backtrace-rs/issues/397 for more details. +[build] +rustflags = ['-C', 'force-unwind-tables'] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 021a86c4d..2caf51cf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: - 'compiler/**' - 'inko/**' - 'ipm/**' - - 'libstd/**' + - 'std/**' - 'types/**' - 'vm/**' - 'Cargo.*' @@ -22,7 +22,9 @@ on: workflow_dispatch: env: - CARGO_HOME: ${{ github.workspace }}/.cargo + # This directory must be named differently from `.cargo`, otherwise it will + # conflict with our local Cargo configuration. + CARGO_HOME: ${{ github.workspace }}/.cargo-home REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -40,15 +42,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@1.62 + - uses: dtolnay/rust-toolchain@1.63 with: components: 'clippy' - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }} + - name: Install LLVM + run: bash scripts/llvm.sh - run: 'cargo clippy -- -D warnings' cargo-fmt: @@ -61,7 +65,7 @@ jobs: - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }} - run: 'cargo fmt --all --check' @@ -78,13 +82,13 @@ jobs: compiler: strategy: + fail-fast: false matrix: os: - macos-latest - ubuntu-latest - - windows-latest version: - - '1.62' + - '1.63' - stable runs-on: ${{ matrix.os }} steps: @@ -95,37 +99,41 @@ jobs: - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }}-stable + - name: Install LLVM + run: bash scripts/llvm.sh - name: Running tests run: cargo test std: strategy: + fail-fast: false matrix: os: - macos-latest - ubuntu-latest - - windows-latest runs-on: ${{ matrix.os }} needs: - compiler steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@1.62 + - uses: dtolnay/rust-toolchain@1.63 - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }}-stable + - name: Install LLVM + run: bash scripts/llvm.sh - name: Compiling run: cargo build --release - name: Running tests run: | - cd libstd - cargo run --release -p inko -- test + cd std + ../target/release/inko test nightly-container: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index af9cc95ed..23f3b2e63 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ *.egg-info/ __pycache__ /docs/plugins/pygments-inko-lexer/poetry.lock +/build +/std/build diff --git a/Cargo.lock b/Cargo.lock index ce95abcb8..aa915e3e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,21 +3,27 @@ version = 3 [[package]] -name = "abort_on_panic" -version = "2.0.0" +name = "addr2line" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] [[package]] -name = "ahash" -version = "0.8.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", + "memchr", ] [[package]] @@ -29,20 +35,47 @@ 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.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "blake2" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -58,15 +91,11 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "bytecode" -version = "0.10.0" - [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -79,31 +108,40 @@ name = "compiler" version = "0.10.0" dependencies = [ "ast", - "bytecode", + "fnv", "getopts", + "inkwell", "similar-asserts", "types", "unicode-segmentation", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" -version = "0.15.1" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "terminal_size", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -111,12 +149,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -131,15 +168,21 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", "subtle", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -147,16 +190,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "fs_extra" -version = "1.2.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -173,49 +216,71 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + [[package]] name = "inko" version = "0.10.0" dependencies = [ + "blake2", "compiler", "getopts", "jemallocator", - "vm", ] [[package]] -name = "ipm" -version = "0.10.0" +name = "inkwell" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbac11e485159a525867fb7e6aa61981453e6a72f625fde6a4ab3047b0c6dec9" dependencies = [ - "blake2", - "getopts", + "either", + "inkwell_internals", + "libc", + "llvm-sys", + "once_cell", + "parking_lot", +] + +[[package]] +name = "inkwell_internals" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d00c17e264ce02be5bc23d7bff959188ec7137beddd06b8b6b05a7c680ea85" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "jemalloc-sys" -version = "0.3.2" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" +checksum = "f9bd5d616ea7ed58b571b2e209a65759664d7fb021a0819d7a790afc67e47ca1" dependencies = [ "cc", - "fs_extra", "libc", ] [[package]] name = "jemallocator" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" +checksum = "16c2514137880c52b0b4822b563fadd38257c1f380858addb74a400889696ea6" dependencies = [ "jemalloc-sys", "libc", @@ -229,38 +294,31 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] -name = "libffi" -version = "3.0.0" +name = "llvm-sys" +version = "150.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e08093a2ddeee94bd0c830a53d895ff91f1f3bb0f9b3c8c6b00739cdf76bc1d" -dependencies = [ - "abort_on_panic", - "libc", - "libffi-sys", -] - -[[package]] -name = "libffi-sys" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15" +checksum = "64be8a29d08e3165e4ed989b80cbc6b52104c543f16e272b83e2dedb749e81e6" dependencies = [ "cc", + "lazy_static", + "libc", + "regex", + "semver", ] [[package]] -name = "libloading" -version = "0.7.3" +name = "lock_api" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ - "cfg-if", - "winapi", + "autocfg", + "scopeguard", ] [[package]] @@ -278,30 +336,98 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.13.1" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "parking_lot" +version = "0.12.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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "polling" -version = "2.2.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ + "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "winapi", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] [[package]] name = "rand" @@ -326,24 +452,82 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rt" +version = "0.10.0" +dependencies = [ + "backtrace", + "crossbeam-queue", + "crossbeam-utils", + "libc", + "polling", + "rand", + "socket2", + "unicode-segmentation", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "similar" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" dependencies = [ "bstr", "unicode-segmentation", @@ -359,14 +543,20 @@ dependencies = [ "similar", ] +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "socket2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "6d283f86695ae989d1e18440a943880967156325ba025f05049946bff47bcc2b" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -376,39 +566,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] -name = "terminal_size" -version = "0.1.17" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "libc", - "winapi", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "types" version = "0.10.0" -dependencies = [ - "bytecode", -] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "version_check" @@ -416,24 +610,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vm" -version = "0.10.0" -dependencies = [ - "ahash", - "bytecode", - "crossbeam-queue", - "crossbeam-utils", - "libc", - "libffi", - "libloading", - "polling", - "rand", - "socket2", - "unicode-segmentation", - "windows-sys", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -441,75 +617,148 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "cc", + "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", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-targets 0.42.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +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", +] [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows-targets" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 47ed6582f..9be6f9552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ast", "bytecode", "inko", "compiler", "vm", "ipm"] +members = ["ast", "inko", "compiler", "rt"] [profile.release] panic = "abort" diff --git a/Dockerfile b/Dockerfile index fd5ea68b2..0ca062415 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,24 @@ -FROM alpine:3 AS builder +FROM fedora-minimal:38 AS builder -RUN apk add --update make libffi libffi-dev rust cargo build-base +ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL sparse + +# Fedora builds LLVM with libffi support, and when statically linking against +# LLVM the build will fail if libffi-devel isn't installed, hence we include it +# here. See https://gitlab.com/taricorp/llvm-sys.rs/-/issues/41 for some extra +# details. +RUN microdnf install --assumeyes gcc make rust cargo \ + llvm15 llvm15-devel llvm15-static libstdc++-devel libstdc++-static \ + libffi-devel zlib-devel ADD . /inko/ WORKDIR /inko -RUN make build FEATURES=libffi-system PREFIX='/usr' +RUN make build PREFIX='/usr' RUN strip target/release/inko RUN make install PREFIX='/usr' -FROM alpine:3 +FROM fedora-minimal:38 # libgcc is needed because libgcc is dynamically linked to the executable. -RUN apk add --update libffi libffi-dev libgcc +RUN microdnf install --assumeyes libgcc COPY --from=builder ["/usr/bin/inko", "/usr/bin/inko"] COPY --from=builder ["/usr/lib/inko", "/usr/lib/inko/"] diff --git a/Makefile b/Makefile index e071b8507..3e15a7d96 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ else INSTALL_PREFIX = ${PREFIX} endif +# The name of the static runtime library. +RUNTIME_NAME := libinko.a + # The directory to place the Inko executable in. INSTALL_INKO := ${INSTALL_PREFIX}/bin/inko @@ -18,7 +21,10 @@ INSTALL_INKO := ${INSTALL_PREFIX}/bin/inko INSTALL_IPM := ${INSTALL_PREFIX}/bin/ipm # The directory to place the standard library in. -INSTALL_STD := ${INSTALL_PREFIX}/lib/inko/libstd +INSTALL_STD := ${INSTALL_PREFIX}/lib/inko/std + +# The directory to place runtime library files in. +INSTALL_RT := ${INSTALL_PREFIX}/lib/inko/runtime/${RUNTIME_NAME} # The install path of the license file. INSTALL_LICENSE := ${INSTALL_PREFIX}/share/licenses/inko/LICENSE @@ -65,7 +71,7 @@ SOURCE_TAR := ${TMP_DIR}/${VERSION}.tar.gz MANIFEST := ${TMP_DIR}/manifest.txt build: - INKO_LIBSTD=${INSTALL_STD} cargo build --release ${FEATURES_OPTION} + INKO_STD=${INSTALL_STD} INKO_RT=${INSTALL_RT} cargo build --release ${FEATURES_OPTION} ${TMP_DIR}: mkdir -p "${@}" @@ -79,13 +85,12 @@ ${SOURCE_TAR}: ${TMP_DIR} LICENSE \ Makefile \ ast \ - bytecode \ compiler \ inko \ - libstd/src \ - vm \ + std/src \ + rt \ types \ - ipm \ + pkg \ | gzip > "${@}" release/source: ${SOURCE_TAR} @@ -119,33 +124,30 @@ release/publish: release/versions release/changelog release/commit release/tag ${INSTALL_STD}: mkdir -p "${@}" - cp -r libstd/src/* "${@}" + cp -r std/src/* "${@}" + +${INSTALL_RT}: + install -D -m644 target/release/${RUNTIME_NAME} "${@}" ${INSTALL_INKO}: - mkdir -p "$$(dirname ${@})" - install -m755 target/release/inko "${@}" + install -D -m755 target/release/inko "${@}" ${INSTALL_IPM}: - mkdir -p "$$(dirname ${@})" - install -m755 target/release/ipm "${@}" + install -D -m755 target/release/ipm "${@}" ${INSTALL_LICENSE}: - mkdir -p "$$(dirname ${@})" - install -m644 LICENSE "${@}" + install -D -m644 LICENSE "${@}" install: ${INSTALL_STD} \ + ${INSTALL_RT} \ ${INSTALL_INKO} \ ${INSTALL_IPM} \ ${INSTALL_LICENSE} -uninstall: - rm -rf ${INSTALL_STD} - rm -f ${INSTALL_INKO} - rm -f ${INSTALL_IPM} - rm -rf ${INSTALL_PREFIX}/share/licenses/inko - clean: rm -rf "${TMP_DIR}" + rm -rf build + rm -rf std/build cargo clean docs/install: @@ -183,6 +185,6 @@ rustfmt: .PHONY: release/source release/manifest release/changelog release/versions .PHONY: release/commit release/publish release/tag -.PHONY: build install uninstall clean -.PHONY: libstd/test rustfmt rustfmt-check clippy +.PHONY: build install clean +.PHONY: rustfmt rustfmt-check clippy .PHONY: docs/install docs/build docs/server docs/publish docs/versions diff --git a/README.md b/README.md index ab46970c2..e6ae82aca 100644 --- a/README.md +++ b/README.md @@ -2,86 +2,16 @@ Inko is a language for building concurrent software with confidence. Inko makes it easy to build concurrent software, without having to worry about -unpredictable performance, unexpected runtime errors, race conditions, and type -errors. +unpredictable performance, unexpected runtime errors, or race conditions. Inko features deterministic automatic memory management, move semantics, static -typing, type-safe concurrency, efficient error handling, and more. +typing, type-safe concurrency, efficient error handling, and more. Inko source +code is compiled to machine code using [LLVM](https://llvm.org/). -Inko supports 64-bits Linux, macOS and Windows, and installing Inko is quick and -easy. - -For more information, refer to the [Inko website](https://inko-lang.org/). - -## Features - -- Deterministic automatic memory management based on single ownership -- Easy concurrency through lightweight isolated processes -- Static typing -- Error handling done right -- An efficient, compact and portable bytecode interpreter -- Pattern matching -- Algebraic data types - -## Examples - -Here's what "Hello, World!" looks like in Inko: - -```inko -import std::stdio::STDOUT - -class async Main { - fn async main { - STDOUT.new.print('Hello, World!') - } -} -``` - -And here's how you'd define a concurrent counter: - -```inko -class async Counter { - let @value: Int - - fn async mut increment { - @value += 1 - } - - fn async value -> Int { - @value.clone - } -} - -class async Main { - fn async main { - let counter = Counter { @value = 0 } - - # "Main" will wait for the results of these calls, without blocking any OS - # threads. - counter.increment - counter.increment - counter.value # => 2 - } -} -``` - -Inko uses single ownership like Rust, but unlike Rust our implementation is -easier and less frustrating to use. For example, defining self-referential data -types such as doubly linked lists is trivial, and doesn't require unsafe code or -raw pointers: - -```inko -class Node[T] { - let @next: Option[Node[T]] - let @previous: Option[mut Node[T]] - let @value: T -} - -class List[T] { - let @head: Option[Node[T]] - let @tail: Option[mut Node[T]] -} -``` +For more information, refer to the [Inko website](https://inko-lang.org/) or +[the documentation](https://docs.inko-lang.org). If you'd like to follow the +development of Inko, consider joining [our Discord +server](https://discord.gg/seeURxHxCb). ## Installing diff --git a/ast/src/lexer.rs b/ast/src/lexer.rs index d5617a001..1ea3e0730 100644 --- a/ast/src/lexer.rs +++ b/ast/src/lexer.rs @@ -161,16 +161,13 @@ pub enum TokenKind { Sub, SubAssign, Throw, - Throws, Trait, True, Try, - TryPanic, Uni, UnicodeEscape, UnsignedShr, UnsignedShrAssign, - When, While, Whitespace, } @@ -261,12 +258,9 @@ impl TokenKind { TokenKind::Sub => "a '-'", TokenKind::SubAssign => "a '-='", TokenKind::Throw => "the 'throw' keyword", - TokenKind::Throws => "a '!!'", TokenKind::Trait => "the 'trait' keyword", TokenKind::Try => "the 'try' keyword", - TokenKind::TryPanic => "the 'try!' keyword", TokenKind::UnicodeEscape => "an Unicode escape sequence", - TokenKind::When => "the 'when' keyword", TokenKind::While => "the 'while' keyword", TokenKind::Whitespace => "whitespace", TokenKind::Mut => "the 'mut' keyword", @@ -335,8 +329,6 @@ impl Token { | TokenKind::Throw | TokenKind::Trait | TokenKind::Try - | TokenKind::TryPanic - | TokenKind::When | TokenKind::While | TokenKind::Mut | TokenKind::Recover @@ -924,12 +916,6 @@ impl Lexer { self.position += 2; self.token(TokenKind::Ne, start, self.line) } - EXCLAMATION => { - let start = self.position; - - self.position += 2; - self.token(TokenKind::Throws, start, self.line) - } _ => self.invalid(self.position, self.position + 1), } } @@ -952,7 +938,7 @@ impl Lexer { self.advance_identifier_bytes(); - let mut value = self.slice_string(start, self.position); + let value = self.slice_string(start, self.position); // We use this approach so that: // @@ -975,16 +961,7 @@ impl Lexer { "for" => TokenKind::For, "let" => TokenKind::Let, "ref" => TokenKind::Ref, - "try" => { - if self.current_byte() == EXCLAMATION { - self.position += 1; - - value.push('!'); - TokenKind::TryPanic - } else { - TokenKind::Try - } - } + "try" => TokenKind::Try, "mut" => TokenKind::Mut, "uni" => TokenKind::Uni, "pub" => TokenKind::Pub, @@ -997,7 +974,6 @@ impl Lexer { "loop" => TokenKind::Loop, "next" => TokenKind::Next, "self" => TokenKind::SelfObject, - "when" => TokenKind::When, "move" => TokenKind::Move, "true" => TokenKind::True, "case" => TokenKind::Case, @@ -1475,7 +1451,6 @@ mod tests { assert!(tok(TokenKind::Trait, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::True, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Try, "", 1..=1, 1..=1).is_keyword()); - assert!(tok(TokenKind::When, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::While, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Recover, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Nil, "", 1..=1, 1..=1).is_keyword()); @@ -2047,7 +2022,6 @@ mod tests { fn test_lexer_exclamation() { assert_token!("!", Invalid, "!", 1..=1, 1..=1); assert_token!("!=", Ne, "!=", 1..=1, 1..=2); - assert_token!("!!", Throws, "!!", 1..=1, 1..=2); } #[test] @@ -2072,7 +2046,6 @@ mod tests { assert_token!("let", Let, "let", 1..=1, 1..=3); assert_token!("ref", Ref, "ref", 1..=1, 1..=3); assert_token!("try", Try, "try", 1..=1, 1..=3); - assert_token!("try!", TryPanic, "try!", 1..=1, 1..=4); assert_token!("mut", Mut, "mut", 1..=1, 1..=3); assert_token!("uni", Uni, "uni", 1..=1, 1..=3); assert_token!("pub", Pub, "pub", 1..=1, 1..=3); @@ -2083,7 +2056,6 @@ mod tests { assert_token!("loop", Loop, "loop", 1..=1, 1..=4); assert_token!("next", Next, "next", 1..=1, 1..=4); assert_token!("self", SelfObject, "self", 1..=1, 1..=4); - assert_token!("when", When, "when", 1..=1, 1..=4); assert_token!("move", Move, "move", 1..=1, 1..=4); assert_token!("true", True, "true", 1..=1, 1..=4); assert_token!("case", Case, "case", 1..=1, 1..=4); diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index ce2cc1657..303fd623f 100644 --- a/ast/src/nodes.rs +++ b/ast/src/nodes.rs @@ -152,18 +152,6 @@ impl Node for Constant { } } -#[derive(Debug, PartialEq, Eq)] -pub struct Async { - pub expression: Expression, - pub location: SourceLocation, -} - -impl Node for Async { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct Call { pub receiver: Option, @@ -381,7 +369,6 @@ pub struct DefineMethod { pub name: Identifier, pub type_parameters: Option, pub arguments: Option, - pub throw_type: Option, pub return_type: Option, pub body: Option, pub location: SourceLocation, @@ -547,6 +534,7 @@ pub struct ReopenClass { pub class_name: Constant, pub body: ImplementationExpressions, pub location: SourceLocation, + pub bounds: Option, } impl Node for ReopenClass { @@ -555,10 +543,38 @@ impl Node for ReopenClass { } } +#[cfg_attr(feature = "cargo-clippy", allow(clippy::large_enum_variant))] +#[derive(Debug, PartialEq, Eq)] +pub enum Requirement { + Trait(TypeName), + Mutable(SourceLocation), +} + +impl Node for Requirement { + fn location(&self) -> &SourceLocation { + match self { + Requirement::Trait(n) => &n.location, + Requirement::Mutable(loc) => loc, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Requirements { + pub values: Vec, + pub location: SourceLocation, +} + +impl Node for Requirements { + fn location(&self) -> &SourceLocation { + &self.location + } +} + #[derive(Debug, PartialEq, Eq)] pub struct TypeBound { pub name: Constant, - pub requirements: TypeNames, + pub requirements: Requirements, pub location: SourceLocation, } @@ -638,12 +654,9 @@ pub enum Expression { And(Box), Or(Box), TypeCast(Box), - Index(Box), - SetIndex(Box), Throw(Box), Return(Box), Try(Box), - TryPanic(Box), If(Box), Match(Box), Loop(Box), @@ -651,7 +664,6 @@ pub enum Expression { True(Box), False(Box), Nil(Box), - Async(Box), ClassLiteral(Box), Scope(Box), Array(Box), @@ -684,7 +696,6 @@ impl Node for Expression { Expression::AssignSetter(ref typ) => typ.location(), Expression::AssignVariable(ref typ) => typ.location(), Expression::ReplaceVariable(ref typ) => typ.location(), - Expression::Async(ref typ) => typ.location(), Expression::Binary(ref typ) => typ.location(), Expression::BinaryAssignField(ref typ) => typ.location(), Expression::BinaryAssignSetter(ref typ) => typ.location(), @@ -702,7 +713,6 @@ impl Node for Expression { Expression::Group(ref typ) => typ.location(), Expression::Identifier(ref typ) => typ.location(), Expression::If(ref typ) => typ.location(), - Expression::Index(ref typ) => typ.location(), Expression::Int(ref typ) => typ.location(), Expression::Loop(ref typ) => typ.location(), Expression::Match(ref typ) => typ.location(), @@ -712,13 +722,11 @@ impl Node for Expression { Expression::Return(ref typ) => typ.location(), Expression::Scope(ref typ) => typ.location(), Expression::SelfObject(ref typ) => typ.location(), - Expression::SetIndex(ref typ) => typ.location(), Expression::SingleString(ref typ) => typ.location(), Expression::Throw(ref typ) => typ.location(), Expression::True(ref typ) => typ.location(), Expression::Nil(ref typ) => typ.location(), Expression::Try(ref typ) => typ.location(), - Expression::TryPanic(ref typ) => typ.location(), Expression::Tuple(ref typ) => typ.location(), Expression::TypeCast(ref typ) => typ.location(), Expression::While(ref typ) => typ.location(), @@ -755,7 +763,7 @@ impl Node for TypeNames { #[derive(Debug, PartialEq, Eq)] pub struct TypeParameter { pub name: Constant, - pub requirements: Option, + pub requirements: Option, pub location: SourceLocation, } @@ -899,7 +907,6 @@ impl Node for ReferrableType { #[derive(Debug, PartialEq, Eq)] pub struct ClosureType { pub arguments: Option, - pub throw_type: Option, pub return_type: Option, pub location: SourceLocation, } @@ -1046,7 +1053,6 @@ impl Node for BlockArguments { pub struct Closure { pub moving: bool, pub arguments: Option, - pub throw_type: Option, pub return_type: Option, pub body: Expressions, pub location: SourceLocation, @@ -1250,33 +1256,6 @@ impl Node for TypeCast { } } -#[derive(Debug, PartialEq, Eq)] -pub struct Index { - pub receiver: Expression, - pub index: Expression, - pub location: SourceLocation, -} - -impl Node for Index { - fn location(&self) -> &SourceLocation { - &self.location - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct SetIndex { - pub receiver: Expression, - pub index: Expression, - pub value: Expression, - pub location: SourceLocation, -} - -impl Node for SetIndex { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct Throw { pub value: Expression, @@ -1301,35 +1280,9 @@ impl Node for Return { } } -#[derive(Debug, PartialEq, Eq)] -pub struct TryBlock { - pub value: Expression, - pub location: SourceLocation, -} - -impl Node for TryBlock { - fn location(&self) -> &SourceLocation { - &self.location - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct ElseBlock { - pub body: Expressions, - pub argument: Option, - pub location: SourceLocation, -} - -impl Node for ElseBlock { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct Try { - pub try_block: TryBlock, - pub else_block: Option, + pub expression: Expression, pub location: SourceLocation, } @@ -1339,18 +1292,6 @@ impl Node for Try { } } -#[derive(Debug, PartialEq, Eq)] -pub struct TryPanic { - pub try_block: TryBlock, - pub location: SourceLocation, -} - -impl Node for TryPanic { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct IfCondition { pub condition: Expression, diff --git a/ast/src/parser.rs b/ast/src/parser.rs index 8a2dcb59a..4d202c70c 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -524,45 +524,12 @@ impl Parser { fn optional_type_parameter_requirements( &mut self, - ) -> Result, ParseError> { + ) -> Result, ParseError> { if self.peek().kind != TokenKind::Colon { return Ok(None); } - self.next(); - - let mut values = Vec::new(); - - loop { - let token = self.require()?; - - values.push(self.type_name_with_optional_namespace(token)?); - - let after = self.peek(); - - match after.kind { - TokenKind::Add => { - self.next(); - } - TokenKind::BracketClose | TokenKind::Comma => { - break; - } - _ => { - error!( - after.location.clone(), - "Expected a '+' or a ']', found '{}' instead", - after.value - ); - } - } - } - - let location = SourceLocation::start_end( - values.first().unwrap().location(), - values.last().unwrap().location(), - ); - - Ok(Some(TypeNames { values, location })) + Ok(Some(self.requirements()?)) } fn reference_type( @@ -631,15 +598,13 @@ impl Parser { start: Token, ) -> Result { let arguments = self.optional_block_argument_types()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let end_loc = location!(return_type) - .or_else(|| location!(throw_type)) .or_else(|| location!(arguments)) .unwrap_or(&start.location); let location = SourceLocation::start_end(&start.location, end_loc); - Ok(ClosureType { arguments, throw_type, return_type, location }) + Ok(ClosureType { arguments, return_type, location }) } fn optional_block_argument_types( @@ -733,18 +698,6 @@ impl Parser { Ok(BlockArgument { name, value_type, location }) } - fn optional_throw_type(&mut self) -> Result, ParseError> { - if self.peek().kind != TokenKind::Throws { - return Ok(None); - } - - self.next(); - - let start = self.require()?; - - Ok(Some(self.type_reference(start)?)) - } - fn optional_return_type(&mut self) -> Result, ParseError> { if self.peek().kind != TokenKind::Arrow { return Ok(None); @@ -767,7 +720,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; @@ -780,7 +732,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body: Some(body), @@ -822,7 +773,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; @@ -835,7 +785,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body: Some(body), @@ -863,7 +812,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; @@ -876,7 +824,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body: Some(body), @@ -1112,22 +1059,28 @@ impl Parser { fn type_bound(&mut self, start: Token) -> Result { let name = Constant::from(start); - let requirements = self.type_bound_requirements()?; + let requirements = self.requirements()?; let location = SourceLocation::start_end(name.location(), requirements.location()); Ok(TypeBound { name, requirements, location }) } - fn type_bound_requirements(&mut self) -> Result { + fn requirements(&mut self) -> Result { self.expect(TokenKind::Colon)?; let mut values = Vec::new(); loop { let token = self.require()?; + let req = match token.kind { + TokenKind::Mut => Requirement::Mutable(token.location), + _ => Requirement::Trait( + self.type_name_with_optional_namespace(token)?, + ), + }; - values.push(self.type_name_with_optional_namespace(token)?); + values.push(req); let after = self.peek(); @@ -1146,7 +1099,7 @@ impl Parser { values.last().unwrap().location(), ); - Ok(TypeNames { values, location }) + Ok(Requirements { values, location }) } fn reopen_class( @@ -1155,14 +1108,16 @@ impl Parser { class_token: Token, ) -> Result { let class_name = Constant::from(class_token); + let bounds = self.optional_type_bounds()?; let body = self.reopen_class_expressions()?; - let location = - SourceLocation::start_end(&start.location, body.location()); + let end_loc = location!(bounds).unwrap_or_else(|| body.location()); + let location = SourceLocation::start_end(&start.location, end_loc); Ok(TopLevelExpression::ReopenClass(Box::new(ReopenClass { class_name, body, location, + bounds, }))) } @@ -1291,7 +1246,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body = if self.peek().kind == TokenKind::CurlyOpen { let body_token = self.expect(TokenKind::CurlyOpen)?; @@ -1302,7 +1256,6 @@ impl Parser { }; let end_loc = location!(body) .or_else(|| location!(return_type)) - .or_else(|| location!(throw_type)) .or_else(|| location!(arguments)) .or_else(|| location!(type_parameters)) .unwrap_or_else(|| name.location()); @@ -1314,7 +1267,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body, @@ -1368,28 +1320,6 @@ impl Parser { }) } - fn expression_with_optional_curly_braces( - &mut self, - ) -> Result<(Expression, SourceLocation), ParseError> { - if self.peek().kind == TokenKind::CurlyOpen { - let open = self.next(); - let token = self.require()?; - let expr = self.expression(token)?; - let close = self.expect(TokenKind::CurlyClose)?; - - Ok(( - expr, - SourceLocation::start_end(&open.location, &close.location), - )) - } else { - let token = self.require()?; - let expr = self.expression(token)?; - let loc = expr.location().clone(); - - Ok((expr, loc)) - } - } - fn type_cast(&mut self, start: Token) -> Result { let mut node = self.boolean_and_or(start)?; @@ -1491,67 +1421,25 @@ impl Parser { } fn postfix(&mut self, start: Token) -> Result { - let start_line = *start.location.line_range.start(); let mut node = self.value(start)?; loop { let peeked = self.peek(); - match peeked.kind { - TokenKind::Dot => { - node = self.call_with_receiver(node)?; - } - TokenKind::BracketOpen - if *peeked.location.line_range.start() == start_line => - { - node = self.index_expression(node)?; - } - _ => break, + if let TokenKind::Dot = peeked.kind { + node = self.call_with_receiver(node)?; + } else { + break; } } Ok(node) } - fn index_expression( - &mut self, - receiver: Expression, - ) -> Result { - self.next(); - - let index_token = self.require()?; - let index = self.expression(index_token)?; - let close = self.expect(TokenKind::BracketClose)?; - - if self.peek().kind == TokenKind::Assign { - self.next(); - - let value_token = self.require()?; - let value = self.expression(value_token)?; - let location = SourceLocation::start_end( - receiver.location(), - value.location(), - ); - - Ok(Expression::SetIndex(Box::new(SetIndex { - receiver, - index, - value, - location, - }))) - } else { - let location = - SourceLocation::start_end(receiver.location(), &close.location); - - Ok(Expression::Index(Box::new(Index { receiver, index, location }))) - } - } - fn value(&mut self, start: Token) -> Result { // When updating this match, also update the one used for parsing return // value expressions. let value = match start.kind { - TokenKind::Async => self.async_expression(start)?, TokenKind::BracketOpen => self.array_literal(start)?, TokenKind::Break => self.break_loop(start), TokenKind::Constant => self.constant(start)?, @@ -1578,7 +1466,6 @@ impl Parser { TokenKind::True => self.true_literal(start), TokenKind::Nil => self.nil_literal(start), TokenKind::Try => self.try_expression(start)?, - TokenKind::TryPanic => self.try_panic_expression(start)?, TokenKind::While => self.while_expression(start)?, TokenKind::Let => self.define_variable(start)?, _ => { @@ -2268,20 +2155,13 @@ impl Parser { false }; let arguments = self.optional_closure_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; let location = SourceLocation::start_end(&start.location, &body.location); - let closure = Closure { - moving, - body, - arguments, - throw_type, - return_type, - location, - }; + let closure = + Closure { moving, body, arguments, return_type, location }; Ok(Expression::Closure(Box::new(closure))) } @@ -2376,17 +2256,6 @@ impl Parser { Expression::False(Box::new(False { location: start.location })) } - fn async_expression( - &mut self, - start: Token, - ) -> Result { - let (expression, expr_loc) = - self.expression_with_optional_curly_braces()?; - let location = SourceLocation::start_end(&start.location, &expr_loc); - - Ok(Expression::Async(Box::new(Async { expression, location }))) - } - fn group_or_tuple( &mut self, start: Token, @@ -2490,8 +2359,7 @@ impl Parser { == start.location.line_range.start(); let value = match peeked.kind { - TokenKind::Async - | TokenKind::BracketOpen + TokenKind::BracketOpen | TokenKind::Break | TokenKind::Constant | TokenKind::CurlyOpen @@ -2518,7 +2386,6 @@ impl Parser { | TokenKind::Throw | TokenKind::True | TokenKind::Try - | TokenKind::TryPanic | TokenKind::While if same_line => { @@ -2539,60 +2406,12 @@ impl Parser { &mut self, start: Token, ) -> Result { - let try_block = self.try_block()?; - let else_block = self.optional_else_block()?; - let end_loc = - location!(else_block).unwrap_or_else(|| try_block.location()); + let expr_token = self.require()?; + let expression = self.expression(expr_token)?; + let end_loc = expression.location(); let location = SourceLocation::start_end(&start.location, end_loc); - Ok(Expression::Try(Box::new(Try { try_block, else_block, location }))) - } - - fn try_panic_expression( - &mut self, - start: Token, - ) -> Result { - let try_block = self.try_block()?; - let location = - SourceLocation::start_end(&start.location, try_block.location()); - - Ok(Expression::TryPanic(Box::new(TryPanic { try_block, location }))) - } - - fn optional_try_binding( - &mut self, - ) -> Result, ParseError> { - if self.peek().kind != TokenKind::ParenOpen { - return Ok(None); - } - - self.next(); - - let start = self.require()?; - let arg = self.define_closure_argument(start)?; - - self.expect(TokenKind::ParenClose)?; - Ok(Some(arg)) - } - - fn try_block(&mut self) -> Result { - let (value, location) = self.expression_with_optional_curly_braces()?; - - Ok(TryBlock { value, location }) - } - - fn optional_else_block(&mut self) -> Result, ParseError> { - if self.peek().kind != TokenKind::Else { - return Ok(None); - } - - let start = self.next(); - let binding = self.optional_try_binding()?; - let body = self.expressions_with_optional_curly_braces()?; - let location = - SourceLocation::start_end(&start.location, body.location()); - - Ok(Some(ElseBlock { argument: binding, body, location })) + Ok(Expression::Try(Box::new(Try { expression, location }))) } fn if_expression( @@ -3760,7 +3579,6 @@ mod tests { parser.type_reference(start).unwrap(), Type::Closure(Box::new(ClosureType { arguments: None, - throw_type: None, return_type: None, location: cols(1, 2) })) @@ -3787,37 +3605,12 @@ mod tests { }))], location: cols(4, 6) }), - throw_type: None, return_type: None, location: cols(1, 6) })) ); } - #[test] - fn test_type_reference_with_closure_type_with_throw_type() { - let mut parser = parser("fn !! T"); - let start = parser.require().unwrap(); - - assert_eq!( - parser.type_reference(start).unwrap(), - Type::Closure(Box::new(ClosureType { - arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "T".to_string(), - location: cols(7, 7), - }, - arguments: None, - location: cols(7, 7) - }))), - return_type: None, - location: cols(1, 7) - })) - ); - } - #[test] fn test_type_reference_with_closure_type_with_return_type() { let mut parser = parser("fn -> T"); @@ -3827,7 +3620,6 @@ mod tests { parser.type_reference(start).unwrap(), Type::Closure(Box::new(ClosureType { arguments: None, - throw_type: None, return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, @@ -3844,7 +3636,7 @@ mod tests { #[test] fn test_type_reference_with_full_closure_type() { - let mut parser = parser("fn (B) !! C -> D"); + let mut parser = parser("fn (B) -> D"); let start = parser.require().unwrap(); assert_eq!( @@ -3862,25 +3654,16 @@ mod tests { }))], location: cols(4, 6) }), - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "C".to_string(), - location: cols(11, 11), - }, - arguments: None, - location: cols(11, 11) - }))), return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, name: "D".to_string(), - location: cols(16, 16), + location: cols(11, 11), }, arguments: None, - location: cols(16, 16) + location: cols(11, 11) }))), - location: cols(1, 16) + location: cols(1, 11) })) ); } @@ -3977,7 +3760,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -3999,7 +3781,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4021,7 +3802,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4043,7 +3823,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4065,7 +3844,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4087,7 +3865,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4109,7 +3886,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4145,7 +3921,6 @@ mod tests { location: cols(8, 10) }), arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4172,9 +3947,9 @@ mod tests { name: "T".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { + requirements: Some(Requirements { values: vec![ - TypeName { + Requirement::Trait(TypeName { name: Constant { source: None, name: "A".to_string(), @@ -4182,8 +3957,8 @@ mod tests { }, arguments: None, location: cols(12, 12) - }, - TypeName { + }), + Requirement::Trait(TypeName { name: Constant { source: None, name: "B".to_string(), @@ -4191,7 +3966,7 @@ mod tests { }, arguments: None, location: cols(16, 16) - } + }) ], location: cols(12, 16) }), @@ -4200,7 +3975,6 @@ mod tests { location: cols(8, 17) }), arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4261,7 +4035,6 @@ mod tests { ], location: cols(8, 19) }), - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4272,39 +4045,6 @@ mod tests { ); } - #[test] - fn test_method_with_throw_type() { - assert_eq!( - top(parse("fn foo !! A {}")), - TopLevelExpression::DefineMethod(Box::new(DefineMethod { - public: false, - operator: false, - kind: MethodKind::Instance, - name: Identifier { - name: "foo".to_string(), - location: cols(4, 6) - }, - type_parameters: None, - arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "A".to_string(), - location: cols(11, 11), - }, - arguments: None, - location: cols(11, 11) - }))), - return_type: None, - body: Some(Expressions { - values: Vec::new(), - location: cols(13, 14) - }), - location: cols(1, 14) - })) - ); - } - #[test] fn test_method_with_return_type() { assert_eq!( @@ -4319,7 +4059,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, @@ -4352,7 +4091,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -4372,7 +4110,6 @@ mod tests { assert_error!("fn foo [A: ] {}", cols(12, 12)); assert_error!("fn foo (A: ) {}", cols(9, 9)); assert_error!("fn foo (a: ) {}", cols(12, 12)); - assert_error!("fn foo !! {}", cols(11, 11)); assert_error!("fn foo -> {}", cols(11, 11)); assert_error!("fn foo {", cols(8, 8)); assert_error!("fn foo", cols(6, 6)); @@ -4501,7 +4238,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4539,7 +4275,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4575,8 +4310,8 @@ mod tests { name: "B".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { - values: vec![TypeName { + requirements: Some(Requirements { + values: vec![Requirement::Trait(TypeName { name: Constant { source: None, name: "X".to_string(), @@ -4584,7 +4319,7 @@ mod tests { }, arguments: None, location: cols(12, 12) - }], + })], location: cols(12, 12) }), location: cols(9, 12) @@ -4626,8 +4361,8 @@ mod tests { name: "B".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { - values: vec![TypeName { + requirements: Some(Requirements { + values: vec![Requirement::Trait(TypeName { name: Constant { source: Some(Identifier { name: "a".to_string(), @@ -4638,7 +4373,7 @@ mod tests { }, arguments: None, location: cols(12, 15) - }], + })], location: cols(12, 15) }), location: cols(9, 15) @@ -4679,7 +4414,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4717,7 +4451,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4758,7 +4491,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4799,7 +4531,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4840,7 +4571,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5006,7 +4736,7 @@ mod tests { ); assert_eq!( - top(parse("impl A for B if X: A + B, Y: C {}")), + top(parse("impl A for B if X: A + B, Y: C + mut {}")), TopLevelExpression::ImplementTrait(Box::new(ImplementTrait { trait_name: TypeName { name: Constant { @@ -5024,7 +4754,7 @@ mod tests { }, body: ImplementationExpressions { values: Vec::new(), - location: cols(32, 33) + location: cols(38, 39) }, bounds: Some(TypeBounds { values: vec![ @@ -5034,9 +4764,9 @@ mod tests { name: "X".to_string(), location: cols(17, 17) }, - requirements: TypeNames { + requirements: Requirements { values: vec![ - TypeName { + Requirement::Trait(TypeName { name: Constant { source: None, name: "A".to_string(), @@ -5044,8 +4774,8 @@ mod tests { }, arguments: None, location: cols(20, 20) - }, - TypeName { + }), + Requirement::Trait(TypeName { name: Constant { source: None, name: "B".to_string(), @@ -5053,7 +4783,7 @@ mod tests { }, arguments: None, location: cols(24, 24) - }, + }), ], location: cols(20, 24) }, @@ -5065,24 +4795,27 @@ mod tests { name: "Y".to_string(), location: cols(27, 27) }, - requirements: TypeNames { - values: vec![TypeName { - name: Constant { - source: None, - name: "C".to_string(), + requirements: Requirements { + values: vec![ + Requirement::Trait(TypeName { + name: Constant { + source: None, + name: "C".to_string(), + location: cols(30, 30) + }, + arguments: None, location: cols(30, 30) - }, - arguments: None, - location: cols(30, 30) - }], - location: cols(30, 30) + }), + Requirement::Mutable(cols(34, 36)) + ], + location: cols(30, 36) }, - location: cols(27, 30) + location: cols(27, 36) } ], - location: cols(17, 30) + location: cols(17, 36) }), - location: cols(1, 33) + location: cols(1, 39) })) ); @@ -5114,7 +4847,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5144,6 +4876,7 @@ mod tests { values: Vec::new(), location: cols(8, 9) }, + bounds: None, location: cols(1, 9) })) ); @@ -5167,7 +4900,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5177,6 +4909,7 @@ mod tests { }], location: cols(8, 20) }, + bounds: None, location: cols(1, 20) })) ); @@ -5200,7 +4933,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5210,9 +4942,41 @@ mod tests { }], location: cols(8, 26) }, + bounds: None, location: cols(1, 26) })) ); + + assert_eq!( + top(parse("impl A if T: mut {}")), + TopLevelExpression::ReopenClass(Box::new(ReopenClass { + class_name: Constant { + source: None, + name: "A".to_string(), + location: cols(6, 6) + }, + body: ImplementationExpressions { + values: Vec::new(), + location: cols(18, 19) + }, + bounds: Some(TypeBounds { + values: vec![TypeBound { + name: Constant { + source: None, + name: "T".to_string(), + location: cols(11, 11) + }, + requirements: Requirements { + values: vec![Requirement::Mutable(cols(14, 16))], + location: cols(14, 16) + }, + location: cols(11, 16) + }], + location: cols(11, 16) + }), + location: cols(1, 16) + })) + ); } #[test] @@ -5236,7 +5000,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5246,6 +5009,7 @@ mod tests { }], location: cols(8, 27) }, + bounds: None, location: cols(1, 27) })) ); @@ -5395,8 +5159,8 @@ mod tests { name: "B".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { - values: vec![TypeName { + requirements: Some(Requirements { + values: vec![Requirement::Trait(TypeName { name: Constant { source: None, name: "X".to_string(), @@ -5404,7 +5168,7 @@ mod tests { }, arguments: None, location: cols(12, 12) - }], + })], location: cols(12, 12) }), location: cols(9, 12) @@ -5455,7 +5219,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: None, location: cols(11, 16) @@ -5491,7 +5254,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: None, location: cols(11, 16) @@ -5504,9 +5266,9 @@ mod tests { } #[test] - fn test_trait_with_required_method_with_throw_type() { + fn test_trait_with_required_method_with_return_type() { assert_eq!( - top(parse("trait A { fn foo !! A }")), + top(parse("trait A { fn foo -> A }")), TopLevelExpression::DefineTrait(Box::new(DefineTrait { public: false, name: Constant { @@ -5527,7 +5289,7 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { + return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, name: "A".to_string(), @@ -5536,7 +5298,6 @@ mod tests { arguments: None, location: cols(21, 21) }))), - return_type: None, body: None, location: cols(11, 21) }], @@ -5548,53 +5309,9 @@ mod tests { } #[test] - fn test_trait_with_required_method_with_return_type() { + fn test_trait_with_required_method_with_arguments() { assert_eq!( - top(parse("trait A { fn foo -> A }")), - TopLevelExpression::DefineTrait(Box::new(DefineTrait { - public: false, - name: Constant { - source: None, - name: "A".to_string(), - location: cols(7, 7) - }, - type_parameters: None, - requirements: None, - body: TraitExpressions { - values: vec![DefineMethod { - public: false, - operator: false, - kind: MethodKind::Instance, - name: Identifier { - name: "foo".to_string(), - location: cols(14, 16) - }, - type_parameters: None, - arguments: None, - throw_type: None, - return_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "A".to_string(), - location: cols(21, 21) - }, - arguments: None, - location: cols(21, 21) - }))), - body: None, - location: cols(11, 21) - }], - location: cols(9, 23) - }, - location: cols(1, 23) - })) - ); - } - - #[test] - fn test_trait_with_required_method_with_arguments() { - assert_eq!( - top(parse("trait A { fn foo (a: A) }")), + top(parse("trait A { fn foo (a: A) }")), TopLevelExpression::DefineTrait(Box::new(DefineTrait { public: false, name: Constant { @@ -5633,7 +5350,6 @@ mod tests { }], location: cols(18, 23) }), - throw_type: None, return_type: None, body: None, location: cols(11, 23) @@ -5680,7 +5396,6 @@ mod tests { location: cols(18, 20) }), arguments: None, - throw_type: None, return_type: None, body: None, location: cols(11, 20) @@ -5716,7 +5431,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5755,7 +5469,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -7151,47 +6864,6 @@ mod tests { ); } - #[test] - fn test_async_expression() { - assert_eq!( - expr("async 10.foo"), - Expression::Async(Box::new(Async { - expression: Expression::Call(Box::new(Call { - receiver: Some(Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(7, 8) - }))), - name: Identifier { - name: "foo".to_string(), - location: cols(10, 12) - }, - arguments: None, - location: cols(7, 12) - })), - location: cols(1, 12) - })) - ); - - assert_eq!( - expr("async { 10.foo }"), - Expression::Async(Box::new(Async { - expression: Expression::Call(Box::new(Call { - receiver: Some(Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(9, 10) - }))), - name: Identifier { - name: "foo".to_string(), - location: cols(12, 14) - }, - arguments: None, - location: cols(9, 14) - })), - location: cols(1, 16) - })) - ); - } - #[test] fn test_call_with_trailing_blocks_without_parentheses() { assert_eq!( @@ -7207,7 +6879,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7241,7 +6912,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7275,7 +6945,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7306,7 +6975,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7343,7 +7011,6 @@ mod tests { Expression::Closure(Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: Vec::new(), @@ -7540,7 +7207,6 @@ mod tests { Expression::Closure(Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7558,7 +7224,6 @@ mod tests { Expression::Closure(Box::new(Closure { moving: true, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7586,7 +7251,6 @@ mod tests { }], location: cols(4, 6) }), - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7622,7 +7286,6 @@ mod tests { }], location: cols(4, 9) }), - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7635,38 +7298,11 @@ mod tests { })) ); - assert_eq!( - expr("fn !! T { 10 }"), - Expression::Closure(Box::new(Closure { - moving: false, - arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "T".to_string(), - location: cols(7, 7) - }, - arguments: None, - location: cols(7, 7) - }))), - return_type: None, - body: Expressions { - values: vec![Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(11, 12) - }))], - location: cols(9, 14) - }, - location: cols(1, 14) - })) - ); - assert_eq!( expr("fn -> T { 10 }"), Expression::Closure(Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, @@ -7691,7 +7327,6 @@ mod tests { #[test] fn test_invalid_closures() { assert_error_expr!("fn {", cols(4, 4)); - assert_error_expr!("fn !!", cols(5, 5)); assert_error_expr!("fn ->", cols(5, 5)); assert_error_expr!("fn =>", cols(4, 5)); } @@ -8084,70 +7719,6 @@ mod tests { ); } - #[test] - fn test_index_expression() { - assert_eq!( - expr("10[1]"), - Expression::Index(Box::new(Index { - receiver: Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(1, 2) - })), - index: Expression::Int(Box::new(IntLiteral { - value: "1".to_string(), - location: cols(4, 4) - })), - location: cols(1, 5) - })) - ); - - assert_eq!( - expr("10[1] = 2"), - Expression::SetIndex(Box::new(SetIndex { - receiver: Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(1, 2) - })), - index: Expression::Int(Box::new(IntLiteral { - value: "1".to_string(), - location: cols(4, 4) - })), - value: Expression::Int(Box::new(IntLiteral { - value: "2".to_string(), - location: cols(9, 9) - })), - location: cols(1, 9) - })) - ); - } - - #[test] - fn test_index_on_separate_line() { - let mut parser = parser("10\n[1]"); - - parser.next(); - - let start = parser.require().unwrap(); - let expr = parser.expression(start).unwrap(); - - assert_eq!( - expr, - Expression::Array(Box::new(Array { - values: vec![Expression::Int(Box::new(IntLiteral { - value: "1".to_string(), - location: location(2..=2, 2..=2) - }))], - location: location(2..=2, 1..=3) - })) - ); - } - - #[test] - fn test_invalid_indexing() { - assert_error_expr!("10[1", cols(4, 4)); - assert_error_expr!("10[1] =", cols(7, 7)); - } - #[test] fn test_throw_expression() { assert_eq!( @@ -8200,7 +7771,6 @@ mod tests { moving: false, arguments: None, return_type: None, - throw_type: None, body: Expressions { values: Vec::new(), location: cols(11, 12) @@ -8391,219 +7961,13 @@ mod tests { assert_eq!( expr("try a"), Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), + expression: Expression::Identifier(Box::new(Identifier { + name: "a".to_string(), location: cols(5, 5) - }, - else_block: None, + })), location: cols(1, 5) })) ); - - assert_eq!( - expr("try { a }"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(7, 7) - })), - location: cols(5, 9) - }, - else_block: None, - location: cols(1, 9) - })) - ); - - assert_eq!( - expr("try a else b"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - argument: None, - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(12, 12) - } - ))], - location: cols(12, 12) - }, - location: cols(7, 12) - }), - location: cols(1, 12) - })) - ); - - assert_eq!( - expr("try a else { b }"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(14, 14) - } - ))], - location: cols(12, 16) - }, - argument: None, - location: cols(7, 16) - }), - location: cols(1, 16) - })) - ); - - assert_eq!( - expr("try a else (arg) b"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(18, 18) - } - ))], - location: cols(18, 18) - }, - argument: Some(BlockArgument { - name: Identifier { - name: "arg".to_string(), - location: cols(13, 15) - }, - value_type: None, - location: cols(13, 15) - }), - location: cols(7, 18) - }), - location: cols(1, 18) - })) - ); - - assert_eq!( - expr("try a else (arg: T) b"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(21, 21) - } - ))], - location: cols(21, 21) - }, - argument: Some(BlockArgument { - name: Identifier { - name: "arg".to_string(), - location: cols(13, 15) - }, - value_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "T".to_string(), - location: cols(18, 18) - }, - arguments: None, - location: cols(18, 18) - }))), - location: cols(13, 18) - }), - location: cols(7, 21) - }), - location: cols(1, 21) - })) - ); - - assert_eq!( - expr("try { a } else (arg) { b }"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(7, 7) - })), - location: cols(5, 9) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(24, 24) - } - ))], - location: cols(22, 26) - }, - argument: Some(BlockArgument { - name: Identifier { - name: "arg".to_string(), - location: cols(17, 19) - }, - value_type: None, - location: cols(17, 19) - }), - location: cols(11, 26) - }), - location: cols(1, 26) - })) - ); - } - - #[test] - fn test_try_panic_expression() { - assert_eq!( - expr("try! a"), - Expression::TryPanic(Box::new(TryPanic { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(6, 6) - })), - location: cols(6, 6) - }, - location: cols(1, 6) - })) - ); - } - - #[test] - fn test_invalid_try_expressions() { - assert_error_expr!("try a else", cols(10, 10)); - assert_error_expr!("try a else (arg)", cols(16, 16)); } #[test] diff --git a/ast/src/source_location.rs b/ast/src/source_location.rs index 38f07df23..16c4e3409 100644 --- a/ast/src/source_location.rs +++ b/ast/src/source_location.rs @@ -27,6 +27,10 @@ impl SourceLocation { ..=(*end.column_range.end()), } } + + pub fn line_column(&self) -> (usize, usize) { + (*self.line_range.start(), *self.column_range.start()) + } } impl fmt::Debug for SourceLocation { diff --git a/bytecode/Cargo.toml b/bytecode/Cargo.toml deleted file mode 100644 index 80c725a58..000000000 --- a/bytecode/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "bytecode" -version = "0.10.0" # VERSION -authors = ["Yorick Peterse "] -edition = "2021" -license = "MPL-2.0" - -[lib] -doctest = false diff --git a/bytecode/src/lib.rs b/bytecode/src/lib.rs deleted file mode 100644 index ee07ff27b..000000000 --- a/bytecode/src/lib.rs +++ /dev/null @@ -1,1312 +0,0 @@ -//! Bytecode types shared between the compiler and VM. -use std::fmt; - -/// A value is an owned reference. -pub const REF_OWNED: u16 = 0; - -/// A value is a regular reference/borrow. -pub const REF_REF: u16 = 1; - -/// A value is an atomic value. -pub const REF_ATOMIC: u16 = 2; - -/// A value is an immediate or permanent value, i.e. a value that doesn't need -/// to be dropped. -pub const REF_PERMANENT: u16 = 3; - -/// The bytes that every bytecode file must start with. -pub const SIGNATURE_BYTES: [u8; 4] = [105, 110, 107, 111]; // "inko" - -/// The current version of the bytecode format. -pub const VERSION: u8 = 1; - -/// The tag that marks the start of an integer constant. -pub const CONST_INTEGER: u8 = 0; - -/// The tag that marks the start of a float constant. -pub const CONST_FLOAT: u8 = 1; - -/// The tag that marks the start of a string constant. -pub const CONST_STRING: u8 = 2; - -/// The tag that marks the start of an array constant. -pub const CONST_ARRAY: u8 = 3; - -/// Enum containing all possible instruction types. -/// -/// When adding new opcodes, you must also add them to the `Opcode::from_byte` -/// method. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[repr(u8)] -pub enum Opcode { - Allocate, - ArrayAllocate, - ArrayClear, - ArrayDrop, - ArrayGet, - ArrayLength, - ArrayPop, - ArrayPush, - ArrayRemove, - ArraySet, - Branch, - BranchResult, - BuiltinFunctionCall, - ByteArrayAllocate, - ByteArrayClear, - ByteArrayClone, - ByteArrayDrop, - ByteArrayEquals, - ByteArrayGet, - ByteArrayLength, - ByteArrayPop, - ByteArrayPush, - ByteArrayRemove, - ByteArraySet, - CallDynamic, - CallStatic, - CallVirtual, - CheckRefs, - Decrement, - DecrementAtomic, - Exit, - FloatAdd, - FloatCeil, - FloatClone, - FloatDiv, - FloatEq, - FloatFloor, - FloatGe, - FloatGt, - FloatIsInf, - FloatIsNan, - FloatLe, - FloatLt, - FloatMod, - FloatMul, - FloatRound, - FloatSub, - FloatToInt, - FloatToString, - Free, - FutureDrop, - FutureGet, - FutureGetFor, - FuturePoll, - GetConstant, - GetFalse, - GetField, - GetNil, - GetTrue, - GetUndefined, - Goto, - Increment, - IntAdd, - IntBitAnd, - IntBitNot, - IntBitOr, - IntBitXor, - IntClone, - IntDiv, - IntEq, - IntGe, - IntGt, - IntLe, - IntLt, - IntMod, - IntMul, - IntPow, - IntRotateLeft, - IntRotateRight, - IntShl, - IntShr, - IntSub, - IntToFloat, - IntToString, - IntUnsignedShr, - IntWrappingAdd, - IntWrappingMul, - IntWrappingSub, - IsUndefined, - JumpTable, - MoveRegister, - MoveResult, - ObjectEq, - Panic, - Pop, - ProcessAllocate, - ProcessFinishTask, - ProcessGetField, - ProcessSend, - ProcessSendAsync, - ProcessSetField, - ProcessSuspend, - ProcessWriteResult, - Push, - Reduce, - RefKind, - Return, - SetField, - StringByte, - StringConcat, - StringDrop, - StringEq, - StringSize, - Throw, -} - -impl Opcode { - pub fn from_byte(byte: u8) -> Result { - let opcode = match byte { - 0 => Opcode::Allocate, - 1 => Opcode::ArrayAllocate, - 2 => Opcode::ArrayClear, - 3 => Opcode::ArrayDrop, - 4 => Opcode::ArrayGet, - 5 => Opcode::ArrayLength, - 6 => Opcode::ArrayPop, - 7 => Opcode::ArrayPush, - 8 => Opcode::ArrayRemove, - 9 => Opcode::ArraySet, - 10 => Opcode::BuiltinFunctionCall, - 11 => Opcode::ByteArrayAllocate, - 12 => Opcode::ByteArrayClear, - 13 => Opcode::ByteArrayClone, - 14 => Opcode::ByteArrayDrop, - 15 => Opcode::ByteArrayEquals, - 16 => Opcode::ByteArrayGet, - 17 => Opcode::ByteArrayLength, - 18 => Opcode::ByteArrayPop, - 19 => Opcode::ByteArrayPush, - 20 => Opcode::ByteArrayRemove, - 21 => Opcode::ByteArraySet, - 22 => Opcode::CallDynamic, - 23 => Opcode::CallVirtual, - 24 => Opcode::CheckRefs, - 25 => Opcode::MoveRegister, - 26 => Opcode::Decrement, - 27 => Opcode::Exit, - 28 => Opcode::FloatAdd, - 29 => Opcode::FloatCeil, - 30 => Opcode::FloatClone, - 31 => Opcode::FloatDiv, - 32 => Opcode::FloatEq, - 33 => Opcode::FloatFloor, - 34 => Opcode::FloatGe, - 35 => Opcode::FloatGt, - 36 => Opcode::FloatIsInf, - 37 => Opcode::FloatIsNan, - 38 => Opcode::FloatLe, - 39 => Opcode::FloatLt, - 40 => Opcode::FloatMod, - 41 => Opcode::FloatMul, - 42 => Opcode::FloatRound, - 43 => Opcode::FloatSub, - 44 => Opcode::FloatToInt, - 45 => Opcode::FloatToString, - 46 => Opcode::FutureDrop, - 47 => Opcode::FutureGet, - 48 => Opcode::FutureGetFor, - 49 => Opcode::GetConstant, - 50 => Opcode::GetFalse, - 51 => Opcode::GetField, - 52 => Opcode::CallStatic, - 53 => Opcode::GetNil, - 54 => Opcode::GetTrue, - 55 => Opcode::GetUndefined, - 56 => Opcode::Goto, - 57 => Opcode::Branch, - 58 => Opcode::BranchResult, - 59 => Opcode::Increment, - 60 => Opcode::IntAdd, - 61 => Opcode::IntBitAnd, - 62 => Opcode::IntBitOr, - 63 => Opcode::IntBitXor, - 64 => Opcode::IntClone, - 65 => Opcode::IntDiv, - 66 => Opcode::IntEq, - 67 => Opcode::IntGe, - 68 => Opcode::IntGt, - 69 => Opcode::IntLe, - 70 => Opcode::IntLt, - 71 => Opcode::IntMod, - 72 => Opcode::IntMul, - 73 => Opcode::IntPow, - 74 => Opcode::IntShl, - 75 => Opcode::IntShr, - 76 => Opcode::IntSub, - 77 => Opcode::IntToFloat, - 78 => Opcode::IntToString, - 79 => Opcode::IsUndefined, - 80 => Opcode::RefKind, - 81 => Opcode::MoveResult, - 82 => Opcode::ObjectEq, - 83 => Opcode::Panic, - 84 => Opcode::ProcessAllocate, - 85 => Opcode::ProcessGetField, - 86 => Opcode::ProcessSendAsync, - 87 => Opcode::ProcessSend, - 88 => Opcode::ProcessSetField, - 89 => Opcode::ProcessSuspend, - 90 => Opcode::ProcessWriteResult, - 91 => Opcode::Free, - 92 => Opcode::Reduce, - 93 => Opcode::Return, - 94 => Opcode::SetField, - 95 => Opcode::StringByte, - 96 => Opcode::StringConcat, - 97 => Opcode::StringDrop, - 98 => Opcode::StringEq, - 99 => Opcode::StringSize, - 100 => Opcode::Throw, - 101 => Opcode::DecrementAtomic, - 102 => Opcode::ProcessFinishTask, - 103 => Opcode::JumpTable, - 104 => Opcode::Push, - 105 => Opcode::Pop, - 106 => Opcode::FuturePoll, - 107 => Opcode::IntBitNot, - 108 => Opcode::IntRotateLeft, - 109 => Opcode::IntRotateRight, - 110 => Opcode::IntWrappingAdd, - 111 => Opcode::IntWrappingSub, - 112 => Opcode::IntWrappingMul, - 113 => Opcode::IntUnsignedShr, - _ => return Err(format!("The opcode {} is invalid", byte)), - }; - - Ok(opcode) - } - - pub fn to_int(self) -> u8 { - // This must be kept in sync with `Opcode::from_byte()`. - match self { - Opcode::Allocate => 0, - Opcode::ArrayAllocate => 1, - Opcode::ArrayClear => 2, - Opcode::ArrayDrop => 3, - Opcode::ArrayGet => 4, - Opcode::ArrayLength => 5, - Opcode::ArrayPop => 6, - Opcode::ArrayPush => 7, - Opcode::ArrayRemove => 8, - Opcode::ArraySet => 9, - Opcode::BuiltinFunctionCall => 10, - Opcode::ByteArrayAllocate => 11, - Opcode::ByteArrayClear => 12, - Opcode::ByteArrayClone => 13, - Opcode::ByteArrayDrop => 14, - Opcode::ByteArrayEquals => 15, - Opcode::ByteArrayGet => 16, - Opcode::ByteArrayLength => 17, - Opcode::ByteArrayPop => 18, - Opcode::ByteArrayPush => 19, - Opcode::ByteArrayRemove => 20, - Opcode::ByteArraySet => 21, - Opcode::CallDynamic => 22, - Opcode::CallVirtual => 23, - Opcode::CheckRefs => 24, - Opcode::MoveRegister => 25, - Opcode::Decrement => 26, - Opcode::Exit => 27, - Opcode::FloatAdd => 28, - Opcode::FloatCeil => 29, - Opcode::FloatClone => 30, - Opcode::FloatDiv => 31, - Opcode::FloatEq => 32, - Opcode::FloatFloor => 33, - Opcode::FloatGe => 34, - Opcode::FloatGt => 35, - Opcode::FloatIsInf => 36, - Opcode::FloatIsNan => 37, - Opcode::FloatLe => 38, - Opcode::FloatLt => 39, - Opcode::FloatMod => 40, - Opcode::FloatMul => 41, - Opcode::FloatRound => 42, - Opcode::FloatSub => 43, - Opcode::FloatToInt => 44, - Opcode::FloatToString => 45, - Opcode::FutureDrop => 46, - Opcode::FutureGet => 47, - Opcode::FutureGetFor => 48, - Opcode::GetConstant => 49, - Opcode::GetFalse => 50, - Opcode::GetField => 51, - Opcode::CallStatic => 52, - Opcode::GetNil => 53, - Opcode::GetTrue => 54, - Opcode::GetUndefined => 55, - Opcode::Goto => 56, - Opcode::Branch => 57, - Opcode::BranchResult => 58, - Opcode::Increment => 59, - Opcode::IntAdd => 60, - Opcode::IntBitAnd => 61, - Opcode::IntBitOr => 62, - Opcode::IntBitXor => 63, - Opcode::IntClone => 64, - Opcode::IntDiv => 65, - Opcode::IntEq => 66, - Opcode::IntGe => 67, - Opcode::IntGt => 68, - Opcode::IntLe => 69, - Opcode::IntLt => 70, - Opcode::IntMod => 71, - Opcode::IntMul => 72, - Opcode::IntPow => 73, - Opcode::IntShl => 74, - Opcode::IntShr => 75, - Opcode::IntSub => 76, - Opcode::IntToFloat => 77, - Opcode::IntToString => 78, - Opcode::IsUndefined => 79, - Opcode::RefKind => 80, - Opcode::MoveResult => 81, - Opcode::ObjectEq => 82, - Opcode::Panic => 83, - Opcode::ProcessAllocate => 84, - Opcode::ProcessGetField => 85, - Opcode::ProcessSendAsync => 86, - Opcode::ProcessSend => 87, - Opcode::ProcessSetField => 88, - Opcode::ProcessSuspend => 89, - Opcode::ProcessWriteResult => 90, - Opcode::Free => 91, - Opcode::Reduce => 92, - Opcode::Return => 93, - Opcode::SetField => 94, - Opcode::StringByte => 95, - Opcode::StringConcat => 96, - Opcode::StringDrop => 97, - Opcode::StringEq => 98, - Opcode::StringSize => 99, - Opcode::Throw => 100, - Opcode::DecrementAtomic => 101, - Opcode::ProcessFinishTask => 102, - Opcode::JumpTable => 103, - Opcode::Push => 104, - Opcode::Pop => 105, - Opcode::FuturePoll => 106, - Opcode::IntBitNot => 107, - Opcode::IntRotateLeft => 108, - Opcode::IntRotateRight => 109, - Opcode::IntWrappingAdd => 110, - Opcode::IntWrappingSub => 111, - Opcode::IntWrappingMul => 112, - Opcode::IntUnsignedShr => 113, - } - } - - pub fn name(self) -> &'static str { - match self { - Opcode::Allocate => "allocate", - Opcode::ArrayAllocate => "array_allocate", - Opcode::ArrayClear => "array_clear", - Opcode::ArrayDrop => "array_drop", - Opcode::ArrayGet => "array_get", - Opcode::ArrayLength => "array_length", - Opcode::ArrayPop => "array_pop", - Opcode::ArrayPush => "array_push", - Opcode::ArrayRemove => "array_remove", - Opcode::ArraySet => "array_set", - Opcode::BuiltinFunctionCall => "builtin_function_call", - Opcode::ByteArrayAllocate => "byte_array_allocate", - Opcode::ByteArrayClear => "byte_array_clear", - Opcode::ByteArrayClone => "byte_array_clone", - Opcode::ByteArrayDrop => "byte_array_drop", - Opcode::ByteArrayEquals => "byte_array_equals", - Opcode::ByteArrayGet => "byte_array_get", - Opcode::ByteArrayLength => "byte_array_length", - Opcode::ByteArrayPop => "byte_array_pop", - Opcode::ByteArrayPush => "byte_array_push", - Opcode::ByteArrayRemove => "byte_array_remove", - Opcode::ByteArraySet => "byte_array_set", - Opcode::CallDynamic => "call_dynamic", - Opcode::CallVirtual => "call_virtual", - Opcode::CheckRefs => "check_refs", - Opcode::MoveRegister => "move_register", - Opcode::Decrement => "decrement", - Opcode::Exit => "exit", - Opcode::FloatAdd => "float_add", - Opcode::FloatCeil => "float_ceil", - Opcode::FloatClone => "float_clone", - Opcode::FloatDiv => "float_div", - Opcode::FloatEq => "float_eq", - Opcode::FloatFloor => "float_floor", - Opcode::FloatGe => "float_ge", - Opcode::FloatGt => "float_gt", - Opcode::FloatIsInf => "float_is_inf", - Opcode::FloatIsNan => "float_is_nan", - Opcode::FloatLe => "float_le", - Opcode::FloatLt => "float_lt", - Opcode::FloatMod => "float_mod", - Opcode::FloatMul => "float_mul", - Opcode::FloatRound => "float_round", - Opcode::FloatSub => "float_sub", - Opcode::FloatToInt => "float_to_int", - Opcode::FloatToString => "float_to_string", - Opcode::FutureDrop => "future_drop", - Opcode::FutureGet => "future_get", - Opcode::FutureGetFor => "future_get_for", - Opcode::GetConstant => "get_constant", - Opcode::GetFalse => "get_false", - Opcode::GetField => "get_field", - Opcode::CallStatic => "call_static", - Opcode::GetNil => "get_nil", - Opcode::GetTrue => "get_true", - Opcode::GetUndefined => "get_undefined", - Opcode::Goto => "goto", - Opcode::Branch => "branch", - Opcode::BranchResult => "branch_result", - Opcode::Increment => "increment", - Opcode::IntAdd => "int_add", - Opcode::IntBitAnd => "int_bit_and", - Opcode::IntBitOr => "int_bit_or", - Opcode::IntBitXor => "int_bit_xor", - Opcode::IntClone => "int_clone", - Opcode::IntDiv => "int_div", - Opcode::IntEq => "int_eq", - Opcode::IntGe => "int_ge", - Opcode::IntGt => "int_gt", - Opcode::IntLe => "int_le", - Opcode::IntLt => "int_lt", - Opcode::IntMod => "int_mod", - Opcode::IntMul => "int_mul", - Opcode::IntPow => "int_pow", - Opcode::IntShl => "int_shl", - Opcode::IntShr => "int_shr", - Opcode::IntSub => "int_sub", - Opcode::IntToFloat => "int_to_float", - Opcode::IntToString => "int_to_string", - Opcode::RefKind => "is_ref", - Opcode::MoveResult => "move_result", - Opcode::ObjectEq => "object_eq", - Opcode::Panic => "panic", - Opcode::ProcessAllocate => "process_allocate", - Opcode::ProcessGetField => "process_get_field", - Opcode::ProcessSendAsync => "process_send_async", - Opcode::ProcessSend => "process_send", - Opcode::ProcessSetField => "process_set_field", - Opcode::ProcessSuspend => "process_suspend", - Opcode::ProcessWriteResult => "process_write_result", - Opcode::Free => "free", - Opcode::Reduce => "reduce", - Opcode::Return => "return", - Opcode::SetField => "set_field", - Opcode::StringByte => "string_byte", - Opcode::StringConcat => "string_concat", - Opcode::StringDrop => "string_drop", - Opcode::StringEq => "string_eq", - Opcode::StringSize => "string_size", - Opcode::Throw => "throw", - Opcode::IsUndefined => "is_undefined", - Opcode::DecrementAtomic => "decrement_atomic", - Opcode::ProcessFinishTask => "process_finish_task", - Opcode::JumpTable => "jump_table", - Opcode::Push => "push", - Opcode::Pop => "pop", - Opcode::FuturePoll => "future_poll", - Opcode::IntBitNot => "int_bit_not", - Opcode::IntRotateLeft => "int_rotate_left", - Opcode::IntRotateRight => "int_rotate_right", - Opcode::IntWrappingAdd => "int_wrapping_add", - Opcode::IntWrappingSub => "int_wrapping_sub", - Opcode::IntWrappingMul => "int_wrapping_mul", - Opcode::IntUnsignedShr => "int_unsigned_shr", - } - } - - pub fn writes(self) -> bool { - // This list doesn't have to be exhaustive, as long as we cover the - // instructions directly exposed to the standard library. - !matches!( - self, - Opcode::ArrayClear - | Opcode::ArrayDrop - | Opcode::ArrayPush - | Opcode::ByteArrayClear - | Opcode::ByteArrayDrop - | Opcode::ByteArrayPush - | Opcode::Exit - | Opcode::Panic - | Opcode::ProcessSuspend - | Opcode::StringDrop - ) - } - - pub fn arity(self) -> usize { - match self { - Opcode::Allocate => 3, - Opcode::ArrayAllocate => 1, - Opcode::ArrayClear => 1, - Opcode::ArrayDrop => 1, - Opcode::ArrayGet => 3, - Opcode::ArrayLength => 2, - Opcode::ArrayPop => 2, - Opcode::ArrayPush => 2, - Opcode::ArrayRemove => 3, - Opcode::ArraySet => 4, - Opcode::Branch => 3, - Opcode::BranchResult => 2, - Opcode::BuiltinFunctionCall => 4, - Opcode::ByteArrayAllocate => 1, - Opcode::ByteArrayClear => 1, - Opcode::ByteArrayClone => 2, - Opcode::ByteArrayDrop => 1, - Opcode::ByteArrayEquals => 3, - Opcode::ByteArrayGet => 3, - Opcode::ByteArrayLength => 2, - Opcode::ByteArrayPop => 2, - Opcode::ByteArrayPush => 2, - Opcode::ByteArrayRemove => 3, - Opcode::ByteArraySet => 4, - Opcode::CallDynamic => 3, - Opcode::CallVirtual => 2, - Opcode::CheckRefs => 1, - Opcode::Decrement => 1, - Opcode::DecrementAtomic => 2, - Opcode::Exit => 1, - Opcode::FloatAdd => 3, - Opcode::FloatCeil => 2, - Opcode::FloatClone => 2, - Opcode::FloatDiv => 3, - Opcode::FloatEq => 3, - Opcode::FloatFloor => 2, - Opcode::FloatGe => 3, - Opcode::FloatGt => 3, - Opcode::FloatIsInf => 2, - Opcode::FloatIsNan => 2, - Opcode::FloatLe => 3, - Opcode::FloatLt => 3, - Opcode::FloatMod => 3, - Opcode::FloatMul => 3, - Opcode::FloatRound => 3, - Opcode::FloatSub => 3, - Opcode::FloatToInt => 2, - Opcode::FloatToString => 2, - Opcode::Free => 1, - Opcode::FutureDrop => 2, - Opcode::FutureGet => 1, - Opcode::FutureGetFor => 2, - Opcode::CallStatic => 3, - Opcode::GetConstant => 3, - Opcode::GetFalse => 1, - Opcode::GetField => 3, - Opcode::GetNil => 1, - Opcode::GetTrue => 1, - Opcode::GetUndefined => 1, - Opcode::Goto => 1, - Opcode::Increment => 2, - Opcode::IntAdd => 3, - Opcode::IntBitAnd => 3, - Opcode::IntBitOr => 3, - Opcode::IntBitXor => 3, - Opcode::IntClone => 2, - Opcode::IntDiv => 3, - Opcode::IntEq => 3, - Opcode::IntGe => 3, - Opcode::IntGt => 3, - Opcode::IntLe => 3, - Opcode::IntLt => 3, - Opcode::IntMod => 3, - Opcode::IntMul => 3, - Opcode::IntPow => 3, - Opcode::IntShl => 3, - Opcode::IntShr => 3, - Opcode::IntSub => 3, - Opcode::IntToFloat => 2, - Opcode::IntToString => 2, - Opcode::IsUndefined => 2, - Opcode::MoveRegister => 2, - Opcode::MoveResult => 1, - Opcode::ObjectEq => 3, - Opcode::Panic => 1, - Opcode::ProcessAllocate => 3, - Opcode::ProcessFinishTask => 1, - Opcode::ProcessGetField => 3, - Opcode::ProcessSend => 3, - Opcode::ProcessSendAsync => 3, - Opcode::ProcessSetField => 3, - Opcode::ProcessSuspend => 1, - Opcode::ProcessWriteResult => 3, - Opcode::Reduce => 1, - Opcode::RefKind => 2, - Opcode::Return => 1, - Opcode::SetField => 3, - Opcode::StringByte => 3, - Opcode::StringConcat => 1, - Opcode::StringDrop => 1, - Opcode::StringEq => 3, - Opcode::StringSize => 2, - Opcode::JumpTable => 2, - Opcode::Throw => 2, - Opcode::Push => 1, - Opcode::Pop => 1, - Opcode::FuturePoll => 2, - Opcode::IntBitNot => 2, - Opcode::IntRotateLeft => 3, - Opcode::IntRotateRight => 3, - Opcode::IntWrappingAdd => 3, - Opcode::IntWrappingSub => 3, - Opcode::IntWrappingMul => 3, - Opcode::IntUnsignedShr => 3, - } - } - - pub fn rewind_before_call(self) -> bool { - matches!( - self, - Opcode::BuiltinFunctionCall - | Opcode::FutureGet - | Opcode::FutureGetFor - ) - } -} - -/// A fixed-width VM instruction. -pub struct Instruction { - /// The instruction opcode/type. - pub opcode: Opcode, - - /// The arguments/operands of the instruction. - /// - /// This field is private so other code won't depend on this field having a - /// particular shape. - pub arguments: [u16; 5], -} - -impl fmt::Debug for Instruction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut fmt = f.debug_tuple(self.opcode.name()); - - for index in 0..self.opcode.arity() { - fmt.field(&self.arguments[index]); - } - - fmt.finish() - } -} - -impl Instruction { - pub fn new(opcode: Opcode, arguments: [u16; 5]) -> Self { - Instruction { opcode, arguments } - } - - pub fn zero(opcode: Opcode) -> Self { - Self::new(opcode, [0, 0, 0, 0, 0]) - } - - pub fn one(opcode: Opcode, arg0: u16) -> Self { - Self::new(opcode, [arg0, 0, 0, 0, 0]) - } - - pub fn two(opcode: Opcode, arg0: u16, arg1: u16) -> Self { - Self::new(opcode, [arg0, arg1, 0, 0, 0]) - } - - pub fn three(opcode: Opcode, arg0: u16, arg1: u16, arg2: u16) -> Self { - Self::new(opcode, [arg0, arg1, arg2, 0, 0]) - } - - pub fn four( - opcode: Opcode, - arg0: u16, - arg1: u16, - arg2: u16, - arg3: u16, - ) -> Self { - Self::new(opcode, [arg0, arg1, arg2, arg3, 0]) - } - - /// Returns the value of the given instruction argument. - /// - /// This method is always inlined to ensure bounds checking is optimised - /// away when using literal index values. - #[inline(always)] - pub fn arg(&self, index: usize) -> u16 { - self.arguments[index] - } - - #[inline(always)] - pub fn u32_arg(&self, one: usize, two: usize) -> u32 { - let arg1 = u16::to_le_bytes(self.arg(one)); - let arg2 = u16::to_le_bytes(self.arg(two)); - - u32::from_le_bytes([arg1[0], arg1[1], arg2[0], arg2[1]]) - } - - #[inline(always)] - pub fn u64_arg( - &self, - one: usize, - two: usize, - three: usize, - four: usize, - ) -> u64 { - let arg1 = u16::to_le_bytes(self.arg(one)); - let arg2 = u16::to_le_bytes(self.arg(two)); - let arg3 = u16::to_le_bytes(self.arg(three)); - let arg4 = u16::to_le_bytes(self.arg(four)); - - u64::from_le_bytes([ - arg1[0], arg1[1], arg2[0], arg2[1], arg3[0], arg3[1], arg4[0], - arg4[1], - ]) - } -} - -#[repr(u16)] -#[derive(Copy, Clone)] -pub enum BuiltinFunction { - ByteArrayDrainToString, - ByteArrayToString, - ChildProcessDrop, - ChildProcessSpawn, - ChildProcessStderrClose, - ChildProcessStderrRead, - ChildProcessStdinClose, - ChildProcessStdinFlush, - ChildProcessStdinWriteBytes, - ChildProcessStdinWriteString, - ChildProcessStdoutClose, - ChildProcessStdoutRead, - ChildProcessTryWait, - ChildProcessWait, - DirectoryCreate, - DirectoryCreateRecursive, - DirectoryList, - DirectoryRemove, - DirectoryRemoveRecursive, - EnvArguments, - EnvExecutable, - EnvGet, - EnvGetWorkingDirectory, - EnvHomeDirectory, - EnvPlatform, - EnvSetWorkingDirectory, - EnvTempDirectory, - EnvVariables, - FFIFunctionAttach, - FFIFunctionCall, - FFIFunctionDrop, - FFILibraryDrop, - FFILibraryOpen, - FFIPointerAddress, - FFIPointerAttach, - FFIPointerFromAddress, - FFIPointerRead, - FFIPointerWrite, - FFITypeAlignment, - FFITypeSize, - FileCopy, - FileDrop, - FileFlush, - FileOpenAppendOnly, - FileOpenReadAppend, - FileOpenReadOnly, - FileOpenReadWrite, - FileOpenWriteOnly, - FileRead, - FileRemove, - FileSeek, - FileSize, - FileWriteBytes, - FileWriteString, - HasherDrop, - HasherNew, - HasherToHash, - HasherWriteInt, - PathAccessedAt, - PathCreatedAt, - PathExists, - PathIsDirectory, - PathIsFile, - PathModifiedAt, - ProcessStacktraceDrop, - ProcessCallFrameLine, - ProcessCallFrameName, - ProcessCallFramePath, - ProcessStacktrace, - RandomBytes, - RandomFloat, - RandomFloatRange, - RandomInt, - RandomIntRange, - SocketAcceptIp, - SocketAcceptUnix, - SocketAddressPairAddress, - SocketAddressPairDrop, - SocketAddressPairPort, - SocketAllocateIpv4, - SocketAllocateIpv6, - SocketAllocateUnix, - SocketBind, - SocketConnect, - SocketDrop, - SocketGetBroadcast, - SocketGetKeepalive, - SocketGetLinger, - SocketGetNodelay, - SocketGetOnlyV6, - SocketGetRecvSize, - SocketGetReuseAddress, - SocketGetReusePort, - SocketGetSendSize, - SocketGetTtl, - SocketListen, - SocketLocalAddress, - SocketPeerAddress, - SocketRead, - SocketReceiveFrom, - SocketSendBytesTo, - SocketSendStringTo, - SocketSetBroadcast, - SocketSetKeepalive, - SocketSetLinger, - SocketSetNodelay, - SocketSetOnlyV6, - SocketSetRecvSize, - SocketSetReuseAddress, - SocketSetReusePort, - SocketSetSendSize, - SocketSetTtl, - SocketShutdownRead, - SocketShutdownReadWrite, - SocketShutdownWrite, - SocketTryClone, - SocketWriteBytes, - SocketWriteString, - StderrFlush, - StderrWriteBytes, - StderrWriteString, - StdinRead, - StdoutFlush, - StdoutWriteBytes, - StdoutWriteString, - StringToByteArray, - StringToFloat, - StringToInt, - StringToLower, - StringToUpper, - TimeMonotonic, - TimeSystem, - TimeSystemOffset, - CpuCores, - StringCharacters, - StringCharactersNext, - StringCharactersDrop, - StringConcatArray, - ArrayReserve, - ArrayCapacity, - ProcessStacktraceLength, - FloatToBits, - FloatFromBits, - RandomNew, - RandomFromInt, - RandomDrop, - StringSliceBytes, - ByteArraySlice, - ByteArrayAppend, - ByteArrayCopyFrom, - ByteArrayResize, -} - -impl BuiltinFunction { - pub fn to_int(self) -> u16 { - match self { - BuiltinFunction::ByteArrayDrainToString => 0, - BuiltinFunction::ByteArrayToString => 1, - BuiltinFunction::ChildProcessDrop => 2, - BuiltinFunction::ChildProcessSpawn => 3, - BuiltinFunction::ChildProcessStderrClose => 4, - BuiltinFunction::ChildProcessStderrRead => 5, - BuiltinFunction::ChildProcessStdinClose => 6, - BuiltinFunction::ChildProcessStdinFlush => 7, - BuiltinFunction::ChildProcessStdinWriteBytes => 8, - BuiltinFunction::ChildProcessStdinWriteString => 9, - BuiltinFunction::ChildProcessStdoutClose => 10, - BuiltinFunction::ChildProcessStdoutRead => 11, - BuiltinFunction::ChildProcessTryWait => 12, - BuiltinFunction::ChildProcessWait => 13, - BuiltinFunction::EnvArguments => 14, - BuiltinFunction::EnvExecutable => 15, - BuiltinFunction::EnvGet => 16, - BuiltinFunction::EnvGetWorkingDirectory => 17, - BuiltinFunction::EnvHomeDirectory => 18, - BuiltinFunction::EnvPlatform => 19, - BuiltinFunction::EnvSetWorkingDirectory => 20, - BuiltinFunction::EnvTempDirectory => 21, - BuiltinFunction::EnvVariables => 22, - BuiltinFunction::FFIFunctionAttach => 23, - BuiltinFunction::FFIFunctionCall => 24, - BuiltinFunction::FFIFunctionDrop => 25, - BuiltinFunction::FFILibraryDrop => 26, - BuiltinFunction::FFILibraryOpen => 27, - BuiltinFunction::FFIPointerAddress => 28, - BuiltinFunction::FFIPointerAttach => 29, - BuiltinFunction::FFIPointerFromAddress => 30, - BuiltinFunction::FFIPointerRead => 31, - BuiltinFunction::FFIPointerWrite => 32, - BuiltinFunction::FFITypeAlignment => 33, - BuiltinFunction::FFITypeSize => 34, - BuiltinFunction::DirectoryCreate => 35, - BuiltinFunction::DirectoryCreateRecursive => 36, - BuiltinFunction::DirectoryList => 37, - BuiltinFunction::DirectoryRemove => 38, - BuiltinFunction::DirectoryRemoveRecursive => 39, - BuiltinFunction::FileCopy => 40, - BuiltinFunction::FileDrop => 41, - BuiltinFunction::FileFlush => 42, - BuiltinFunction::FileOpenAppendOnly => 43, - BuiltinFunction::FileOpenReadAppend => 44, - BuiltinFunction::FileOpenReadOnly => 45, - BuiltinFunction::FileOpenReadWrite => 46, - BuiltinFunction::FileOpenWriteOnly => 47, - BuiltinFunction::FileRead => 48, - BuiltinFunction::FileRemove => 49, - BuiltinFunction::FileSeek => 50, - BuiltinFunction::FileSize => 51, - BuiltinFunction::FileWriteBytes => 52, - BuiltinFunction::FileWriteString => 53, - BuiltinFunction::PathAccessedAt => 54, - BuiltinFunction::PathCreatedAt => 55, - BuiltinFunction::PathExists => 56, - BuiltinFunction::PathIsDirectory => 57, - BuiltinFunction::PathIsFile => 58, - BuiltinFunction::PathModifiedAt => 59, - BuiltinFunction::HasherDrop => 60, - BuiltinFunction::HasherNew => 61, - BuiltinFunction::HasherToHash => 62, - BuiltinFunction::HasherWriteInt => 63, - BuiltinFunction::ProcessStacktraceDrop => 64, - BuiltinFunction::ProcessCallFrameLine => 65, - BuiltinFunction::ProcessCallFrameName => 66, - BuiltinFunction::ProcessCallFramePath => 67, - BuiltinFunction::ProcessStacktrace => 68, - BuiltinFunction::RandomBytes => 69, - BuiltinFunction::RandomFloat => 70, - BuiltinFunction::RandomFloatRange => 71, - BuiltinFunction::RandomInt => 72, - BuiltinFunction::RandomIntRange => 73, - BuiltinFunction::SocketAcceptIp => 74, - BuiltinFunction::SocketAcceptUnix => 75, - BuiltinFunction::SocketAddressPairAddress => 76, - BuiltinFunction::SocketAddressPairDrop => 77, - BuiltinFunction::SocketAddressPairPort => 78, - BuiltinFunction::SocketAllocateIpv4 => 79, - BuiltinFunction::SocketAllocateIpv6 => 80, - BuiltinFunction::SocketAllocateUnix => 81, - BuiltinFunction::SocketBind => 82, - BuiltinFunction::SocketConnect => 83, - BuiltinFunction::SocketDrop => 84, - BuiltinFunction::SocketGetBroadcast => 85, - BuiltinFunction::SocketGetKeepalive => 86, - BuiltinFunction::SocketGetLinger => 87, - BuiltinFunction::SocketGetNodelay => 88, - BuiltinFunction::SocketGetOnlyV6 => 89, - BuiltinFunction::SocketGetRecvSize => 90, - BuiltinFunction::SocketGetReuseAddress => 91, - BuiltinFunction::SocketGetReusePort => 92, - BuiltinFunction::SocketGetSendSize => 93, - BuiltinFunction::SocketGetTtl => 94, - BuiltinFunction::SocketListen => 95, - BuiltinFunction::SocketLocalAddress => 96, - BuiltinFunction::SocketPeerAddress => 97, - BuiltinFunction::SocketRead => 98, - BuiltinFunction::SocketReceiveFrom => 99, - BuiltinFunction::SocketSendBytesTo => 100, - BuiltinFunction::SocketSendStringTo => 101, - BuiltinFunction::SocketSetBroadcast => 102, - BuiltinFunction::SocketSetKeepalive => 103, - BuiltinFunction::SocketSetLinger => 104, - BuiltinFunction::SocketSetNodelay => 105, - BuiltinFunction::SocketSetOnlyV6 => 106, - BuiltinFunction::SocketSetRecvSize => 107, - BuiltinFunction::SocketSetReuseAddress => 108, - BuiltinFunction::SocketSetReusePort => 109, - BuiltinFunction::SocketSetSendSize => 110, - BuiltinFunction::SocketSetTtl => 111, - BuiltinFunction::SocketShutdownRead => 112, - BuiltinFunction::SocketShutdownReadWrite => 113, - BuiltinFunction::SocketShutdownWrite => 114, - BuiltinFunction::SocketTryClone => 115, - BuiltinFunction::SocketWriteBytes => 116, - BuiltinFunction::SocketWriteString => 117, - BuiltinFunction::StderrFlush => 118, - BuiltinFunction::StderrWriteBytes => 119, - BuiltinFunction::StderrWriteString => 120, - BuiltinFunction::StdinRead => 121, - BuiltinFunction::StdoutFlush => 122, - BuiltinFunction::StdoutWriteBytes => 123, - BuiltinFunction::StdoutWriteString => 124, - BuiltinFunction::StringToByteArray => 125, - BuiltinFunction::StringToFloat => 126, - BuiltinFunction::StringToInt => 127, - BuiltinFunction::StringToLower => 128, - BuiltinFunction::StringToUpper => 129, - BuiltinFunction::TimeMonotonic => 130, - BuiltinFunction::TimeSystem => 131, - BuiltinFunction::TimeSystemOffset => 132, - BuiltinFunction::CpuCores => 133, - BuiltinFunction::StringCharacters => 134, - BuiltinFunction::StringCharactersNext => 135, - BuiltinFunction::StringCharactersDrop => 136, - BuiltinFunction::StringConcatArray => 137, - BuiltinFunction::ArrayReserve => 138, - BuiltinFunction::ArrayCapacity => 139, - BuiltinFunction::ProcessStacktraceLength => 140, - BuiltinFunction::FloatToBits => 141, - BuiltinFunction::FloatFromBits => 142, - BuiltinFunction::RandomNew => 143, - BuiltinFunction::RandomFromInt => 144, - BuiltinFunction::RandomDrop => 145, - BuiltinFunction::StringSliceBytes => 146, - BuiltinFunction::ByteArraySlice => 147, - BuiltinFunction::ByteArrayAppend => 148, - BuiltinFunction::ByteArrayCopyFrom => 149, - BuiltinFunction::ByteArrayResize => 150, - } - } - - pub fn name(self) -> &'static str { - match self { - BuiltinFunction::ChildProcessDrop => "child_process_drop", - BuiltinFunction::ChildProcessSpawn => "child_process_spawn", - BuiltinFunction::ChildProcessStderrClose => { - "child_process_stderr_close" - } - BuiltinFunction::ChildProcessStderrRead => { - "child_process_stderr_read" - } - BuiltinFunction::ChildProcessStdinClose => { - "child_process_stdin_close" - } - BuiltinFunction::ChildProcessStdinFlush => { - "child_process_stdin_flush" - } - BuiltinFunction::ChildProcessStdinWriteBytes => { - "child_process_stdin_write_bytes" - } - BuiltinFunction::ChildProcessStdinWriteString => { - "child_process_stdin_write_string" - } - BuiltinFunction::ChildProcessStdoutClose => { - "child_process_stdout_close" - } - BuiltinFunction::ChildProcessStdoutRead => { - "child_process_stdout_read" - } - BuiltinFunction::ChildProcessTryWait => "child_process_try_wait", - BuiltinFunction::ChildProcessWait => "child_process_wait", - BuiltinFunction::EnvArguments => "env_arguments", - BuiltinFunction::EnvExecutable => "env_executable", - BuiltinFunction::EnvGet => "env_get", - BuiltinFunction::EnvGetWorkingDirectory => { - "env_get_working_directory" - } - BuiltinFunction::EnvHomeDirectory => "env_home_directory", - BuiltinFunction::EnvPlatform => "env_platform", - BuiltinFunction::EnvSetWorkingDirectory => { - "env_set_working_directory" - } - BuiltinFunction::EnvTempDirectory => "env_temp_directory", - BuiltinFunction::EnvVariables => "env_variables", - BuiltinFunction::FFIFunctionAttach => "ffi_function_attach", - BuiltinFunction::FFIFunctionCall => "ffi_function_call", - BuiltinFunction::FFIFunctionDrop => "ffi_function_drop", - BuiltinFunction::FFILibraryDrop => "ffi_library_drop", - BuiltinFunction::FFILibraryOpen => "ffi_library_open", - BuiltinFunction::FFIPointerAddress => "ffi_pointer_address", - BuiltinFunction::FFIPointerAttach => "ffi_pointer_attach", - BuiltinFunction::FFIPointerFromAddress => { - "ffi_pointer_from_address" - } - BuiltinFunction::FFIPointerRead => "ffi_pointer_read", - BuiltinFunction::FFIPointerWrite => "ffi_pointer_write", - BuiltinFunction::FFITypeAlignment => "ffi_type_alignment", - BuiltinFunction::FFITypeSize => "ffi_type_size", - BuiltinFunction::DirectoryCreate => "directory_create", - BuiltinFunction::DirectoryCreateRecursive => { - "directory_create_recursive" - } - BuiltinFunction::DirectoryList => "directory_list", - BuiltinFunction::DirectoryRemove => "directory_remove", - BuiltinFunction::DirectoryRemoveRecursive => { - "directory_remove_recursive" - } - BuiltinFunction::FileCopy => "file_copy", - BuiltinFunction::FileDrop => "file_drop", - BuiltinFunction::FileFlush => "file_flush", - BuiltinFunction::FileOpenAppendOnly => "file_open_append_only", - BuiltinFunction::FileOpenReadAppend => "file_open_read_append", - BuiltinFunction::FileOpenReadOnly => "file_open_read_only", - BuiltinFunction::FileOpenReadWrite => "file_open_read_write", - BuiltinFunction::FileOpenWriteOnly => "file_open_write_only", - BuiltinFunction::FileRead => "file_read", - BuiltinFunction::FileRemove => "file_remove", - BuiltinFunction::FileSeek => "file_seek", - BuiltinFunction::FileSize => "file_size", - BuiltinFunction::FileWriteBytes => "file_write_bytes", - BuiltinFunction::FileWriteString => "file_write_string", - BuiltinFunction::PathAccessedAt => "path_accessed_at", - BuiltinFunction::PathCreatedAt => "path_created_at", - BuiltinFunction::PathExists => "path_exists", - BuiltinFunction::PathIsDirectory => "path_is_directory", - BuiltinFunction::PathIsFile => "path_is_file", - BuiltinFunction::PathModifiedAt => "path_modified_at", - BuiltinFunction::HasherDrop => "hasher_drop", - BuiltinFunction::HasherNew => "hasher_new", - BuiltinFunction::HasherToHash => "hasher_to_hash", - BuiltinFunction::HasherWriteInt => "hasher_write_int", - BuiltinFunction::ProcessStacktraceDrop => "process_stacktrace_drop", - BuiltinFunction::ProcessCallFrameLine => "process_call_frame_line", - BuiltinFunction::ProcessCallFrameName => "process_call_frame_name", - BuiltinFunction::ProcessCallFramePath => "process_call_frame_path", - BuiltinFunction::ProcessStacktrace => "process_stacktrace", - BuiltinFunction::RandomBytes => "random_bytes", - BuiltinFunction::RandomFloat => "random_float", - BuiltinFunction::RandomFloatRange => "random_float_range", - BuiltinFunction::RandomIntRange => "random_int_range", - BuiltinFunction::RandomInt => "random_int", - BuiltinFunction::SocketAcceptIp => "socket_accept_ip", - BuiltinFunction::SocketAcceptUnix => "socket_accept_unix", - BuiltinFunction::SocketAddressPairAddress => { - "socket_address_pair_address" - } - BuiltinFunction::SocketAddressPairDrop => { - "socket_address_pair_drop" - } - BuiltinFunction::SocketAddressPairPort => { - "socket_address_pair_port" - } - BuiltinFunction::SocketAllocateIpv4 => "socket_allocate_ipv4", - BuiltinFunction::SocketAllocateIpv6 => "socket_allocate_ipv6", - BuiltinFunction::SocketAllocateUnix => "socket_allocate_unix", - BuiltinFunction::SocketBind => "socket_bind", - BuiltinFunction::SocketConnect => "socket_connect", - BuiltinFunction::SocketDrop => "socket_drop", - BuiltinFunction::SocketGetBroadcast => "socket_get_broadcast", - BuiltinFunction::SocketGetKeepalive => "socket_get_keepalive", - BuiltinFunction::SocketGetLinger => "socket_get_linger", - BuiltinFunction::SocketGetNodelay => "socket_get_nodelay", - BuiltinFunction::SocketGetOnlyV6 => "socket_get_only_v6", - BuiltinFunction::SocketGetRecvSize => "socket_get_recv_size", - BuiltinFunction::SocketGetReuseAddress => { - "socket_get_reuse_address" - } - BuiltinFunction::SocketGetReusePort => "socket_get_reuse_port", - BuiltinFunction::SocketGetSendSize => "socket_get_send_size", - BuiltinFunction::SocketGetTtl => "socket_get_ttl", - BuiltinFunction::SocketListen => "socket_listen", - BuiltinFunction::SocketLocalAddress => "socket_local_address", - BuiltinFunction::SocketPeerAddress => "socket_peer_address", - BuiltinFunction::SocketRead => "socket_read", - BuiltinFunction::SocketReceiveFrom => "socket_receive_from", - BuiltinFunction::SocketSendBytesTo => "socket_send_bytes_to", - BuiltinFunction::SocketSendStringTo => "socket_send_string_to", - BuiltinFunction::SocketSetBroadcast => "socket_set_broadcast", - BuiltinFunction::SocketSetKeepalive => "socket_set_keepalive", - BuiltinFunction::SocketSetLinger => "socket_set_linger", - BuiltinFunction::SocketSetNodelay => "socket_set_nodelay", - BuiltinFunction::SocketSetOnlyV6 => "socket_set_only_v6", - BuiltinFunction::SocketSetRecvSize => "socket_set_recv_size", - BuiltinFunction::SocketSetReuseAddress => { - "socket_set_reuse_address" - } - BuiltinFunction::SocketSetReusePort => "socket_set_reuse_port", - BuiltinFunction::SocketSetSendSize => "socket_set_send_size", - BuiltinFunction::SocketSetTtl => "socket_set_ttl", - BuiltinFunction::SocketShutdownRead => "socket_shutdown_read", - BuiltinFunction::SocketShutdownReadWrite => { - "socket_shutdown_read_write" - } - BuiltinFunction::SocketShutdownWrite => "socket_shutdown_write", - BuiltinFunction::SocketTryClone => "socket_try_clone", - BuiltinFunction::SocketWriteBytes => "socket_write_bytes", - BuiltinFunction::SocketWriteString => "socket_write_string", - BuiltinFunction::StderrFlush => "stderr_flush", - BuiltinFunction::StderrWriteBytes => "stderr_write_bytes", - BuiltinFunction::StderrWriteString => "stderr_write_string", - BuiltinFunction::StdinRead => "stdin_read", - BuiltinFunction::StdoutFlush => "stdout_flush", - BuiltinFunction::StdoutWriteBytes => "stdout_write_bytes", - BuiltinFunction::StdoutWriteString => "stdout_write_string", - BuiltinFunction::TimeMonotonic => "time_monotonic", - BuiltinFunction::TimeSystem => "time_system", - BuiltinFunction::TimeSystemOffset => "time_system_offset", - BuiltinFunction::StringToLower => "string_to_lower", - BuiltinFunction::StringToUpper => "string_to_upper", - BuiltinFunction::StringToByteArray => "string_to_byte_array", - BuiltinFunction::StringToFloat => "string_to_float", - BuiltinFunction::StringToInt => "string_to_int", - BuiltinFunction::ByteArrayDrainToString => { - "byte_array_drain_to_string" - } - BuiltinFunction::ByteArrayToString => "byte_array_to_string", - BuiltinFunction::CpuCores => "cpu_cores", - BuiltinFunction::StringCharacters => "string_characters", - BuiltinFunction::StringCharactersNext => "string_characters_next", - BuiltinFunction::StringCharactersDrop => "string_characters_drop", - BuiltinFunction::StringConcatArray => "string_concat_array", - BuiltinFunction::ArrayReserve => "array_reserve", - BuiltinFunction::ArrayCapacity => "array_capacity", - BuiltinFunction::ProcessStacktraceLength => { - "process_stacktrace_length" - } - BuiltinFunction::FloatToBits => "float_to_bits", - BuiltinFunction::FloatFromBits => "float_from_bits", - BuiltinFunction::RandomNew => "random_new", - BuiltinFunction::RandomFromInt => "random_from_int", - BuiltinFunction::RandomDrop => "random_drop", - BuiltinFunction::StringSliceBytes => "string_slice_bytes", - BuiltinFunction::ByteArraySlice => "byte_array_slice", - BuiltinFunction::ByteArrayAppend => "byte_array_append", - BuiltinFunction::ByteArrayCopyFrom => "byte_array_copy_from", - BuiltinFunction::ByteArrayResize => "byte_array_resize", - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::mem::size_of; - - #[test] - fn test_opcode_from_byte() { - assert_eq!(Opcode::from_byte(93), Ok(Opcode::Return)); - assert_eq!( - Opcode::from_byte(255), - Err("The opcode 255 is invalid".to_string()) - ); - } - - #[test] - fn test_arg() { - let ins = Instruction::new(Opcode::GetConstant, [1, 2, 0, 0, 0]); - - assert_eq!(ins.arg(0), 1); - } - - #[test] - fn test_u32_arg() { - let ins = Instruction::new(Opcode::Return, [0, 14, 1, 1, 1]); - - assert_eq!(ins.u32_arg(1, 2), 65_550); - } - - #[test] - fn test_u64_arg() { - let ins0 = Instruction::new(Opcode::Return, [0, 14, 1, 0, 0]); - let ins1 = Instruction::new(Opcode::Return, [0, 14, 1, 1, 1]); - - assert_eq!(ins0.u64_arg(1, 2, 3, 4), 65_550); - assert_eq!(ins1.u64_arg(1, 2, 3, 4), 281_479_271_743_502); - } - - #[test] - fn test_type_size() { - assert_eq!(size_of::(), 12); - } -} diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index da997514c..5a5e6d399 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -4,6 +4,7 @@ version = "0.10.0" # VERSION authors = ["Yorick Peterse "] edition = "2021" license = "MPL-2.0" +build = "build.rs" [lib] doctest = false @@ -12,8 +13,9 @@ doctest = false unicode-segmentation = "^1.8" getopts = "^0.2" ast = { path = "../ast" } -bytecode = { path = "../bytecode" } types = { path = "../types" } +fnv = "^1.0" +inkwell = { version = "^0.1", features = ["llvm15-0"] } [dev-dependencies] similar-asserts = "^1.1" diff --git a/compiler/build.rs b/compiler/build.rs new file mode 100644 index 000000000..7be520255 --- /dev/null +++ b/compiler/build.rs @@ -0,0 +1,49 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + // To make development easier we default to using repository-local paths. + let rt = env::var("INKO_RT") + .map(PathBuf::from) + .unwrap_or_else(|_| runtime_directory()); + + let std = env::var("INKO_STD") + .map(PathBuf::from) + .unwrap_or_else(|_| std_directory()); + + println!("cargo:rerun-if-env-changed=INKO_STD"); + println!("cargo:rerun-if-env-changed=INKO_RT"); + println!( + "cargo:rustc-env=INKO_STD={}", + std.canonicalize().unwrap_or(std).display(), + ); + println!( + "cargo:rustc-env=INKO_RT={}", + rt.canonicalize().unwrap_or(rt).display() + ); +} + +fn std_directory() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("std") + .join("src") +} + +fn runtime_directory() -> PathBuf { + let target = env::var("TARGET").unwrap(); + let profile = env::var("PROFILE").unwrap(); + let base = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("target"); + let without_target = base.join(&profile); + let with_target = base.join(target).join(profile); + + if with_target.is_dir() { + with_target + } else { + without_target + } +} diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs deleted file mode 100644 index c83b9bae5..000000000 --- a/compiler/src/codegen.rs +++ /dev/null @@ -1,899 +0,0 @@ -//! Converting MIR to bytecode. -use crate::mir::{self, CloneKind, Constant, LocationId, Method, Mir}; -use bytecode::{Instruction, Opcode}; -use bytecode::{ - CONST_ARRAY, CONST_FLOAT, CONST_INTEGER, CONST_STRING, SIGNATURE_BYTES, - VERSION, -}; -use std::collections::{HashMap, HashSet, VecDeque}; -use types::{ClassId, Database, MethodId, FIRST_USER_CLASS_ID}; - -const REGISTERS_LIMIT: usize = u16::MAX as usize; -const BLOCKS_LIMIT: usize = u16::MAX as usize; -const JUMP_TABLES_LIMIT: usize = u16::MAX as usize; - -/// Method table sizes are multiplied by this value in an attempt to reduce the -/// amount of collisions when performing dynamic dispatch. -/// -/// While this increases the amount of memory needed per method table, it's not -/// really significant: each slot only takes up one word of memory. On a 64-bits -/// system this means you can fit a total of 131 072 slots in 1 MiB. In -/// addition, this cost is a one-time and constant cost, whereas collisions -/// introduce a cost that you may have to pay every time you perform dynamic -/// dispatch. -const METHOD_TABLE_FACTOR: usize = 4; - -/// The class-local index of the dropper method. -const DROPPER_INDEX: u16 = 0; - -/// The class-local index of the "call" method for closures. -const CALL_CLOSURE_INDEX: u16 = 1; - -fn nearest_power_of_two(mut value: usize) -> usize { - if value == 0 { - return 0; - } - - value -= 1; - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value |= value >> 32; - value += 1; - - value -} - -fn split_u32(value: u32) -> (u16, u16) { - let bytes = u32::to_le_bytes(value); - - ( - u16::from_le_bytes([bytes[0], bytes[1]]), - u16::from_le_bytes([bytes[2], bytes[3]]), - ) -} - -fn push_values(buffer: &mut Vec, values: &[mir::RegisterId]) { - for val in values { - buffer.push(Instruction::one(Opcode::Push, val.0 as u16)); - } -} - -fn number_of_methods(info: &HashMap, id: ClassId) -> u16 { - info.get(&id).unwrap().method_slots -} - -pub(crate) struct Bytecode { - pub(crate) bytes: Vec, -} - -pub(crate) struct Buffer { - pub(crate) bytes: Vec, -} - -impl Buffer { - fn new() -> Self { - Self { bytes: Vec::new() } - } - - fn len(&self) -> usize { - self.bytes.len() - } - - fn append(&mut self, other: &mut Self) { - self.bytes.append(&mut other.bytes); - } - - fn write_u8(&mut self, value: u8) { - self.bytes.push(value); - } - - fn write_bool(&mut self, value: bool) { - self.bytes.push(value as u8); - } - - fn write_u16(&mut self, value: u16) { - self.bytes.extend_from_slice(&u16::to_le_bytes(value)); - } - - fn write_u32(&mut self, value: u32) { - self.bytes.extend_from_slice(&u32::to_le_bytes(value)); - } - - fn write_u64(&mut self, value: u64) { - self.bytes.extend_from_slice(&u64::to_le_bytes(value)); - } - - fn write_i64(&mut self, value: i64) { - self.bytes.extend_from_slice(&i64::to_le_bytes(value)); - } - - fn write_f64(&mut self, value: f64) { - self.bytes.extend_from_slice(&u64::to_le_bytes(value.to_bits())); - } - - fn write_string(&mut self, value: &str) { - self.write_u32(value.len() as u32); - self.bytes.extend_from_slice(value.as_bytes()); - } -} - -struct ClassInfo { - /// A globally unique, monotonically increasing index for this class. - /// - /// MIR may remove classes as part of certain optimisations, so the class - /// IDs may not be monotonically increasing. The VM expects a list of - /// classes and IDs, without any holes between them. To achieve this we - /// assign every class ID a monotonically increasing index, and use that - /// index when generating bytecode. - index: u32, - - /// The number of slots to reserve for methods. - method_slots: u16, - - /// The number of fields the class defines. - fields: u8, -} - -struct MethodInfo { - index: u16, - hash: u32, -} - -/// A compiler pass that lowers MIR into a bytecode file. -pub(crate) struct Lower<'a> { - db: &'a Database, - mir: &'a Mir, - class_info: &'a HashMap, - method_info: &'a HashMap, - constant_indexes: HashMap, -} - -impl<'a> Lower<'a> { - pub(crate) fn run_all(db: &'a Database, mir: Mir) -> Bytecode { - let mut method_hashes: HashMap<&str, u32> = HashMap::new(); - let mut method_info = HashMap::new(); - let mut class_info = HashMap::new(); - let mut class_index = FIRST_USER_CLASS_ID; - - // These methods are given fixed hash codes so they always reside in a - // fixed slot, removing the need for dynamic dispatch when calling these - // methods. - method_hashes.insert(types::DROPPER_METHOD, DROPPER_INDEX as u32); - method_hashes.insert(types::CALL_METHOD, CALL_CLOSURE_INDEX as u32); - - for (&class_id, class) in &mir.classes { - let index = if class_id.0 < FIRST_USER_CLASS_ID { - class_id.0 - } else { - let idx = class_index; - - class_index += 1; - idx - }; - - let num_methods = class_id.number_of_methods(db); - - // For classes with very few methods, the cost of handling - // collisions is so low we can just keep the table sizes as small as - // possible. - let raw_size = if num_methods <= 4 { - num_methods - } else { - num_methods * METHOD_TABLE_FACTOR - }; - - // The number of methods is a power of two, as this allows the use - // of the & operator instead of the % operator. The & operator - // requires far fewer instructions compared to the the % operator. - let size = nearest_power_of_two(raw_size); - let mut buckets = vec![false; size]; - - for &method_id in &class.methods { - // We use a state of the art hashing algorithm (patent pending): - // each unique name is simply assigned a monotonically - // increasing 32-bits unsigned integer. This is safe because as - // part of type-checking we limit the total number of methods to - // fit in this number. - // - // Indexes can be safely cast to a u16 because we limit the - // number of methods per class to fit in this limit. - let next_hash = method_hashes.len() as u32; - let hash = *method_hashes - .entry(method_id.name(db)) - .or_insert(next_hash); - - // Because the number of methods/buckets is a power of two, we - // can also use the & operator here. The subtractions of 1 are - // needed to ensure the & operator works correctly: - // - // 15 % 8 => 7 - // 15 & 8 => 8 - // 15 & (8 - 1) => 7 - let raw_index = hash as usize & (size - 1); - let mut index = raw_index; - - while buckets[index] { - index = (index + 1) & (buckets.len() - 1); - } - - buckets[index] = true; - - let info = MethodInfo { index: index as u16, hash }; - - method_info.insert(method_id, info); - } - - let info = ClassInfo { - index, - method_slots: size as u16, - fields: class_id.number_of_fields(db) as u8, - }; - - class_info.insert(class_id, info); - } - - for mir_trait in mir.traits.values() { - for method_id in mir_trait - .id - .required_methods(db) - .into_iter() - .chain(mir_trait.id.default_methods(db)) - { - let next_hash = method_hashes.len() as u32; - let hash = *method_hashes - .entry(method_id.name(db)) - .or_insert(next_hash); - - method_info.insert(method_id, MethodInfo { index: 0, hash }); - } - } - - let mut buffer = Buffer::new(); - let main_mod = db.module(db.main_module().unwrap().as_str()); - let main_class = match main_mod.symbol(db, types::MAIN_CLASS) { - Some(types::Symbol::Class(id)) => id, - _ => unreachable!(), - }; - let main_method = main_class.method(db, types::MAIN_METHOD).unwrap(); - let main_class_idx = class_info.get(&main_class).unwrap().index; - let main_method_idx = method_info.get(&main_method).unwrap().index; - - buffer.bytes.extend_from_slice(&SIGNATURE_BYTES); - buffer.write_u8(VERSION); - buffer.write_u32(mir.modules.len() as u32); - buffer.write_u32(mir.classes.len() as u32); - - buffer.write_u16(number_of_methods(&class_info, ClassId::int())); - buffer.write_u16(number_of_methods(&class_info, ClassId::float())); - buffer.write_u16(number_of_methods(&class_info, ClassId::string())); - buffer.write_u16(number_of_methods(&class_info, ClassId::array())); - buffer.write_u16(number_of_methods(&class_info, ClassId::boolean())); - buffer.write_u16(number_of_methods(&class_info, ClassId::nil())); - buffer.write_u16(number_of_methods(&class_info, ClassId::byte_array())); - buffer.write_u16(number_of_methods(&class_info, ClassId::future())); - - buffer.write_u32(main_class_idx); - buffer.write_u16(main_method_idx); - - for index in 0..mir.modules.len() { - let mut chunk = Lower { - db, - mir: &mir, - constant_indexes: HashMap::new(), - class_info: &class_info, - method_info: &method_info, - } - .run(index); - - buffer.write_u64(chunk.len() as u64); - buffer.append(&mut chunk); - } - - Bytecode { bytes: buffer.bytes } - } - - pub(crate) fn run(mut self, module_index: usize) -> Buffer { - let mir_mod = self.mir.modules[module_index].clone(); - - for id in mir_mod.constants { - let val = self.mir.constants.get(&id).cloned().unwrap(); - - self.constant_index(val); - } - - let mut classes_buffer = Buffer::new(); - - classes_buffer.write_u16(mir_mod.classes.len() as u16); - - for class_id in mir_mod.classes { - self.class(class_id, &mut classes_buffer); - } - - let mut module_buffer = Buffer::new(); - - module_buffer.write_u32(module_index as u32); - - // We currently don't validate the number of constants, based on the - // idea that you are _extremely_ unlikely to ever need more than - // (2^32)-1 constants in a single module. - module_buffer.write_u32(self.constant_indexes.len() as u32); - - for (cons, index) in &self.constant_indexes { - module_buffer.write_u32(*index); - - self.constant(cons, &mut module_buffer); - } - - let class_idx = - self.class_info.get(&mir_mod.id.class(self.db)).unwrap().index; - - module_buffer.write_u32(class_idx); - module_buffer.append(&mut classes_buffer); - module_buffer - } - - fn constant(&self, cons: &Constant, buffer: &mut Buffer) { - match cons { - Constant::Int(v) => { - buffer.write_u8(CONST_INTEGER); - buffer.write_i64(*v); - } - Constant::Float(v) => { - buffer.write_u8(CONST_FLOAT); - buffer.write_f64(*v); - } - Constant::String(v) => { - buffer.write_u8(CONST_STRING); - buffer.write_string(v); - } - Constant::Array(values) => { - buffer.write_u8(CONST_ARRAY); - buffer.write_u16(values.len() as u16); - - for cons in values { - self.constant(cons, buffer); - } - } - } - } - - fn class(&mut self, class_id: ClassId, buffer: &mut Buffer) { - let info = self.class_info.get(&class_id).unwrap(); - let mir_class = self.mir.classes.get(&class_id).unwrap(); - - buffer.write_u32(info.index); - buffer.write_bool(class_id.kind(self.db).is_async()); - buffer.write_string(class_id.name(self.db)); - buffer.write_u8(info.fields); - buffer.write_u16(info.method_slots); - buffer.write_u16(mir_class.methods.len() as u16); - - for &mid in &mir_class.methods { - self.method(mid, buffer); - } - } - - fn method(&mut self, method_id: MethodId, buffer: &mut Buffer) { - let info = self.method_info.get(&method_id).unwrap(); - let method = self.mir.methods.get(&method_id).unwrap(); - let regs = method.registers.len(); - let mut jump_tables = Vec::new(); - let mut locations = Vec::new(); - - // if method.id.name(self.db) == "foo" { - // println!( - // "{}", - // crate::mir::printer::to_dot(self.db, self.mir, &[&method]) - // ); - // } - - // This should never happen, unless somebody is writing _really_ - // crazy code, or due to a compiler bug. Because of this, we just - // resort to a simple assertion. - assert!(regs <= REGISTERS_LIMIT); - assert!(method.body.blocks.len() <= BLOCKS_LIMIT); - - buffer.write_u16(info.index); - buffer.write_u32(info.hash); - buffer.write_u16(regs as u16); - - self.instructions(method, buffer, &mut jump_tables, &mut locations); - self.location_table(buffer, locations); - - assert!(jump_tables.len() <= JUMP_TABLES_LIMIT); - - buffer.write_u16(jump_tables.len() as u16); - - for table in jump_tables { - // MIR already limits the possible switch (and thus jump table - // values), e.g. by limiting the number of enum variants. - buffer.write_u16(table.len() as u16); - - for value in table { - buffer.write_u32(value); - } - } - } - - fn instructions( - &mut self, - method: &Method, - buffer: &mut Buffer, - jump_tables: &mut Vec>, - locations: &mut Vec<(usize, LocationId)>, - ) { - let mut instructions = Vec::new(); - let mut queue = VecDeque::new(); - let mut visited = HashSet::new(); - let mut offsets = vec![0_u16; method.body.blocks.len()]; - - // Arguments are pushed in order, meaning that for arguments `(a, b, c)` - // the stack would be `[a, b, c]`, and thus the first pop produces the - // last argument. - let num_args = method.id.number_of_arguments(self.db) as u16; - let arg_range = if method.id.is_instance_method(self.db) { - 0..=num_args - } else { - // For static methods the receiver isn't passed, so we subtract one - // and saturate at zero. This way a static method with a single - // argument still generates one pop(). - 0..=num_args.saturating_sub(1) - }; - - for index in arg_range.rev() { - instructions.push(Instruction::one(Opcode::Pop, index)); - } - - queue.push_back(method.body.start_id); - visited.insert(method.body.start_id); - - while let Some(block_id) = queue.pop_front() { - let block = &method.body.blocks[block_id.0]; - let offset = instructions.len() as u16; - - offsets[block_id.0] = offset; - - for ins in &block.instructions { - self.instruction( - method, - ins, - &mut instructions, - jump_tables, - locations, - ); - } - - for &child in &block.successors { - if visited.insert(child) { - queue.push_back(child); - } - } - } - - for ins in &mut instructions { - match ins.opcode { - Opcode::Goto => { - ins.arguments[0] = offsets[ins.arg(0) as usize]; - } - Opcode::Branch => { - ins.arguments[1] = offsets[ins.arg(1) as usize]; - ins.arguments[2] = offsets[ins.arg(2) as usize]; - } - Opcode::BranchResult => { - ins.arguments[0] = offsets[ins.arg(0) as usize]; - ins.arguments[1] = offsets[ins.arg(1) as usize]; - } - _ => {} - } - } - - // For jump tables we don't need to update the instruction, but the jump - // tables themselves. - for table in jump_tables { - for index in 0..table.len() { - table[index] = offsets[table[index] as usize] as u32; - } - } - - buffer.write_u32(instructions.len() as u32); - - for ins in instructions { - buffer.write_u8(ins.opcode.to_int()); - - for index in 0..ins.opcode.arity() { - buffer.write_u16(ins.arg(index as usize)); - } - } - } - - fn instruction( - &mut self, - method: &Method, - instruction: &mir::Instruction, - buffer: &mut Vec, - jump_tables: &mut Vec>, - locations: &mut Vec<(usize, LocationId)>, - ) { - match instruction { - mir::Instruction::Nil(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::GetNil, reg)); - } - mir::Instruction::CheckRefs(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::CheckRefs, reg)); - } - mir::Instruction::Reduce(ins) => { - buffer.push(Instruction::one(Opcode::Reduce, ins.amount)); - } - mir::Instruction::Goto(ins) => { - let block_id = ins.block.0 as u16; - - buffer.push(Instruction::one(Opcode::Goto, block_id)); - } - mir::Instruction::Branch(ins) => { - let reg = ins.condition.0 as u16; - let ok = ins.if_true.0 as u16; - let err = ins.if_false.0 as u16; - - buffer.push(Instruction::three(Opcode::Branch, reg, ok, err)); - } - mir::Instruction::BranchResult(ins) => { - let ok = ins.ok.0 as u16; - let err = ins.error.0 as u16; - - buffer.push(Instruction::two(Opcode::BranchResult, ok, err)); - } - mir::Instruction::JumpTable(ins) => { - let reg = ins.register.0 as u16; - let idx = jump_tables.len() as u16; - let table = ins.blocks.iter().map(|b| b.0 as u32).collect(); - - jump_tables.push(table); - buffer.push(Instruction::two(Opcode::JumpTable, reg, idx)); - } - mir::Instruction::Return(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::Return, reg)); - } - mir::Instruction::ReturnAsync(ins) => { - let op = Opcode::ProcessWriteResult; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::three(op, reg, val, 0)); - } - mir::Instruction::Throw(ins) => { - let reg = ins.register.0 as u16; - let unwind = 1; - - buffer.push(Instruction::two(Opcode::Throw, reg, unwind)); - } - mir::Instruction::ThrowAsync(ins) => { - let op = Opcode::ProcessWriteResult; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::three(op, reg, val, 1)); - } - mir::Instruction::Finish(v) => { - let op = Opcode::ProcessFinishTask; - let arg = v.terminate as u16; - - buffer.push(Instruction::one(op, arg)); - } - mir::Instruction::AllocateArray(ins) => { - let op = Opcode::ArrayAllocate; - let reg = ins.register.0 as u16; - - push_values(buffer, &ins.values); - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::True(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::GetTrue, reg)); - } - mir::Instruction::False(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::GetFalse, reg)); - } - mir::Instruction::Float(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let idx = self.constant_index(Constant::Float(ins.value)); - let (arg1, arg2) = split_u32(idx); - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::Int(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let idx = self.constant_index(Constant::Int(ins.value)); - let (arg1, arg2) = split_u32(idx); - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::String(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let idx = - self.constant_index(Constant::String(ins.value.clone())); - let (arg1, arg2) = split_u32(idx); - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::Strings(ins) => { - let op = Opcode::StringConcat; - let reg = ins.register.0 as u16; - - push_values(buffer, &ins.values); - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::MoveRegister(ins) => { - let reg = ins.target.0 as u16; - let src = ins.source.0 as u16; - - buffer.push(Instruction::two(Opcode::MoveRegister, reg, src)); - } - mir::Instruction::MoveResult(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::MoveResult, reg)); - } - mir::Instruction::Allocate(ins) => { - let reg = ins.register.0 as u16; - let idx = self.class_info.get(&ins.class).unwrap().index; - let (arg1, arg2) = split_u32(idx); - let op = if ins.class.kind(self.db).is_async() { - Opcode::ProcessAllocate - } else { - Opcode::Allocate - }; - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::CallStatic(ins) => { - let op = Opcode::CallStatic; - let class = self.class_info.get(&ins.class).unwrap().index; - let method = self.method_info.get(&ins.method).unwrap().index; - let (arg1, arg2) = split_u32(class); - - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, arg1, arg2, method)); - } - mir::Instruction::CallVirtual(ins) => { - let op = Opcode::CallVirtual; - let rec = ins.receiver.0 as u16; - let idx = self.method_info.get(&ins.method).unwrap().index; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, rec, idx)); - } - mir::Instruction::CallBuiltin(ins) => { - let op = Opcode::BuiltinFunctionCall; - let idx = ins.id.to_int(); - - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::one(op, idx)); - } - mir::Instruction::CallClosure(ins) => { - let op = Opcode::CallVirtual; - let rec = ins.receiver.0 as u16; - let idx = CALL_CLOSURE_INDEX; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, rec, idx)); - } - mir::Instruction::CallDropper(ins) => { - let op = Opcode::CallVirtual; - let rec = ins.receiver.0 as u16; - let idx = DROPPER_INDEX; - - buffer.push(Instruction::one(Opcode::Push, rec)); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, rec, idx)); - } - mir::Instruction::CallDynamic(ins) => { - let op = Opcode::CallDynamic; - let rec = ins.receiver.0 as u16; - let (hash1, hash2) = - split_u32(self.method_info.get(&ins.method).unwrap().hash); - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, rec, hash1, hash2)); - } - mir::Instruction::Clone(ins) => { - let reg = ins.register.0 as u16; - let src = ins.source.0 as u16; - let op = match ins.kind { - CloneKind::Float => Opcode::FloatClone, - CloneKind::Int => Opcode::IntClone, - CloneKind::Process | CloneKind::String => Opcode::Increment, - CloneKind::Other => Opcode::MoveRegister, - }; - - buffer.push(Instruction::two(op, reg, src)); - } - mir::Instruction::Decrement(ins) => { - let op = Opcode::Decrement; - let reg = ins.register.0 as u16; - - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::DecrementAtomic(ins) => { - let op = Opcode::DecrementAtomic; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, reg, val)); - } - mir::Instruction::Free(ins) => { - let op = Opcode::Free; - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::GetConstant(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let val = self.mir.constants.get(&ins.id).cloned().unwrap(); - let (idx1, idx2) = split_u32(self.constant_index(val)); - - buffer.push(Instruction::three(op, reg, idx1, idx2)); - } - mir::Instruction::GetField(ins) => { - let rec_typ = method.registers.value_type(ins.receiver); - let stype = method.id.self_type(self.db); - let op = if rec_typ.is_async(self.db, stype) { - Opcode::ProcessGetField - } else { - Opcode::GetField - }; - - let reg = ins.register.0 as u16; - let rec = ins.receiver.0 as u16; - let idx = ins.field.index(self.db) as u16; - - buffer.push(Instruction::three(op, reg, rec, idx)); - } - mir::Instruction::SetField(ins) => { - let rec_typ = method.registers.value_type(ins.receiver); - let stype = method.id.self_type(self.db); - let op = if rec_typ.is_async(self.db, stype) { - Opcode::ProcessSetField - } else { - Opcode::SetField - }; - - let rec = ins.receiver.0 as u16; - let idx = ins.field.index(self.db) as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::three(op, rec, idx, val)); - } - mir::Instruction::Increment(ins) => { - let op = Opcode::Increment; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::two(op, reg, val)); - } - mir::Instruction::IntEq(ins) => { - let op = Opcode::IntEq; - let reg = ins.register.0 as u16; - let left = ins.left.0 as u16; - let right = ins.right.0 as u16; - - buffer.push(Instruction::three(op, reg, left, right)); - } - mir::Instruction::StringEq(ins) => { - let op = Opcode::StringEq; - let reg = ins.register.0 as u16; - let left = ins.left.0 as u16; - let right = ins.right.0 as u16; - - buffer.push(Instruction::three(op, reg, left, right)); - } - mir::Instruction::RawInstruction(ins) => { - let mut args = [0, 0, 0, 0, 0]; - - for (idx, arg) in args.iter_mut().enumerate() { - if let Some(reg) = ins.arguments.get(idx) { - *arg = reg.0 as u16; - } else { - break; - } - } - - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::new(ins.opcode, args)); - } - mir::Instruction::RefKind(ins) => { - let op = Opcode::RefKind; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::two(op, reg, val)); - } - mir::Instruction::Send(ins) => { - let op = Opcode::ProcessSend; - let rec = ins.receiver.0 as u16; - let idx = self.method_info.get(&ins.method).unwrap().index; - let wait = ins.wait as u16; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, rec, idx, wait)); - } - mir::Instruction::SendAsync(ins) => { - let op = Opcode::ProcessSendAsync; - let reg = ins.register.0 as u16; - let rec = ins.receiver.0 as u16; - let idx = self.method_info.get(&ins.method).unwrap().index; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, reg, rec, idx)); - } - mir::Instruction::Drop(_) => { - // This instruction is expanded before converting MIR to - // bytecode, and thus shouldn't occur at this point. - unreachable!(); - } - } - } - - fn location_table( - &mut self, - buffer: &mut Buffer, - locations: Vec<(usize, LocationId)>, - ) { - let mut entries = Vec::new(); - - for (index, id) in locations { - let loc = self.mir.location(id); - let name = loc.method.name(self.db).clone(); - let file = loc.module.file(self.db).to_string_lossy().into_owned(); - let line = *loc.range.line_range.start() as u16; - let file_idx = self.constant_index(Constant::String(file)); - let name_idx = self.constant_index(Constant::String(name)); - - entries.push((index as u32, line, file_idx, name_idx)); - } - - buffer.write_u16(entries.len() as u16); - - for (index, line, file, name) in entries { - buffer.write_u32(index); - buffer.write_u16(line); - buffer.write_u32(file); - buffer.write_u32(name); - } - } - - fn constant_index(&mut self, constant: Constant) -> u32 { - let len = self.constant_indexes.len() as u32; - - *self.constant_indexes.entry(constant).or_insert(len) - } -} diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index 2a40cd104..0c99f8d10 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -1,20 +1,22 @@ -use crate::codegen; -use crate::config::{Config, IMAGE_EXT, SOURCE, SOURCE_EXT}; +use crate::config::{BuildDirectories, Output}; +use crate::config::{Config, SOURCE, SOURCE_EXT}; use crate::hir; +use crate::linker::link; +use crate::llvm; use crate::mir::{passes as mir, Mir}; use crate::modules_parser::{ModulesParser, ParsedModule}; use crate::state::State; use crate::type_check::define_types::{ - CheckTraitImplementations, CheckTypeParameters, DefineFields, - DefineTraitRequirements, DefineTypeParameterRequirements, - DefineTypeParameters, DefineTypes, DefineVariants, ImplementTraits, - InsertPrelude, + define_runtime_result_fields, CheckTraitImplementations, + CheckTypeParameters, DefineFields, DefineTraitRequirements, + DefineTypeParameterRequirements, DefineTypeParameters, DefineTypes, + DefineVariants, ImplementTraits, InsertPrelude, }; use crate::type_check::expressions::{DefineConstants, Expressions}; use crate::type_check::imports::DefineImportedTypes; use crate::type_check::methods::{ - define_builtin_functions, CheckMainMethod, DefineMethods, - DefineModuleMethodNames, ImplementTraitMethods, + CheckMainMethod, DefineMethods, DefineModuleMethodNames, + ImplementTraitMethods, }; use std::env::current_dir; use std::ffi::OsStr; @@ -52,31 +54,26 @@ impl Compiler { self.all_source_modules()? }; - let ast_modules = ModulesParser::new(&mut self.state).run(input); + let ast = ModulesParser::new(&mut self.state).run(input); + let hir = self.compile_hir(ast)?; - self.compile_to_mir(ast_modules)?; - Ok(()) + self.compile_mir(hir).map(|_| ()) } - pub fn compile_to_file( + pub fn run( &mut self, file: Option, ) -> Result { - let input = self.main_module_path(file)?; - let code = self.compile_bytecode(input.clone())?; - let path = self.write_code(input, code); - - Ok(path) - } + let file = self.main_module_path(file)?; + let main_mod = self.state.db.main_module().unwrap().clone(); + let ast = ModulesParser::new(&mut self.state) + .run(vec![(main_mod, file.clone())]); - pub fn compile_to_memory( - &mut self, - file: Option, - ) -> Result, CompileError> { - let input = self.main_module_path(file)?; - let code = self.compile_bytecode(input)?; + let hir = self.compile_hir(ast)?; + let mut mir = self.compile_mir(hir)?; - Ok(code.bytes) + self.optimise_mir(&mut mir); + self.compile_machine_code(&mir, file) } pub fn print_diagnostics(&self) { @@ -114,54 +111,45 @@ impl Compiler { Ok(path) } - fn compile_bytecode( + fn compile_mir( &mut self, - file: PathBuf, - ) -> Result { - let main_mod = self.state.db.main_module().unwrap().clone(); - let ast_modules = - ModulesParser::new(&mut self.state).run(vec![(main_mod, file)]); - - self.compile_to_mir(ast_modules).map(|mut mir| { - self.optimise_mir(&mut mir); - codegen::Lower::run_all(&self.state.db, mir) - }) - } - - fn compile_to_mir( - &mut self, - ast_modules: Vec, + mut modules: Vec, ) -> Result { - let mut hir_mods = if let Some(v) = self.lower_to_hir(ast_modules) { - v - } else { - return Err(CompileError::Invalid); - }; - - if !self.type_check(&mut hir_mods) { + if !self.check_types(&mut modules) { return Err(CompileError::Invalid); } - self.lower_to_mir(hir_mods) + let mut mir = Mir::new(); + + mir::check_global_limits(&mut self.state) + .map_err(CompileError::Internal)?; + + if mir::DefineConstants::run_all(&mut self.state, &mut mir, &modules) + && mir::LowerToMir::run_all(&mut self.state, &mut mir, modules) + { + Ok(mir) + } else { + Err(CompileError::Invalid) + } } - fn lower_to_hir( + fn compile_hir( &mut self, modules: Vec, - ) -> Option> { + ) -> Result, CompileError> { let hir = hir::LowerToHir::run_all(&mut self.state, modules); // Errors produced at this state are likely to result in us not being // able to compile the program properly (e.g. imported modules don't // exist), so we bail out right away. if self.state.diagnostics.has_errors() { - None + Err(CompileError::Invalid) } else { - Some(hir) + Ok(hir) } } - fn type_check(&mut self, modules: &mut Vec) -> bool { + fn check_types(&mut self, modules: &mut Vec) -> bool { let state = &mut self.state; DefineTypes::run_all(state, modules) @@ -176,62 +164,48 @@ impl Compiler { && CheckTypeParameters::run_all(state, modules) && DefineVariants::run_all(state, modules) && DefineFields::run_all(state, modules) + && define_runtime_result_fields(state) && DefineMethods::run_all(state, modules) && CheckMainMethod::run(state) && ImplementTraitMethods::run_all(state, modules) - && define_builtin_functions(state) && DefineConstants::run_all(state, modules) && Expressions::run_all(state, modules) } - fn lower_to_mir( - &mut self, - modules: Vec, - ) -> Result { - let state = &mut self.state; - let mut mir = Mir::new(); - - mir::check_global_limits(state).map_err(CompileError::Internal)?; - - if mir::DefineConstants::run_all(state, &mut mir, &modules) - && mir::LowerToMir::run_all(state, &mut mir, modules) - { - Ok(mir) - } else { - Err(CompileError::Invalid) - } - } - fn optimise_mir(&mut self, mir: &mut Mir) { mir::ExpandDrop::run_all(&self.state.db, mir); + mir::ExpandReference::run_all(&self.state.db, mir); mir::clean_up_basic_blocks(mir); } - fn write_code( - &self, + fn compile_machine_code( + &mut self, + mir: &Mir, main_file: PathBuf, - code: codegen::Bytecode, - ) -> PathBuf { - let path = self.state.config.output.clone().unwrap_or_else(|| { - let name = main_file - .file_stem() - .map(|s| s.to_string_lossy().into_owned()) - .unwrap_or_else(|| "main".to_string()); - - let dir = if self.state.config.build.is_dir() { - self.state.config.build.clone() - } else { - current_dir().unwrap_or_else(|_| PathBuf::new()) - }; + ) -> Result { + let dirs = BuildDirectories::new(&self.state.config); + + dirs.create().map_err(CompileError::Internal)?; + + let exe = match &self.state.config.output { + Output::Derive => { + let name = main_file + .file_stem() + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or_else(|| "main".to_string()); - let mut path = dir.join(name); + dirs.bin.join(name) + } + Output::File(name) => dirs.bin.join(name), + Output::Path(path) => path.clone(), + }; - path.set_extension(IMAGE_EXT); - path - }); + let objects = llvm::passes::Compile::run_all(&self.state, &dirs, mir) + .map_err(CompileError::Internal)?; - std::fs::write(&path, code.bytes).unwrap(); - path + link(&self.state.config, &exe, &objects) + .map_err(CompileError::Internal)?; + Ok(exe) } fn module_name_from_path(&self, file: &Path) -> ModuleName { @@ -239,11 +213,11 @@ impl Compiler { .ok() .or_else(|| file.strip_prefix(&self.state.config.tests).ok()) .or_else(|| { - // This allows us to check e.g. `./libstd/src/std/string.inko` + // This allows us to check e.g. `./std/src/std/string.inko` // while the current working directory is `.`. This is useful // when e.g. checking files using a text editor, as they would // likely have the working directory set to `.` and not - // `./libstd`. + // `./std`. let mut components = file.components(); if components.any(|c| c.as_os_str() == SOURCE) { diff --git a/compiler/src/config.rs b/compiler/src/config.rs index 633f0ec77..a43ecdc73 100644 --- a/compiler/src/config.rs +++ b/compiler/src/config.rs @@ -1,15 +1,14 @@ //! Configuration for the compiler. use crate::presenters::{JSONPresenter, Presenter, TextPresenter}; use crate::source_paths::SourcePaths; +use crate::target::Target; use std::env; -use std::path::PathBuf; +use std::fs::create_dir_all; +use std::path::{Path, PathBuf}; use types::module_name::ModuleName; /// The extension to use for source files. -pub const SOURCE_EXT: &str = "inko"; - -/// The extension to use for bytecode files. -pub const IMAGE_EXT: &str = "ibi"; +pub(crate) const SOURCE_EXT: &str = "inko"; /// The name of the module to compile if no explicit file/module is provided. pub(crate) const MAIN_MODULE: &str = "main"; @@ -26,11 +25,91 @@ const TESTS: &str = "test"; /// The name of the directory to store build files in. const BUILD: &str = "build"; +fn create_directory(path: &Path) -> Result<(), String> { + if path.is_dir() { + return Ok(()); + } + + create_dir_all(path) + .map_err(|err| format!("Failed to create {}: {}", path.display(), err)) +} + +/// A type storing the various build directories to use. +pub(crate) struct BuildDirectories { + /// The base build directory. + pub(crate) build: PathBuf, + + /// The directory to store object files in. + pub(crate) objects: PathBuf, + + /// The directory to place executable files in. + pub(crate) bin: PathBuf, +} + +impl BuildDirectories { + pub(crate) fn new(config: &Config) -> BuildDirectories { + let build = config.build.join(config.mode.directory_name()); + let objects = build.join("objects"); + let bin = build.clone(); + + BuildDirectories { build, objects, bin } + } + + pub(crate) fn create(&self) -> Result<(), String> { + create_directory(&self.build) + .and_then(|_| create_directory(&self.objects)) + .and_then(|_| create_directory(&self.bin)) + } +} + +/// A type describing to what degree a program should be optimised. +#[derive(Clone, Copy)] +pub enum Mode { + /// A mode suitable for development and debugging. + /// + /// This mode favours fast compile times over runtime performance. For + /// releases/deployments you should use the dedicated release mode. + Debug, + + /// A mode suitable for releases. + /// + /// In this mode a reasonable number of optimisations is enabled, such that + /// there's a healthy balance between runtime performance and compile times. + Release, +} + +impl Mode { + pub(crate) fn directory_name(self) -> &'static str { + match self { + Mode::Debug => "debug", + Mode::Release => "release", + } + } +} + +/// A type describing where to write the executable to. +pub enum Output { + /// Derive the output path from the main module, and place it in the default + /// output directory. + Derive, + + /// Write the executable to the default output directory, but using the + /// given name. + File(String), + + /// Write the executable to the given path. + Path(PathBuf), +} + /// A type for storing compiler configuration, such as the source directories to /// search for modules. pub struct Config { /// The directory containing the Inko's standard library. - pub(crate) libstd: PathBuf, + pub(crate) std: PathBuf, + + /// The directory containing runtime library files to link to the generated + /// code. + pub runtime: PathBuf, /// The directory containing the project's source code. pub(crate) source: PathBuf, @@ -48,51 +127,49 @@ pub struct Config { /// third-party dependencies. pub sources: SourcePaths, + /// The path to save the executable at. + pub output: Output, + + /// The optimisation mode to apply when compiling code. + pub mode: Mode, + /// The presenter to use for displaying diagnostics. pub(crate) presenter: Box, /// Modules to implicitly import and process. pub(crate) implicit_imports: Vec, - /// The file to write a compiled bytecode file to. - pub output: Option, + /// The target to compile code for. + pub(crate) target: Target, } impl Config { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::new()); - let libstd = option_env!("INKO_LIBSTD") - .map(PathBuf::from) - .unwrap_or_else(|| { - // To ease the development process, we default to the standard - // library directory in the Git repository. This way you don't - // need to set any environment variables during development. - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join("libstd") - .join(SOURCE) - }); + let std = PathBuf::from(env!("INKO_STD")); Self { - libstd, + std, + runtime: PathBuf::from(env!("INKO_RT")), source: cwd.join(SOURCE), tests: cwd.join(TESTS), build: cwd.join(BUILD), - dependencies: cwd.join(DEP), + dependencies: cwd.join(DEP).join(SOURCE), sources: SourcePaths::new(), presenter: Box::new(TextPresenter::with_colors()), implicit_imports: vec![], - output: None, + output: Output::Derive, + target: Target::native(), + mode: Mode::Debug, } } fn add_default_source_directories(&mut self) { - if self.libstd.is_dir() { - self.sources.add(self.libstd.clone()); + if self.std.is_dir() { + self.sources.add(self.std.clone()); } - if self.source.is_dir() && self.source != self.libstd { + if self.source.is_dir() && self.source != self.std { self.sources.add(self.source.clone()); } @@ -116,6 +193,15 @@ impl Config { Ok(()) } + pub fn set_target(&mut self, name: &str) -> Result<(), String> { + if let Some(val) = Target::from_str(name) { + self.target = val; + Ok(()) + } else { + Err(format!("The target '{}' isn't supported", name)) + } + } + pub(crate) fn main_source_module(&self) -> PathBuf { let mut main_file = self.source.join(MAIN_MODULE); diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index e07bb5079..03fced2da 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -8,29 +8,21 @@ use std::path::PathBuf; pub(crate) enum DiagnosticId { DuplicateSymbol, InvalidAssign, - InvalidBound, InvalidCall, - InvalidClass, InvalidConstExpr, InvalidFile, InvalidImplementation, InvalidMethod, InvalidSyntax, - InvalidTry, InvalidType, MissingTrait, - PrivateSymbol, InvalidSymbol, InvalidLoopKeyword, InvalidThrow, MissingField, - InvalidRef, InvalidPattern, - InvalidField, - MissingThrow, Unreachable, - MovedVariable, - InvalidMove, + Moved, InvalidMatch, LimitReached, MissingMain, @@ -47,23 +39,15 @@ impl fmt::Display for DiagnosticId { DiagnosticId::InvalidSymbol => "invalid-symbol", DiagnosticId::InvalidType => "invalid-type", DiagnosticId::MissingTrait => "missing-trait", - DiagnosticId::InvalidBound => "invalid-bound", DiagnosticId::InvalidMethod => "invalid-method", DiagnosticId::InvalidImplementation => "invalid-implementation", - DiagnosticId::InvalidClass => "invalid-class", - DiagnosticId::PrivateSymbol => "private-symbol", - DiagnosticId::InvalidTry => "invalid-try", DiagnosticId::InvalidAssign => "invalid-assign", DiagnosticId::InvalidLoopKeyword => "invalid-loop-keyword", DiagnosticId::InvalidThrow => "invalid-throw", DiagnosticId::MissingField => "missing-field", - DiagnosticId::InvalidRef => "invalid-ref", DiagnosticId::InvalidPattern => "invalid-pattern", - DiagnosticId::InvalidField => "invalid-field", - DiagnosticId::MissingThrow => "missing-throw", DiagnosticId::Unreachable => "unreachable", - DiagnosticId::MovedVariable => "moved-variable", - DiagnosticId::InvalidMove => "invalid-move", + DiagnosticId::Moved => "moved", DiagnosticId::InvalidMatch => "invalid-match", DiagnosticId::LimitReached => "limit-reached", DiagnosticId::MissingMain => "missing-main", @@ -304,7 +288,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!("The field '{}' is private", name), file, location, @@ -321,7 +305,7 @@ impl Diagnostics { self.error( DiagnosticId::InvalidType, format!( - "Incorrect type: expected '{}', found '{}'", + "Expected a value of type '{}', found '{}'", expected, given ), file, @@ -392,20 +376,6 @@ impl Diagnostics { ); } - pub(crate) fn throw_not_allowed( - &mut self, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::InvalidThrow, - "Throwing isn't allowed, as the surrounding closure or method \ - doesn't specify a throw type", - file, - location, - ); - } - pub(crate) fn incorrect_call_arguments( &mut self, given: usize, @@ -539,37 +509,6 @@ impl Diagnostics { ); } - pub(crate) fn never_throws( - &mut self, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::InvalidTry, - "This expression never throws", - file, - location, - ); - } - - pub(crate) fn missing_throw( - &mut self, - name: String, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::MissingThrow, - format!( - "A value of type '{}' is expected to be thrown, \ - but no value is ever thrown", - name - ), - file, - location, - ); - } - pub(crate) fn unreachable( &mut self, file: PathBuf, @@ -602,33 +541,35 @@ impl Diagnostics { ); } - pub(crate) fn unsendable_type( + pub(crate) fn unsendable_argument( &mut self, - name: String, + argument: String, file: PathBuf, location: SourceLocation, ) { self.error( DiagnosticId::InvalidType, format!( - "Values of type '{}' can't be sent between processes", - name + "The receiver of this call requires sendable arguments, \ + but '{}' isn't sendable", + argument, ), file, location, ); } - pub(crate) fn unsendable_type_in_recover( + pub(crate) fn unsendable_return_type( &mut self, name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidType, + DiagnosticId::InvalidCall, format!( - "Values of type '{}' can't be captured by recover expressions", + "The receiver of this call requires a sendable return type, \ + but '{}' isn't sendable", name ), file, @@ -636,61 +577,57 @@ impl Diagnostics { ); } - pub(crate) fn unsendable_field_value( + pub(crate) fn unsendable_async_type( &mut self, - field_name: &str, - type_name: String, + name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidSymbol, + DiagnosticId::InvalidType, format!( - "The field '{}' can't be assigned a value of type '{}', \ - as it's not sendable", - field_name, type_name + "Values of type '{}' can't be sent between processes", + name ), file, location, ); } - pub(crate) fn unsendable_return_type( + pub(crate) fn unsendable_type_in_recover( &mut self, - name: &str, - type_name: String, + name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidCall, + DiagnosticId::InvalidType, format!( - "The method '{}' isn't available because its receiver is a \ - unique value, and the return type ('{}') isn't sendable", - name, type_name + "Values of type '{}' can't be captured by recover expressions", + name ), file, location, ); } - pub(crate) fn unsendable_throw_type( + pub(crate) fn unsendable_field_value( &mut self, - name: &str, + field_name: &str, type_name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidCall, + DiagnosticId::InvalidSymbol, format!( - "The method '{}' isn't available because its receiver is a \ - unique value, and the throw type ('{}') isn't sendable", - name, type_name + "The field '{}' can't be assigned a value of type '{}', \ + as it's not sendable", + field_name, type_name ), file, location, - ) + ); } pub(crate) fn self_in_closure_in_recover( @@ -732,7 +669,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::MovedVariable, + DiagnosticId::Moved, format!("'{}' can't be used as it has been moved", name), file, location, @@ -746,7 +683,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::MovedVariable, + DiagnosticId::Moved, format!("'{}' can't be used, as 'self' has been moved", name), file, location, @@ -760,7 +697,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::InvalidMove, + DiagnosticId::Moved, format!( "This closure can't capture '{}', as '{}' has been moved", name, name, @@ -777,7 +714,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::InvalidMove, + DiagnosticId::Moved, format!( "'{}' can't be moved inside a loop, as its value \ would be unavailable in the next iteration", @@ -805,20 +742,38 @@ impl Diagnostics { ); } - pub(crate) fn cant_infer_throw_type( + pub(crate) fn cant_assign_type( &mut self, + name: &str, file: PathBuf, location: SourceLocation, ) { self.error( DiagnosticId::InvalidType, - "The throw type of this expression can't be inferred", + format!( + "Values of type '{}' can't be assigned to variables or fields", + name + ), + file, + location, + ) + } + + pub(crate) fn string_literal_too_large( + &mut self, + limit: usize, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::LimitReached, + format!("String literals can't be greater than {} bytes", limit), file, location, ); } - pub(crate) fn cant_assign_type( + pub(crate) fn type_parameter_already_mutable( &mut self, name: &str, file: PathBuf, @@ -826,24 +781,71 @@ impl Diagnostics { ) { self.error( DiagnosticId::InvalidType, + format!("The type parameter '{}' is already mutable", name), + file, + location, + ); + } + + pub(crate) fn invalid_try( + &mut self, + name: String, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidThrow, format!( - "Values of type '{}' can't be assigned to variables or fields", + "This expression must return an 'Option' or 'Result', \ + found '{}'", name ), file, location, - ) + ); } - pub(crate) fn string_literal_too_large( + pub(crate) fn try_not_available( &mut self, - limit: usize, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::LimitReached, - format!("String literals can't be greater than {} bytes", limit), + DiagnosticId::InvalidThrow, + "'try' can only be used in methods that return an \ + 'Option' or 'Result'", + file, + location, + ); + } + + pub(crate) fn throw_not_available( + &mut self, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidThrow, + "'throw' can only be used in methods that return a 'Result'", + file, + location, + ); + } + + pub(crate) fn invalid_throw( + &mut self, + error: String, + returns: String, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidThrow, + format!( + "Can't throw '{}' as the surrounding method \ + returns a '{}'", + error, returns, + ), file, location, ); diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 92e5efa00..6956e79c1 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -11,9 +11,7 @@ use ::ast::source_location::SourceLocation; use std::path::PathBuf; use std::str::FromStr; -const SET_INDEX_METHOD: &str = "set_index"; const BUILTIN_RECEIVER: &str = "_INKO"; -const TRY_BINDING_VAR: &str = "$error"; #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct IntLiteral { @@ -115,16 +113,6 @@ pub(crate) struct Call { pub(crate) receiver: Option, pub(crate) name: Identifier, pub(crate) arguments: Vec, - pub(crate) else_block: Option, - pub(crate) location: SourceLocation, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct AsyncCall { - pub(crate) info: Option, - pub(crate) receiver: Expression, - pub(crate) name: Identifier, - pub(crate) arguments: Vec, pub(crate) location: SourceLocation, } @@ -133,7 +121,6 @@ pub(crate) struct BuiltinCall { pub(crate) info: Option, pub(crate) name: Identifier, pub(crate) arguments: Vec, - pub(crate) else_block: Option, pub(crate) location: SourceLocation, } @@ -179,7 +166,6 @@ pub(crate) struct AssignSetter { pub(crate) receiver: Expression, pub(crate) name: Identifier, pub(crate) value: Expression, - pub(crate) else_block: Option, pub(crate) location: SourceLocation, } @@ -226,7 +212,6 @@ pub(crate) struct DefineInstanceMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -239,7 +224,6 @@ pub(crate) struct DefineModuleMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -253,7 +237,6 @@ pub(crate) struct DefineRequiredMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) method_id: Option, pub(crate) location: SourceLocation, @@ -265,7 +248,6 @@ pub(crate) struct DefineStaticMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -279,7 +261,6 @@ pub(crate) struct DefineAsyncMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -383,6 +364,7 @@ pub(crate) struct ReopenClass { pub(crate) class_id: Option, pub(crate) class_name: Constant, pub(crate) body: Vec, + pub(crate) bounds: Vec, pub(crate) location: SourceLocation, } @@ -397,6 +379,7 @@ pub(crate) enum ReopenClassExpression { pub(crate) struct TypeBound { pub(crate) name: Constant, pub(crate) requirements: Vec, + pub(crate) mutable: bool, pub(crate) location: SourceLocation, } @@ -419,11 +402,11 @@ pub(crate) struct Scope { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct Index { - pub(crate) info: Option, - pub(crate) receiver: Expression, - pub(crate) index: Expression, +pub(crate) struct Try { + pub(crate) expression: Expression, pub(crate) location: SourceLocation, + pub(crate) kind: types::ThrowKind, + pub(crate) return_type: types::TypeRef, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -435,7 +418,6 @@ pub(crate) enum Expression { AssignSetter(Box), AssignVariable(Box), ReplaceVariable(Box), - AsyncCall(Box), Break(Box), BuiltinCall(Box), Call(Box), @@ -446,10 +428,8 @@ pub(crate) enum Expression { FieldRef(Box), Float(Box), IdentifierRef(Box), - Index(Box), ClassLiteral(Box), Int(Box), - Invalid(Box), Loop(Box), Match(Box), Mut(Box), @@ -466,6 +446,7 @@ pub(crate) enum Expression { Tuple(Box), TypeCast(Box), Recover(Box), + Try(Box), } impl Expression { @@ -478,7 +459,6 @@ impl Expression { Expression::AssignSetter(ref n) => &n.location, Expression::AssignVariable(ref n) => &n.location, Expression::ReplaceVariable(ref n) => &n.location, - Expression::AsyncCall(ref n) => &n.location, Expression::Break(ref n) => &n.location, Expression::BuiltinCall(ref n) => &n.location, Expression::Call(ref n) => &n.location, @@ -489,10 +469,8 @@ impl Expression { Expression::FieldRef(ref n) => &n.location, Expression::Float(ref n) => &n.location, Expression::IdentifierRef(ref n) => &n.location, - Expression::Index(ref n) => &n.location, Expression::ClassLiteral(ref n) => &n.location, Expression::Int(ref n) => &n.location, - Expression::Invalid(ref n) => n, Expression::Loop(ref n) => &n.location, Expression::Match(ref n) => &n.location, Expression::Mut(ref n) => &n.location, @@ -509,6 +487,7 @@ impl Expression { Expression::Tuple(ref n) => &n.location, Expression::TypeCast(ref n) => &n.location, Expression::Recover(ref n) => &n.location, + Expression::Try(ref n) => &n.location, } } @@ -552,6 +531,15 @@ impl ConstExpression { Self::Invalid(ref l) => l, } } + + pub(crate) fn is_simple_literal(&self) -> bool { + matches!( + self, + ConstExpression::Int(_) + | ConstExpression::Float(_) + | ConstExpression::String(_) + ) + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -559,6 +547,7 @@ pub(crate) struct TypeParameter { pub(crate) type_parameter_id: Option, pub(crate) name: Constant, pub(crate) requirements: Vec, + pub(crate) mutable: bool, pub(crate) location: SourceLocation, } @@ -614,7 +603,6 @@ pub(crate) enum ReferrableType { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct ClosureType { pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) location: SourceLocation, pub(crate) resolved_type: types::TypeRef, @@ -741,7 +729,6 @@ pub(crate) struct Closure { pub(crate) resolved_type: types::TypeRef, pub(crate) moving: bool, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -846,6 +833,7 @@ pub(crate) struct TypeCast { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Throw { pub(crate) resolved_type: types::TypeRef, + pub(crate) return_type: types::TypeRef, pub(crate) value: Expression, pub(crate) location: SourceLocation, } @@ -857,13 +845,6 @@ pub(crate) struct Return { pub(crate) location: SourceLocation, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct ElseBlock { - pub(crate) body: Vec, - pub(crate) argument: Option, - pub(crate) location: SourceLocation, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct TuplePattern { pub(crate) field_ids: Vec, @@ -1088,7 +1069,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1202,7 +1182,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1223,7 +1202,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1235,8 +1213,6 @@ impl<'a> LowerToHir<'a> { &mut self, node: ast::DefineMethod, ) -> DefineInstanceMethod { - self.check_operator_requirements(&node); - DefineInstanceMethod { public: node.public, kind: match node.kind { @@ -1248,7 +1224,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1260,8 +1235,6 @@ impl<'a> LowerToHir<'a> { &mut self, node: ast::DefineMethod, ) -> Box { - self.check_operator_requirements(&node); - Box::new(DefineRequiredMethod { public: node.public, kind: match node.kind { @@ -1273,7 +1246,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), method_id: None, location: node.location, @@ -1281,7 +1253,7 @@ impl<'a> LowerToHir<'a> { } fn optional_type_bounds( - &self, + &mut self, node: Option, ) -> Vec { if let Some(types) = node { @@ -1291,12 +1263,30 @@ impl<'a> LowerToHir<'a> { } } - fn type_bound(&self, node: ast::TypeBound) -> TypeBound { - TypeBound { - name: self.constant(node.name), - requirements: self.type_names(node.requirements), - location: node.location, + fn type_bound(&mut self, node: ast::TypeBound) -> TypeBound { + let name = self.constant(node.name); + let mut mutable = false; + let mut requirements = Vec::new(); + + for req in node.requirements.values { + match req { + ast::Requirement::Trait(n) => { + requirements.push(self.type_name(n)) + } + ast::Requirement::Mutable(loc) if mutable => { + self.state.diagnostics.type_parameter_already_mutable( + &name.name, + self.file(), + loc, + ); + } + ast::Requirement::Mutable(_) => { + mutable = true; + } + } } + + TypeBound { name, requirements, mutable, location: node.location } } fn define_trait(&mut self, node: ast::DefineTrait) -> TopLevelExpression { @@ -1340,6 +1330,7 @@ impl<'a> LowerToHir<'a> { class_id: None, class_name: self.constant(node.class_name), body: self.reopen_class_expressions(node.body), + bounds: self.optional_type_bounds(node.bounds), location: node.location, })) } @@ -1512,7 +1503,6 @@ impl<'a> LowerToHir<'a> { fn closure_type(&self, node: ast::ClosureType) -> Box { Box::new(ClosureType { arguments: self.optional_types(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), location: node.location, resolved_type: types::TypeRef::Unknown, @@ -1553,9 +1543,36 @@ impl<'a> LowerToHir<'a> { fn type_parameter(&mut self, node: ast::TypeParameter) -> TypeParameter { let name = self.constant(node.name); let location = node.location; - let requirements = self.optional_type_names(node.requirements); + let mut mutable = false; + let mut requirements = Vec::new(); + + if let Some(reqs) = node.requirements { + for req in reqs.values { + match req { + ast::Requirement::Trait(n) => { + requirements.push(self.type_name(n)) + } + ast::Requirement::Mutable(loc) if mutable => { + self.state.diagnostics.type_parameter_already_mutable( + &name.name, + self.file(), + loc, + ); + } + ast::Requirement::Mutable(_) => { + mutable = true; + } + } + } + } - TypeParameter { type_parameter_id: None, name, requirements, location } + TypeParameter { + type_parameter_id: None, + name, + requirements, + location, + mutable, + } } fn optional_type_names( @@ -1717,7 +1734,6 @@ impl<'a> LowerToHir<'a> { location: loc.clone(), }, arguments: Vec::new(), - else_block: None, location: loc, })) } @@ -1884,7 +1900,7 @@ impl<'a> LowerToHir<'a> { Expression::Float(self.float_literal(*node)) } ast::Expression::Binary(node) => { - Expression::Call(self.binary(*node, None)) + Expression::Call(self.binary(*node)) } ast::Expression::Field(node) => { Expression::FieldRef(self.field_ref(*node)) @@ -1896,7 +1912,6 @@ impl<'a> LowerToHir<'a> { Expression::IdentifierRef(self.identifier_ref(*node)) } ast::Expression::Call(node) => self.call(*node), - ast::Expression::Async(node) => self.async_call(*node), ast::Expression::AssignVariable(node) => { Expression::AssignVariable(self.assign_variable(*node)) } @@ -1964,12 +1979,6 @@ impl<'a> LowerToHir<'a> { ast::Expression::TypeCast(node) => { Expression::TypeCast(self.type_cast(*node)) } - ast::Expression::Index(node) => { - Expression::Index(self.index_expression(*node)) - } - ast::Expression::SetIndex(node) => { - Expression::Call(self.set_index_expression(*node)) - } ast::Expression::Throw(node) => { Expression::Throw(self.throw_expression(*node)) } @@ -1977,7 +1986,6 @@ impl<'a> LowerToHir<'a> { Expression::Return(self.return_expression(*node)) } ast::Expression::Try(node) => self.try_expression(*node), - ast::Expression::TryPanic(node) => self.try_panic(*node), ast::Expression::If(node) => { Expression::Match(self.if_expression(*node)) } @@ -2005,11 +2013,7 @@ impl<'a> LowerToHir<'a> { } } - fn binary( - &mut self, - node: ast::Binary, - else_block: Option, - ) -> Box { + fn binary(&mut self, node: ast::Binary) -> Box { let op = self.binary_operator(&node.operator); Box::new(Call { @@ -2022,7 +2026,6 @@ impl<'a> LowerToHir<'a> { arguments: vec![Argument::Positional(Box::new( self.expression(node.right), ))], - else_block, location: node.location, }) } @@ -2067,7 +2070,6 @@ impl<'a> LowerToHir<'a> { info: None, name: self.identifier(node.name), arguments: self.optional_builtin_call_arguments(node.arguments), - else_block: None, location: node.location, })); } @@ -2077,7 +2079,6 @@ impl<'a> LowerToHir<'a> { receiver: node.receiver.map(|n| self.expression(n)), name: self.identifier(node.name), arguments: self.optional_call_arguments(node.arguments), - else_block: None, location: node.location, })) } @@ -2145,46 +2146,6 @@ impl<'a> LowerToHir<'a> { } } - fn async_call(&mut self, async_node: ast::Async) -> Expression { - let result = match self.expression(async_node.expression) { - Expression::Call(node) => { - if let Some(receiver) = node.receiver { - return Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver, - name: node.name, - arguments: node.arguments, - location: async_node.location, - })); - } - - Expression::Call(node) - } - Expression::AssignSetter(node) => { - return Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver: node.receiver, - name: Identifier { - name: node.name.name + "=", - location: node.name.location, - }, - arguments: vec![Argument::Positional(Box::new(node.value))], - location: node.location, - })); - } - expr => expr, - }; - - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - "The 'async' keyword requires a method call with a receiver", - self.file(), - async_node.location, - ); - - result - } - fn assign_variable( &mut self, node: ast::AssignVariable, @@ -2256,7 +2217,6 @@ impl<'a> LowerToHir<'a> { arguments: vec![Argument::Positional(Box::new( self.expression(node.value), ))], - else_block: None, location: node.location.clone(), })), resolved_type: types::TypeRef::Unknown, @@ -2290,7 +2250,6 @@ impl<'a> LowerToHir<'a> { arguments: vec![Argument::Positional(Box::new( self.expression(node.value), ))], - else_block: None, location: node.location.clone(), })), resolved_type: types::TypeRef::Unknown, @@ -2304,7 +2263,6 @@ impl<'a> LowerToHir<'a> { receiver: self.expression(node.receiver), name: self.identifier(node.name), value: self.expression(node.value), - else_block: None, location: node.location, }) } @@ -2323,7 +2281,6 @@ impl<'a> LowerToHir<'a> { receiver: Some(setter_rec.clone()), name: name.clone(), arguments: Vec::new(), - else_block: None, location: getter_loc, })); @@ -2341,10 +2298,8 @@ impl<'a> LowerToHir<'a> { name: op.method_name().to_string(), location: node.operator.location, }, - else_block: None, location: node.location.clone(), })), - else_block: None, location: node.location, }) } @@ -2355,7 +2310,6 @@ impl<'a> LowerToHir<'a> { resolved_type: types::TypeRef::Unknown, moving: node.moving, arguments: self.optional_block_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.expressions(node.body), location: node.location, @@ -2488,35 +2442,10 @@ impl<'a> LowerToHir<'a> { }) } - fn index_expression(&mut self, node: ast::Index) -> Box { - Box::new(Index { - info: None, - receiver: self.expression(node.receiver), - index: self.expression(node.index), - location: node.location, - }) - } - - fn set_index_expression(&mut self, node: ast::SetIndex) -> Box { - Box::new(Call { - kind: types::CallKind::Unknown, - receiver: Some(self.expression(node.receiver)), - name: Identifier { - name: SET_INDEX_METHOD.to_string(), - location: node.location.clone(), - }, - arguments: vec![ - Argument::Positional(Box::new(self.expression(node.index))), - Argument::Positional(Box::new(self.expression(node.value))), - ], - else_block: None, - location: node.location, - }) - } - fn throw_expression(&mut self, node: ast::Throw) -> Box { Box::new(Throw { resolved_type: types::TypeRef::Unknown, + return_type: types::TypeRef::Unknown, value: self.expression(node.value), location: node.location, }) @@ -2532,130 +2461,13 @@ impl<'a> LowerToHir<'a> { }) } - /// Desugars a `try` expression such that it always has an explicit `else`. - /// - /// This desugars this: - /// - /// try x - /// - /// Into this: - /// - /// try x else (error) throw error - /// - /// If an explicit `else` is already present, no desugaring is applied. fn try_expression(&mut self, node: ast::Try) -> Expression { - let else_block = if let Some(else_block) = node.else_block { - let body = self.expressions(else_block.body); - let binding = else_block.argument.map(|n| self.block_argument(n)); - - ElseBlock { body, argument: binding, location: else_block.location } - } else { - let location = node.location.clone(); - let throw = self.throw_variable(TRY_BINDING_VAR, location.clone()); - let binding = self.generated_variable_definition( - TRY_BINDING_VAR, - location.clone(), - ); - - ElseBlock { body: vec![throw], argument: Some(binding), location } - }; - - self.try_else(node.try_block, else_block, node.location) - } - - /// Desugars a `try!` expression into a `try` that panics. - /// - /// Expressions like this: - /// - /// try! x - /// - /// Are desugared into this: - /// - /// try x else (error) _INKO.panic(error.to_string) - fn try_panic(&mut self, node: ast::TryPanic) -> Expression { - let location = node.location.clone(); - let panic = self.hidden_panic(TRY_BINDING_VAR, location.clone()); - let binding = self - .generated_variable_definition(TRY_BINDING_VAR, location.clone()); - let else_block = - ElseBlock { body: vec![panic], argument: Some(binding), location }; - - self.try_else(node.try_block, else_block, node.location) - } - - fn try_else( - &mut self, - try_block: ast::TryBlock, - else_block: ElseBlock, - location: SourceLocation, - ) -> Expression { - match try_block.value { - ast::Expression::Identifier(n) => { - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: self.identifier(*n), - arguments: Vec::new(), - else_block: Some(else_block), - location, - })) - } - ast::Expression::Constant(n) => Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { name: n.name, location: n.location }, - arguments: Vec::new(), - else_block: Some(else_block), - location, - })), - ast::Expression::Call(call) => { - if self.is_builtin_call(&call) { - if !self.module.is_std(&self.state.db) { - self.state.diagnostics.invalid_builtin_function( - self.file(), - call.location.clone(), - ); - } - - Expression::BuiltinCall(Box::new(BuiltinCall { - info: None, - name: self.identifier(call.name), - arguments: self - .optional_builtin_call_arguments(call.arguments), - else_block: Some(else_block), - location, - })) - } else { - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: call.receiver.map(|n| self.expression(n)), - name: self.identifier(call.name), - arguments: self.optional_call_arguments(call.arguments), - else_block: Some(else_block), - location, - })) - } - } - ast::Expression::AssignSetter(node) => { - Expression::AssignSetter(Box::new(AssignSetter { - kind: types::CallKind::Unknown, - receiver: self.expression(node.receiver), - name: self.identifier(node.name), - value: self.expression(node.value), - else_block: Some(else_block), - location, - })) - } - ast::Expression::Binary(node) => { - Expression::Call(self.binary(*node, Some(else_block))) - } - _ => { - let loc = try_block.value.location().clone(); - - self.state.diagnostics.never_throws(self.file(), loc.clone()); - Expression::Invalid(Box::new(loc)) - } - } + Expression::Try(Box::new(Try { + expression: self.expression(node.expression), + kind: types::ThrowKind::Unknown, + location: node.location, + return_type: types::TypeRef::Unknown, + })) } fn if_expression(&mut self, node: ast::If) -> Box { @@ -2914,61 +2726,6 @@ impl<'a> LowerToHir<'a> { nodes.into_iter().map(|n| self.pattern(n)).collect() } - fn throw_variable( - &self, - name: &str, - location: SourceLocation, - ) -> Expression { - Expression::Throw(Box::new(Throw { - resolved_type: types::TypeRef::Unknown, - value: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: name.to_string(), - location: location.clone(), - })), - location, - })) - } - - fn hidden_panic( - &self, - variable: &str, - location: SourceLocation, - ) -> Expression { - Expression::BuiltinCall(Box::new(BuiltinCall { - info: None, - name: Identifier { - name: types::CompilerMacro::PanicThrown.name().to_string(), - location: location.clone(), - }, - arguments: vec![Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: variable.to_string(), - location: location.clone(), - }, - ))], - else_block: None, - location, - })) - } - - fn generated_variable_definition( - &self, - name: &str, - location: SourceLocation, - ) -> BlockArgument { - BlockArgument { - variable_id: None, - name: Identifier { - name: name.to_string(), - location: location.clone(), - }, - value_type: None, - location, - } - } - fn break_expression(&self, location: SourceLocation) -> Expression { Expression::Break(Box::new(Break { location })) } @@ -2989,21 +2746,6 @@ impl<'a> LowerToHir<'a> { location.clone(), ); } - - fn check_operator_requirements(&mut self, node: &ast::DefineMethod) { - if !node.operator { - return; - } - - if let Some(throws) = node.throw_type.as_ref() { - self.state.diagnostics.error( - DiagnosticId::InvalidMethod, - "Operator methods can't throw", - self.file(), - throws.location().clone(), - ); - } - } } #[cfg(test)] @@ -3374,7 +3116,7 @@ mod tests { #[test] fn test_lower_closure_type() { - let hir = lower_type("fn (A) !! B -> C"); + let hir = lower_type("fn (A) -> C"); assert_eq!( hir, @@ -3389,27 +3131,17 @@ mod tests { arguments: Vec::new(), location: cols(13, 13) }))], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "B".to_string(), - location: cols(19, 19) - }, - arguments: Vec::new(), - location: cols(19, 19) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "C".to_string(), - location: cols(24, 24) + location: cols(19, 19) }, arguments: Vec::new(), - location: cols(24, 24) + location: cols(19, 19) }))), - location: cols(9, 24), + location: cols(9, 19), resolved_type: types::TypeRef::Unknown, })) ); @@ -3417,8 +3149,7 @@ mod tests { #[test] fn test_lower_module_method() { - let (hir, diags) = - lower_top_expr("fn foo[A: X](a: B) !! C -> D { 10 }"); + let (hir, diags) = lower_top_expr("fn foo[A: X](a: B) -> D { 10 }"); assert_eq!(diags, 0); assert_eq!( @@ -3445,6 +3176,7 @@ mod tests { arguments: Vec::new(), location: cols(11, 11) }], + mutable: false, location: cols(8, 11) }], arguments: vec![MethodArgument { @@ -3464,33 +3196,23 @@ mod tests { })), location: cols(14, 17) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(23, 23) - }, - arguments: Vec::new(), - location: cols(23, 23) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(28, 28) + location: cols(23, 23) }, arguments: Vec::new(), - location: cols(28, 28) + location: cols(23, 23) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(32, 33) + location: cols(27, 28) }))], method_id: None, - location: cols(1, 35), + location: cols(1, 30), })), ); } @@ -3522,6 +3244,7 @@ mod tests { arguments: Vec::new(), location: cols(12, 12) }], + mutable: false, location: cols(9, 12) }], body: vec![ClassExpression::Field(Box::new(DefineField { @@ -3635,6 +3358,7 @@ mod tests { arguments: Vec::new(), location: cols(20, 20) }], + mutable: false, location: cols(17, 20) }], body: vec![ClassExpression::Field(Box::new(DefineField { @@ -3685,8 +3409,7 @@ mod tests { #[test] fn test_lower_class_with_static_method() { let hir = - lower_top_expr("class A { fn static a[A](b: B) !! C -> D { 10 } }") - .0; + lower_top_expr("class A { fn static a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -3710,6 +3433,7 @@ mod tests { location: cols(23, 23) }, requirements: Vec::new(), + mutable: false, location: cols(23, 23) }], arguments: vec![MethodArgument { @@ -3729,36 +3453,26 @@ mod tests { })), location: cols(26, 29) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(35, 35) - }, - arguments: Vec::new(), - location: cols(35, 35) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(40, 40) + location: cols(35, 35) }, arguments: Vec::new(), - location: cols(40, 40) + location: cols(35, 35) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(44, 45) + location: cols(39, 40) }))], method_id: None, - location: cols(11, 47), + location: cols(11, 42), } ))], - location: cols(1, 49) + location: cols(1, 44) })), ); } @@ -3766,8 +3480,7 @@ mod tests { #[test] fn test_lower_class_with_async_method() { let hir = - lower_top_expr("class A { fn async a[A](b: B) !! C -> D { 10 } }") - .0; + lower_top_expr("class A { fn async a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -3792,6 +3505,7 @@ mod tests { location: cols(22, 22) }, requirements: Vec::new(), + mutable: false, location: cols(22, 22) }], arguments: vec![MethodArgument { @@ -3811,44 +3525,33 @@ mod tests { })), location: cols(25, 28) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(34, 34) - }, - arguments: Vec::new(), - location: cols(34, 34) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(39, 39) + location: cols(34, 34) }, arguments: Vec::new(), - location: cols(39, 39) + location: cols(34, 34) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(43, 44) + location: cols(38, 39) }))], method_id: None, - location: cols(11, 46), + location: cols(11, 41), } ))], - location: cols(1, 48) + location: cols(1, 43) })), ); } #[test] fn test_lower_class_with_instance_method() { - let hir = - lower_top_expr("class A { fn a[A](b: B) !! C -> D { 10 } }").0; + let hir = lower_top_expr("class A { fn a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -3873,6 +3576,7 @@ mod tests { location: cols(16, 16) }, requirements: Vec::new(), + mutable: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -3892,47 +3596,30 @@ mod tests { })), location: cols(19, 22) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(28, 28) - }, - arguments: Vec::new(), - location: cols(28, 28) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(33, 33) + location: cols(28, 28) }, arguments: Vec::new(), - location: cols(33, 33) + location: cols(28, 28) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(37, 38) + location: cols(32, 33) }))], method_id: None, - location: cols(11, 40) + location: cols(11, 35) } ))], - location: cols(1, 42) + location: cols(1, 37) })), ); } - #[test] - fn test_lower_instance_operator_method_with_throw() { - let diags = lower_top_expr("class A { fn + !! A {} }").1; - - assert_eq!(diags, 1); - } - #[test] fn test_lower_static_operator_method() { let diags = lower_top_expr("class A { fn static + {} }").1; @@ -3964,6 +3651,7 @@ mod tests { location: cols(9, 9) }, requirements: Vec::new(), + mutable: false, location: cols(9, 9) }], requirements: vec![TypeName { @@ -4005,7 +3693,7 @@ mod tests { #[test] fn test_lower_trait_with_required_method() { - let hir = lower_top_expr("trait A { fn a[A](b: B) !! C -> D }").0; + let hir = lower_top_expr("trait A { fn a[A](b: B) -> D }").0; assert_eq!( hir, @@ -4030,6 +3718,7 @@ mod tests { location: cols(16, 16) }, requirements: Vec::new(), + mutable: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4049,31 +3738,21 @@ mod tests { })), location: cols(19, 22) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(28, 28) - }, - arguments: Vec::new(), - location: cols(28, 28) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(33, 33) + location: cols(28, 28) }, arguments: Vec::new(), - location: cols(33, 33) + location: cols(28, 28) }))), method_id: None, - location: cols(11, 33) + location: cols(11, 28) } ))], - location: cols(1, 35) + location: cols(1, 30) })) ); } @@ -4100,7 +3779,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, method_id: None, location: cols(11, 19) @@ -4133,7 +3811,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, @@ -4147,8 +3824,7 @@ mod tests { #[test] fn test_lower_trait_with_default_method() { - let hir = - lower_top_expr("trait A { fn a[A](b: B) !! C -> D { 10 } }").0; + let hir = lower_top_expr("trait A { fn a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -4173,6 +3849,7 @@ mod tests { location: cols(16, 16) }, requirements: Vec::new(), + mutable: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4192,56 +3869,67 @@ mod tests { })), location: cols(19, 22) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(28, 28) - }, - arguments: Vec::new(), - location: cols(28, 28) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(33, 33) + location: cols(28, 28) }, arguments: Vec::new(), - location: cols(33, 33) + location: cols(28, 28) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(37, 38) + location: cols(32, 33) }))], method_id: None, - location: cols(11, 40) + location: cols(11, 35) } ))], - location: cols(1, 42) + location: cols(1, 37) })) ); } #[test] fn test_lower_reopen_empty_class() { - let hir = lower_top_expr("impl A {}").0; - assert_eq!( - hir, + lower_top_expr("impl A {}").0, TopLevelExpression::Reopen(Box::new(ReopenClass { class_id: None, class_name: Constant { name: "A".to_string(), location: cols(6, 6) }, + bounds: Vec::new(), body: Vec::new(), location: cols(1, 9) })) ); + + assert_eq!( + lower_top_expr("impl A if T: mut {}").0, + TopLevelExpression::Reopen(Box::new(ReopenClass { + class_id: None, + class_name: Constant { + name: "A".to_string(), + location: cols(6, 6) + }, + bounds: vec![TypeBound { + name: Constant { + name: "T".to_string(), + location: cols(11, 11) + }, + requirements: Vec::new(), + mutable: true, + location: cols(11, 16), + }], + body: Vec::new(), + location: cols(1, 16) + })) + ); } #[test] @@ -4266,13 +3954,13 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, location: cols(10, 18) } ))], + bounds: Vec::new(), location: cols(1, 20) })) ); @@ -4299,13 +3987,13 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, location: cols(10, 25), } ))], + bounds: Vec::new(), location: cols(1, 27) })) ); @@ -4333,13 +4021,13 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, location: cols(10, 24) } ))], + bounds: Vec::new(), location: cols(1, 26) })) ); @@ -4416,7 +4104,7 @@ mod tests { #[test] fn test_lower_trait_implementation_with_bounds() { - let hir = lower_top_expr("impl A for B if T: X {}").0; + let hir = lower_top_expr("impl A for B if T: X + mut {}").0; assert_eq!( hir, @@ -4449,11 +4137,12 @@ mod tests { }, arguments: Vec::new(), location: cols(20, 20) - }], - location: cols(17, 20) + },], + mutable: true, + location: cols(17, 26) }], body: Vec::new(), - location: cols(1, 23), + location: cols(1, 29), trait_instance: None, class_instance: None, })) @@ -4491,7 +4180,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, @@ -4535,7 +4223,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, @@ -4760,7 +4447,6 @@ mod tests { location: cols(11, 12) }, arguments: Vec::new(), - else_block: None, location: cols(11, 12) })), StringValue::Text(Box::new(StringText { @@ -4837,7 +4523,6 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(10, 10) }, - else_block: None, location: cols(8, 12) })) ); @@ -4927,7 +4612,6 @@ mod tests { location: cols(10, 11) })) ))], - else_block: None, location: cols(8, 12) })) ); @@ -4959,7 +4643,6 @@ mod tests { })), location: cols(10, 14) }))], - else_block: None, location: cols(8, 15) })) ); @@ -4985,7 +4668,6 @@ mod tests { location: cols(10, 10) }, arguments: Vec::new(), - else_block: None, location: cols(8, 10) })) ); @@ -5008,52 +4690,18 @@ mod tests { resolved_type: types::TypeRef::Unknown, location: cols(18, 19) }))], - else_block: None, location: cols(8, 20) })) ); } #[test] - fn test_lower_try_builtin_call() { - let hir = lower_expr("fn a { try _INKO.foo(10) else 0 }").0; - - assert_eq!( - hir, - Expression::BuiltinCall(Box::new(BuiltinCall { - info: None, - name: Identifier { - name: "foo".to_string(), - location: cols(18, 20) - }, - arguments: vec![Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(22, 23) - }))], - else_block: Some(ElseBlock { - body: vec![Expression::Int(Box::new(IntLiteral { - resolved_type: types::TypeRef::Unknown, - value: 0, - location: cols(31, 31) - }))], - argument: None, - location: cols(26, 31) - }), - location: cols(8, 31) - })) - ); - } - - #[test] - fn test_lower_try_builtin_call_outside_stdlib() { + fn test_lower_builtin_call_outside_stdlib() { let name = ModuleName::new("foo"); - let ast = Parser::new( - "fn a { try _INKO.foo(10) else 0 }".into(), - "test.inko".into(), - ) - .parse() - .expect("Failed to parse the module"); + let ast = + Parser::new("fn a { _INKO.foo(10) }".into(), "test.inko".into()) + .parse() + .expect("Failed to parse the module"); let ast = ParsedModule { ast, name }; let mut state = State::new(Config::new()); @@ -5081,114 +4729,11 @@ mod tests { resolved_type: types::TypeRef::Unknown, location: cols(21, 22) }))], - else_block: None, location: cols(8, 23) })) ); } - #[test] - fn test_lower_async_call() { - let (hir, diags) = lower_expr("fn a { async a.b(10) }"); - - assert_eq!(diags, 0); - assert_eq!( - hir, - Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(14, 14) - })), - name: Identifier { - name: "b".to_string(), - location: cols(16, 16) - }, - arguments: vec![Argument::Positional(Box::new( - Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(18, 19) - })) - ))], - location: cols(8, 20) - })) - ); - } - - #[test] - fn test_lower_async_setter() { - let (hir, diags) = lower_expr("fn a { async a.b = 10 }"); - - assert_eq!(diags, 0); - assert_eq!( - hir, - Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(14, 14) - })), - name: Identifier { - name: "b=".to_string(), - location: cols(16, 16) - }, - arguments: vec![Argument::Positional(Box::new( - Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(20, 21) - })) - ))], - location: cols(14, 21) - })) - ); - } - - #[test] - fn test_lower_async_call_without_receiver() { - let (hir, diags) = lower_expr("fn a { async a(10) }"); - - assert_eq!(diags, 1); - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "a".to_string(), - location: cols(14, 14) - }, - arguments: vec![Argument::Positional(Box::new( - Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(16, 17) - })) - ))], - else_block: None, - location: cols(14, 18) - })) - ); - } - - #[test] - fn test_lower_async_call_with_identifier() { - let (hir, diags) = lower_expr("fn a { async a }"); - - assert_eq!(diags, 1); - assert_eq!( - hir, - Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(14, 14) - })) - ); - } - #[test] fn test_lower_assign_variable() { let hir = lower_expr("fn a { a = 10 }").0; @@ -5307,7 +4852,6 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(10, 11) }, - else_block: None, location: cols(8, 13) })), resolved_type: types::TypeRef::Unknown, @@ -5338,7 +4882,6 @@ mod tests { value: 1, location: cols(14, 14) })), - else_block: None, location: cols(8, 14) })) ); @@ -5377,7 +4920,6 @@ mod tests { location: cols(10, 10) }, arguments: Vec::new(), - else_block: None, location: cols(8, 10) }))), arguments: vec![Argument::Positional(Box::new( @@ -5391,10 +4933,8 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(12, 13) }, - else_block: None, location: cols(8, 15) })), - else_block: None, location: cols(8, 15) })) ); @@ -5428,7 +4968,6 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(11, 12) }, - else_block: None, location: cols(8, 14) })), resolved_type: types::TypeRef::Unknown, @@ -5439,7 +4978,7 @@ mod tests { #[test] fn test_lower_closure() { - let hir = lower_expr("fn a { fn (a: T) !! A -> B { 10 } }").0; + let hir = lower_expr("fn a { fn (a: T) -> B { 10 } }").0; assert_eq!( hir, @@ -5465,32 +5004,22 @@ mod tests { }))), location: cols(12, 15), }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "A".to_string(), - location: cols(21, 21) - }, - arguments: Vec::new(), - location: cols(21, 21) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "B".to_string(), - location: cols(26, 26) + location: cols(21, 21) }, arguments: Vec::new(), - location: cols(26, 26) + location: cols(21, 21) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(30, 31) + location: cols(25, 26) }))], - location: cols(8, 33) + location: cols(8, 28) })) ); } @@ -5743,70 +5272,6 @@ mod tests { ); } - #[test] - fn test_lower_index_expression() { - let hir = lower_expr("fn a { a[0] }").0; - - assert_eq!( - hir, - Expression::Index(Box::new(Index { - info: None, - receiver: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(8, 8) - })), - index: Expression::Int(Box::new(IntLiteral { - value: 0, - resolved_type: types::TypeRef::Unknown, - location: cols(10, 10) - })), - location: cols(8, 11) - })) - ); - } - - #[test] - fn test_lower_set_index_expression() { - let hir = lower_expr("fn a { a[0] = 1 }").0; - - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: Some(Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(8, 8) - } - ))), - name: Identifier { - name: SET_INDEX_METHOD.to_string(), - location: cols(8, 15) - }, - arguments: vec![ - Argument::Positional(Box::new(Expression::Int(Box::new( - IntLiteral { - value: 0, - resolved_type: types::TypeRef::Unknown, - location: cols(10, 10) - } - )))), - Argument::Positional(Box::new(Expression::Int(Box::new( - IntLiteral { - value: 1, - resolved_type: types::TypeRef::Unknown, - location: cols(15, 15) - } - )))), - ], - else_block: None, - location: cols(8, 15) - })) - ); - } - #[test] fn test_lower_throw_expression() { let hir = lower_expr("fn a { throw 10 }").0; @@ -5815,6 +5280,7 @@ mod tests { hir, Expression::Throw(Box::new(Throw { resolved_type: types::TypeRef::Unknown, + return_type: types::TypeRef::Unknown, value: Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, @@ -5858,135 +5324,24 @@ mod tests { } #[test] - fn test_lower_try_without_else() { - let hir = lower_expr("fn a { try a }").0; - - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "a".to_string(), - location: cols(12, 12) - }, - arguments: Vec::new(), - else_block: Some(ElseBlock { - body: vec![Expression::Throw(Box::new(Throw { - resolved_type: types::TypeRef::Unknown, - value: Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 12) - } - )), - location: cols(8, 12) - }))], - argument: Some(BlockArgument { - variable_id: None, - name: Identifier { - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 12) - }, - value_type: None, - location: cols(8, 12) - }), - location: cols(8, 12) - }), - location: cols(8, 12) - })) - ); - } - - #[test] - fn test_lower_try_with_else() { - let hir = lower_expr("fn a { try aa else (e) throw e }").0; + fn test_lower_try() { + let hir = lower_expr("fn a { try a() }").0; assert_eq!( hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "aa".to_string(), - location: cols(12, 13) - }, - arguments: Vec::new(), - else_block: Some(ElseBlock { - body: vec![Expression::Throw(Box::new(Throw { - resolved_type: types::TypeRef::Unknown, - value: Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "e".to_string(), - location: cols(30, 30) - } - )), - location: cols(24, 30) - }))], - argument: Some(BlockArgument { - variable_id: None, - name: Identifier { - name: "e".to_string(), - location: cols(21, 21) - }, - value_type: None, - location: cols(21, 21) - }), - location: cols(15, 30) - }), - location: cols(8, 30) - })) - ); - } - - #[test] - fn test_lower_try_panic() { - let hir = lower_expr("fn a { try! aa }").0; - - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "aa".to_string(), - location: cols(13, 14) - }, - arguments: Vec::new(), - else_block: Some(ElseBlock { - body: vec![Expression::BuiltinCall(Box::new( - BuiltinCall { - info: None, - name: Identifier { - name: types::CompilerMacro::PanicThrown - .name() - .to_string(), - location: cols(8, 14) - }, - arguments: vec![Expression::IdentifierRef( - Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 14) - }) - )], - else_block: None, - location: cols(8, 14) - } - ))], - argument: Some(BlockArgument { - variable_id: None, - name: Identifier { - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 14) - }, - value_type: None, - location: cols(8, 14) - }), - location: cols(8, 14) - }), + Expression::Try(Box::new(Try { + expression: Expression::Call(Box::new(Call { + kind: types::CallKind::Unknown, + receiver: None, + name: Identifier { + name: "a".to_string(), + location: cols(12, 12) + }, + arguments: Vec::new(), + location: cols(12, 14) + })), + kind: types::ThrowKind::Unknown, + return_type: types::TypeRef::Unknown, location: cols(8, 14) })) ); @@ -6234,6 +5589,7 @@ mod tests { location: cols(19, 19) }, requirements: Vec::new(), + mutable: false, location: cols(19, 19) }], body: vec![ diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index fb7e84a64..0a7b77c90 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,14 +1,17 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::enum_variant_names))] -mod codegen; mod diagnostics; mod hir; +mod linker; +mod llvm; mod mir; mod modules_parser; mod presenters; mod source_paths; mod state; +mod symbol_names; +pub mod target; mod type_check; #[cfg(test)] diff --git a/compiler/src/linker.rs b/compiler/src/linker.rs new file mode 100644 index 000000000..e1cde5729 --- /dev/null +++ b/compiler/src/linker.rs @@ -0,0 +1,108 @@ +use crate::config::Config; +use crate::target::OperatingSystem; +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 lld_is_available() -> bool { + Command::new("ld.lld") + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and_then(|mut child| child.wait()) + .map_or(false, |status| status.success()) +} + +pub(crate) fn link( + config: &Config, + 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"); + + // 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 + // `-l`. + for path in paths { + cmd.arg(path); + } + + match config.target.os { + OperatingSystem::Linux + | OperatingSystem::Freebsd + | OperatingSystem::Openbsd => { + // macOS includes libm in the standard C library, so there's no need + // to explicitly include it. + cmd.arg("-lm"); + } + _ => {} + } + + cmd.arg("-o"); + cmd.arg(output); + + // On platforms where lld isn't the default (e.g. FreeBSD), we'll use it if + // available, speeding up the linking process. + if let OperatingSystem::Linux = config.target.os { + if lld_is_available() { + cmd.arg("-fuse-ld=lld"); + } + } + + let rt_path = runtime_library(config).ok_or_else(|| { + format!("No runtime is available for target '{}'", config.target) + })?; + + cmd.arg(&rt_path); + + cmd.stdin(Stdio::null()); + cmd.stderr(Stdio::piped()); + cmd.stdout(Stdio::null()); + + let child = cmd + .spawn() + .map_err(|err| format!("Failed to start the linker: {err}"))?; + + let output = child + .wait_with_output() + .map_err(|err| format!("Failed to wait for the linker: {err}"))?; + + if output.status.success() { + Ok(()) + } else { + Err(format!( + "The linker exited with status code {}:\n{}", + output.status.code().unwrap_or(0), + String::from_utf8_lossy(&output.stderr), + )) + } +} diff --git a/compiler/src/llvm.rs b/compiler/src/llvm.rs new file mode 100644 index 000000000..fda7f1bd5 --- /dev/null +++ b/compiler/src/llvm.rs @@ -0,0 +1,10 @@ +//! Lowering of Inko MIR into LLVM IR. + +pub(crate) mod builder; +pub(crate) mod constants; +pub(crate) mod context; +pub(crate) mod layouts; +pub(crate) mod method_hasher; +pub(crate) mod module; +pub(crate) mod passes; +pub(crate) mod runtime_function; diff --git a/compiler/src/llvm/builder.rs b/compiler/src/llvm/builder.rs new file mode 100644 index 000000000..b86ae833b --- /dev/null +++ b/compiler/src/llvm/builder.rs @@ -0,0 +1,775 @@ +use crate::llvm::constants::{ + INT_MASK, INT_SHIFT, MAX_INT, MIN_INT, UNTAG_MASK, +}; +use crate::llvm::context::Context; +use inkwell::basic_block::BasicBlock; +use inkwell::builder; +use inkwell::debug_info::{ + debug_metadata_version, AsDIScope, DICompileUnit, DIFlags, + DIFlagsConstants, DILocation, DIScope, DISubprogram, DWARFEmissionKind, + DWARFSourceLanguage, DebugInfoBuilder, +}; +use inkwell::module::{FlagBehavior, Module as InkwellModule}; +use inkwell::types::{ArrayType, BasicType, FunctionType, StructType}; +use inkwell::values::{ + AggregateValue, ArrayValue, BasicMetadataValueEnum, BasicValue, + BasicValueEnum, CallSiteValue, FloatValue, FunctionValue, + InstructionOpcode, InstructionValue, IntValue, PointerValue, +}; +use inkwell::{ + AddressSpace, AtomicOrdering, AtomicRMWBinOp, FloatPredicate, IntPredicate, +}; +use std::path::Path; + +/// A wrapper around an LLVM Builder that provides some additional methods. +pub(crate) struct Builder<'ctx> { + inner: builder::Builder<'ctx>, + pub(crate) function: FunctionValue<'ctx>, + pub(crate) context: &'ctx Context, +} + +impl<'ctx> Builder<'ctx> { + pub(crate) fn new( + context: &'ctx Context, + function: FunctionValue<'ctx>, + ) -> Self { + Self { inner: context.create_builder(), context, function } + } + + pub(crate) fn argument(&self, index: u32) -> BasicValueEnum<'ctx> { + self.function.get_nth_param(index).unwrap() + } + + pub(crate) fn arguments( + &self, + ) -> impl Iterator> { + self.function.get_param_iter() + } + + pub(crate) fn extract_field>( + &self, + receiver: R, + index: u32, + ) -> BasicValueEnum<'ctx> { + self.inner.build_extract_value(receiver, index, "").unwrap() + } + + pub(crate) fn load_field( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + ) -> BasicValueEnum<'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, "") + } + + pub(crate) fn field_address( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + ) -> PointerValue<'ctx> { + self.inner.build_struct_gep(receiver_type, receiver, index, "").unwrap() + } + + pub(crate) fn array_field_index_address( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + field: u32, + index: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + if !receiver_type + .get_field_type_at_index(field) + .map_or(false, |v| v.is_array_type()) + { + // In practise we'll never reach this point, but the check exists + // anyway to ensure this method doesn't segfault the compiler due to + // an invalid `getelementptr` instruction. + panic!("The field doesn't point to an array"); + } + + unsafe { + self.inner.build_gep( + receiver_type, + receiver, + &[self.u32_literal(0), self.u32_literal(field), index], + "", + ) + } + } + + pub(crate) fn load_array_index( + &self, + array_type: ArrayType<'ctx>, + array: PointerValue<'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_load(array_type.get_element_type(), ptr, "") + } + + pub(crate) fn store_array_field>( + &self, + array_type: ArrayType<'ctx>, + array: PointerValue<'ctx>, + index: u32, + 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.store(ptr, value); + } + + pub(crate) fn store_field>( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + value: V, + ) { + let field_ptr = self.field_address(receiver_type, receiver, index); + + self.store(field_ptr, value); + } + + pub(crate) fn store>( + &self, + variable: PointerValue<'ctx>, + value: V, + ) { + self.inner.build_store(variable, value); + } + + pub(crate) fn load>( + &self, + typ: T, + variable: PointerValue<'ctx>, + ) -> BasicValueEnum<'ctx> { + self.inner.build_load(typ, variable, "") + } + + pub(crate) fn load_untyped_pointer( + &self, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load( + self.context.i8_type().ptr_type(AddressSpace::default()), + variable, + ) + .into_pointer_value() + } + + pub(crate) fn load_pointer>( + &self, + typ: T, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load( + typ.as_basic_type_enum().ptr_type(AddressSpace::default()), + variable, + ) + .into_pointer_value() + } + + pub(crate) fn load_function_pointer( + &self, + typ: FunctionType<'ctx>, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load(typ.ptr_type(AddressSpace::default()), variable) + .into_pointer_value() + } + + pub(crate) fn call( + &self, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum<'ctx>], + ) -> BasicValueEnum<'ctx> { + self.inner + .build_call(function, arguments, "") + .try_as_basic_value() + .left() + .unwrap() + } + + pub(crate) fn indirect_call( + &self, + typ: FunctionType<'ctx>, + func: PointerValue<'ctx>, + args: &[BasicMetadataValueEnum<'ctx>], + ) -> CallSiteValue<'ctx> { + self.inner.build_indirect_call(typ, func, args, "") + } + + pub(crate) fn call_void( + &self, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum<'ctx>], + ) { + self.inner.build_call(function, arguments, ""); + } + + pub(crate) fn pointer_to_int( + &self, + value: PointerValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_ptr_to_int(value, self.context.i64_type(), "") + } + + pub(crate) fn u8_literal(&self, value: u8) -> IntValue<'ctx> { + self.context.i8_type().const_int(value as u64, false) + } + + pub(crate) fn i64_literal(&self, value: i64) -> IntValue<'ctx> { + self.u64_literal(value as u64) + } + + pub(crate) fn u16_literal(&self, value: u16) -> IntValue<'ctx> { + self.context.i16_type().const_int(value as u64, false) + } + + pub(crate) fn u32_literal(&self, value: u32) -> IntValue<'ctx> { + self.context.i32_type().const_int(value as u64, false) + } + + pub(crate) fn u64_literal(&self, value: u64) -> IntValue<'ctx> { + self.context.i64_type().const_int(value, false) + } + + pub(crate) fn f64_literal(&self, value: f64) -> FloatValue<'ctx> { + self.context.f64_type().const_float(value) + } + + pub(crate) fn string_literal( + &self, + value: &str, + ) -> (PointerValue<'ctx>, IntValue<'ctx>) { + let string = + self.inner.build_global_string_ptr(value, "").as_pointer_value(); + let len = self.u64_literal(value.len() as _); + + (string, len) + } + + pub(crate) fn atomic_add( + &self, + pointer: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner + .build_atomicrmw( + AtomicRMWBinOp::Add, + pointer, + value, + AtomicOrdering::AcquireRelease, + ) + .unwrap() + } + + pub(crate) fn atomic_sub( + &self, + pointer: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner + .build_atomicrmw( + AtomicRMWBinOp::Sub, + pointer, + value, + AtomicOrdering::AcquireRelease, + ) + .unwrap() + } + + pub(crate) fn int_eq( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::EQ, lhs, rhs, "") + } + + pub(crate) fn int_gt( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SGT, lhs, rhs, "") + } + + pub(crate) fn int_ge( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SGE, lhs, rhs, "") + } + + pub(crate) fn int_lt( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SLT, lhs, rhs, "") + } + + pub(crate) fn int_le( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SLE, lhs, rhs, "") + } + + pub(crate) fn int_sub( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_sub(lhs, rhs, "") + } + + pub(crate) fn int_add( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_add(lhs, rhs, "") + } + + pub(crate) fn int_mul( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_mul(lhs, rhs, "") + } + + pub(crate) fn int_div( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_signed_div(lhs, rhs, "") + } + + pub(crate) fn int_rem( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_signed_rem(lhs, rhs, "") + } + + pub(crate) fn bit_and( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_and(lhs, rhs, "") + } + + pub(crate) fn bit_or( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_or(lhs, rhs, "") + } + + pub(crate) fn bit_xor( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_xor(lhs, rhs, "") + } + + pub(crate) fn bit_not(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { + self.inner.build_not(value, "") + } + + pub(crate) fn left_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_left_shift(lhs, rhs, "") + } + + pub(crate) fn right_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_right_shift(lhs, rhs, false, "") + } + + pub(crate) fn signed_right_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_right_shift(lhs, rhs, true, "") + } + + pub(crate) fn int_to_float( + &self, + value: IntValue<'ctx>, + ) -> FloatValue<'ctx> { + let typ = self.context.f64_type(); + + self.inner + .build_cast(InstructionOpcode::SIToFP, value, typ, "") + .into_float_value() + } + + pub(crate) fn int_to_int(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { + self.inner.build_int_cast(value, self.context.i64_type(), "") + } + + pub(crate) fn int_to_pointer( + &self, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + self.inner.build_int_to_ptr(value, self.context.pointer_type(), "") + } + + pub(crate) fn float_add( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_add(lhs, rhs, "") + } + + pub(crate) fn float_sub( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_sub(lhs, rhs, "") + } + + pub(crate) fn float_div( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_div(lhs, rhs, "") + } + + pub(crate) fn float_mul( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_mul(lhs, rhs, "") + } + + pub(crate) fn float_rem( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_rem(lhs, rhs, "") + } + + pub(crate) fn float_eq( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OEQ, lhs, rhs, "") + } + + pub(crate) fn float_lt( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OLT, lhs, rhs, "") + } + + pub(crate) fn float_le( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OLE, lhs, rhs, "") + } + + pub(crate) fn float_gt( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OGT, lhs, rhs, "") + } + + pub(crate) fn float_ge( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OGE, lhs, rhs, "") + } + + pub(crate) fn float_is_nan( + &self, + value: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::UNO, value, value, "") + } + + pub(crate) fn tagged_int(&self, value: i64) -> Option> { + if (MIN_INT..=MAX_INT).contains(&value) { + let addr = (value << INT_SHIFT) as u64 | (INT_MASK as u64); + let int = self.i64_literal(addr as i64); + + Some(int.const_to_pointer(self.context.pointer_type())) + } else { + None + } + } + + pub(crate) fn bitcast, T: BasicType<'ctx>>( + &self, + value: V, + typ: T, + ) -> BasicValueEnum<'ctx> { + self.inner.build_bitcast(value, typ, "") + } + + pub(crate) fn untagged( + &self, + pointer: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + let tagged_addr = self.pointer_to_int(pointer); + let mask = self.u64_literal(UNTAG_MASK); + let addr = self.bit_and(tagged_addr, mask); + + self.int_to_pointer(addr) + } + + pub(crate) fn before_instruction( + &self, + instruction: InstructionValue<'ctx>, + ) { + self.inner.position_before(&instruction); + } + + pub(crate) fn first_block(&self) -> BasicBlock<'ctx> { + self.function.get_first_basic_block().unwrap() + } + + pub(crate) fn add_block(&self) -> BasicBlock<'ctx> { + self.context.append_basic_block(self.function) + } + + pub(crate) fn switch_to_block(&self, block: BasicBlock<'ctx>) { + self.inner.position_at_end(block); + } + + pub(crate) fn alloca>( + &self, + typ: T, + ) -> PointerValue<'ctx> { + self.inner.build_alloca(typ, "") + } + + pub(crate) fn jump(&self, block: BasicBlock<'ctx>) { + self.inner.build_unconditional_branch(block); + } + + pub(crate) fn return_value(&self, val: Option<&dyn BasicValue<'ctx>>) { + self.inner.build_return(val); + } + + pub(crate) fn branch( + &self, + condition: IntValue<'ctx>, + true_block: BasicBlock<'ctx>, + false_block: BasicBlock<'ctx>, + ) { + self.inner.build_conditional_branch(condition, true_block, false_block); + } + + pub(crate) fn switch( + &self, + value: IntValue<'ctx>, + cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], + fallback: BasicBlock<'ctx>, + ) { + self.inner.build_switch(value, fallback, cases); + } + + pub(crate) fn exhaustive_switch( + &self, + value: IntValue<'ctx>, + cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], + ) { + self.switch(value, cases, cases[0].1); + } + + pub(crate) fn unreachable(&self) { + self.inner.build_unreachable(); + } + + pub(crate) fn string_bytes(&self, value: &str) -> ArrayValue<'ctx> { + let bytes = value + .bytes() + .map(|v| self.context.i8_type().const_int(v as _, false)) + .collect::>(); + + self.context.i8_type().const_array(&bytes) + } + + pub(crate) fn new_stack_slot>( + &self, + value_type: T, + ) -> PointerValue<'ctx> { + let builder = Builder::new(self.context, self.function); + let block = self.first_block(); + + if let Some(ins) = block.get_first_instruction() { + builder.before_instruction(ins); + } else { + builder.switch_to_block(block); + } + + builder.alloca(value_type) + } + + pub(crate) fn debug_scope(&self) -> DIScope<'ctx> { + self.function.get_subprogram().unwrap().as_debug_info_scope() + } + + pub(crate) fn set_debug_location(&self, location: DILocation<'ctx>) { + self.inner.set_current_debug_location(location); + } + + pub(crate) fn set_debug_function(&self, function: DISubprogram) { + self.function.set_subprogram(function); + } +} + +/// A wrapper around the LLVM types used for building debugging information. +pub(crate) struct DebugBuilder<'ctx> { + inner: DebugInfoBuilder<'ctx>, + unit: DICompileUnit<'ctx>, + context: &'ctx Context, +} + +impl<'ctx> DebugBuilder<'ctx> { + pub(crate) fn new( + module: &InkwellModule<'ctx>, + context: &'ctx Context, + path: &Path, + ) -> DebugBuilder<'ctx> { + let version = + context.i32_type().const_int(debug_metadata_version() as _, false); + + module.add_basic_value_flag( + "Debug Info Version", + FlagBehavior::Warning, + version, + ); + + let file_name = + path.file_name().and_then(|p| p.to_str()).unwrap_or("unknown"); + let dir_name = + path.parent().and_then(|p| p.to_str()).unwrap_or("unknown"); + let (inner, unit) = module.create_debug_info_builder( + true, + DWARFSourceLanguage::C, + file_name, + dir_name, + "Inko", + false, + "", + 0, + "", + DWARFEmissionKind::Full, + 0, + false, + false, + "", + "", + ); + + DebugBuilder { inner, context, unit } + } + + pub(crate) fn new_location( + &self, + line: usize, + column: usize, + scope: DIScope<'ctx>, + ) -> DILocation<'ctx> { + self.inner.create_debug_location( + &self.context.inner, + line as u32, + column as u32, + scope, + None, + ) + } + + pub(crate) fn new_function( + &self, + name: &str, + mangled_name: &str, + line: usize, + private: bool, + optimised: bool, + ) -> DISubprogram<'ctx> { + let file = self.unit.get_file(); + let typ = + self.inner.create_subroutine_type(file, None, &[], DIFlags::PUBLIC); + let scope = self.unit.as_debug_info_scope(); + + self.inner.create_function( + scope, + name, + Some(mangled_name), + file, + line as u32, + typ, + private, + true, + line as u32, + DIFlags::PUBLIC, + optimised, + ) + } + + pub(crate) fn finalize(&self) { + self.inner.finalize(); + } +} diff --git a/compiler/src/llvm/constants.rs b/compiler/src/llvm/constants.rs new file mode 100644 index 000000000..c1a04934b --- /dev/null +++ b/compiler/src/llvm/constants.rs @@ -0,0 +1,74 @@ +/// The mask to use for tagged integers. +pub(crate) const INT_MASK: i64 = 0b001; + +/// The number of bits to shift for tagged integers. +pub(crate) const INT_SHIFT: usize = 1; + +/// The minimum integer value that can be stored as a tagged signed integer. +pub(crate) const MIN_INT: i64 = i64::MIN >> INT_SHIFT; + +/// The maximum integer value that can be stored as a tagged signed integer. +pub(crate) const MAX_INT: i64 = i64::MAX >> INT_SHIFT; + +/// The mask to use to check if a value is a tagged integer or reference. +pub(crate) const TAG_MASK: i64 = 0b11; + +/// The mask to apply to get rid of the tagging bits. +pub(crate) const UNTAG_MASK: u64 = (!TAG_MASK) as u64; + +/// The offset to apply to access a regular field. +/// +/// The object header occupies the first field (as an inline struct), so all +/// user-defined fields start at the next field. +pub(crate) const FIELD_OFFSET: usize = 1; + +/// The offset to apply to access a process field. +pub(crate) const PROCESS_FIELD_OFFSET: usize = 2; + +/// The mask to use for checking if a value is a reference. +pub(crate) const REF_MASK: i64 = 0b10; + +/// The field index of the `State` field that contains the `true` singleton. +pub(crate) const TRUE_INDEX: u32 = 0; + +/// The field index of the `State` field that contains the `false` singleton. +pub(crate) const FALSE_INDEX: u32 = 1; + +/// The field index of the `State` field that contains the `nil` singleton. +pub(crate) const NIL_INDEX: u32 = 2; + +pub(crate) const HEADER_CLASS_INDEX: u32 = 0; +pub(crate) const HEADER_KIND_INDEX: u32 = 1; +pub(crate) const HEADER_REFS_INDEX: u32 = 2; + +pub(crate) const BOXED_INT_VALUE_INDEX: u32 = 1; +pub(crate) const BOXED_FLOAT_VALUE_INDEX: u32 = 1; + +pub(crate) const CLASS_METHODS_COUNT_INDEX: u32 = 2; +pub(crate) const CLASS_METHODS_INDEX: u32 = 3; + +pub(crate) const METHOD_HASH_INDEX: u32 = 0; +pub(crate) const METHOD_FUNCTION_INDEX: u32 = 1; + +// The values used to represent the kind of a value/reference. These values +// must match the values used by `Kind` in the runtime library. +pub(crate) const OWNED_KIND: u8 = 0; +pub(crate) const REF_KIND: u8 = 1; +pub(crate) const ATOMIC_KIND: u8 = 2; +pub(crate) const PERMANENT_KIND: u8 = 3; +pub(crate) const INT_KIND: u8 = 4; +pub(crate) const FLOAT_KIND: u8 = 5; + +pub(crate) const LLVM_RESULT_VALUE_INDEX: u32 = 0; +pub(crate) const LLVM_RESULT_STATUS_INDEX: u32 = 1; + +pub(crate) const CONTEXT_STATE_INDEX: u32 = 0; +pub(crate) const CONTEXT_PROCESS_INDEX: u32 = 1; +pub(crate) const CONTEXT_ARGS_INDEX: u32 = 2; + +pub(crate) const MESSAGE_ARGUMENTS_INDEX: u32 = 2; + +pub(crate) const DROPPER_INDEX: u32 = 0; +pub(crate) const CLOSURE_CALL_INDEX: u32 = 1; +pub(crate) const HASH_KEY0_INDEX: u32 = 11; +pub(crate) const HASH_KEY1_INDEX: u32 = 12; diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs new file mode 100644 index 000000000..886016647 --- /dev/null +++ b/compiler/src/llvm/context.rs @@ -0,0 +1,146 @@ +use inkwell::basic_block::BasicBlock; +use inkwell::builder::Builder; +use inkwell::module::Module; +use inkwell::types::{ + ArrayType, BasicTypeEnum, FloatType, IntType, PointerType, StructType, + VoidType, +}; +use inkwell::values::FunctionValue; +use inkwell::{context, AddressSpace}; +use std::mem::size_of; + +/// A wrapper around an LLVM Context that provides some additional methods. +pub(crate) struct Context { + pub(crate) inner: context::Context, +} + +impl Context { + pub(crate) fn new() -> Self { + Self { inner: context::Context::create() } + } + + pub(crate) fn pointer_type(&self) -> PointerType<'_> { + self.inner.i8_type().ptr_type(AddressSpace::default()) + } + + pub(crate) fn bool_type(&self) -> IntType { + self.inner.bool_type() + } + + pub(crate) fn i8_type(&self) -> IntType { + self.inner.i8_type() + } + + pub(crate) fn i16_type(&self) -> IntType { + self.inner.i16_type() + } + + pub(crate) fn i32_type(&self) -> IntType { + self.inner.i32_type() + } + + pub(crate) fn i64_type(&self) -> IntType { + self.inner.i64_type() + } + + pub(crate) fn f64_type(&self) -> FloatType { + self.inner.f64_type() + } + + pub(crate) fn void_type(&self) -> VoidType { + self.inner.void_type() + } + + pub(crate) fn rust_string_type(&self) -> ArrayType<'_> { + self.inner.i8_type().array_type(size_of::() as u32) + } + + pub(crate) fn rust_vec_type(&self) -> ArrayType<'_> { + self.inner.i8_type().array_type(size_of::>() as u32) + } + + pub(crate) fn opaque_struct<'a>(&'a self, name: &str) -> StructType<'a> { + self.inner.opaque_struct_type(name) + } + + pub(crate) fn struct_type<'a>( + &'a self, + fields: &[BasicTypeEnum], + ) -> StructType<'a> { + self.inner.struct_type(fields, false) + } + + pub(crate) fn class_type<'a>( + &'a self, + methods: usize, + name: &str, + method_type: StructType<'a>, + ) -> StructType<'a> { + let name_type = self.rust_string_type(); + let class_type = self.inner.opaque_struct_type(name); + + class_type.set_body( + &[ + // Name + name_type.into(), + // Instance size + self.inner.i32_type().into(), + // Number of methods + self.inner.i16_type().into(), + // The method table entries. We use an array instead of one + // field per method, as this allows us to generate indexes + // (using `getelementptr`) that are out of bounds. This is + // necessary for dynamic dispatch as we don't statically know + // the number of methods a receiver's class has. + method_type.array_type(methods as _).into(), + ], + false, + ); + class_type + } + + /// Returns the layout for a built-in type such as Int or String (i.e a type + /// with only a single value field). + pub(crate) fn builtin_type<'a>( + &'a self, + name: &str, + header: StructType<'a>, + value: BasicTypeEnum, + ) -> StructType<'a> { + let typ = self.opaque_struct(name); + + typ.set_body(&[header.into(), value], false); + typ + } + + pub(crate) fn append_basic_block<'a>( + &'a self, + function: FunctionValue<'a>, + ) -> BasicBlock<'a> { + self.inner.append_basic_block(function, "") + } + + pub(crate) fn create_builder(&self) -> Builder { + self.inner.create_builder() + } + + pub(crate) fn create_module(&self, name: &str) -> Module { + self.inner.create_module(name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_context_type_sizes() { + let ctx = Context::new(); + + // These tests exists just to make sure the layouts match that which the + // runtime expects. This would only ever fail if Rust suddenly changes + // the layout of String/Vec. + assert_eq!(ctx.rust_string_type().len(), 24); + assert_eq!(ctx.rust_vec_type().len(), 24); + } +} diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs new file mode 100644 index 000000000..cdbcb9c3e --- /dev/null +++ b/compiler/src/llvm/layouts.rs @@ -0,0 +1,440 @@ +use crate::llvm::constants::{CLOSURE_CALL_INDEX, DROPPER_INDEX}; +use crate::llvm::context::Context; +use crate::llvm::method_hasher::MethodHasher; +use crate::mir::Mir; +use crate::state::State; +use crate::target::OperatingSystem; +use inkwell::types::{ + BasicMetadataTypeEnum, BasicTypeEnum, FunctionType, StructType, +}; +use inkwell::AddressSpace; +use std::cmp::max; +use std::collections::HashMap; +use types::{ + ClassId, MethodId, MethodSource, ARRAY_ID, BOOLEAN_ID, BYTE_ARRAY_ID, + CALL_METHOD, CHANNEL_ID, DROPPER_METHOD, FLOAT_ID, INT_ID, NIL_ID, + STRING_ID, +}; + +/// The size of an object header. +const HEADER_SIZE: u32 = 16; + +/// Method table sizes are multiplied by this value in an attempt to reduce the +/// amount of collisions when performing dynamic dispatch. +/// +/// While this increases the amount of memory needed per method table, it's not +/// really significant: each slot only takes up one word of memory. On a 64-bits +/// system this means you can fit a total of 131 072 slots in 1 MiB. In +/// addition, this cost is a one-time and constant cost, whereas collisions +/// introduce a cost that you may have to pay every time you perform dynamic +/// dispatch. +const METHOD_TABLE_FACTOR: usize = 4; + +/// The minimum number of slots in a method table. +/// +/// This value is used to ensure that even types with few methods have as few +/// collisions as possible. +/// +/// This value _must_ be a power of two. +const METHOD_TABLE_MIN_SIZE: usize = 64; + +/// Rounds the given value to the nearest power of two. +fn round_methods(mut value: usize) -> usize { + if value == 0 { + return 0; + } + + value -= 1; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + value += 1; + + value +} + +pub(crate) struct MethodInfo<'ctx> { + pub(crate) index: u16, + pub(crate) hash: u64, + pub(crate) collision: bool, + pub(crate) signature: FunctionType<'ctx>, + pub(crate) colliding: Vec, +} + +/// Types and layout information to expose to all modules. +pub(crate) struct Layouts<'ctx> { + /// The layout of an empty class. + /// + /// This is used for generating dynamic dispatch code, as we don't know the + /// exact class in such cases. + pub(crate) empty_class: StructType<'ctx>, + + /// The type to use for Inko methods (used for dynamic dispatch). + pub(crate) method: StructType<'ctx>, + + /// All MIR classes and their corresponding structure layouts. + pub(crate) classes: HashMap>, + + /// The structure layouts for all class instances. + pub(crate) instances: HashMap>, + + /// The structure layout of the runtime's `State` type. + pub(crate) state: StructType<'ctx>, + + /// The layout of object headers. + pub(crate) header: StructType<'ctx>, + + /// The layout of the runtime's result type. + pub(crate) result: StructType<'ctx>, + + /// The layout of the context type passed to async methods. + pub(crate) context: StructType<'ctx>, + + /// The layout to use for the type that stores the built-in type method + /// counts. + pub(crate) method_counts: StructType<'ctx>, + + /// Information about methods defined on classes, such as their signatures + /// and hash codes. + pub(crate) methods: HashMap>, + + /// The layout of messages sent to processes. + pub(crate) message: StructType<'ctx>, +} + +impl<'ctx> Layouts<'ctx> { + pub(crate) fn new( + state: &State, + mir: &Mir, + context: &'ctx Context, + ) -> Self { + let db = &state.db; + let space = AddressSpace::default(); + let mut class_layouts = HashMap::new(); + let mut instance_layouts = HashMap::new(); + let mut methods = HashMap::new(); + let header = context.struct_type(&[ + context.pointer_type().into(), // Class + context.i8_type().into(), // Kind + context.i32_type().into(), // References + ]); + + let method = context.struct_type(&[ + context.i64_type().into(), // Hash + context.pointer_type().into(), // Function pointer + ]); + + // We only include the fields that we need in the compiler. This is + // fine/safe is we only use the state type through pointers, so the + // exact size doesn't matter. + let state_layout = context.struct_type(&[ + context.pointer_type().into(), // true + context.pointer_type().into(), // false + context.pointer_type().into(), // nil + context.pointer_type().into(), // Int class + context.pointer_type().into(), // Float class + context.pointer_type().into(), // String class + context.pointer_type().into(), // Array class + context.pointer_type().into(), // Bool class + context.pointer_type().into(), // Nil class + context.pointer_type().into(), // ByteArray class + context.pointer_type().into(), // Channel class + context.pointer_type().into(), // hash_key0 + context.pointer_type().into(), // hash_key1 + ]); + + let context_layout = context.struct_type(&[ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + context.pointer_type().into(), // Arguments pointer + ]); + + let result_layout = context.struct_type(&[ + context.pointer_type().into(), // Tag + context.pointer_type().into(), // Value + ]); + + instance_layouts.insert(ClassId::result(), result_layout); + + let method_counts_layout = context.struct_type(&[ + context.i16_type().into(), // Int + context.i16_type().into(), // Float + context.i16_type().into(), // String + context.i16_type().into(), // Array + context.i16_type().into(), // Bool + context.i16_type().into(), // Nil + context.i16_type().into(), // ByteArray + context.i16_type().into(), // Channel + ]); + + let message_layout = context.struct_type(&[ + context.pointer_type().into(), // Function + context.i8_type().into(), // Length + context.pointer_type().array_type(0).into(), // Arguments + ]); + + let mut method_hasher = MethodHasher::new(); + + // We need to define the method information for trait methods, as + // this information is necessary when generating dynamic dispatch code. + // + // This information is defined first so we can update the `collision` + // flag when generating this information for method implementations. + for mir_trait in mir.traits.values() { + for method in mir_trait + .id + .required_methods(db) + .into_iter() + .chain(mir_trait.id.default_methods(db)) + { + let name = method.name(db); + let hash = method_hasher.hash(name); + let mut args: Vec = vec![ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + context.pointer_type().into(), // Receiver + ]; + + for _ in 0..method.number_of_arguments(db) { + args.push(context.pointer_type().into()); + } + + let signature = context.pointer_type().fn_type(&args, false); + + methods.insert( + method, + MethodInfo { + index: 0, + hash, + signature, + collision: false, + colliding: Vec::new(), + }, + ); + } + } + + for (id, mir_class) in &mir.classes { + // We size classes larger than actually needed in an attempt to + // reduce collisions when performing dynamic dispatch. + let methods_len = max( + round_methods(mir_class.methods.len()) * METHOD_TABLE_FACTOR, + METHOD_TABLE_MIN_SIZE, + ); + let name = + format!("{}::{}", id.module(db).name(db).as_str(), id.name(db)); + let class = context.class_type( + methods_len, + &format!("{}::class", name), + method, + ); + let instance = match id.0 { + INT_ID => context.builtin_type( + &name, + header, + context.i64_type().into(), + ), + FLOAT_ID => context.builtin_type( + &name, + header, + context.f64_type().into(), + ), + STRING_ID => context.builtin_type( + &name, + header, + context.pointer_type().into(), + ), + ARRAY_ID => context.builtin_type( + &name, + header, + context.rust_vec_type().into(), + ), + BOOLEAN_ID | NIL_ID => { + let typ = context.opaque_struct(&name); + + typ.set_body(&[header.into()], false); + typ + } + BYTE_ARRAY_ID => context.builtin_type( + &name, + header, + context.rust_vec_type().into(), + ), + CHANNEL_ID => context.builtin_type( + &name, + header, + context.pointer_type().into(), + ), + _ => { + // First we forward-declare the structures, as fields + // may need to refer to other classes regardless of + // ordering. + context.opaque_struct(&name) + } + }; + + let mut buckets = vec![false; methods_len]; + let max_bucket = methods_len.saturating_sub(1); + + // The slot for the dropper method has to be set first to ensure + // other methods are never hashed into this slot, regardless of the + // order we process them in. + if !buckets.is_empty() { + buckets[DROPPER_INDEX as usize] = true; + } + + // Define the method signatures once (so we can cheaply retrieve + // them whenever needed), and assign the methods to their method + // table slots. + for &method in &mir_class.methods { + let name = method.name(db); + let hash = method_hasher.hash(name); + let mut collision = false; + let index = if mir_class.id.kind(db).is_closure() { + // For closures we use a fixed layout so we can call its + // methods using virtual dispatch instead of dynamic + // dispatch. + match method.name(db).as_str() { + DROPPER_METHOD => DROPPER_INDEX as usize, + CALL_METHOD => CLOSURE_CALL_INDEX as usize, + _ => unreachable!(), + } + } else if name == DROPPER_METHOD { + // Droppers always go in slot 0 so we can efficiently call + // them even when types aren't statically known. + DROPPER_INDEX as usize + } else { + let mut index = hash as usize & (methods_len - 1); + + while buckets[index] { + collision = true; + index = (index + 1) & max_bucket; + } + + index + }; + + buckets[index] = true; + + // We track collisions so we can generate more optimal dynamic + // dispatch code if we statically know one method never collides + // with another method in the same class. + if collision { + if let MethodSource::Implementation(_, orig) = + method.source(db) + { + // We have to track the original method as defined in + // the trait, not the implementation defined for the + // class. This is because when we generate the dynamic + // dispatch code, we only know about the trait method. + methods.get_mut(&orig).unwrap().collision = true; + methods + .get_mut(&orig) + .unwrap() + .colliding + .push(mir_class.id); + } + } + + let typ = if method.is_async(db) { + context.void_type().fn_type( + &[context_layout.ptr_type(space).into()], + false, + ) + } else { + let mut args: Vec = vec![ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + ]; + + if method.is_instance_method(db) { + args.push(context.pointer_type().into()); + } + + for _ in 0..method.number_of_arguments(db) { + args.push(context.pointer_type().into()); + } + + context.pointer_type().fn_type(&args, false) + }; + + methods.insert( + method, + MethodInfo { + index: index as u16, + hash, + signature: typ, + collision, + colliding: Vec::new(), + }, + ); + } + + class_layouts.insert(*id, class); + instance_layouts.insert(*id, instance); + } + + let process_size = + if let OperatingSystem::Linux = state.config.target.os { + // Mutexes are smaller on Linux, resulting in a smaller process + // size, so we have to take that into account when calculating + // field offsets. + 112 + } else { + 128 + }; + + for id in mir.classes.keys() { + if id.is_builtin() { + continue; + } + + let layout = instance_layouts[id]; + let mut fields: Vec = vec![header.into()]; + + // For processes we need to take into account the space between the + // header and the first field. We don't actually care about that + // state in the generated code, so we just insert a single member + // that covers it. + if id.kind(db).is_async() { + fields.push( + context + .i8_type() + .array_type(process_size - HEADER_SIZE) + .into(), + ); + } + + for _ in 0..id.number_of_fields(db) { + fields.push(context.pointer_type().into()); + } + + layout.set_body(&fields, false); + } + + Self { + empty_class: context.class_type(0, "", method), + method, + classes: class_layouts, + instances: instance_layouts, + state: state_layout, + header, + result: result_layout, + context: context_layout, + method_counts: method_counts_layout, + methods, + message: message_layout, + } + } + + pub(crate) fn methods(&self, class: ClassId) -> u32 { + self.classes[&class] + .get_field_type_at_index(3) + .unwrap() + .into_array_type() + .len() + } +} diff --git a/compiler/src/llvm/method_hasher.rs b/compiler/src/llvm/method_hasher.rs new file mode 100644 index 000000000..7b850d9ab --- /dev/null +++ b/compiler/src/llvm/method_hasher.rs @@ -0,0 +1,96 @@ +use fnv::{FnvHashMap, FnvHashSet}; + +/// A type for generating method hash codes. +/// +/// These hash codes are used as part of dynamic dispatch. Each method name is +/// given a globally unique hash code. We don't need to consider the entire +/// method's signature as Inko doesn't allow overloading of methods. +/// +/// The algorithm used by this hasher is FNV-1a, as it's one of the fastest +/// not-so-terrible hash function for small inputs. +pub(crate) struct MethodHasher<'a> { + hashes: FnvHashMap<&'a str, u64>, + used: FnvHashSet, +} + +impl<'a> MethodHasher<'a> { + pub(crate) fn new() -> MethodHasher<'a> { + // We can't predict how many unique method names there are without + // counting them, which would involve hashing, which in turn likely + // wouldn't make this hasher any faster. + // + // Instead we conservatively assume every program needs at least this + // many slots, reducing the amount of rehashing necessary without + // reserving way too much memory. + let size = 512; + + MethodHasher { + hashes: FnvHashMap::with_capacity_and_hasher( + size, + Default::default(), + ), + used: FnvHashSet::with_capacity_and_hasher( + size, + Default::default(), + ), + } + } + + pub(crate) fn hash(&mut self, name: &'a str) -> u64 { + if let Some(&hash) = self.hashes.get(name) { + return hash; + } + + let mut base = 0xcbf29ce484222325; + + for &byte in name.as_bytes() { + base = self.round(base, byte as u64); + } + + // Bytes are in the range from 0..255. By starting the extra value at + // 256 we're (hopefully) less likely to produce collisions with method + // names that are one byte longer than our current method name. + let mut extra = 256_u64; + let mut hash = base; + + // FNV isn't a perfect hash function, so collisions are possible. In + // this case we just add a number to the base hash until we produce a + // unique hash. + while self.used.contains(&hash) { + hash = self.round(base, extra); + extra = extra.wrapping_add(1); + } + + self.hashes.insert(name, hash); + self.used.insert(hash); + hash + } + + fn round(&self, hash: u64, value: u64) -> u64 { + (hash ^ value).wrapping_mul(0x100_0000_01b3) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash() { + let mut hasher = MethodHasher::new(); + + assert_eq!(hasher.hash("foo"), hasher.hash("foo")); + assert_ne!(hasher.hash("foo"), hasher.hash("bar")); + } + + #[test] + fn test_hash_conflict() { + let mut hasher = MethodHasher::new(); + + let hash = hasher.hash("foo"); + + hasher.hashes.remove("foo"); + + assert_ne!(hasher.hash("foo"), hash); + } +} diff --git a/compiler/src/llvm/module.rs b/compiler/src/llvm/module.rs new file mode 100644 index 000000000..41fce6544 --- /dev/null +++ b/compiler/src/llvm/module.rs @@ -0,0 +1,148 @@ +use crate::llvm::builder::DebugBuilder; +use crate::llvm::context::Context; +use crate::llvm::layouts::Layouts; +use crate::llvm::runtime_function::RuntimeFunction; +use crate::mir::Constant; +use crate::symbol_names::SYMBOL_PREFIX; +use inkwell::intrinsics::Intrinsic; +use inkwell::types::BasicTypeEnum; +use inkwell::values::{BasicValue, FunctionValue, GlobalValue}; +use inkwell::{module, AddressSpace}; +use std::collections::HashMap; +use std::ops::Deref; +use std::path::Path; +use types::module_name::ModuleName; +use types::{ClassId, MethodId}; + +/// A wrapper around an LLVM Module that provides some additional methods. +pub(crate) struct Module<'a, 'ctx> { + pub(crate) inner: module::Module<'ctx>, + pub(crate) context: &'ctx Context, + pub(crate) name: ModuleName, + pub(crate) layouts: &'a Layouts<'ctx>, + pub(crate) literals: HashMap>, + pub(crate) debug_builder: DebugBuilder<'ctx>, +} + +impl<'a, 'ctx> Module<'a, 'ctx> { + pub(crate) fn new( + context: &'ctx Context, + layouts: &'a Layouts<'ctx>, + name: ModuleName, + path: &Path, + ) -> Self { + let inner = context.create_module(name.as_str()); + let debug_builder = DebugBuilder::new(&inner, context, path); + + Self { + inner, + context, + name, + layouts, + literals: HashMap::new(), + debug_builder, + } + } + + pub(crate) fn add_global(&self, name: &str) -> GlobalValue<'ctx> { + let typ = self.context.pointer_type(); + let space = AddressSpace::default(); + + self.inner.add_global(typ, Some(space), name) + } + + pub(crate) fn add_literal( + &mut self, + value: &Constant, + ) -> GlobalValue<'ctx> { + if let Some(&global) = self.literals.get(value) { + global + } else { + let name = format!( + "{}L_{}_{}", + SYMBOL_PREFIX, + self.name, + self.literals.len() + ); + + let global = self.add_global(&name); + + global.set_initializer( + &self.context.pointer_type().const_null().as_basic_value_enum(), + ); + + self.literals.insert(value.clone(), global); + global + } + } + + pub(crate) fn add_constant(&mut self, name: &str) -> GlobalValue<'ctx> { + self.inner.get_global(name).unwrap_or_else(|| self.add_global(name)) + } + + pub(crate) fn add_class( + &mut self, + id: ClassId, + name: &str, + ) -> GlobalValue<'ctx> { + self.inner.get_global(name).unwrap_or_else(|| { + let space = AddressSpace::default(); + let typ = self.layouts.classes[&id].ptr_type(space); + + self.inner.add_global(typ, Some(space), name) + }) + } + + pub(crate) fn add_method( + &self, + name: &str, + method: MethodId, + ) -> FunctionValue<'ctx> { + self.inner.get_function(name).unwrap_or_else(|| { + self.inner.add_function( + name, + self.layouts.methods[&method].signature, + None, + ) + }) + } + + pub(crate) fn add_setup_function(&self, name: &str) -> FunctionValue<'ctx> { + if let Some(func) = self.inner.get_function(name) { + func + } else { + let space = AddressSpace::default(); + let args = [self.layouts.state.ptr_type(space).into()]; + let typ = self.context.void_type().fn_type(&args, false); + + self.inner.add_function(name, typ, None) + } + } + + pub(crate) fn runtime_function( + &self, + function: RuntimeFunction, + ) -> FunctionValue<'ctx> { + self.inner + .get_function(function.name()) + .unwrap_or_else(|| function.build(self)) + } + + pub(crate) fn intrinsic( + &self, + name: &str, + args: &[BasicTypeEnum<'ctx>], + ) -> FunctionValue<'ctx> { + Intrinsic::find(name) + .and_then(|intr| intr.get_declaration(&self.inner, args)) + .unwrap() + } +} + +impl<'a, 'ctx> Deref for Module<'a, 'ctx> { + type Target = module::Module<'ctx>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs new file mode 100644 index 000000000..3f1ebe1ca --- /dev/null +++ b/compiler/src/llvm/passes.rs @@ -0,0 +1,5339 @@ +use crate::config::BuildDirectories; +use crate::llvm::builder::Builder; +use crate::llvm::constants::{ + ATOMIC_KIND, BOXED_FLOAT_VALUE_INDEX, BOXED_INT_VALUE_INDEX, + CLASS_METHODS_COUNT_INDEX, CLASS_METHODS_INDEX, CLOSURE_CALL_INDEX, + CONTEXT_ARGS_INDEX, CONTEXT_PROCESS_INDEX, CONTEXT_STATE_INDEX, + DROPPER_INDEX, FALSE_INDEX, FIELD_OFFSET, FLOAT_KIND, HASH_KEY0_INDEX, + HASH_KEY1_INDEX, HEADER_CLASS_INDEX, HEADER_KIND_INDEX, HEADER_REFS_INDEX, + INT_KIND, INT_MASK, INT_SHIFT, LLVM_RESULT_STATUS_INDEX, + LLVM_RESULT_VALUE_INDEX, MAX_INT, MESSAGE_ARGUMENTS_INDEX, + METHOD_FUNCTION_INDEX, METHOD_HASH_INDEX, MIN_INT, NIL_INDEX, OWNED_KIND, + PERMANENT_KIND, PROCESS_FIELD_OFFSET, REF_KIND, REF_MASK, TAG_MASK, + TRUE_INDEX, +}; +use crate::llvm::context::Context; +use crate::llvm::layouts::Layouts; +use crate::llvm::module::Module; +use crate::llvm::runtime_function::RuntimeFunction; +use crate::mir::{ + CloneKind, Constant, Instruction, LocationId, Method, Mir, RegisterId, +}; +use crate::state::State; +use crate::symbol_names::SymbolNames; +use crate::target::Architecture; +use inkwell::basic_block::BasicBlock; +use inkwell::passes::{PassManager, PassManagerBuilder}; +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; +use inkwell::types::{ + BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, +}; +use inkwell::values::{ + BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, + FunctionValue, GlobalValue, IntValue, PointerValue, +}; +use inkwell::AddressSpace; +use inkwell::OptimizationLevel; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; +use types::module_name::ModuleName; +use types::{BuiltinFunction, ClassId, Database}; + +/// A compiler pass that compiles Inko MIR into object files using LLVM. +pub(crate) struct Compile<'a, 'b, 'ctx> { + db: &'a Database, + mir: &'a Mir, + module_index: usize, + layouts: &'a Layouts<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'b mut Module<'a, 'ctx>, + + /// All native functions and the class IDs they belong to. + functions: HashMap>>, +} + +impl<'a, 'b, 'ctx> Compile<'a, 'b, 'ctx> { + /// Compiles all the modules into object files. + /// + /// The return value is a list of file paths of the object files. + pub(crate) fn run_all( + state: &'a State, + directories: &BuildDirectories, + mir: &'a Mir, + ) -> Result, String> { + let context = Context::new(); + let types = Layouts::new(state, mir, &context); + let names = SymbolNames::new(&state.db, mir); + let mut modules = Vec::with_capacity(mir.modules.len()); + + for module_index in 0..mir.modules.len() { + let mod_id = mir.modules[module_index].id; + let name = mod_id.name(&state.db).clone(); + let path = mod_id.file(&state.db); + let mut module = Module::new(&context, &types, name, &path); + + Compile { + db: &state.db, + mir, + module_index, + names: &names, + context: &context, + module: &mut module, + layouts: &types, + functions: HashMap::new(), + } + .run(); + + modules.push(module); + } + + let main_module = Module::new( + &context, + &types, + ModuleName::new("$main"), + Path::new("$main.inko"), + ); + + GenerateMain::new( + &state.db, + mir, + &types, + &names, + &context, + &main_module, + ) + .run(); + + modules.push(main_module); + + match state.config.target.arch { + Architecture::Amd64 => { + Target::initialize_x86(&InitializationConfig::default()); + } + Architecture::Arm64 => { + Target::initialize_aarch64(&InitializationConfig::default()); + } + } + + // LLVM's optimisation level controls which passes to run, but some/many + // of those may not be relevant to Inko, while slowing down compile + // times. Thus instead of using this knob, we provide our own list of + // passes. Swift and Rust (and possibly others) take a similar approach. + let opt = OptimizationLevel::None; + let reloc = RelocMode::PIC; + let model = CodeModel::Default; + let triple = TargetTriple::create(&state.config.target.llvm_triple()); + let target = Target::from_triple(&triple).unwrap(); + let target_machine = target + .create_target_machine(&triple, "", "", opt, reloc, model) + .unwrap(); + let layout = target_machine.get_target_data().get_data_layout(); + let pm_builder = PassManagerBuilder::create(); + let pm = PassManager::create(()); + + pm_builder.set_optimization_level(opt); + pm_builder.populate_module_pass_manager(&pm); + pm.add_promote_memory_to_register_pass(); + + for module in &modules { + module.set_data_layout(&layout); + module.set_triple(&triple); + pm.run_on(&module.inner); + } + + let mut paths = Vec::with_capacity(modules.len()); + + for module in &modules { + let path = directories + .objects + .join(format!("{}.o", module.name.normalized_name())); + + target_machine + .write_to_file(&module.inner, FileType::Object, path.as_path()) + .map_err(|err| { + format!("Failed to create {}: {}", path.display(), err) + })?; + + paths.push(path); + } + + Ok(paths) + } + + pub(crate) fn run(mut self) { + for &class_id in &self.mir.modules[self.module_index].classes { + for method_id in &self.mir.classes[&class_id].methods { + let func = LowerMethod::new( + self.db, + self.mir, + self.layouts, + self.context, + self.names, + self.module, + &self.mir.methods[method_id], + ) + .run(); + + self.functions + .entry(class_id) + .or_insert_with(Vec::new) + .push(func); + } + } + + self.generate_setup_function(); + self.module.debug_builder.finalize(); + } + + fn generate_setup_function(&mut self) { + let mod_id = self.mir.modules[self.module_index].id; + let space = AddressSpace::default(); + let fn_name = &self.names.setup_functions[&mod_id]; + let fn_val = self.module.add_setup_function(fn_name); + let builder = Builder::new(self.context, fn_val); + let entry_block = self.context.append_basic_block(fn_val); + + builder.switch_to_block(entry_block); + + let state_var = builder.alloca(self.layouts.state.ptr_type(space)); + let method_var = builder.alloca(self.layouts.method); + + builder.store(state_var, fn_val.get_nth_param(0).unwrap()); + + let body = self.context.append_basic_block(fn_val); + + builder.jump(body); + builder.switch_to_block(body); + + // Allocate all classes defined in this module, and store them in their + // corresponding globals. + for &class_id in &self.mir.modules[self.module_index].classes { + let raw_name = class_id.name(self.db); + let name_ptr = builder.string_literal(raw_name).0.into(); + let fields_len = self + .context + .i8_type() + .const_int(class_id.number_of_fields(self.db) as _, false) + .into(); + let methods_len = self + .context + .i16_type() + .const_int( + (self.layouts.methods(class_id) as usize) as _, + false, + ) + .into(); + + let class_new = if class_id.kind(self.db).is_async() { + self.module.runtime_function(RuntimeFunction::ClassProcess) + } else { + self.module.runtime_function(RuntimeFunction::ClassObject) + }; + + let layout = self.layouts.classes[&class_id]; + let global_name = &self.names.classes[&class_id]; + let global = self.module.add_class(class_id, global_name); + + // The class globals must have an initializer, otherwise LLVM treats + // them as external globals. + global.set_initializer( + &layout.ptr_type(space).const_null().as_basic_value_enum(), + ); + + let state = builder.load_pointer(self.layouts.state, state_var); + + // Built-in classes are defined in the runtime library, so we should + // look them up instead of creating a new one. + let class_ptr = if class_id.is_builtin() { + // The first three fields in the State type are the singletons, + // followed by the built-in classes, hence the offset of 3. + builder + .load_field(self.layouts.state, state, class_id.0 + 3) + .into_pointer_value() + } else { + builder + .call(class_new, &[name_ptr, fields_len, methods_len]) + .into_pointer_value() + }; + + for method in &self.mir.classes[&class_id].methods { + let info = &self.layouts.methods[method]; + let name = &self.names.methods[method]; + let func = self + .module + .get_function(name) + .unwrap() + .as_global_value() + .as_pointer_value(); + + let slot = builder.u32_literal(info.index as u32); + let method_addr = builder.array_field_index_address( + self.layouts.empty_class, + class_ptr, + CLASS_METHODS_INDEX, + slot, + ); + + let hash = builder.u64_literal(info.hash); + let layout = self.layouts.method; + let hash_idx = METHOD_HASH_INDEX; + let func_idx = METHOD_FUNCTION_INDEX; + + builder.store_field(layout, method_var, hash_idx, hash); + builder.store_field(layout, method_var, func_idx, func); + + let method = builder.load(layout, method_var); + + builder.store(method_addr, method); + } + + builder.store(global.as_pointer_value(), class_ptr); + } + + // Populate the globals for the constants defined in this module. + for &cid in &self.mir.modules[self.module_index].constants { + let name = &self.names.constants[&cid]; + let global = self.module.add_constant(name); + let value = &self.mir.constants[&cid]; + + global.set_initializer( + &self.context.pointer_type().const_null().as_basic_value_enum(), + ); + self.set_constant_global(&builder, state_var, value, global); + } + + // Populate the globals for the literals defined in this module. + for (value, global) in &self.module.literals { + self.set_constant_global(&builder, state_var, value, *global); + } + + builder.return_value(None); + } + + fn set_constant_global( + &self, + builder: &Builder<'ctx>, + state_var: PointerValue<'ctx>, + constant: &Constant, + global: GlobalValue<'ctx>, + ) -> PointerValue<'ctx> { + let global = global.as_pointer_value(); + let value = self.permanent_value(builder, state_var, constant); + + builder.store(global, value); + global + } + + fn permanent_value( + &self, + builder: &Builder<'ctx>, + state_var: PointerValue<'ctx>, + constant: &Constant, + ) -> BasicValueEnum<'ctx> { + let state = builder.load_pointer(self.layouts.state, state_var).into(); + + match constant { + Constant::Int(val) => { + if let Some(ptr) = builder.tagged_int(*val) { + ptr.into() + } else { + let val = builder.i64_literal(*val).into(); + let func = self + .module + .runtime_function(RuntimeFunction::IntBoxedPermanent); + + builder.call(func, &[state, val]) + } + } + Constant::Float(val) => { + let val = builder.context.f64_type().const_float(*val).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatBoxedPermanent); + + builder.call(func, &[state, val]) + } + Constant::String(val) => { + let bytes_typ = + builder.context.i8_type().array_type(val.len() as _); + let bytes_var = builder.alloca(bytes_typ); + let bytes = builder.string_bytes(val); + + builder.store(bytes_var, bytes); + + let len = builder.u64_literal(val.len() as u64).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StringNewPermanent); + + builder.call(func, &[state, bytes_var.into(), len]) + } + Constant::Array(values) => { + let len = builder.u64_literal(values.len() as u64).into(); + let new_func = self + .module + .runtime_function(RuntimeFunction::ArrayNewPermanent); + let push_func = + self.module.runtime_function(RuntimeFunction::ArrayPush); + let array = builder.call(new_func, &[state, len]); + + for val in values.iter() { + let ptr = self + .permanent_value(builder, state_var, val) + .into_pointer_value(); + + builder.call(push_func, &[state, array.into(), ptr.into()]); + } + + array + } + } + } +} + +/// A pass for lowering the MIR of a single method. +pub struct LowerMethod<'a, 'b, 'ctx> { + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + + /// The MIR method that we're lowering to LLVM. + method: &'b Method, + + /// A map of method names to their mangled names. + /// + /// We cache these so we don't have to recalculate them on every reference. + names: &'a SymbolNames, + + /// The builder to use for generating instructions. + builder: Builder<'ctx>, + + /// The LLVM module the generated code belongs to. + module: &'b mut Module<'a, 'ctx>, + + /// MIR registers and their corresponding LLVM stack variables. + variables: HashMap>, + + /// The LLVM types for each MIR register. + variable_types: HashMap>, +} + +impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { + fn new( + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + context: &'ctx Context, + names: &'a SymbolNames, + module: &'b mut Module<'a, 'ctx>, + method: &'b Method, + ) -> Self { + let function = module.add_method(&names.methods[&method.id], method.id); + let builder = Builder::new(context, function); + + LowerMethod { + db, + mir, + layouts, + method, + names, + module, + builder, + variables: HashMap::new(), + variable_types: HashMap::new(), + } + } + + fn run(&mut self) -> FunctionValue<'ctx> { + if self.method.id.is_async(self.db) { + self.async_method(); + } else { + self.regular_method(); + } + + self.builder.function + } + + fn regular_method(&mut self) { + let entry_block = self.builder.add_block(); + + self.builder.switch_to_block(entry_block); + + let space = AddressSpace::default(); + let state_var = + self.builder.new_stack_slot(self.layouts.state.ptr_type(space)); + let proc_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + + // Build the stores for all the arguments, including the generated ones. + self.builder.store(state_var, self.builder.argument(0)); + self.builder.store(proc_var, self.builder.argument(1)); + + self.define_register_variables(); + + for (arg, reg) in + self.builder.arguments().skip(2).zip(self.method.arguments.iter()) + { + self.builder.store(self.variables[reg], arg); + } + + let (line, _) = self.mir.location(self.method.location).line_column(); + let debug_func = self.module.debug_builder.new_function( + self.method.id.name(self.db), + &self.names.methods[&self.method.id], + line, + self.method.id.is_private(self.db), + false, + ); + + self.builder.set_debug_function(debug_func); + self.method_body(state_var, proc_var); + } + + fn async_method(&mut self) { + let entry_block = self.builder.add_block(); + + self.builder.switch_to_block(entry_block); + + let space = AddressSpace::default(); + let state_typ = self.layouts.state.ptr_type(space); + let state_var = self.builder.new_stack_slot(state_typ); + let proc_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let num_args = self.method.arguments.len() as u32; + let args_type = + self.builder.context.pointer_type().array_type(num_args); + let args_var = self.builder.new_stack_slot(args_type.ptr_type(space)); + let ctx_var = + self.builder.new_stack_slot(self.layouts.context.ptr_type(space)); + + self.define_register_variables(); + + // Destructure the context into its components. This is necessary as the + // context only lives until the first yield. + self.builder.store(ctx_var, self.builder.argument(0)); + + let ctx = self.builder.load_pointer(self.layouts.context, ctx_var); + + self.builder.store( + state_var, + self.builder.load_field( + self.layouts.context, + ctx, + CONTEXT_STATE_INDEX, + ), + ); + self.builder.store( + proc_var, + self.builder.load_field( + self.layouts.context, + ctx, + CONTEXT_PROCESS_INDEX, + ), + ); + + let args = self + .builder + .load_field(self.layouts.context, ctx, CONTEXT_ARGS_INDEX) + .into_pointer_value(); + + self.builder.store(args_var, args); + + // For async methods we don't include the receiver in the message, as + // this is redundant, and keeps message sizes as compact as possible. + // Instead, we load the receiver from the context. + let self_var = self.variables[&self.method.arguments[0]]; + + self.builder.store( + self_var, + self.builder.load(self.builder.context.pointer_type(), proc_var), + ); + + // Populate the argument stack variables according to the values stored + // in the context structure. + for (index, reg) in self.method.arguments.iter().skip(1).enumerate() { + let var = self.variables[reg]; + let args = self.builder.load_pointer(args_type, args_var); + let val = self + .builder + .load_array_index(args_type, args, index) + .into_pointer_value(); + + self.builder.store(var, val); + } + + let (line, _) = self.mir.location(self.method.location).line_column(); + let debug_func = self.module.debug_builder.new_function( + self.method.id.name(self.db), + &self.names.methods[&self.method.id], + line, + self.method.id.is_private(self.db), + false, + ); + + self.builder.set_debug_function(debug_func); + self.method_body(state_var, proc_var); + } + + fn method_body( + &mut self, + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + ) { + let mut queue = VecDeque::new(); + let mut visited = HashSet::new(); + let mut llvm_blocks = Vec::with_capacity(self.method.body.blocks.len()); + + for _ in 0..self.method.body.blocks.len() { + llvm_blocks.push(self.builder.add_block()); + } + + self.builder.jump(llvm_blocks[self.method.body.start_id.0]); + + queue.push_back(self.method.body.start_id); + visited.insert(self.method.body.start_id); + + while let Some(block_id) = queue.pop_front() { + let mir_block = &self.method.body.blocks[block_id.0]; + let llvm_block = llvm_blocks[block_id.0]; + + self.builder.switch_to_block(llvm_block); + + for ins in &mir_block.instructions { + self.instruction(&llvm_blocks, state_var, proc_var, ins); + } + + for &child in &mir_block.successors { + if visited.insert(child) { + queue.push_back(child); + } + } + } + } + + fn instruction( + &mut self, + all_blocks: &[BasicBlock], + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + ins: &Instruction, + ) { + match ins { + Instruction::CallBuiltin(ins) => { + self.set_debug_location(ins.location); + + match ins.name { + BuiltinFunction::IntAdd => { + self.checked_int_operation( + "llvm.sadd.with.overflow", + state_var, + proc_var, + self.variables[&ins.register], + self.variables[&ins.arguments[0]], + self.variables[&ins.arguments[1]], + ); + } + BuiltinFunction::IntSub => { + self.checked_int_operation( + "llvm.ssub.with.overflow", + state_var, + proc_var, + self.variables[&ins.register], + self.variables[&ins.arguments[0]], + self.variables[&ins.arguments[1]], + ); + } + BuiltinFunction::IntMul => { + self.checked_int_operation( + "llvm.smul.with.overflow", + state_var, + proc_var, + self.variables[&ins.register], + self.variables[&ins.arguments[0]], + self.variables[&ins.arguments[1]], + ); + } + BuiltinFunction::IntDiv => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_division_overflow(proc_var, lhs, rhs); + + let raw = self.builder.int_div(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntRem => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_division_overflow(proc_var, lhs, rhs); + + let raw = self.builder.int_rem(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitAnd => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.bit_and(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitOr => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.bit_or(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitNot => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_int(val_var); + let raw = self.builder.bit_not(val); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitXor => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.bit_xor(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_eq(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntGt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_gt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntGe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_ge(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntLe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_le(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntLt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_lt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntPow => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let lhs = self.read_int(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::IntPow); + let raw = self + .builder + .call(func, &[proc, lhs, rhs]) + .into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatAdd => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_add(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatSub => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_sub(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatDiv => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_div(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatMul => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_mul(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatMod => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_rem( + self.builder.float_add( + self.builder.float_rem(lhs, rhs), + rhs, + ), + rhs, + ); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatCeil => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let func = self.module.intrinsic( + "llvm.ceil", + &[self.builder.context.f64_type().into()], + ); + let raw = self + .builder + .call(func, &[val.into()]) + .into_float_value(); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatFloor => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let func = self.module.intrinsic( + "llvm.floor", + &[self.builder.context.f64_type().into()], + ); + let raw = self + .builder + .call(func, &[val.into()]) + .into_float_value(); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatRound => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = self.read_float(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatRound); + let res = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = self.read_float(lhs_var).into(); + let rhs = self.read_float(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatEq); + let res = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatToBits => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let bits = self + .builder + .bitcast(val, self.builder.context.i64_type()) + .into_int_value(); + let res = self.new_int(state_var, bits); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatFromBits => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_int(val_var); + let bits = self + .builder + .bitcast(val, self.builder.context.f64_type()) + .into_float_value(); + let res = self.new_float(state_var, bits); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatGt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_gt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatGe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_ge(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatLt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_lt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatLe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_le(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatIsInf => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let fabs = self.module.intrinsic( + "llvm.fabs", + &[self.builder.context.f64_type().into()], + ); + + let pos_val = self + .builder + .call(fabs, &[val.into()]) + .into_float_value(); + + let pos_inf = self.builder.f64_literal(f64::INFINITY); + let cond = self.builder.float_eq(pos_val, pos_inf); + let res = self.new_bool(state_var, cond); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatIsNan => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let raw = self.builder.float_is_nan(val); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatToInt => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var).into(); + let func = self.module.intrinsic( + "llvm.fptosi.sat", + &[ + self.builder.context.i64_type().into(), + self.builder.context.f64_type().into(), + ], + ); + + let raw = + self.builder.call(func, &[val]).into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatToString => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.read_float(val_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatToString); + let res = self.builder.call(func, &[state, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayCapacity => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayCapacity; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayClear => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayClear; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayDrop => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayGet => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let idx_var = self.variables[&ins.arguments[1]]; + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let index = self.read_int(idx_var).into(); + let func_name = RuntimeFunction::ArrayGet; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[array, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayLength => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayLength; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayPop => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayPop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayPush => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let value_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let tagged = + self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(tagged).into(); + let value = + self.builder.load_untyped_pointer(value_var).into(); + let func_name = RuntimeFunction::ArrayPush; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, array, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayRemove => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let idx_var = self.variables[&ins.arguments[1]]; + let val = self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(val).into(); + let idx = self.read_int(idx_var).into(); + let func_name = RuntimeFunction::ArrayRemove; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[array, idx]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayReserve => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let amount_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(val).into(); + let amount = self.read_int(amount_var).into(); + let func_name = RuntimeFunction::ArrayReserve; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, array, amount]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArraySet => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let value_var = self.variables[&ins.arguments[2]]; + let tagged = + self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(tagged).into(); + let index = self.read_int(index_var).into(); + let value = + self.builder.load_untyped_pointer(value_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ArraySet); + let res = + self.builder.call(func, &[array, index, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayNew => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayNew); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayAppend => { + let reg_var = self.variables[&ins.register]; + let target_var = self.variables[&ins.arguments[0]]; + let source_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let target = self + .builder + .untagged( + self.builder.load_untyped_pointer(target_var), + ) + .into(); + let source = self + .builder + .untagged( + self.builder.load_untyped_pointer(source_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayAppend); + let res = + self.builder.call(func, &[state, target, source]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayClear => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayClear); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayClone => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayClone); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayCopyFrom => { + let reg_var = self.variables[&ins.register]; + let target_var = self.variables[&ins.arguments[0]]; + let source_var = self.variables[&ins.arguments[1]]; + let start_var = self.variables[&ins.arguments[2]]; + let length_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let target = self + .builder + .untagged( + self.builder.load_untyped_pointer(target_var), + ) + .into(); + let source = self + .builder + .untagged( + self.builder.load_untyped_pointer(source_var), + ) + .into(); + let start = self.read_int(start_var).into(); + let length = self.read_int(length_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayCopyFrom, + ); + let res = self.builder.call( + func, + &[state, target, source, start, length], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayDrainToString => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayDrainToString, + ); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayToString => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayToString, + ); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayDrop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayDrop); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = self + .builder + .untagged( + self.builder.load_untyped_pointer(lhs_var), + ) + .into(); + let rhs = self + .builder + .untagged( + self.builder.load_untyped_pointer(rhs_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayEq); + let res = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayGet => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let index = self.read_int(index_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayGet); + let res = self.builder.call(func, &[array, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayLength => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayLength); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayPop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayPop); + let res = self.builder.call(func, &[array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayPush => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let value_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let value = self.read_int(value_var).into(); + let func_name = RuntimeFunction::ByteArrayPush; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, array, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayRemove => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let index = self.read_int(index_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayRemove); + let res = self.builder.call(func, &[array, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayResize => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; + let fill_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let fill = self.read_int(fill_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayResize); + let res = self + .builder + .call(func, &[state, array, size, fill]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArraySet => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let value_var = self.variables[&ins.arguments[2]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let index = self.read_int(index_var).into(); + let value = self.read_int(value_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArraySet); + let res = + self.builder.call(func, &[array, index, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArraySlice => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let length_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let start = self.read_int(start_var).into(); + let length = self.read_int(length_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArraySlice); + let res = self + .builder + .call(func, &[state, array, start, length]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessSpawn => { + let reg_var = self.variables[&ins.register]; + let program_var = self.variables[&ins.arguments[0]]; + let args_var = self.variables[&ins.arguments[1]]; + let env_var = self.variables[&ins.arguments[2]]; + let stdin_var = self.variables[&ins.arguments[3]]; + let stdout_var = self.variables[&ins.arguments[4]]; + let stderr_var = self.variables[&ins.arguments[5]]; + let dir_var = self.variables[&ins.arguments[6]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let program = self + .builder + .load_untyped_pointer(program_var) + .into(); + let args = + self.builder.load_untyped_pointer(args_var).into(); + let env = + self.builder.load_untyped_pointer(env_var).into(); + let stdin = self.read_int(stdin_var).into(); + let stdout = self.read_int(stdout_var).into(); + let stderr = self.read_int(stderr_var).into(); + let dir = + self.builder.load_untyped_pointer(dir_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessSpawn, + ); + let res = self.builder.call( + func, + &[ + proc, program, args, env, stdin, stdout, + stderr, dir, + ], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessDrop => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessDrop, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStderrClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStderrClose, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStderrRead => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStderrRead, + ); + let res = self + .builder + .call(func, &[state, proc, child, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinClose, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinFlush => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinFlush, + ); + let res = + self.builder.call(func, &[state, proc, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinWriteBytes => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinWriteBytes, + ); + let res = + self.builder.call(func, &[state, proc, child, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinWriteString => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let input_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinWriteString, + ); + let res = self + .builder + .call(func, &[state, proc, child, input]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdoutClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdoutClose, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdoutRead => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdoutRead, + ); + let res = self + .builder + .call(func, &[state, proc, child, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessTryWait => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessTryWait, + ); + let res = self.builder.call(func, &[child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessWait => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessWait, + ); + let res = self.builder.call(func, &[proc, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::CpuCores => { + let reg_var = self.variables[&ins.register]; + let func = self + .module + .runtime_function(RuntimeFunction::CpuCores); + let res = self.builder.call(func, &[]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryCreate => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryCreate; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryCreateRecursive => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = + RuntimeFunction::DirectoryCreateRecursive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryList => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryList; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryRemove => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryRemove; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryRemoveRecursive => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryRemoveAll; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvArguments => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvArguments; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvExecutable => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvExecutable; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvGet => { + let reg_var = self.variables[&ins.register]; + let name_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let name = + self.builder.load_untyped_pointer(name_var).into(); + let func_name = RuntimeFunction::EnvGet; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, name]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvGetWorkingDirectory => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvGetWorkingDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvHomeDirectory => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvHomeDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvSetWorkingDirectory => { + let reg_var = self.variables[&ins.register]; + let dir_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let dir = + self.builder.load_untyped_pointer(dir_var).into(); + let func_name = RuntimeFunction::EnvSetWorkingDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, dir]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvTempDirectory => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvTempDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvVariables => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvVariables; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::Exit => { + let status_var = self.variables[&ins.arguments[0]]; + let status = self.read_int(status_var).into(); + let func_name = RuntimeFunction::Exit; + let func = self.module.runtime_function(func_name); + + self.builder.call_void(func, &[status]); + self.builder.unreachable(); + } + BuiltinFunction::FileCopy => { + let reg_var = self.variables[&ins.register]; + let from_var = self.variables[&ins.arguments[0]]; + let to_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let from = + self.builder.load_untyped_pointer(from_var).into(); + let to = + self.builder.load_untyped_pointer(to_var).into(); + let func_name = RuntimeFunction::FileCopy; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, from, to]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileDrop => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let func_name = RuntimeFunction::FileDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, file]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileFlush => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let func_name = RuntimeFunction::FileFlush; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, file]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileOpen => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let mode_var = self.variables[&ins.arguments[1]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let mode = self.read_int(mode_var).into(); + let func_name = RuntimeFunction::FileOpen; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc, path, mode]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileRead => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::FileRead; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, file, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileRemove => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::FileRemove; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileSeek => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let off_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let off = self.read_int(off_var).into(); + let func_name = RuntimeFunction::FileSeek; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, path, off]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileSize => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::FileSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileWriteBytes => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func_name = RuntimeFunction::FileWriteBytes; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, file, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileWriteString => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let func_name = RuntimeFunction::FileWriteString; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, file, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelReceive => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelReceive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc, chan]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelReceiveUntil => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let time_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::ChannelReceiveUntil; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, chan, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelDrop => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, chan]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelWait => { + let reg_var = self.variables[&ins.register]; + let chans_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var); + let proc = self.builder.load_untyped_pointer(proc_var); + + // The standard library uses a reference in the wait() + // method, so we need to clear the reference bit before + // using the pointer. + let chans = self.builder.untagged( + self.builder.load_untyped_pointer(chans_var), + ); + let func_name = RuntimeFunction::ChannelWait; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state.into(), proc.into(), chans.into()], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelNew => { + let reg_var = self.variables[&ins.register]; + let cap_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let cap = self.read_int(cap_var).into(); + let func_name = RuntimeFunction::ChannelNew; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, cap]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelSend => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let msg_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let msg = + self.builder.load_untyped_pointer(msg_var).into(); + let func_name = RuntimeFunction::ChannelSend; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, chan, msg]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelTryReceive => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelTryReceive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc, chan]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntRotateLeft => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self.module.intrinsic( + "llvm.fshl", + &[self.builder.context.i64_type().into()], + ); + let raw = self + .builder + .call(func, &[lhs, lhs, rhs]) + .into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntRotateRight => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self.module.intrinsic( + "llvm.fshr", + &[self.builder.context.i64_type().into()], + ); + let raw = self + .builder + .call(func, &[lhs, lhs, rhs]) + .into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntShl => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_shift_bits(proc_var, lhs, rhs); + + let raw = self.builder.left_shift(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntShr => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_shift_bits(proc_var, lhs, rhs); + + let raw = self.builder.signed_right_shift(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntUnsignedShr => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_shift_bits(proc_var, lhs, rhs); + + let raw = self.builder.right_shift(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntToFloat => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_int(val_var); + let raw = self.builder.int_to_float(val); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntToString => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let func = self + .module + .runtime_function(RuntimeFunction::IntToString); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.read_int(val_var).into(); + let ret = self.builder.call(func, &[state, val]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::IntWrappingAdd => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_add(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntWrappingMul => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_mul(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntWrappingSub => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_sub(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ObjectEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.builder.load_untyped_pointer(lhs_var); + let rhs = self.builder.load_untyped_pointer(rhs_var); + let raw = self.builder.int_eq( + self.builder.pointer_to_int(lhs), + self.builder.pointer_to_int(rhs), + ); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::Panic => { + let val_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::ProcessPanic; + let func = self.module.runtime_function(func_name); + + self.builder.call_void(func, &[proc, val]); + self.builder.unreachable(); + } + BuiltinFunction::PathAccessedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathAccessedAt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathCreatedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathCreatedAt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathModifiedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathModifiedAt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathExpand => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathExpand; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathExists => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathExists; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathIsDirectory => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathIsDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathIsFile => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathIsFile; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFrameLine => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let func_name = RuntimeFunction::ProcessStackFrameLine; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let index = self.read_int(index_var).into(); + let res = + self.builder.call(func, &[state, trace, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFrameName => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let func_name = RuntimeFunction::ProcessStackFrameName; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let index = self.read_int(index_var).into(); + let res = + self.builder.call(func, &[state, trace, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFramePath => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let func_name = RuntimeFunction::ProcessStackFramePath; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let index = self.read_int(index_var).into(); + let res = + self.builder.call(func, &[state, trace, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStacktrace => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::ProcessStacktrace; + let func = self.module.runtime_function(func_name); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let res = self.builder.call(func, &[proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStacktraceDrop => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let func_name = RuntimeFunction::ProcessStacktraceDrop; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let res = self.builder.call(func, &[state, trace]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStacktraceLength => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let func_name = + RuntimeFunction::ProcessStacktraceLength; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let res = self.builder.call(func, &[state, trace]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessSuspend => { + let reg_var = self.variables[&ins.register]; + let time_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::ProcessSuspend; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomBytes => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::RandomBytes; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, rng, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomDrop => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, rng]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomFloat => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomFloat; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, rng]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomFloatRange => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let min_var = self.variables[&ins.arguments[1]]; + let max_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let min = self.read_float(min_var).into(); + let max = self.read_float(max_var).into(); + let func_name = RuntimeFunction::RandomFloatRange; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, rng, min, max]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomFromInt => { + let reg_var = self.variables[&ins.register]; + let seed_var = self.variables[&ins.arguments[0]]; + let seed = self.read_int(seed_var).into(); + let func_name = RuntimeFunction::RandomFromInt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[seed]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomInt => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomInt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, rng]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomIntRange => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let min_var = self.variables[&ins.arguments[1]]; + let max_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let min = self.read_int(min_var).into(); + let max = self.read_int(max_var).into(); + let func_name = RuntimeFunction::RandomIntRange; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, rng, min, max]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomNew => { + let reg_var = self.variables[&ins.register]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::RandomNew; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAccept => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let time_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketAccept; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, sock, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairAddress => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[0]]; + let pair = + self.builder.load_untyped_pointer(pair_var).into(); + let func_name = + RuntimeFunction::SocketAddressPairAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[pair]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairDrop => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let pair = + self.builder.load_untyped_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, pair]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairPort => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[0]]; + let pair = + self.builder.load_untyped_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairPort; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[pair]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketNew => { + let reg_var = self.variables[&ins.register]; + let proto_var = self.variables[&ins.arguments[0]]; + let kind_var = self.variables[&ins.arguments[1]]; + let proto = self.read_int(proto_var).into(); + let kind = self.read_int(kind_var).into(); + let func_name = RuntimeFunction::SocketNew; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proto, kind]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketBind => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let addr_var = self.variables[&ins.arguments[1]]; + let port_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let func_name = RuntimeFunction::SocketBind; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, sock, addr, port]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketConnect => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let addr_var = self.variables[&ins.arguments[1]]; + let port_var = self.variables[&ins.arguments[2]]; + let time_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketConnect; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, addr, port, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketDrop => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketListen => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketListen; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketLocalAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketLocalAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketPeerAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketPeerAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketRead => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let time_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketRead; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, size, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketReceiveFrom => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let time_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketReceiveFrom; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, size, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSendBytesTo => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let addr_var = self.variables[&ins.arguments[2]]; + let port_var = self.variables[&ins.arguments[3]]; + let time_var = self.variables[&ins.arguments[4]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketSendBytesTo; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state, proc, sock, buf, addr, port, time], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSendStringTo => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let addr_var = self.variables[&ins.arguments[2]]; + let port_var = self.variables[&ins.arguments[3]]; + let time_var = self.variables[&ins.arguments[4]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketSendStringTo; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state, proc, sock, buf, addr, port, time], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetBroadcast => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetBroadcast; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetKeepalive => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetKeepalive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetLinger => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetLinger; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetNodelay => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetNodelay; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetOnlyV6 => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetOnlyV6; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetRecvSize => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetRecvSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetReuseAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetReuseAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetReusePort => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetReusePort; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetSendSize => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetSendSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetTtl => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetTtl; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownRead => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownRead; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownReadWrite => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = + RuntimeFunction::SocketShutdownReadWrite; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownWrite => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownWrite; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketTryClone => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketTryClone; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketWriteBytes => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let time_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketWriteBytes; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketWriteString => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let time_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketWriteString; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StderrFlush => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::StderrFlush; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StderrWriteBytes => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = self + .builder + .untagged( + self.builder.load_untyped_pointer(input_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::StderrWriteBytes, + ); + + let ret = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::StderrWriteString => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::StderrWriteString, + ); + + let ret = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::StdinRead => { + let reg_var = self.variables[&ins.register]; + let buf_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::StdinRead; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StdoutFlush => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::StdoutFlush; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StdoutWriteBytes => { + let reg_var = self.variables[&ins.register]; + let buf_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func_name = RuntimeFunction::StdoutWriteBytes; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StdoutWriteString => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func_name = RuntimeFunction::StdoutWriteString; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringByte => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let index = self.read_int(index_var).into(); + let func_name = RuntimeFunction::StringByte; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[string, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringCharacters => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringCharacters; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringCharactersDrop => { + let reg_var = self.variables[&ins.register]; + let iter_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let iter = + self.builder.load_untyped_pointer(iter_var).into(); + let func_name = RuntimeFunction::StringCharactersDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, iter]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringCharactersNext => { + let reg_var = self.variables[&ins.register]; + let iter_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let iter = + self.builder.load_untyped_pointer(iter_var).into(); + let func_name = RuntimeFunction::StringCharactersNext; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, iter]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringConcat => { + let reg_var = self.variables[&ins.register]; + let len = + self.builder.i64_literal(ins.arguments.len() as _); + let temp_type = self + .builder + .context + .pointer_type() + .array_type(ins.arguments.len() as _); + let temp_var = self.builder.new_stack_slot(temp_type); + + for (idx, reg) in ins.arguments.iter().enumerate() { + let val = self + .builder + .load_untyped_pointer(self.variables[reg]); + + self.builder.store_array_field( + temp_type, temp_var, idx as _, val, + ); + } + + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::StringConcat; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, temp_var.into(), len.into()]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringConcatArray => { + let reg_var = self.variables[&ins.register]; + let ary_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let ary = + self.builder.load_untyped_pointer(ary_var).into(); + let func_name = RuntimeFunction::StringConcatArray; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, ary]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringDrop => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = + self.builder.load_untyped_pointer(lhs_var).into(); + let rhs = + self.builder.load_untyped_pointer(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StringEquals); + let ret = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::StringSize => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringSliceBytes => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let len_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let start = self.read_int(start_var).into(); + let len = self.read_int(len_var).into(); + let func_name = RuntimeFunction::StringSliceBytes; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, string, start, len]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToByteArray => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringToByteArray; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToFloat => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let end_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let start = self.read_int(start_var).into(); + let end = self.read_int(end_var).into(); + let func_name = RuntimeFunction::StringToFloat; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, string, start, end]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToInt => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let radix_var = self.variables[&ins.arguments[1]]; + let start_var = self.variables[&ins.arguments[2]]; + let end_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let radix = self.read_int(radix_var).into(); + let start = self.read_int(start_var).into(); + let end = self.read_int(end_var).into(); + let func_name = RuntimeFunction::StringToInt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state, proc, string, radix, start, end], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToLower => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringToLower; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToUpper => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringToUpper; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::TimeMonotonic => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::TimeMonotonic; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::TimeSystem => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::TimeSystem; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::TimeSystemOffset => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::TimeSystemOffset; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::HashKey0 => { + let reg_var = self.variables[&ins.register]; + let typ = self.layouts.state; + let state = self.builder.load_pointer(typ, state_var); + let index = HASH_KEY0_INDEX; + let res = self.builder.load_field(typ, state, index); + + self.builder.store(reg_var, res); + } + BuiltinFunction::HashKey1 => { + let reg_var = self.variables[&ins.register]; + let typ = self.layouts.state; + let state = self.builder.load_pointer(typ, state_var); + let index = HASH_KEY1_INDEX; + let res = self.builder.load_field(typ, state, index); + + self.builder.store(reg_var, res); + } + BuiltinFunction::Moved => unreachable!(), + } + } + Instruction::Goto(ins) => { + self.builder.jump(all_blocks[ins.block.0]); + } + Instruction::Return(ins) => { + let var = self.variables[&ins.register]; + let val = + self.builder.load(self.builder.context.pointer_type(), var); + + self.builder.return_value(Some(&val)); + } + Instruction::Array(ins) => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let len = + self.builder.u64_literal(ins.values.len() as u64).into(); + let new_func = + self.module.runtime_function(RuntimeFunction::ArrayNew); + let push_func = + self.module.runtime_function(RuntimeFunction::ArrayPush); + let array = self.builder.call(new_func, &[state, len]); + + for reg in ins.values.iter() { + let var = self.variables[reg]; + let val = self + .builder + .load(self.builder.context.pointer_type(), var) + .into_pointer_value() + .into(); + + self.builder.call(push_func, &[state, array.into(), val]); + } + + self.builder.store(reg_var, array); + } + Instruction::Branch(ins) => { + let cond_var = self.variables[&ins.condition]; + let cond_ptr = self.builder.load_untyped_pointer(cond_var); + + // Load the `true` singleton from `State`. + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let bool_ptr = self + .builder + .load_field(self.layouts.state, state, TRUE_INDEX) + .into_pointer_value(); + + // Since our booleans are heap objects we have to + // compare pointer addresses, and as such first have to + // cast our pointers to ints. + let cond_int = self.builder.pointer_to_int(cond_ptr); + let bool_int = self.builder.pointer_to_int(bool_ptr); + let cond = self.builder.int_eq(cond_int, bool_int); + + self.builder.branch( + cond, + all_blocks[ins.if_true.0], + all_blocks[ins.if_false.0], + ); + } + Instruction::Switch(ins) => { + let reg_var = self.variables[&ins.register]; + let val = self.builder.load_untyped_pointer(reg_var); + let addr = self.builder.pointer_to_int(val); + let shift = self.builder.i64_literal(INT_SHIFT as i64); + let untagged = self.builder.signed_right_shift(addr, shift); + let mut cases = Vec::with_capacity(ins.blocks.len()); + + for (index, block) in ins.blocks.iter().enumerate() { + cases.push(( + self.builder.u64_literal(index as u64), + all_blocks[block.0], + )); + } + + self.builder.exhaustive_switch(untagged, &cases); + } + Instruction::SwitchKind(ins) => { + let val_var = self.variables[&ins.register]; + let kind_var = self.kind_of(val_var); + let kind = self + .builder + .load(self.builder.context.i8_type(), kind_var) + .into_int_value(); + + // Now we can generate the switch that jumps to the correct + // block based on the value kind. + let owned_block = all_blocks[ins.blocks[0].0]; + let ref_block = all_blocks[ins.blocks[1].0]; + let atomic_block = all_blocks[ins.blocks[2].0]; + let perm_block = all_blocks[ins.blocks[3].0]; + let int_block = all_blocks[ins.blocks[4].0]; + let float_block = all_blocks[ins.blocks[5].0]; + let cases = [ + (self.builder.u8_literal(OWNED_KIND), owned_block), + (self.builder.u8_literal(REF_KIND), ref_block), + (self.builder.u8_literal(ATOMIC_KIND), atomic_block), + (self.builder.u8_literal(PERMANENT_KIND), perm_block), + (self.builder.u8_literal(INT_KIND), int_block), + (self.builder.u8_literal(FLOAT_KIND), float_block), + ]; + + self.builder.exhaustive_switch(kind, &cases); + } + Instruction::Nil(ins) => { + let result = self.variables[&ins.register]; + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let val = self.builder.load_field( + self.layouts.state, + state, + NIL_INDEX, + ); + + self.builder.store(result, val); + } + Instruction::True(ins) => { + let result = self.variables[&ins.register]; + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let val = self.builder.load_field( + self.layouts.state, + state, + TRUE_INDEX, + ); + + self.builder.store(result, val); + } + Instruction::False(ins) => { + let result = self.variables[&ins.register]; + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let val = self.builder.load_field( + self.layouts.state, + state, + FALSE_INDEX, + ); + + self.builder.store(result, val); + } + Instruction::Int(ins) => { + let var = self.variables[&ins.register]; + + if let Some(ptr) = self.builder.tagged_int(ins.value) { + self.builder.store(var, ptr); + } else { + let global = self + .module + .add_literal(&Constant::Int(ins.value)) + .as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + } + Instruction::Float(ins) => { + let var = self.variables[&ins.register]; + let global = self + .module + .add_literal(&Constant::Float(ins.value)) + .as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + Instruction::String(ins) => { + let var = self.variables[&ins.register]; + let global = self + .module + .add_literal(&Constant::String(Rc::new(ins.value.clone()))) + .as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + Instruction::MoveRegister(ins) => { + let source = self.variables[&ins.source]; + let target = self.variables[&ins.target]; + let typ = self.variable_types[&ins.source]; + + self.builder.store(target, self.builder.load(typ, source)); + } + Instruction::CallStatic(ins) => { + self.set_debug_location(ins.location); + + let func_name = &self.names.methods[&ins.method]; + let func = self.module.add_method(func_name, ins.method); + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + ]; + + for reg in &ins.arguments { + args.push( + self.builder + .load_untyped_pointer(self.variables[reg]) + .into(), + ); + } + + self.call(ins.register, func, &args); + } + Instruction::CallInstance(ins) => { + self.set_debug_location(ins.location); + + let rec_var = self.variables[&ins.receiver]; + let func_name = &self.names.methods[&ins.method]; + let func = self.module.add_method(func_name, ins.method); + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + self.builder.load_untyped_pointer(rec_var).into(), + ]; + + for reg in &ins.arguments { + args.push( + self.builder + .load_untyped_pointer(self.variables[reg]) + .into(), + ); + } + + self.call(ins.register, func, &args); + } + Instruction::CallDynamic(ins) => { + self.set_debug_location(ins.location); + + // For dynamic dispatch we use hashing as described in + // https://thume.ca/2019/07/29/shenanigans-with-hash-tables/. + // + // Probing is only performed if collisions are known to be + // possible for a certain hash. + let loop_start = self.builder.add_block(); + let after_loop = self.builder.add_block(); + + let index_type = self.builder.context.i64_type(); + let index_var = self.builder.new_stack_slot(index_type); + let rec_var = self.variables[&ins.receiver]; + + let rec = self.builder.load_untyped_pointer(rec_var); + let info = &self.layouts.methods[&ins.method]; + let rec_class = self.class_of(rec); + let rec_type = self.layouts.empty_class; + + // (class.method_slots - 1) as u64 + let len = self.builder.int_to_int( + self.builder.int_sub( + self.builder + .load_field( + rec_type, + rec_class, + CLASS_METHODS_COUNT_INDEX, + ) + .into_int_value(), + self.builder.u16_literal(1), + ), + ); + + let hash = self.builder.u64_literal(info.hash); + + self.builder.store(index_var, hash); + + let space = AddressSpace::default(); + let func_type = info.signature; + let func_var = + self.builder.new_stack_slot(func_type.ptr_type(space)); + + self.builder.jump(loop_start); + + // The start of the probing loop (probing is necessary). + self.builder.switch_to_block(loop_start); + + // slot = index & len + let index = + self.builder.load(index_type, index_var).into_int_value(); + let slot = self.builder.bit_and(index, len); + let method_addr = self.builder.array_field_index_address( + rec_type, + rec_class, + CLASS_METHODS_INDEX, + slot, + ); + let method = self + .builder + .load(self.layouts.method, method_addr) + .into_struct_value(); + + // We only generate the probing code when it's actually + // necessary. In practise most dynamic dispatch call sites won't + // need probing. + if info.collision { + let ne_block = self.builder.add_block(); + + // method.hash == hash + let mhash = self + .builder + .extract_field(method, METHOD_HASH_INDEX) + .into_int_value(); + let hash_eq = self.builder.int_eq(mhash, hash); + + self.builder.branch(hash_eq, after_loop, ne_block); + + // The block to jump to when the hash codes didn't match. + self.builder.switch_to_block(ne_block); + self.builder.store( + index_var, + self.builder + .int_add(index, self.builder.u64_literal(1)), + ); + self.builder.jump(loop_start); + } else { + self.builder.jump(after_loop); + } + + // The block to jump to at the end of the loop, used for + // calling the native function. + self.builder.switch_to_block(after_loop); + + self.builder.store( + func_var, + self.builder.extract_field(method, METHOD_FUNCTION_INDEX), + ); + + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + rec.into(), + ]; + + for reg in &ins.arguments { + let val = self + .builder + .load_untyped_pointer(self.variables[reg]) + .into(); + + args.push(val); + } + + let func_val = + self.builder.load_function_pointer(func_type, func_var); + + self.indirect_call(ins.register, func_type, func_val, &args); + } + Instruction::CallClosure(ins) => { + self.set_debug_location(ins.location); + + let rec_var = self.variables[&ins.receiver]; + let space = AddressSpace::default(); + + // For closures we generate the signature on the fly, as the + // method for `call` isn't always clearly defined: for an + // argument typed as a closure, we don't know what the actual + // method is, thus we can't retrieve an existing signature. + let mut sig_args: Vec = vec![ + self.layouts.state.ptr_type(space).into(), // State + self.builder.context.pointer_type().into(), // Process + self.builder.context.pointer_type().into(), // Closure + ]; + + for _ in &ins.arguments { + sig_args.push(self.builder.context.pointer_type().into()); + } + + // Load the method from the method table. + let rec = self.builder.load_untyped_pointer(rec_var); + let untagged = self.builder.untagged(rec); + let class = self + .builder + .load_field( + self.layouts.header, + untagged, + HEADER_CLASS_INDEX, + ) + .into_pointer_value(); + + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + rec.into(), + ]; + + for reg in &ins.arguments { + args.push( + self.builder + .load_untyped_pointer(self.variables[reg]) + .into(), + ); + } + + let slot = self.builder.u32_literal(CLOSURE_CALL_INDEX); + let method_addr = self.builder.array_field_index_address( + self.layouts.empty_class, + class, + CLASS_METHODS_INDEX, + slot, + ); + + let method = self + .builder + .load(self.layouts.method, method_addr) + .into_struct_value(); + + let func_val = self + .builder + .extract_field(method, METHOD_FUNCTION_INDEX) + .into_pointer_value(); + + let func_type = self + .builder + .context + .pointer_type() + .fn_type(&sig_args, false); + + self.indirect_call(ins.register, func_type, func_val, &args); + } + Instruction::CallDropper(ins) => { + self.set_debug_location(ins.location); + + let rec_var = self.variables[&ins.receiver]; + let space = AddressSpace::default(); + let sig_args: Vec = vec![ + self.layouts.state.ptr_type(space).into(), // State + self.builder.context.pointer_type().into(), // Process + self.builder.context.pointer_type().into(), // Receiver + ]; + + let rec = self.builder.load_untyped_pointer(rec_var); + let untagged = self.builder.untagged(rec); + let class = self + .builder + .load_field( + self.layouts.header, + untagged, + HEADER_CLASS_INDEX, + ) + .into_pointer_value(); + + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let proc = self.builder.load_untyped_pointer(proc_var); + let args: Vec = + vec![state.into(), proc.into(), rec.into()]; + + let slot = self.builder.u32_literal(DROPPER_INDEX); + let method_addr = self.builder.array_field_index_address( + self.layouts.empty_class, + class, + CLASS_METHODS_INDEX, + slot, + ); + + let method = self + .builder + .load(self.layouts.method, method_addr) + .into_struct_value(); + + let func_val = self + .builder + .extract_field(method, METHOD_FUNCTION_INDEX) + .into_pointer_value(); + + let func_type = self + .builder + .context + .pointer_type() + .fn_type(&sig_args, false); + + self.indirect_call(ins.register, func_type, func_val, &args); + } + Instruction::Send(ins) => { + let rec_var = self.variables[&ins.receiver]; + let method_name = &self.names.methods[&ins.method]; + let method = self + .module + .add_method(method_name, ins.method) + .as_global_value() + .as_pointer_value() + .into(); + let len = + self.builder.u8_literal(ins.arguments.len() as u8).into(); + let message_new = + self.module.runtime_function(RuntimeFunction::MessageNew); + let send_message = self + .module + .runtime_function(RuntimeFunction::ProcessSendMessage); + let message = self + .builder + .call(message_new, &[method, len]) + .into_pointer_value(); + + // The receiver doesn't need to be stored in the message, as + // each async method sets `self` to the process running it. + for (index, reg) in ins.arguments.iter().enumerate() { + let val = + self.builder.load_untyped_pointer(self.variables[reg]); + let slot = self.builder.u32_literal(index as u32); + let addr = self.builder.array_field_index_address( + self.layouts.message, + message, + MESSAGE_ARGUMENTS_INDEX, + slot, + ); + + self.builder.store(addr, val); + } + + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sender = self.builder.load_untyped_pointer(proc_var).into(); + let receiver = + self.builder.load_untyped_pointer(rec_var).into(); + + self.builder.call_void( + send_message, + &[state, sender, receiver, message.into()], + ); + } + Instruction::GetField(ins) + if ins.class.kind(self.db).is_extern() => + { + let reg_var = self.variables[&ins.register]; + let rec_var = self.variables[&ins.receiver]; + let layout = self.layouts.instances[&ins.class]; + let index = ins.field.index(self.db) as u32; + let rec = + self.builder.load(layout, rec_var).into_struct_value(); + let field = self.builder.extract_field(rec, index); + + self.builder.store(reg_var, field); + } + Instruction::SetField(ins) + if ins.class.kind(self.db).is_extern() => + { + let rec_var = self.variables[&ins.receiver]; + let val_var = self.variables[&ins.value]; + let layout = self.layouts.instances[&ins.class]; + let index = ins.field.index(self.db) as u32; + let val = self.builder.load_untyped_pointer(val_var); + + self.builder.store_field(layout, rec_var, index, val); + } + Instruction::GetField(ins) => { + let reg_var = self.variables[&ins.register]; + let rec_var = self.variables[&ins.receiver]; + let base = if ins.class.kind(self.db).is_async() { + PROCESS_FIELD_OFFSET + } else { + FIELD_OFFSET + }; + + let index = (base + ins.field.index(self.db)) as u32; + let layout = self.layouts.instances[&ins.class]; + let rec = self + .builder + .untagged(self.builder.load_untyped_pointer(rec_var)); + let field = self.builder.load_field(layout, rec, index); + + self.builder.store(reg_var, field); + } + Instruction::SetField(ins) => { + let rec_var = self.variables[&ins.receiver]; + let val_var = self.variables[&ins.value]; + let base = if ins.class.kind(self.db).is_async() { + PROCESS_FIELD_OFFSET + } else { + FIELD_OFFSET + }; + + let index = (base + ins.field.index(self.db)) as u32; + let val = self.builder.load_untyped_pointer(val_var); + let layout = self.layouts.instances[&ins.class]; + let rec = self + .builder + .untagged(self.builder.load_untyped_pointer(rec_var)); + + self.builder.store_field(layout, rec, index, val); + } + Instruction::CheckRefs(ins) => { + let var = self.variables[&ins.register]; + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let check = self.builder.load_untyped_pointer(var).into(); + let func = + self.module.runtime_function(RuntimeFunction::CheckRefs); + + self.builder.call_void(func, &[proc, check]); + } + Instruction::Free(ins) => { + let var = self.variables[&ins.register]; + let free = self.builder.load_untyped_pointer(var).into(); + let func = self.module.runtime_function(RuntimeFunction::Free); + + self.builder.call_void(func, &[free]); + } + Instruction::Clone(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.source]; + let val = self.builder.load_untyped_pointer(val_var); + + match ins.kind { + CloneKind::Float => { + let state = self + .builder + .load_pointer(self.layouts.state, state_var); + let func = self + .module + .runtime_function(RuntimeFunction::FloatClone); + let result = self + .builder + .call(func, &[state.into(), val.into()]) + .into_pointer_value(); + + self.builder.store(reg_var, result); + } + CloneKind::Int => { + let addr = self.builder.pointer_to_int(val); + let mask = self.builder.i64_literal(INT_MASK); + let bits = self.builder.bit_and(addr, mask); + let cond = self.builder.int_eq(bits, mask); + let after_block = self.builder.add_block(); + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + + self.builder.branch(cond, tagged_block, heap_block); + + // The block to jump to when the Int is a tagged Int. + self.builder.switch_to_block(tagged_block); + self.builder.store(reg_var, val); + self.builder.jump(after_block); + + // The block to jump to when the Int is a boxed Int. + self.builder.switch_to_block(heap_block); + + let func = self + .module + .runtime_function(RuntimeFunction::IntClone); + let state = self + .builder + .load_pointer(self.layouts.state, state_var); + let result = self + .builder + .call(func, &[state.into(), val.into()]) + .into_pointer_value(); + + self.builder.store(reg_var, result); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + } + } + } + Instruction::Increment(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.value]; + let val = self.builder.load_untyped_pointer(val_var); + let header = self.builder.untagged(val); + let one = self.builder.u32_literal(1); + let old = self + .builder + .load_field(self.layouts.header, header, HEADER_REFS_INDEX) + .into_int_value(); + let new = self.builder.int_add(old, one); + + self.builder.store_field( + self.layouts.header, + header, + HEADER_REFS_INDEX, + new, + ); + + let old_addr = self.builder.pointer_to_int(val); + let mask = self.builder.i64_literal(REF_MASK); + let new_addr = self.builder.bit_or(old_addr, mask); + let ref_ptr = self.builder.int_to_pointer(new_addr); + + self.builder.store(reg_var, ref_ptr); + } + Instruction::Decrement(ins) => { + let var = self.variables[&ins.register]; + let header = self + .builder + .untagged(self.builder.load_untyped_pointer(var)); + + let old_refs = self + .builder + .load_field(self.layouts.header, header, HEADER_REFS_INDEX) + .into_int_value(); + let one = self.builder.u32_literal(1); + let new_refs = self.builder.int_sub(old_refs, one); + + self.builder.store_field( + self.layouts.header, + header, + HEADER_REFS_INDEX, + new_refs, + ); + } + Instruction::IncrementAtomic(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.value]; + let val = self.builder.load_untyped_pointer(val_var); + let one = self.builder.u32_literal(1); + let field = self.builder.field_address( + self.layouts.header, + val, + HEADER_REFS_INDEX, + ); + + self.builder.atomic_add(field, one); + self.builder.store(reg_var, val); + } + Instruction::DecrementAtomic(ins) => { + let var = self.variables[&ins.register]; + let header = + self.builder.load_pointer(self.layouts.header, var); + let decr_block = self.builder.add_block(); + let drop_block = all_blocks[ins.if_true.0]; + let after_block = all_blocks[ins.if_false.0]; + let kind = self + .builder + .load_field(self.layouts.header, header, HEADER_KIND_INDEX) + .into_int_value(); + let perm_kind = self.builder.u8_literal(PERMANENT_KIND); + let is_perm = self.builder.int_eq(kind, perm_kind); + + self.builder.branch(is_perm, after_block, decr_block); + + // The block to jump to when the value isn't a permanent value, + // and its reference count should be decremented. + self.builder.switch_to_block(decr_block); + + let one = self.builder.u32_literal(1); + let refs = self.builder.field_address( + self.layouts.header, + header, + HEADER_REFS_INDEX, + ); + let old_refs = self.builder.atomic_sub(refs, one); + let is_zero = self.builder.int_eq(old_refs, one); + + self.builder.branch(is_zero, drop_block, after_block); + } + Instruction::Allocate(ins) + if ins.class.kind(self.db).is_extern() => + { + // Defining the alloca already reserves (uninitialised) memory, + // so there's nothing we actually need to do here. Setting the + // fields is done using separate instructions. + } + Instruction::Allocate(ins) => { + let reg_var = self.variables[&ins.register]; + let name = &self.names.classes[&ins.class]; + let global = + self.module.add_class(ins.class, name).as_pointer_value(); + let class = self.builder.load_untyped_pointer(global); + let func = + self.module.runtime_function(RuntimeFunction::Allocate); + let ptr = self.builder.call(func, &[class.into()]); + + self.builder.store(reg_var, ptr); + } + Instruction::Spawn(ins) => { + let reg_var = self.variables[&ins.register]; + let name = &self.names.classes[&ins.class]; + let global = + self.module.add_class(ins.class, name).as_pointer_value(); + let class = self.builder.load_untyped_pointer(global).into(); + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::ProcessNew); + let ptr = self.builder.call(func, &[proc, class]); + + self.builder.store(reg_var, ptr); + } + Instruction::GetConstant(ins) => { + let var = self.variables[&ins.register]; + let name = &self.names.constants[&ins.id]; + let global = self.module.add_constant(name).as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + Instruction::Reduce(ins) => { + let amount = self + .builder + .context + .i16_type() + .const_int(ins.amount as u64, false) + .into(); + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::Reduce); + + self.builder.call_void(func, &[proc, amount]); + } + Instruction::Finish(ins) => { + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let terminate = self + .builder + .context + .bool_type() + .const_int(ins.terminate as _, false) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ProcessFinishMessage); + + self.builder.call_void(func, &[proc, terminate]); + self.builder.unreachable(); + } + Instruction::Reference(_) => unreachable!(), + Instruction::Drop(_) => unreachable!(), + } + } + + fn kind_of( + &mut self, + pointer_variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + // Instead of fiddling with phi nodes we just inject a new stack slot in + // the entry block and use that. clang takes a similar approach when + // building switch() statements. + let result = + self.builder.new_stack_slot(self.builder.context.i8_type()); + let int_block = self.builder.add_block(); + let ref_block = self.builder.add_block(); + let header_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let pointer = self.builder.load_untyped_pointer(pointer_variable); + let addr = self.builder.pointer_to_int(pointer); + let mask = self.builder.i64_literal(TAG_MASK); + let bits = self.builder.bit_and(addr, mask); + + // This generates the equivalent of the following: + // + // match ptr as usize & MASK { + // INT_MASK => ... + // MASK => ... + // REF_MASK => ... + // _ => ... + // } + self.builder.switch( + bits, + &[ + (self.builder.i64_literal(INT_MASK), int_block), + // Uneven tagged integers will have both the first and second + // bit set to 1, so we also need to handle such values here. + (self.builder.i64_literal(TAG_MASK), int_block), + (self.builder.i64_literal(REF_MASK), ref_block), + ], + header_block, + ); + + // The case for when the value is a tagged integer. + self.builder.switch_to_block(int_block); + self.builder.store(result, self.builder.u8_literal(INT_KIND)); + self.builder.jump(after_block); + + // The case for when the value is a reference. + self.builder.switch_to_block(ref_block); + self.builder.store(result, self.builder.u8_literal(REF_KIND)); + self.builder.jump(after_block); + + // The fallback case where we read the kind from the object header. This + // generates the equivalent of `(*(ptr as *mut Header)).kind`. + self.builder.switch_to_block(header_block); + + let header_val = self + .builder + .load_field(self.layouts.header, pointer, HEADER_KIND_INDEX) + .into_int_value(); + + self.builder.store(result, header_val); + self.builder.jump(after_block); + self.builder.switch_to_block(after_block); + result + } + + fn class_of(&mut self, receiver: PointerValue<'ctx>) -> PointerValue<'ctx> { + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let class_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let int_global = self + .module + .add_class(ClassId::int(), &self.names.classes[&ClassId::int()]); + + let addr = self.builder.pointer_to_int(receiver); + let mask = self.builder.i64_literal(INT_MASK); + let bits = self.builder.bit_and(addr, mask); + let is_tagged = self.builder.int_eq(bits, mask); + + self.builder.branch(is_tagged, tagged_block, heap_block); + + // The block to jump to when the receiver is a tagged integer. + self.builder.switch_to_block(tagged_block); + self.builder.store( + class_var, + self.builder.load_untyped_pointer(int_global.as_pointer_value()), + ); + self.builder.jump(after_block); + + // The block to jump to when the receiver is a heap object. In this case + // we read the class from the (untagged) header. + self.builder.switch_to_block(heap_block); + + let header = self.builder.untagged(receiver); + let class = self + .builder + .load_field(self.layouts.header, header, HEADER_CLASS_INDEX) + .into_pointer_value(); + + self.builder.store(class_var, class); + self.builder.jump(after_block); + + // The block to jump to to load the method pointer. + self.builder.switch_to_block(after_block); + self.builder.load_pointer(self.layouts.empty_class, class_var) + } + + fn read_int(&mut self, variable: PointerValue<'ctx>) -> IntValue<'ctx> { + let pointer = self.builder.load_untyped_pointer(variable); + let res_type = self.builder.context.i64_type(); + let res_var = self.builder.new_stack_slot(res_type); + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + + let addr = self.builder.pointer_to_int(pointer); + let mask = self.builder.i64_literal(INT_MASK); + let bits = self.builder.bit_and(addr, mask); + let cond = self.builder.int_eq(bits, mask); + + self.builder.branch(cond, tagged_block, heap_block); + + // The block to jump to when the Int is a tagged Int. + self.builder.switch_to_block(tagged_block); + + let shift = self.builder.i64_literal(INT_SHIFT as i64); + let untagged = self.builder.signed_right_shift(addr, shift); + + self.builder.store(res_var, untagged); + self.builder.jump(after_block); + + // The block to jump to when the Int is a heap Int. + self.builder.switch_to_block(heap_block); + + let layout = self.layouts.instances[&ClassId::int()]; + + self.builder.store( + res_var, + self.builder.load_field(layout, pointer, BOXED_INT_VALUE_INDEX), + ); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + self.builder.load(res_type, res_var).into_int_value() + } + + fn read_float(&mut self, variable: PointerValue<'ctx>) -> FloatValue<'ctx> { + let layout = self.layouts.instances[&ClassId::float()]; + let ptr = self.builder.load_pointer(layout, variable); + + self.builder + .load_field(layout, ptr, BOXED_FLOAT_VALUE_INDEX) + .into_float_value() + } + + fn new_float( + &mut self, + state_var: PointerValue<'ctx>, + value: FloatValue<'ctx>, + ) -> PointerValue<'ctx> { + let func = self.module.runtime_function(RuntimeFunction::FloatBoxed); + let state = self.builder.load_pointer(self.layouts.state, state_var); + + self.builder + .call(func, &[state.into(), value.into()]) + .into_pointer_value() + } + + fn checked_int_operation( + &mut self, + name: &str, + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + reg_var: PointerValue<'ctx>, + lhs_var: PointerValue<'ctx>, + rhs_var: PointerValue<'ctx>, + ) { + let ok_block = self.builder.add_block(); + let err_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let add = self + .module + .intrinsic(name, &[self.builder.context.i64_type().into()]); + + let res = self + .builder + .call(add, &[lhs.into(), rhs.into()]) + .into_struct_value(); + + // Check if we overflowed the operation. + let new_val = self + .builder + .extract_field(res, LLVM_RESULT_VALUE_INDEX) + .into_int_value(); + let overflow = self + .builder + .extract_field(res, LLVM_RESULT_STATUS_INDEX) + .into_int_value(); + + self.builder.branch(overflow, err_block, ok_block); + + // The block to jump to if the operation didn't overflow. + { + self.builder.switch_to_block(ok_block); + + let val = self.new_int(state_var, new_val); + + self.builder.store(reg_var, val); + self.builder.jump(after_block); + } + + // The block to jump to if the operation overflowed. + self.builder.switch_to_block(err_block); + + let proc = self.builder.load_untyped_pointer(proc_var); + + self.builder.call_void(func, &[proc.into(), lhs.into(), rhs.into()]); + self.builder.unreachable(); + self.builder.switch_to_block(after_block); + } + + fn new_int( + &mut self, + state_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + let res_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let and_block = self.builder.add_block(); + + let min = self.builder.i64_literal(MIN_INT); + let max = self.builder.i64_literal(MAX_INT); + + self.builder.branch( + self.builder.int_ge(value, min), + and_block, + heap_block, + ); + + // The block to jump to when we're larger than or equal to the minimum + // value for a tagged Int. + self.builder.switch_to_block(and_block); + self.builder.branch( + self.builder.int_le(value, max), + tagged_block, + heap_block, + ); + + // The block to jump to when the Int fits in a tagged pointer. + self.builder.switch_to_block(tagged_block); + + let shift = self.builder.i64_literal(INT_SHIFT as i64); + let mask = self.builder.i64_literal(INT_MASK); + let addr = + self.builder.bit_or(self.builder.left_shift(value, shift), mask); + + self.builder.store(res_var, self.builder.int_to_pointer(addr)); + self.builder.jump(after_block); + + // The block to jump to when the Int must be boxed. + self.builder.switch_to_block(heap_block); + + let func = self.module.runtime_function(RuntimeFunction::IntBoxed); + let state = self.builder.load_pointer(self.layouts.state, state_var); + let res = self.builder.call(func, &[state.into(), value.into()]); + + self.builder.store(res_var, res); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + self.builder.load_untyped_pointer(res_var) + } + + fn new_bool( + &mut self, + state_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + let result = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let state = self.builder.load_pointer(self.layouts.state, state_var); + let true_block = self.builder.add_block(); + let false_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + + self.builder.branch(value, true_block, false_block); + + // The block to jump to when the condition is true. + self.builder.switch_to_block(true_block); + self.builder.store( + result, + self.builder.load_field(self.layouts.state, state, TRUE_INDEX), + ); + self.builder.jump(after_block); + + // The block to jump to when the condition is false. + self.builder.switch_to_block(false_block); + self.builder.store( + result, + self.builder.load_field(self.layouts.state, state, FALSE_INDEX), + ); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + self.builder.load_untyped_pointer(result) + } + + fn check_division_overflow( + &self, + process_var: PointerValue<'ctx>, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) { + let min = self.builder.i64_literal(i64::MIN); + let minus_one = self.builder.i64_literal(-1); + let zero = self.builder.i64_literal(0); + let and_block = self.builder.add_block(); + let or_block = self.builder.add_block(); + let overflow_block = self.builder.add_block(); + let ok_block = self.builder.add_block(); + + // lhs == MIN AND rhs == -1 + self.builder.branch(self.builder.int_eq(lhs, min), and_block, or_block); + + self.builder.switch_to_block(and_block); + self.builder.branch( + self.builder.int_eq(rhs, minus_one), + overflow_block, + or_block, + ); + + // OR rhs == 0 + self.builder.switch_to_block(or_block); + self.builder.branch( + self.builder.int_eq(rhs, zero), + overflow_block, + ok_block, + ); + + // The block to jump to if an overflow would occur. + self.builder.switch_to_block(overflow_block); + + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let proc = self.builder.load_untyped_pointer(process_var); + + self.builder.call_void(func, &[proc.into(), lhs.into(), rhs.into()]); + self.builder.unreachable(); + + // The block to jump to when it's safe to perform the + // operation. + self.builder.switch_to_block(ok_block); + } + + fn check_shift_bits( + &self, + process_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + bits: IntValue<'ctx>, + ) { + let ok_block = self.builder.add_block(); + let err_block = self.builder.add_block(); + let min = self.builder.i64_literal((i64::BITS - 1) as _); + let cond = self.builder.int_gt(bits, min); + + self.builder.branch(cond, err_block, ok_block); + + // The block to jump to when the operation would overflow. + self.builder.switch_to_block(err_block); + + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let proc = self.builder.load_untyped_pointer(process_var); + + self.builder.call_void(func, &[proc.into(), value.into(), bits.into()]); + self.builder.unreachable(); + + // The block to jump to when all is well. + self.builder.switch_to_block(ok_block); + } + + fn define_register_variables(&mut self) { + let space = AddressSpace::default(); + + for index in 0..self.method.registers.len() { + let id = RegisterId(index as _); + let typ = self.method.registers.value_type(id); + let alloca_typ = if let Some(id) = typ.class_id(self.db) { + let layout = self.layouts.instances[&id]; + + if id.kind(self.db).is_extern() { + layout.as_basic_type_enum() + } else { + layout.ptr_type(space).as_basic_type_enum() + } + } else { + self.builder.context.pointer_type().as_basic_type_enum() + }; + + self.variables.insert(id, self.builder.alloca(alloca_typ)); + self.variable_types.insert(id, alloca_typ); + } + } + + fn register_type(&self, register: RegisterId) -> types::TypeRef { + self.method.registers.value_type(register) + } + + fn call( + &self, + register: RegisterId, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum], + ) { + let var = self.variables[®ister]; + + if self.register_type(register).is_never(self.db) { + self.builder.call_void(function, arguments); + self.builder.unreachable(); + } else { + self.builder.store(var, self.builder.call(function, arguments)); + } + } + + fn indirect_call( + &self, + register: RegisterId, + function_type: FunctionType<'ctx>, + function: PointerValue<'ctx>, + arguments: &[BasicMetadataValueEnum], + ) { + let var = self.variables[®ister]; + + if self.register_type(register).is_never(self.db) { + self.builder.indirect_call(function_type, function, arguments); + self.builder.unreachable(); + } else { + self.builder.store( + var, + self.builder + .indirect_call(function_type, function, arguments) + .try_as_basic_value() + .left() + .unwrap(), + ); + } + } + + fn set_debug_location(&self, location_id: LocationId) { + let scope = self.builder.debug_scope(); + let (line, col) = self.mir.location(location_id).line_column(); + let loc = self.module.debug_builder.new_location(line, col, scope); + + self.builder.set_debug_location(loc); + } +} + +/// A pass for generating the entry module and method (i.e. `main()`). +pub(crate) struct GenerateMain<'a, 'ctx> { + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'a Module<'a, 'ctx>, + builder: Builder<'ctx>, +} + +impl<'a, 'ctx> GenerateMain<'a, 'ctx> { + fn new( + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'a Module<'a, 'ctx>, + ) -> GenerateMain<'a, 'ctx> { + let typ = context.i32_type().fn_type(&[], false); + let function = module.add_function("main", typ, None); + let builder = Builder::new(context, function); + + GenerateMain { db, mir, layouts, names, context, module, builder } + } + + fn run(self) { + let space = AddressSpace::default(); + let entry_block = self.builder.add_block(); + + self.builder.switch_to_block(entry_block); + + let layout = self.layouts.method_counts; + let counts = self.builder.alloca(layout); + + self.set_method_count(counts, ClassId::int()); + self.set_method_count(counts, ClassId::float()); + self.set_method_count(counts, ClassId::string()); + self.set_method_count(counts, ClassId::array()); + self.set_method_count(counts, ClassId::boolean()); + self.set_method_count(counts, ClassId::nil()); + self.set_method_count(counts, ClassId::byte_array()); + self.set_method_count(counts, ClassId::channel()); + + let rt_new = self.module.runtime_function(RuntimeFunction::RuntimeNew); + let rt_start = + self.module.runtime_function(RuntimeFunction::RuntimeStart); + let rt_state = + self.module.runtime_function(RuntimeFunction::RuntimeState); + let rt_drop = + self.module.runtime_function(RuntimeFunction::RuntimeDrop); + let exit = self.module.runtime_function(RuntimeFunction::Exit); + + let runtime = + self.builder.call(rt_new, &[counts.into()]).into_pointer_value(); + let state = + self.builder.call(rt_state, &[runtime.into()]).into_pointer_value(); + + // Call all the module setup functions. This is used to populate + // constants, define classes, etc. + for &id in self.mir.modules.keys() { + let name = &self.names.setup_functions[&id]; + let func = self.module.add_setup_function(name); + + self.builder.call_void(func, &[state.into()]); + } + + let main_class_id = self.db.main_class().unwrap(); + let main_method_id = self.db.main_method().unwrap(); + let main_class_ptr = self + .module + .add_global(&self.names.classes[&main_class_id]) + .as_pointer_value(); + + let main_method = self + .module + .add_function( + &self.names.methods[&main_method_id], + self.context.void_type().fn_type( + &[self.layouts.context.ptr_type(space).into()], + false, + ), + None, + ) + .as_global_value() + .as_pointer_value(); + + let main_class = + self.builder.load_pointer(self.layouts.empty_class, main_class_ptr); + + self.builder.call_void( + rt_start, + &[runtime.into(), main_class.into(), main_method.into()], + ); + + // We'll only reach this code upon successfully finishing the program. + // + // We don't drop the classes and other data as there's no point since + // we're exiting here. We _do_ drop the runtime in case we want to hook + // any additional logic into that step at some point, though technically + // this isn't necessary. + self.builder.call_void(rt_drop, &[runtime.into()]); + self.builder.call_void(exit, &[self.builder.i64_literal(0).into()]); + self.builder.unreachable(); + } + + fn methods(&self, id: ClassId) -> IntValue<'ctx> { + self.context.i16_type().const_int(self.layouts.methods(id) as _, false) + } + + fn set_method_count(&self, counts: PointerValue<'ctx>, class: ClassId) { + let layout = self.layouts.method_counts; + + self.builder.store_field(layout, counts, class.0, self.methods(class)); + } +} diff --git a/compiler/src/llvm/runtime_function.rs b/compiler/src/llvm/runtime_function.rs new file mode 100644 index 000000000..f97e1072d --- /dev/null +++ b/compiler/src/llvm/runtime_function.rs @@ -0,0 +1,1605 @@ +use crate::llvm::module::Module; +use inkwell::values::FunctionValue; +use inkwell::AddressSpace; + +#[derive(Copy, Clone)] +pub(crate) enum RuntimeFunction { + ArrayCapacity, + ArrayClear, + ArrayDrop, + ArrayGet, + ArrayLength, + ArrayNew, + ArrayNewPermanent, + ArrayPop, + ArrayPush, + ArrayRemove, + ArrayReserve, + ArraySet, + ByteArrayAppend, + ByteArrayClear, + ByteArrayClone, + ByteArrayCopyFrom, + ByteArrayDrainToString, + ByteArrayDrop, + ByteArrayEq, + ByteArrayGet, + ByteArrayLength, + ByteArrayNew, + ByteArrayPop, + ByteArrayPush, + ByteArrayRemove, + ByteArrayResize, + ByteArraySet, + ByteArraySlice, + ByteArrayToString, + ChannelDrop, + ChannelNew, + ChannelReceive, + ChannelReceiveUntil, + ChannelSend, + ChannelTryReceive, + ChannelWait, + CheckRefs, + ChildProcessDrop, + ChildProcessSpawn, + ChildProcessStderrClose, + ChildProcessStderrRead, + ChildProcessStdinClose, + ChildProcessStdinFlush, + ChildProcessStdinWriteBytes, + ChildProcessStdinWriteString, + ChildProcessStdoutClose, + ChildProcessStdoutRead, + ChildProcessTryWait, + ChildProcessWait, + ClassObject, + ClassProcess, + CpuCores, + DirectoryCreate, + DirectoryCreateRecursive, + DirectoryList, + DirectoryRemove, + DirectoryRemoveAll, + EnvArguments, + EnvExecutable, + EnvGet, + EnvGetWorkingDirectory, + EnvHomeDirectory, + EnvSetWorkingDirectory, + EnvTempDirectory, + EnvVariables, + Exit, + FileCopy, + FileDrop, + FileFlush, + FileOpen, + FileRead, + FileRemove, + FileSeek, + FileSize, + FileWriteBytes, + FileWriteString, + FloatBoxed, + FloatBoxedPermanent, + FloatClone, + FloatEq, + FloatRound, + FloatToString, + Free, + IntBoxed, + IntBoxedPermanent, + IntClone, + IntOverflow, + IntPow, + IntToString, + MessageNew, + Allocate, + PathAccessedAt, + PathCreatedAt, + PathExists, + PathExpand, + PathIsDirectory, + PathIsFile, + PathModifiedAt, + ProcessFinishMessage, + ProcessNew, + ProcessPanic, + ProcessSendMessage, + ProcessStackFrameLine, + ProcessStackFrameName, + ProcessStackFramePath, + ProcessStacktrace, + ProcessStacktraceDrop, + ProcessStacktraceLength, + ProcessSuspend, + RandomBytes, + RandomDrop, + RandomFloat, + RandomFloatRange, + RandomFromInt, + RandomInt, + RandomIntRange, + RandomNew, + Reduce, + RuntimeDrop, + RuntimeNew, + RuntimeStart, + RuntimeState, + SocketAccept, + SocketAddressPairAddress, + SocketAddressPairDrop, + SocketAddressPairPort, + SocketBind, + SocketConnect, + SocketDrop, + SocketListen, + SocketLocalAddress, + SocketNew, + SocketPeerAddress, + SocketRead, + SocketReceiveFrom, + SocketSendBytesTo, + SocketSendStringTo, + SocketSetBroadcast, + SocketSetKeepalive, + SocketSetLinger, + SocketSetNodelay, + SocketSetOnlyV6, + SocketSetRecvSize, + SocketSetReuseAddress, + SocketSetReusePort, + SocketSetSendSize, + SocketSetTtl, + SocketShutdownRead, + SocketShutdownReadWrite, + SocketShutdownWrite, + SocketTryClone, + SocketWriteBytes, + SocketWriteString, + StderrFlush, + StderrWriteBytes, + StderrWriteString, + StdinRead, + StdoutFlush, + StdoutWriteBytes, + StdoutWriteString, + StringByte, + StringCharacters, + StringCharactersDrop, + StringCharactersNext, + StringConcat, + StringConcatArray, + StringDrop, + StringEquals, + StringNewPermanent, + StringSize, + StringSliceBytes, + StringToByteArray, + StringToFloat, + StringToInt, + StringToLower, + StringToUpper, + TimeMonotonic, + TimeSystem, + TimeSystemOffset, +} + +impl RuntimeFunction { + pub(crate) fn name(self) -> &'static str { + match self { + RuntimeFunction::ArrayCapacity => "inko_array_capacity", + RuntimeFunction::ArrayClear => "inko_array_clear", + RuntimeFunction::ArrayDrop => "inko_array_drop", + RuntimeFunction::ArrayGet => "inko_array_get", + RuntimeFunction::ArrayLength => "inko_array_length", + RuntimeFunction::ArrayNew => "inko_array_new", + RuntimeFunction::ArrayNewPermanent => "inko_array_new_permanent", + RuntimeFunction::ArrayPop => "inko_array_pop", + RuntimeFunction::ArrayPush => "inko_array_push", + RuntimeFunction::ArrayRemove => "inko_array_remove", + RuntimeFunction::ArrayReserve => "inko_array_reserve", + RuntimeFunction::ArraySet => "inko_array_set", + RuntimeFunction::ByteArrayAppend => "inko_byte_array_append", + RuntimeFunction::ByteArrayClear => "inko_byte_array_clear", + RuntimeFunction::ByteArrayClone => "inko_byte_array_clone", + RuntimeFunction::ByteArrayCopyFrom => "inko_byte_array_copy_from", + RuntimeFunction::ByteArrayDrainToString => { + "inko_byte_array_drain_to_string" + } + RuntimeFunction::ByteArrayDrop => "inko_byte_array_drop", + RuntimeFunction::ByteArrayEq => "inko_byte_array_eq", + RuntimeFunction::ByteArrayGet => "inko_byte_array_get", + RuntimeFunction::ByteArrayLength => "inko_byte_array_length", + RuntimeFunction::ByteArrayNew => "inko_byte_array_new", + RuntimeFunction::ByteArrayPop => "inko_byte_array_pop", + RuntimeFunction::ByteArrayPush => "inko_byte_array_push", + RuntimeFunction::ByteArrayRemove => "inko_byte_array_remove", + RuntimeFunction::ByteArrayResize => "inko_byte_array_resize", + RuntimeFunction::ByteArraySet => "inko_byte_array_set", + RuntimeFunction::ByteArraySlice => "inko_byte_array_slice", + RuntimeFunction::ByteArrayToString => "inko_byte_array_to_string", + RuntimeFunction::ChannelDrop => "inko_channel_drop", + RuntimeFunction::ChannelNew => "inko_channel_new", + RuntimeFunction::ChannelReceive => "inko_channel_receive", + RuntimeFunction::ChannelReceiveUntil => { + "inko_channel_receive_until" + } + RuntimeFunction::ChannelSend => "inko_channel_send", + RuntimeFunction::ChannelTryReceive => "inko_channel_try_receive", + RuntimeFunction::ChannelWait => "inko_channel_wait", + RuntimeFunction::CheckRefs => "inko_check_refs", + RuntimeFunction::ChildProcessDrop => "inko_child_process_drop", + RuntimeFunction::ChildProcessSpawn => "inko_child_process_spawn", + RuntimeFunction::ChildProcessStderrClose => { + "inko_child_process_stderr_close" + } + RuntimeFunction::ChildProcessStderrRead => { + "inko_child_process_stderr_read" + } + RuntimeFunction::ChildProcessStdinClose => { + "inko_child_process_stdin_close" + } + RuntimeFunction::ChildProcessStdinFlush => { + "inko_child_process_stdin_flush" + } + RuntimeFunction::ChildProcessStdinWriteBytes => { + "inko_child_process_stdin_write_bytes" + } + RuntimeFunction::ChildProcessStdinWriteString => { + "inko_child_process_stdin_write_string" + } + RuntimeFunction::ChildProcessStdoutClose => { + "inko_child_process_stdout_close" + } + RuntimeFunction::ChildProcessStdoutRead => { + "inko_child_process_stdout_read" + } + RuntimeFunction::ChildProcessTryWait => { + "inko_child_process_try_wait" + } + RuntimeFunction::ChildProcessWait => "inko_child_process_wait", + RuntimeFunction::ClassObject => "inko_class_object", + RuntimeFunction::ClassProcess => "inko_class_process", + RuntimeFunction::CpuCores => "inko_cpu_cores", + RuntimeFunction::DirectoryCreate => "inko_directory_create", + RuntimeFunction::DirectoryCreateRecursive => { + "inko_directory_create_recursive" + } + RuntimeFunction::DirectoryList => "inko_directory_list", + RuntimeFunction::DirectoryRemove => "inko_directory_remove", + RuntimeFunction::DirectoryRemoveAll => "inko_directory_remove_all", + RuntimeFunction::EnvArguments => "inko_env_arguments", + RuntimeFunction::EnvExecutable => "inko_env_executable", + RuntimeFunction::EnvGet => "inko_env_get", + RuntimeFunction::EnvGetWorkingDirectory => { + "inko_env_get_working_directory" + } + RuntimeFunction::EnvHomeDirectory => "inko_env_home_directory", + RuntimeFunction::EnvSetWorkingDirectory => { + "inko_env_set_working_directory" + } + RuntimeFunction::EnvTempDirectory => "inko_env_temp_directory", + RuntimeFunction::EnvVariables => "inko_env_variables", + RuntimeFunction::Exit => "inko_exit", + RuntimeFunction::FileCopy => "inko_file_copy", + RuntimeFunction::FileDrop => "inko_file_drop", + RuntimeFunction::FileFlush => "inko_file_flush", + RuntimeFunction::FileOpen => "inko_file_open", + RuntimeFunction::FileRead => "inko_file_read", + RuntimeFunction::FileRemove => "inko_file_remove", + RuntimeFunction::FileSeek => "inko_file_seek", + RuntimeFunction::FileSize => "inko_file_size", + RuntimeFunction::FileWriteBytes => "inko_file_write_bytes", + RuntimeFunction::FileWriteString => "inko_file_write_string", + RuntimeFunction::FloatBoxed => "inko_float_boxed", + RuntimeFunction::FloatBoxedPermanent => { + "inko_float_boxed_permanent" + } + RuntimeFunction::FloatClone => "inko_float_clone", + RuntimeFunction::FloatEq => "inko_float_eq", + RuntimeFunction::FloatRound => "inko_float_round", + RuntimeFunction::FloatToString => "inko_float_to_string", + RuntimeFunction::Free => "inko_free", + RuntimeFunction::IntBoxed => "inko_int_boxed", + RuntimeFunction::IntBoxedPermanent => "inko_int_boxed_permanent", + RuntimeFunction::IntClone => "inko_int_clone", + RuntimeFunction::IntOverflow => "inko_int_overflow", + RuntimeFunction::IntPow => "inko_int_pow", + RuntimeFunction::IntToString => "inko_int_to_string", + RuntimeFunction::MessageNew => "inko_message_new", + RuntimeFunction::Allocate => "inko_alloc", + RuntimeFunction::PathAccessedAt => "inko_path_accessed_at", + RuntimeFunction::PathCreatedAt => "inko_path_created_at", + RuntimeFunction::PathExists => "inko_path_exists", + RuntimeFunction::PathIsDirectory => "inko_path_is_directory", + RuntimeFunction::PathIsFile => "inko_path_is_file", + RuntimeFunction::PathModifiedAt => "inko_path_modified_at", + RuntimeFunction::ProcessFinishMessage => { + "inko_process_finish_message" + } + RuntimeFunction::ProcessNew => "inko_process_new", + RuntimeFunction::ProcessPanic => "inko_process_panic", + RuntimeFunction::ProcessSendMessage => "inko_process_send_message", + RuntimeFunction::ProcessStacktrace => "inko_process_stacktrace", + RuntimeFunction::ProcessStacktraceDrop => { + "inko_process_stacktrace_drop" + } + RuntimeFunction::ProcessStackFrameLine => { + "inko_process_stack_frame_line" + } + RuntimeFunction::ProcessStackFrameName => { + "inko_process_stack_frame_name" + } + RuntimeFunction::ProcessStackFramePath => { + "inko_process_stack_frame_path" + } + RuntimeFunction::ProcessStacktraceLength => { + "inko_process_stacktrace_length" + } + RuntimeFunction::ProcessSuspend => "inko_process_suspend", + RuntimeFunction::RandomBytes => "inko_random_bytes", + RuntimeFunction::RandomDrop => "inko_random_drop", + RuntimeFunction::RandomFloat => "inko_random_float", + RuntimeFunction::RandomFloatRange => "inko_random_float_range", + RuntimeFunction::RandomFromInt => "inko_random_from_int", + RuntimeFunction::RandomInt => "inko_random_int", + RuntimeFunction::RandomIntRange => "inko_random_int_range", + RuntimeFunction::RandomNew => "inko_random_new", + RuntimeFunction::Reduce => "inko_reduce", + RuntimeFunction::RuntimeDrop => "inko_runtime_drop", + RuntimeFunction::RuntimeNew => "inko_runtime_new", + RuntimeFunction::RuntimeStart => "inko_runtime_start", + RuntimeFunction::RuntimeState => "inko_runtime_state", + RuntimeFunction::SocketAccept => "inko_socket_accept", + RuntimeFunction::SocketAddressPairAddress => { + "inko_socket_address_pair_address" + } + RuntimeFunction::SocketAddressPairDrop => { + "inko_socket_address_pair_drop" + } + RuntimeFunction::SocketAddressPairPort => { + "inko_socket_address_pair_port" + } + RuntimeFunction::SocketBind => "inko_socket_bind", + RuntimeFunction::SocketConnect => "inko_socket_connect", + RuntimeFunction::SocketDrop => "inko_socket_drop", + RuntimeFunction::SocketListen => "inko_socket_listen", + RuntimeFunction::SocketLocalAddress => "inko_socket_local_address", + RuntimeFunction::SocketPeerAddress => "inko_socket_peer_address", + RuntimeFunction::SocketRead => "inko_socket_read", + RuntimeFunction::SocketReceiveFrom => "inko_socket_receive_from", + RuntimeFunction::SocketSendBytesTo => "inko_socket_send_bytes_to", + RuntimeFunction::SocketSendStringTo => "inko_socket_send_string_to", + RuntimeFunction::SocketSetBroadcast => "inko_socket_set_broadcast", + RuntimeFunction::SocketSetKeepalive => "inko_socket_set_keepalive", + RuntimeFunction::SocketSetLinger => "inko_socket_set_linger", + RuntimeFunction::SocketSetNodelay => "inko_socket_set_nodelay", + RuntimeFunction::SocketSetOnlyV6 => "inko_socket_set_only_v6", + RuntimeFunction::SocketSetRecvSize => "inko_socket_set_recv_size", + RuntimeFunction::SocketSetReuseAddress => { + "inko_socket_set_reuse_address" + } + RuntimeFunction::SocketSetReusePort => "inko_socket_set_reuse_port", + RuntimeFunction::SocketSetSendSize => "inko_socket_set_send_size", + RuntimeFunction::SocketSetTtl => "inko_socket_set_ttl", + RuntimeFunction::SocketShutdownRead => "inko_socket_shutdown_read", + RuntimeFunction::SocketShutdownReadWrite => { + "inko_socket_shutdown_read_write" + } + RuntimeFunction::SocketShutdownWrite => { + "inko_socket_shutdown_write" + } + RuntimeFunction::SocketTryClone => "inko_socket_try_clone", + RuntimeFunction::SocketNew => "inko_socket_new", + RuntimeFunction::SocketWriteBytes => "inko_socket_write_bytes", + RuntimeFunction::SocketWriteString => "inko_socket_write_string", + RuntimeFunction::StderrFlush => "inko_stderr_flush", + RuntimeFunction::StderrWriteBytes => "inko_stderr_write_bytes", + RuntimeFunction::StderrWriteString => "inko_stderr_write_string", + RuntimeFunction::StdinRead => "inko_stdin_read", + RuntimeFunction::StdoutFlush => "inko_stdout_flush", + RuntimeFunction::StdoutWriteBytes => "inko_stdout_write_bytes", + RuntimeFunction::StdoutWriteString => "inko_stdout_write_string", + RuntimeFunction::StringByte => "inko_string_byte", + RuntimeFunction::StringCharacters => "inko_string_characters", + RuntimeFunction::StringCharactersDrop => { + "inko_string_characters_drop" + } + RuntimeFunction::StringCharactersNext => { + "inko_string_characters_next" + } + RuntimeFunction::StringConcat => "inko_string_concat", + RuntimeFunction::StringConcatArray => "inko_string_concat_array", + RuntimeFunction::StringDrop => "inko_string_drop", + RuntimeFunction::StringEquals => "inko_string_equals", + RuntimeFunction::StringNewPermanent => "inko_string_new_permanent", + RuntimeFunction::StringSize => "inko_string_size", + RuntimeFunction::StringSliceBytes => "inko_string_slice_bytes", + RuntimeFunction::StringToByteArray => "inko_string_to_byte_array", + RuntimeFunction::StringToFloat => "inko_string_to_float", + RuntimeFunction::StringToInt => "inko_string_to_int", + RuntimeFunction::StringToLower => "inko_string_to_lower", + RuntimeFunction::StringToUpper => "inko_string_to_upper", + RuntimeFunction::TimeMonotonic => "inko_time_monotonic", + RuntimeFunction::TimeSystem => "inko_time_system", + RuntimeFunction::TimeSystemOffset => "inko_time_system_offset", + RuntimeFunction::PathExpand => "inko_path_expand", + } + } + + pub(crate) fn build<'ctx>( + self, + module: &Module<'_, 'ctx>, + ) -> FunctionValue<'ctx> { + let context = module.context; + let space = AddressSpace::default(); + let fn_type = match self { + RuntimeFunction::IntBoxedPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::IntBoxed => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::IntClone => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type(); + + val.fn_type(&[state, val.into()], false) + } + RuntimeFunction::IntOverflow => { + let proc = context.pointer_type().into(); + let lhs = context.i64_type().into(); + let rhs = context.i64_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, lhs, rhs], false) + } + RuntimeFunction::CheckRefs => { + let proc = context.pointer_type().into(); + let val = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, val], false) + } + RuntimeFunction::Free => { + let val = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[val], false) + } + RuntimeFunction::FloatBoxedPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::FloatClone => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type(); + + val.fn_type(&[state, val.into()], false) + } + RuntimeFunction::ArrayNewPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let capa = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, capa], false) + } + RuntimeFunction::ArrayNew => { + let state = module.layouts.state.ptr_type(space).into(); + let capa = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, capa], false) + } + RuntimeFunction::ArrayPush => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, val], false) + } + RuntimeFunction::Reduce => { + let proc = context.pointer_type().into(); + let amount = context.i16_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, amount], false) + } + RuntimeFunction::Allocate => { + let class = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[class], false) + } + RuntimeFunction::StringEquals => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.pointer_type().into(); + let rhs = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::ProcessPanic => { + let proc = context.pointer_type().into(); + let val = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, val], false) + } + RuntimeFunction::IntPow => { + let proc = context.pointer_type().into(); + let lhs = context.i64_type().into(); + let rhs = context.i64_type().into(); + let ret = context.i64_type(); + + ret.fn_type(&[proc, lhs, rhs], false) + } + RuntimeFunction::FloatBoxed => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::FloatEq => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.f64_type().into(); + let rhs = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::FloatRound => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.f64_type().into(); + let rhs = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::FloatToString => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayCapacity => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayClear => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayGet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ArrayLength => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ArrayPop => { + let array = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[array], false) + } + RuntimeFunction::ArrayRemove => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ArrayReserve => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let amount = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, amount], false) + } + RuntimeFunction::ArraySet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let value = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index, value], false) + } + RuntimeFunction::ByteArrayNew => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::ByteArrayPush => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, value], false) + } + RuntimeFunction::ByteArrayPop => { + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array], false) + } + RuntimeFunction::ByteArraySet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index, value], false) + } + RuntimeFunction::ByteArrayGet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ByteArrayRemove => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ByteArrayLength => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayEq => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.pointer_type().into(); + let rhs = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::ByteArrayClear => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayClone => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayToString => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayDrainToString => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArraySlice => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let start = context.i64_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, start, length], false) + } + RuntimeFunction::ByteArrayAppend => { + let state = module.layouts.state.ptr_type(space).into(); + let target = context.pointer_type().into(); + let source = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, target, source], false) + } + RuntimeFunction::ByteArrayCopyFrom => { + let state = module.layouts.state.ptr_type(space).into(); + let target = context.pointer_type().into(); + let source = context.pointer_type().into(); + let start = context.i64_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, target, source, start, length], false) + } + RuntimeFunction::ByteArrayResize => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let size = context.i64_type().into(); + let filler = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, size, filler], false) + } + RuntimeFunction::ChildProcessSpawn => { + let proc = context.pointer_type().into(); + let program = context.pointer_type().into(); + let args = context.pointer_type().into(); + let env = context.pointer_type().into(); + let stdin = context.i64_type().into(); + let stdout = context.i64_type().into(); + let stderr = context.i64_type().into(); + let dir = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[proc, program, args, env, stdin, stdout, stderr, dir], + false, + ) + } + RuntimeFunction::ChildProcessWait => { + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proc, child], false) + } + RuntimeFunction::ChildProcessTryWait => { + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[child], false) + } + RuntimeFunction::ChildProcessDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdoutRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, buffer, size], false) + } + RuntimeFunction::ChildProcessStderrRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, buffer, size], false) + } + RuntimeFunction::ChildProcessStderrClose => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdoutClose => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdinClose => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdinFlush => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child], false) + } + RuntimeFunction::ChildProcessStdinWriteBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, input], false) + } + RuntimeFunction::ChildProcessStdinWriteString => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, input], false) + } + RuntimeFunction::CpuCores => { + let ret = context.pointer_type(); + + ret.fn_type(&[], false) + } + RuntimeFunction::ProcessFinishMessage => { + let proc = context.pointer_type().into(); + let terminate = context.bool_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, terminate], false) + } + RuntimeFunction::RuntimeNew => { + let counts = + module.layouts.method_counts.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[counts], false) + } + RuntimeFunction::RuntimeDrop => { + let runtime = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[runtime], false) + } + RuntimeFunction::RuntimeStart => { + let runtime = context.pointer_type().into(); + let class = context.pointer_type().into(); + let method = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[runtime, class, method], false) + } + RuntimeFunction::RuntimeState => { + let runtime = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[runtime], false) + } + RuntimeFunction::ClassObject => { + let name = context.pointer_type().into(); + let fields = context.i8_type().into(); + let methods = context.i16_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[name, fields, methods], false) + } + RuntimeFunction::ClassProcess => { + let name = context.pointer_type().into(); + let fields = context.i8_type().into(); + let methods = context.i16_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[name, fields, methods], false) + } + RuntimeFunction::MessageNew => { + let method = context.pointer_type().into(); + let length = context.i8_type().into(); + let ret = module.layouts.message.ptr_type(space); + + ret.fn_type(&[method, length], false) + } + RuntimeFunction::ProcessSendMessage => { + let state = module.layouts.state.ptr_type(space).into(); + let sender = context.pointer_type().into(); + let receiver = context.pointer_type().into(); + let message = module.layouts.message.ptr_type(space).into(); + let ret = context.void_type(); + + ret.fn_type(&[state, sender, receiver, message], false) + } + RuntimeFunction::ProcessNew => { + let process = context.pointer_type().into(); + let class = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[process, class], false) + } + RuntimeFunction::TimeMonotonic => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::IntToString => { + let state = module.layouts.state.ptr_type(space).into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, value], false) + } + RuntimeFunction::ChannelDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let chan = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, chan], false) + } + RuntimeFunction::ChannelNew => { + let state = module.layouts.state.ptr_type(space).into(); + let capacity = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, capacity], false) + } + RuntimeFunction::ChannelReceive => { + let proc = context.pointer_type().into(); + let chan = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc, chan], false) + } + RuntimeFunction::ChannelReceiveUntil => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let channel = context.pointer_type().into(); + let time = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, channel, time], false) + } + RuntimeFunction::ChannelSend => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let channel = context.pointer_type().into(); + let message = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, channel, message], false) + } + RuntimeFunction::ChannelTryReceive => { + let proc = context.pointer_type().into(); + let channel = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proc, channel], false) + } + RuntimeFunction::ChannelWait => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let channels = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, channels], false) + } + RuntimeFunction::DirectoryCreate + | RuntimeFunction::DirectoryCreateRecursive + | RuntimeFunction::DirectoryList + | RuntimeFunction::DirectoryRemove + | RuntimeFunction::DirectoryRemoveAll => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::EnvArguments => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvExecutable => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = module.layouts.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvGet => { + let state = module.layouts.state.ptr_type(space).into(); + let name = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, name], false) + } + RuntimeFunction::EnvGetWorkingDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = module.layouts.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvHomeDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = module.layouts.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvSetWorkingDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, path], false) + } + RuntimeFunction::EnvTempDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvVariables => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::Exit => { + let status = context.i64_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[status], false) + } + RuntimeFunction::FileCopy => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let from = context.pointer_type().into(); + let to = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, from, to], false) + } + RuntimeFunction::FileDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let file = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, file], false) + } + RuntimeFunction::FileFlush => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file], false) + } + RuntimeFunction::FileOpen => { + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let mode = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proc, path, mode], false) + } + RuntimeFunction::FileRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file, buffer, size], false) + } + RuntimeFunction::FileRemove => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::FileSeek => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let offset = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file, offset], false) + } + RuntimeFunction::FileSize => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::FileWriteBytes + | RuntimeFunction::FileWriteString => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file, input], false) + } + RuntimeFunction::PathAccessedAt + | RuntimeFunction::PathCreatedAt + | RuntimeFunction::PathModifiedAt => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::PathExpand => { + let state = module.layouts.state.ptr_type(space).into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, path], false) + } + RuntimeFunction::PathExists + | RuntimeFunction::PathIsDirectory + | RuntimeFunction::PathIsFile => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::ProcessStacktrace => { + let proc = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc], false) + } + RuntimeFunction::ProcessStackFrameLine => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace, index], false) + } + RuntimeFunction::ProcessStackFrameName => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace, index], false) + } + RuntimeFunction::ProcessStackFramePath => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace, index], false) + } + RuntimeFunction::ProcessStacktraceLength => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace], false) + } + RuntimeFunction::ProcessStacktraceDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace], false) + } + RuntimeFunction::ProcessSuspend => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let time = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, time], false) + } + RuntimeFunction::RandomBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let rng = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, rng, size], false) + } + RuntimeFunction::RandomDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng], false) + } + RuntimeFunction::RandomFloat => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng], false) + } + RuntimeFunction::RandomFloatRange => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let min = context.f64_type().into(); + let max = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng, min, max], false) + } + RuntimeFunction::RandomFromInt => { + let seed = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[seed], false) + } + RuntimeFunction::RandomInt => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng], false) + } + RuntimeFunction::RandomIntRange => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let min = context.i64_type().into(); + let max = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng, min, max], false) + } + RuntimeFunction::RandomNew => { + let proc = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc], false) + } + RuntimeFunction::SocketAccept => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, socket, deadline], false) + } + RuntimeFunction::SocketAddressPairAddress => { + let pair = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[pair], false) + } + RuntimeFunction::SocketAddressPairDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let pair = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, pair], false) + } + RuntimeFunction::SocketAddressPairPort => { + let pair = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[pair], false) + } + RuntimeFunction::SocketBind => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let address = context.pointer_type().into(); + let port = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, address, port], false) + } + RuntimeFunction::SocketConnect => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let address = context.pointer_type().into(); + let port = context.i64_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[state, proc, socket, address, port, deadline], + false, + ) + } + RuntimeFunction::SocketDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketListen => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketLocalAddress + | RuntimeFunction::SocketPeerAddress => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketRead + | RuntimeFunction::SocketReceiveFrom => { + let state = module.layouts.state.ptr_type(space).into(); + let process = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let amount = context.i64_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[state, process, socket, buffer, amount, deadline], + false, + ) + } + RuntimeFunction::SocketSendBytesTo + | RuntimeFunction::SocketSendStringTo => { + let state = module.layouts.state.ptr_type(space).into(); + let process = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let address = context.pointer_type().into(); + let port = context.i64_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[state, process, socket, buffer, address, port, deadline], + false, + ) + } + RuntimeFunction::SocketSetBroadcast + | RuntimeFunction::SocketSetKeepalive + | RuntimeFunction::SocketSetNodelay + | RuntimeFunction::SocketSetOnlyV6 + | RuntimeFunction::SocketSetReuseAddress + | RuntimeFunction::SocketSetReusePort => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketSetLinger + | RuntimeFunction::SocketSetRecvSize + | RuntimeFunction::SocketSetSendSize + | RuntimeFunction::SocketSetTtl => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketShutdownRead + | RuntimeFunction::SocketShutdownReadWrite + | RuntimeFunction::SocketShutdownWrite => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketTryClone => { + let socket = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[socket], false) + } + RuntimeFunction::StderrFlush | RuntimeFunction::StdoutFlush => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc], false) + } + RuntimeFunction::StdoutWriteString + | RuntimeFunction::StdoutWriteBytes + | RuntimeFunction::StderrWriteString + | RuntimeFunction::StderrWriteBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, input], false) + } + RuntimeFunction::StdinRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, buffer, size], false) + } + RuntimeFunction::StringByte => { + let string = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[string, index], false) + } + RuntimeFunction::StringCharacters => { + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[string], false) + } + RuntimeFunction::StringCharactersDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let input = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, input], false) + } + RuntimeFunction::StringCharactersNext => { + let state = module.layouts.state.ptr_type(space).into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, input], false) + } + RuntimeFunction::StringConcat => { + let state = module.layouts.state.ptr_type(space).into(); + let strings = context.pointer_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, strings, length], false) + } + RuntimeFunction::StringConcatArray => { + let state = module.layouts.state.ptr_type(space).into(); + let strings = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, strings], false) + } + RuntimeFunction::StringDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringNewPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let bytes = context.pointer_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, bytes, length], false) + } + RuntimeFunction::StringSize => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringSliceBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let start = context.i64_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string, start, length], false) + } + RuntimeFunction::StringToByteArray + | RuntimeFunction::StringToLower + | RuntimeFunction::StringToUpper => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringToFloat => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let start = context.i64_type().into(); + let end = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, string, start, end], false) + } + RuntimeFunction::StringToInt => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let string = context.pointer_type().into(); + let radix = context.i64_type().into(); + let start = context.i64_type().into(); + let end = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, string, radix, start, end], false) + } + RuntimeFunction::TimeSystem | RuntimeFunction::TimeSystemOffset => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::SocketNew => { + let proto = context.i64_type().into(); + let kind = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proto, kind], false) + } + RuntimeFunction::SocketWriteBytes + | RuntimeFunction::SocketWriteString => { + let state = module.layouts.state.ptr_type(space).into(); + let process = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, process, socket, buffer, deadline], false) + } + }; + + module.add_function(self.name(), fn_type, None) + } +} diff --git a/compiler/src/mir/mod.rs b/compiler/src/mir/mod.rs index d4a7d71cb..f5e8f8a32 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -3,11 +3,12 @@ //! MIR is used for various optimisations, analysing moves of values, compiling //! pattern matching into decision trees, and more. use ast::source_location::SourceLocation; -use bytecode::{BuiltinFunction, Opcode}; use std::collections::{HashMap, HashSet}; use std::fmt; use std::hash::{Hash, Hasher}; +use std::rc::Rc; use types::collections::IndexMap; +use types::BuiltinFunction; /// The number of reductions to perform after calling a method. const CALL_COST: u16 = 1; @@ -31,14 +32,14 @@ impl Registers { } pub(crate) fn alloc(&mut self, value_type: types::TypeRef) -> RegisterId { - let id = self.values.len(); + let id = self.values.len() as _; self.values.push(Register { value_type }); RegisterId(id) } pub(crate) fn get(&self, register: RegisterId) -> &Register { - &self.values[register.0] + &self.values[register.0 as usize] } pub(crate) fn value_type(&self, register: RegisterId) -> types::TypeRef { @@ -86,7 +87,7 @@ impl Graph { } pub(crate) fn is_connected(&self, block: BlockId) -> bool { - block.0 == 0 || !self.blocks[block.0].predecessors.is_empty() + block == self.start_id || !self.blocks[block.0].predecessors.is_empty() } pub(crate) fn predecessors(&self, block: BlockId) -> Vec { @@ -167,24 +168,26 @@ impl Block { }))); } - pub(crate) fn branch_result( + pub(crate) fn switch( &mut self, - ok: BlockId, - error: BlockId, + register: RegisterId, + blocks: Vec, location: LocationId, ) { - self.instructions.push(Instruction::BranchResult(Box::new( - BranchResult { ok, error, location }, - ))); + self.instructions.push(Instruction::Switch(Box::new(Switch { + register, + blocks, + location, + }))); } - pub(crate) fn jump_table( + pub(crate) fn switch_kind( &mut self, register: RegisterId, blocks: Vec, location: LocationId, ) { - self.instructions.push(Instruction::JumpTable(Box::new(JumpTable { + self.instructions.push(Instruction::SwitchKind(Box::new(SwitchKind { register, blocks, location, @@ -200,39 +203,6 @@ impl Block { .push(Instruction::Return(Box::new(Return { register, location }))); } - pub(crate) fn throw_value( - &mut self, - register: RegisterId, - location: LocationId, - ) { - self.instructions - .push(Instruction::Throw(Box::new(Throw { register, location }))); - } - - pub(crate) fn return_async_value( - &mut self, - register: RegisterId, - value: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::ReturnAsync(Box::new( - ReturnAsync { register, value, location }, - ))); - } - - pub(crate) fn throw_async_value( - &mut self, - register: RegisterId, - value: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::ThrowAsync(Box::new(ThrowAsync { - register, - value, - location, - }))); - } - pub(crate) fn finish(&mut self, terminate: bool, location: LocationId) { self.instructions.push(Instruction::Finish(Box::new(Finish { location, @@ -312,24 +282,13 @@ impl Block { }))); } - pub(crate) fn allocate_array( + pub(crate) fn array( &mut self, register: RegisterId, values: Vec, location: LocationId, ) { - self.instructions.push(Instruction::AllocateArray(Box::new( - AllocateArray { register, values, location }, - ))); - } - - pub(crate) fn strings( - &mut self, - register: RegisterId, - values: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::Strings(Box::new(Strings { + self.instructions.push(Instruction::Array(Box::new(Array { register, values, location, @@ -347,6 +306,19 @@ impl Block { ))); } + pub(crate) fn reference( + &mut self, + register: RegisterId, + value: RegisterId, + location: LocationId, + ) { + self.instructions.push(Instruction::Reference(Box::new(Reference { + register, + value, + location, + }))); + } + pub(crate) fn increment( &mut self, register: RegisterId, @@ -371,14 +343,26 @@ impl Block { }))); } - pub(crate) fn decrement_atomic( + pub(crate) fn increment_atomic( &mut self, register: RegisterId, value: RegisterId, location: LocationId, + ) { + self.instructions.push(Instruction::IncrementAtomic(Box::new( + IncrementAtomic { register, value, location }, + ))); + } + + pub(crate) fn decrement_atomic( + &mut self, + register: RegisterId, + if_true: BlockId, + if_false: BlockId, + location: LocationId, ) { self.instructions.push(Instruction::DecrementAtomic(Box::new( - DecrementAtomic { register, value, location }, + DecrementAtomic { register, if_true, if_false, location }, ))); } @@ -433,38 +417,16 @@ impl Block { }))); } - pub(crate) fn ref_kind( - &mut self, - register: RegisterId, - value: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::RefKind(Box::new(RefKind { - register, - value, - location, - }))); - } - - pub(crate) fn move_result( - &mut self, - register: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::MoveResult(Box::new(MoveResult { - register, - location, - }))); - } - pub(crate) fn call_static( &mut self, + register: RegisterId, class: types::ClassId, method: types::MethodId, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallStatic(Box::new(CallStatic { + register, class, method, arguments, @@ -472,90 +434,68 @@ impl Block { }))); } - pub(crate) fn call_virtual( + pub(crate) fn call_instance( &mut self, + register: RegisterId, receiver: RegisterId, method: types::MethodId, arguments: Vec, location: LocationId, ) { - self.instructions.push(Instruction::CallVirtual(Box::new( - CallVirtual { receiver, method, arguments, location }, + self.instructions.push(Instruction::CallInstance(Box::new( + CallInstance { register, receiver, method, arguments, location }, ))); } pub(crate) fn call_dynamic( &mut self, + register: RegisterId, receiver: RegisterId, method: types::MethodId, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallDynamic(Box::new( - CallDynamic { receiver, method, arguments, location }, + CallDynamic { register, receiver, method, arguments, location }, ))); } pub(crate) fn call_closure( &mut self, + register: RegisterId, receiver: RegisterId, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallClosure(Box::new( - CallClosure { receiver, arguments, location }, + CallClosure { register, receiver, arguments, location }, ))); } pub(crate) fn call_dropper( &mut self, + register: RegisterId, receiver: RegisterId, location: LocationId, ) { self.instructions.push(Instruction::CallDropper(Box::new( - CallDropper { receiver, location }, + CallDropper { register, receiver, location }, ))); } pub(crate) fn call_builtin( &mut self, - id: BuiltinFunction, + register: RegisterId, + name: BuiltinFunction, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallBuiltin(Box::new( - CallBuiltin { id, arguments, location }, + CallBuiltin { register, name, arguments, location }, ))); } - pub(crate) fn raw_instruction( - &mut self, - opcode: Opcode, - arguments: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::RawInstruction(Box::new( - RawInstruction { opcode, arguments, location }, - ))); - } - - pub(crate) fn send_and_wait( - &mut self, - receiver: RegisterId, - method: types::MethodId, - arguments: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::Send(Box::new(Send { - receiver, - method, - arguments, - location, - wait: true, - }))); - } - - pub(crate) fn send_and_forget( + pub(crate) fn send( &mut self, receiver: RegisterId, method: types::MethodId, @@ -567,24 +507,6 @@ impl Block { method, arguments, location, - wait: false, - }))); - } - - pub(crate) fn send_async( - &mut self, - register: RegisterId, - receiver: RegisterId, - method: types::MethodId, - arguments: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::SendAsync(Box::new(SendAsync { - register, - receiver, - method, - arguments, - location, }))); } @@ -592,10 +514,12 @@ impl Block { &mut self, register: RegisterId, receiver: RegisterId, + class: types::ClassId, field: types::FieldId, location: LocationId, ) { self.instructions.push(Instruction::GetField(Box::new(GetField { + class, register, receiver, field, @@ -606,6 +530,7 @@ impl Block { pub(crate) fn set_field( &mut self, receiver: RegisterId, + class: types::ClassId, field: types::FieldId, value: RegisterId, location: LocationId, @@ -613,6 +538,7 @@ impl Block { self.instructions.push(Instruction::SetField(Box::new(SetField { receiver, value, + class, field, location, }))); @@ -631,45 +557,28 @@ impl Block { }))); } - pub(crate) fn get_constant( - &mut self, - register: RegisterId, - id: types::ConstantId, - location: LocationId, - ) { - self.instructions.push(Instruction::GetConstant(Box::new( - GetConstant { register, id, location }, - ))); - } - - pub(crate) fn string_eq( + pub(crate) fn spawn( &mut self, register: RegisterId, - left: RegisterId, - right: RegisterId, + class: types::ClassId, location: LocationId, ) { - self.instructions.push(Instruction::StringEq(Box::new(StringEq { + self.instructions.push(Instruction::Spawn(Box::new(Spawn { register, - left, - right, + class, location, - }))) + }))); } - pub(crate) fn int_eq( + pub(crate) fn get_constant( &mut self, register: RegisterId, - left: RegisterId, - right: RegisterId, + id: types::ConstantId, location: LocationId, ) { - self.instructions.push(Instruction::IntEq(Box::new(IntEq { - register, - left, - right, - location, - }))) + self.instructions.push(Instruction::GetConstant(Box::new( + GetConstant { register, id, location }, + ))); } pub(crate) fn reduce(&mut self, amount: u16, location: LocationId) { @@ -686,8 +595,8 @@ impl Block { pub(crate) enum Constant { Int(i64), Float(f64), - String(String), - Array(Vec), + String(Rc), + Array(Rc>), } impl PartialEq for Constant { @@ -746,7 +655,7 @@ pub(crate) struct Register { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) struct RegisterId(pub(crate) usize); +pub(crate) struct RegisterId(pub(crate) u32); #[derive(Clone)] pub(crate) struct Branch { @@ -756,22 +665,17 @@ pub(crate) struct Branch { pub(crate) location: LocationId, } -/// A jump table/switch instruction. -/// -/// This instruction expects a list of blocks to jump to for their corresponding -/// indexes/values. As such, it currently only supports monotonically increasing -/// values that start at zero. #[derive(Clone)] -pub(crate) struct JumpTable { +pub(crate) struct Switch { pub(crate) register: RegisterId, pub(crate) blocks: Vec, pub(crate) location: LocationId, } #[derive(Clone)] -pub(crate) struct BranchResult { - pub(crate) ok: BlockId, - pub(crate) error: BlockId, +pub(crate) struct SwitchKind { + pub(crate) register: RegisterId, + pub(crate) blocks: Vec, pub(crate) location: LocationId, } @@ -781,7 +685,6 @@ pub(crate) struct Goto { pub(crate) location: LocationId, } -/// Moves a value from one register to another. #[derive(Clone)] pub(crate) struct MoveRegister { pub(crate) source: RegisterId, @@ -789,59 +692,14 @@ pub(crate) struct MoveRegister { pub(crate) location: LocationId, } -/// Checks if the reference count of a register is zero. -/// -/// If the value in the register has any references left, a runtime panic is -/// produced. #[derive(Clone)] pub(crate) struct CheckRefs { pub(crate) register: RegisterId, pub(crate) location: LocationId, } -/// Returns the kind of reference we're dealing with. -/// -/// The following values may be produced: -/// -/// - `0`: owned -/// - `1`: a regular reference -/// - `2`: an atomic type (either a reference or owned) -#[derive(Clone)] -pub(crate) struct RefKind { - pub(crate) register: RegisterId, - pub(crate) value: RegisterId, - pub(crate) location: LocationId, -} - /// Drops a value according to its type. /// -/// This instruction is essentially a macro that expands into one of the -/// following (given the instruction `drop(value)`): -/// -/// For an owned value, it checks the reference count and calls the value's -/// dropper method: -/// -/// check_refs(value) -/// value.$dropper() -/// -/// For a regular reference, it decrements the reference count: -/// -/// decrement(value) -/// -/// For a value that uses atomic reference counting, it decrements the count. -/// If the new count is zero, the dropper method is called: -/// -/// if decrement_atomic(value) { -/// value.$dropper() -/// } -/// -/// For a process it does the same as an atomically reference counted value, -/// except the dropper method is scheduled asynchronously: -/// -/// if decrement_atomic(value) { -/// async value.$dropper() -/// } -/// /// If `dropper` is set to `false`, the dropper method isn't called for a value /// no longer in use. #[derive(Clone)] @@ -853,6 +711,7 @@ pub(crate) struct Drop { #[derive(Clone)] pub(crate) struct CallDropper { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) location: LocationId, } @@ -867,9 +726,6 @@ pub(crate) struct Free { pub(crate) enum CloneKind { Float, Int, - Process, - String, - Other, } /// Clones a value type. @@ -884,20 +740,13 @@ pub(crate) struct Clone { pub(crate) location: LocationId, } -/// Increments the reference count of a value. -/// -/// For regular values this uses regular reference counting. For values that use -/// atomic reference counting (e.g. processes), this instruction uses atomic -/// reference counting. -/// -/// If used on a type parameter, this instruction compiles down to the -/// equivalent of: -/// -/// if atomic(value) { -/// increment_atomic(value) -/// } else { -/// increment(value) -/// } +#[derive(Clone)] +pub(crate) struct Reference { + pub(crate) register: RegisterId, + pub(crate) value: RegisterId, + pub(crate) location: LocationId, +} + #[derive(Clone)] pub(crate) struct Increment { pub(crate) register: RegisterId, @@ -912,16 +761,17 @@ pub(crate) struct Decrement { } #[derive(Clone)] -pub(crate) struct DecrementAtomic { +pub(crate) struct IncrementAtomic { pub(crate) register: RegisterId, pub(crate) value: RegisterId, pub(crate) location: LocationId, } -/// Moves the result of the last method call into a register. #[derive(Clone)] -pub(crate) struct MoveResult { +pub(crate) struct DecrementAtomic { pub(crate) register: RegisterId, + pub(crate) if_true: BlockId, + pub(crate) if_false: BlockId, pub(crate) location: LocationId, } @@ -944,7 +794,7 @@ pub(crate) struct NilLiteral { } #[derive(Clone)] -pub(crate) struct AllocateArray { +pub(crate) struct Array { pub(crate) register: RegisterId, pub(crate) values: Vec, pub(crate) location: LocationId, @@ -956,26 +806,6 @@ pub(crate) struct Return { pub(crate) location: LocationId, } -#[derive(Clone)] -pub(crate) struct Throw { - pub(crate) register: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct ReturnAsync { - pub(crate) register: RegisterId, - pub(crate) value: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct ThrowAsync { - pub(crate) register: RegisterId, - pub(crate) value: RegisterId, - pub(crate) location: LocationId, -} - #[derive(Clone)] pub(crate) struct IntLiteral { pub(crate) register: RegisterId, @@ -997,15 +827,9 @@ pub(crate) struct StringLiteral { pub(crate) location: LocationId, } -#[derive(Clone)] -pub(crate) struct Strings { - pub(crate) register: RegisterId, - pub(crate) values: Vec, - pub(crate) location: LocationId, -} - #[derive(Clone)] pub(crate) struct CallStatic { + pub(crate) register: RegisterId, pub(crate) class: types::ClassId, pub(crate) method: types::MethodId, pub(crate) arguments: Vec, @@ -1013,7 +837,8 @@ pub(crate) struct CallStatic { } #[derive(Clone)] -pub(crate) struct CallVirtual { +pub(crate) struct CallInstance { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) method: types::MethodId, pub(crate) arguments: Vec, @@ -1022,6 +847,7 @@ pub(crate) struct CallVirtual { #[derive(Clone)] pub(crate) struct CallDynamic { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) method: types::MethodId, pub(crate) arguments: Vec, @@ -1030,6 +856,7 @@ pub(crate) struct CallDynamic { #[derive(Clone)] pub(crate) struct CallClosure { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) arguments: Vec, pub(crate) location: LocationId, @@ -1037,14 +864,8 @@ pub(crate) struct CallClosure { #[derive(Clone)] pub(crate) struct CallBuiltin { - pub(crate) id: BuiltinFunction, - pub(crate) arguments: Vec, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct RawInstruction { - pub(crate) opcode: Opcode, + pub(crate) register: RegisterId, + pub(crate) name: BuiltinFunction, pub(crate) arguments: Vec, pub(crate) location: LocationId, } @@ -1055,20 +876,11 @@ pub(crate) struct Send { pub(crate) method: types::MethodId, pub(crate) arguments: Vec, pub(crate) location: LocationId, - pub(crate) wait: bool, -} - -#[derive(Clone)] -pub(crate) struct SendAsync { - pub(crate) register: RegisterId, - pub(crate) receiver: RegisterId, - pub(crate) method: types::MethodId, - pub(crate) arguments: Vec, - pub(crate) location: LocationId, } #[derive(Clone)] pub(crate) struct GetField { + pub(crate) class: types::ClassId, pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) field: types::FieldId, @@ -1077,6 +889,7 @@ pub(crate) struct GetField { #[derive(Clone)] pub(crate) struct SetField { + pub(crate) class: types::ClassId, pub(crate) receiver: RegisterId, pub(crate) value: RegisterId, pub(crate) field: types::FieldId, @@ -1098,18 +911,9 @@ pub(crate) struct Allocate { } #[derive(Clone)] -pub(crate) struct IntEq { +pub(crate) struct Spawn { pub(crate) register: RegisterId, - pub(crate) left: RegisterId, - pub(crate) right: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct StringEq { - pub(crate) register: RegisterId, - pub(crate) left: RegisterId, - pub(crate) right: RegisterId, + pub(crate) class: types::ClassId, pub(crate) location: LocationId, } @@ -1131,95 +935,81 @@ pub(crate) struct Finish { /// sure to also update the compiler pass that removes empty basic blocks. #[derive(Clone)] pub(crate) enum Instruction { - AllocateArray(Box), + Array(Box), Branch(Box), - BranchResult(Box), - JumpTable(Box), + Switch(Box), + SwitchKind(Box), False(Box), Float(Box), Goto(Box), Int(Box), MoveRegister(Box), - MoveResult(Box), Nil(Box), Return(Box), - Throw(Box), - ReturnAsync(Box), - ThrowAsync(Box), String(Box), True(Box), - Strings(Box), CallStatic(Box), - CallVirtual(Box), + CallInstance(Box), CallDynamic(Box), CallClosure(Box), CallDropper(Box), CallBuiltin(Box), Send(Box), - SendAsync(Box), GetField(Box), SetField(Box), CheckRefs(Box), - RefKind(Box), Drop(Box), Free(Box), Clone(Box), + Reference(Box), Increment(Box), Decrement(Box), + IncrementAtomic(Box), DecrementAtomic(Box), - RawInstruction(Box), Allocate(Box), + Spawn(Box), GetConstant(Box), - IntEq(Box), - StringEq(Box), Reduce(Box), Finish(Box), } impl Instruction { - fn location(&self) -> LocationId { + pub(crate) fn location(&self) -> LocationId { match self { Instruction::Branch(ref v) => v.location, - Instruction::BranchResult(ref v) => v.location, - Instruction::JumpTable(ref v) => v.location, + Instruction::Switch(ref v) => v.location, + Instruction::SwitchKind(ref v) => v.location, Instruction::False(ref v) => v.location, Instruction::True(ref v) => v.location, Instruction::Goto(ref v) => v.location, Instruction::MoveRegister(ref v) => v.location, - Instruction::MoveResult(ref v) => v.location, Instruction::Return(ref v) => v.location, - Instruction::Throw(ref v) => v.location, - Instruction::ReturnAsync(ref v) => v.location, - Instruction::ThrowAsync(ref v) => v.location, Instruction::Nil(ref v) => v.location, - Instruction::AllocateArray(ref v) => v.location, + Instruction::Array(ref v) => v.location, Instruction::Int(ref v) => v.location, Instruction::Float(ref v) => v.location, Instruction::String(ref v) => v.location, - Instruction::Strings(ref v) => v.location, Instruction::CallStatic(ref v) => v.location, - Instruction::CallVirtual(ref v) => v.location, + Instruction::CallInstance(ref v) => v.location, Instruction::CallDynamic(ref v) => v.location, Instruction::CallClosure(ref v) => v.location, Instruction::CallDropper(ref v) => v.location, Instruction::CallBuiltin(ref v) => v.location, Instruction::Send(ref v) => v.location, - Instruction::SendAsync(ref v) => v.location, Instruction::GetField(ref v) => v.location, Instruction::SetField(ref v) => v.location, Instruction::CheckRefs(ref v) => v.location, - Instruction::RefKind(ref v) => v.location, Instruction::Drop(ref v) => v.location, Instruction::Free(ref v) => v.location, Instruction::Clone(ref v) => v.location, + Instruction::Reference(ref v) => v.location, Instruction::Increment(ref v) => v.location, Instruction::Decrement(ref v) => v.location, + Instruction::IncrementAtomic(ref v) => v.location, Instruction::DecrementAtomic(ref v) => v.location, - Instruction::RawInstruction(ref v) => v.location, Instruction::Allocate(ref v) => v.location, + Instruction::Spawn(ref v) => v.location, Instruction::GetConstant(ref v) => v.location, - Instruction::IntEq(ref v) => v.location, - Instruction::StringEq(ref v) => v.location, Instruction::Reduce(ref v) => v.location, Instruction::Finish(ref v) => v.location, } @@ -1233,15 +1023,21 @@ impl Instruction { v.condition.0, v.if_true.0, v.if_false.0 ) } - Instruction::BranchResult(ref v) => { + Instruction::Switch(ref v) => { format!( - "branch_result ok = b{}, error = b{}", - v.ok.0, v.error.0 + "switch r{}, {}", + v.register.0, + v.blocks + .iter() + .enumerate() + .map(|(idx, block)| format!("{} = b{}", idx, block.0)) + .collect::>() + .join(", ") ) } - Instruction::JumpTable(ref v) => { + Instruction::SwitchKind(ref v) => { format!( - "jump_table r{}, {}", + "switch_kind r{}, {}", v.register.0, v.blocks .iter() @@ -1290,44 +1086,31 @@ impl Instruction { Instruction::CheckRefs(ref v) => { format!("check_refs r{}", v.register.0) } - Instruction::RefKind(ref v) => { - format!("r{} = ref_kind r{}", v.register.0, v.value.0) - } - Instruction::MoveResult(ref v) => { - format!("r{} = move_result", v.register.0) - } Instruction::Return(ref v) => { format!("return r{}", v.register.0) } - Instruction::Throw(ref v) => { - format!("throw r{}", v.register.0) - } - Instruction::ReturnAsync(ref v) => { - format!("r{} = async return r{}", v.register.0, v.value.0) - } - Instruction::ThrowAsync(ref v) => { - format!("r{} = async throw r{}", v.register.0, v.value.0) - } Instruction::Allocate(ref v) => { format!("r{} = allocate {}", v.register.0, v.class.name(db)) } - Instruction::AllocateArray(ref v) => { - format!("r{} = array [{}]", v.register.0, join(&v.values)) + Instruction::Spawn(ref v) => { + format!("r{} = spawn {}", v.register.0, v.class.name(db)) } - Instruction::Strings(ref v) => { - format!("r{} = strings [{}]", v.register.0, join(&v.values)) + Instruction::Array(ref v) => { + format!("r{} = array [{}]", v.register.0, join(&v.values)) } Instruction::CallStatic(ref v) => { format!( - "call_static {}.{}({})", + "r{} = call_static {}.{}({})", + v.register.0, v.class.name(db), v.method.name(db), join(&v.arguments) ) } - Instruction::CallVirtual(ref v) => { + Instruction::CallInstance(ref v) => { format!( - "call_virtual r{}.{}({})", + "r{} = call_instance r{}.{}({})", + v.register.0, v.receiver.0, v.method.name(db), join(&v.arguments) @@ -1335,7 +1118,8 @@ impl Instruction { } Instruction::CallDynamic(ref v) => { format!( - "call_dynamic r{}.{}({})", + "r{} = call_dynamic r{}.{}({})", + v.register.0, v.receiver.0, v.method.name(db), join(&v.arguments) @@ -1343,33 +1127,26 @@ impl Instruction { } Instruction::CallClosure(ref v) => { format!( - "call_closure r{}({})", + "r{} = call_closure r{}({})", + v.register.0, v.receiver.0, join(&v.arguments) ) } Instruction::CallDropper(ref v) => { - format!("call_dropper r{}", v.receiver.0,) + format!("r{} = call_dropper r{}", v.register.0, v.receiver.0,) } Instruction::CallBuiltin(ref v) => { - format!("call_builtin {}({})", v.id.name(), join(&v.arguments)) - } - Instruction::RawInstruction(ref v) => { - format!("raw {}({})", v.opcode.name(), join(&v.arguments)) - } - Instruction::Send(ref v) => { format!( - "{} r{}.{}({})", - if v.wait { "send" } else { "send_forget" }, - v.receiver.0, - v.method.name(db), + "r{} = call_builtin {}({})", + v.register.0, + v.name.name(), join(&v.arguments) ) } - Instruction::SendAsync(ref v) => { + Instruction::Send(ref v) => { format!( - "r{} = send_async r{}.{}({})", - v.register.0, + "send r{}.{}({})", v.receiver.0, v.method.name(db), join(&v.arguments) @@ -1391,14 +1168,23 @@ impl Instruction { v.value.0 ) } + Instruction::Reference(ref v) => { + format!("r{} = ref r{}", v.register.0, v.value.0) + } Instruction::Increment(ref v) => { format!("r{} = increment r{}", v.register.0, v.value.0) } Instruction::Decrement(ref v) => { format!("decrement r{}", v.register.0) } + Instruction::IncrementAtomic(ref v) => { + format!("r{} = increment_atomic r{}", v.register.0, v.value.0) + } Instruction::DecrementAtomic(ref v) => { - format!("r{} = decrement_atomic r{}", v.register.0, v.value.0) + format!( + "decrement_atomic r{}, true = b{}, false = b{}", + v.register.0, v.if_true.0, v.if_false.0 + ) } Instruction::GetConstant(ref v) => { format!( @@ -1408,18 +1194,6 @@ impl Instruction { v.id.name(db) ) } - Instruction::IntEq(ref v) => { - format!( - "r{} = int_eq r{}, r{}", - v.register.0, v.left.0, v.right.0 - ) - } - Instruction::StringEq(ref v) => { - format!( - "r{} = string_eq r{}, r{}", - v.register.0, v.left.0, v.right.0 - ) - } Instruction::Reduce(ref v) => format!("reduce {}", v.amount), Instruction::Finish(v) => { if v.terminate { "terminate" } else { "finish" }.to_string() @@ -1480,28 +1254,22 @@ pub(crate) struct Method { pub(crate) id: types::MethodId, pub(crate) registers: Registers, pub(crate) body: Graph, + pub(crate) arguments: Vec, + pub(crate) location: LocationId, } impl Method { - pub(crate) fn new(id: types::MethodId) -> Self { - Self { id, body: Graph::new(), registers: Registers::new() } + pub(crate) fn new(id: types::MethodId, location: LocationId) -> Self { + Self { + id, + body: Graph::new(), + registers: Registers::new(), + arguments: Vec::new(), + location, + } } } -pub(crate) struct Location { - /// The module to use for obtaining the file of the location. - /// - /// We store the module here instead of the file so we don't need to - /// duplicate file paths. - pub(crate) module: types::ModuleId, - - /// The method the location is defined in. - pub(crate) method: types::MethodId, - - /// The line and column range. - pub(crate) range: SourceLocation, -} - #[derive(Copy, Clone, Debug)] pub(crate) struct LocationId(usize); @@ -1512,8 +1280,7 @@ pub(crate) struct Mir { pub(crate) classes: HashMap, pub(crate) traits: HashMap, pub(crate) methods: HashMap, - pub(crate) closure_classes: HashMap, - locations: Vec, + locations: Vec, } impl Mir { @@ -1525,7 +1292,6 @@ impl Mir { traits: HashMap::new(), methods: HashMap::new(), locations: Vec::new(), - closure_classes: HashMap::new(), } } @@ -1537,13 +1303,11 @@ impl Mir { pub(crate) fn add_location( &mut self, - module: types::ModuleId, - method: types::MethodId, - range: SourceLocation, + location: SourceLocation, ) -> LocationId { let id = LocationId(self.locations.len()); - self.locations.push(Location { module, method, range }); + self.locations.push(location); id } @@ -1555,7 +1319,7 @@ impl Mir { } } - pub(crate) fn location(&self, index: LocationId) -> &Location { + pub(crate) fn location(&self, index: LocationId) -> &SourceLocation { &self.locations[index.0] } } diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 5c64b6629..a8bd6a3a3 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -8,15 +8,19 @@ use crate::mir::{ }; use crate::state::State; use ast::source_location::SourceLocation; -use bytecode::Opcode; use std::collections::{HashMap, HashSet}; use std::iter::repeat_with; use std::mem::swap; use std::path::PathBuf; -use types::{self, Block as _, FIELDS_LIMIT}; +use std::rc::Rc; +use types::format::format_type; +use types::{ + self, Block as _, TypeBounds, FIELDS_LIMIT, OPTION_NONE, OPTION_SOME, + RESULT_CLASS, RESULT_ERROR, RESULT_MODULE, RESULT_OK, +}; const SELF_NAME: &str = "self"; -const SELF_ID: usize = 0; +const SELF_ID: u32 = 0; const MODULES_LIMIT: usize = u32::MAX as usize; const CLASSES_LIMIT: usize = u32::MAX as usize; @@ -387,36 +391,40 @@ impl<'a> GenerateDropper<'a> { /// the async dropper is the last message. When run, it cleans up the object /// like a regular class, and the process shuts down. fn async_class(&mut self) { + let loc = self.mir.add_location(self.location.clone()); let async_dropper = self.generate_dropper( types::ASYNC_DROPPER_METHOD, types::MethodKind::AsyncMutable, false, true, ); - let (dropper_type, rec) = + let dropper_type = self.method_type(types::DROPPER_METHOD, types::MethodKind::Mutable); - let mut dropper_method = Method::new(dropper_type); + let mut dropper_method = Method::new(dropper_type, loc); let mut lower = LowerMethod::new( self.state, self.mir, self.module, &mut dropper_method, ); - let self_reg = lower.new_self(rec); - let loc = lower.add_location(self.location.clone()); + + lower.prepare(loc); + + let self_reg = lower.self_register; let nil_reg = lower.get_nil(loc); - // When scheduling the real dropper we don't wait to wait, as the - // receiving process may have many messages left to process. - lower.current_block_mut().send_and_forget( + lower.current_block_mut().send( self_reg, async_dropper, Vec::new(), loc, ); - lower.current_block_mut().reduce_call(loc); + lower.reduce_call(types::TypeRef::nil(), loc); lower.current_block_mut().return_value(nil_reg, loc); + assert_eq!(lower.method.arguments.len(), 1); + assert!(lower.method.id.is_instance_method(&self.state.db)); + self.add_method(types::DROPPER_METHOD, dropper_type, dropper_method); } @@ -426,40 +434,40 @@ impl<'a> GenerateDropper<'a> { /// tag, certain fields may be set to NULL. As such we branch based on the /// tag value, and only drop the fields relevant for that tag. fn enum_class(&mut self) { + let loc = self.mir.add_location(self.location.clone()); let name = types::DROPPER_METHOD; - let drop_method_opt = - self.class.id.method(&self.state.db, types::DROP_METHOD); - let (method_type, rec) = - self.method_type(name, types::MethodKind::Mutable); - let mut method = Method::new(method_type); + let class = self.class.id; + let drop_method_opt = class.method(&self.state.db, types::DROP_METHOD); + let method_type = self.method_type(name, types::MethodKind::Mutable); + let mut method = Method::new(method_type, loc); let mut lower = LowerMethod::new(self.state, self.mir, self.module, &mut method); - let loc = lower.add_location(self.location.clone()); - let self_reg = lower.new_self(rec); + + lower.prepare(loc); + + let self_reg = lower.self_register; if let Some(id) = drop_method_opt { - let res = lower.new_register(types::TypeRef::nil()); + let typ = types::TypeRef::nil(); + let res = lower.new_register(typ); - lower.current_block_mut().call_virtual( + lower.current_block_mut().call_instance( + res, self_reg, id, Vec::new(), loc, ); - lower.current_block_mut().reduce_call(loc); - lower.current_block_mut().move_result(res, loc); + lower.reduce_call(typ, loc); } - let variants = self.class.id.variants(lower.db()); + let variants = class.variants(lower.db()); let mut blocks = Vec::new(); let before_block = lower.current_block; let after_block = lower.add_block(); - let enum_fields = self.class.id.enum_fields(lower.db()); - let tag_field = self - .class - .id - .field_by_index(lower.db(), types::ENUM_TAG_INDEX) - .unwrap(); + let enum_fields = class.enum_fields(lower.db()); + let tag_field = + class.field_by_index(lower.db(), types::ENUM_TAG_INDEX).unwrap(); let tag_reg = lower.new_register(types::TypeRef::int()); for var in variants { @@ -473,7 +481,9 @@ impl<'a> GenerateDropper<'a> { for (&field, typ) in fields.iter().zip(members.into_iter()) { let reg = lower.new_register(typ); - lower.current_block_mut().get_field(reg, self_reg, field, loc); + lower + .current_block_mut() + .get_field(reg, self_reg, class, field, loc); lower.drop_register(reg, loc); } @@ -484,8 +494,8 @@ impl<'a> GenerateDropper<'a> { lower .block_mut(before_block) - .get_field(tag_reg, self_reg, tag_field, loc); - lower.block_mut(before_block).jump_table(tag_reg, blocks, loc); + .get_field(tag_reg, self_reg, class, tag_field, loc); + lower.block_mut(before_block).switch(tag_reg, blocks, loc); lower.current_block = after_block; @@ -505,26 +515,30 @@ impl<'a> GenerateDropper<'a> { free_self: bool, terminate: bool, ) -> types::MethodId { - let drop_method_opt = - self.class.id.method(&self.state.db, types::DROP_METHOD); - let (method_type, rec) = self.method_type(name, kind); - let mut method = Method::new(method_type); + let class = self.class.id; + let drop_method_opt = class.method(&self.state.db, types::DROP_METHOD); + let method_type = self.method_type(name, kind); + let loc = self.mir.add_location(self.location.clone()); + let mut method = Method::new(method_type, loc); let mut lower = LowerMethod::new(self.state, self.mir, self.module, &mut method); - let loc = lower.add_location(self.location.clone()); - let self_reg = lower.new_self(rec); + + lower.prepare(loc); + + let self_reg = lower.self_register; if let Some(id) = drop_method_opt { - let res = lower.new_register(types::TypeRef::nil()); + let typ = types::TypeRef::nil(); + let res = lower.new_register(typ); - lower.current_block_mut().call_virtual( + lower.current_block_mut().call_instance( + res, self_reg, id, Vec::new(), loc, ); - lower.current_block_mut().reduce_call(loc); - lower.current_block_mut().move_result(res, loc); + lower.reduce_call(typ, loc); } // We check the ref count _after_ running the destructor, as otherwise a @@ -532,7 +546,7 @@ impl<'a> GenerateDropper<'a> { // references (e.g. a field containing a mutable Array reference). lower.current_block_mut().check_refs(self_reg, loc); - for field in self.class.id.fields(lower.db()) { + for field in class.fields(lower.db()) { let typ = field.value_type(lower.db()); if typ.is_permanent(lower.db()) { @@ -541,7 +555,9 @@ impl<'a> GenerateDropper<'a> { let reg = lower.new_register(typ); - lower.current_block_mut().get_field(reg, self_reg, field, loc); + lower + .current_block_mut() + .get_field(reg, self_reg, class, field, loc); lower.drop_register(reg, loc); } @@ -565,7 +581,7 @@ impl<'a> GenerateDropper<'a> { &mut self, name: &str, kind: types::MethodKind, - ) -> (types::MethodId, types::TypeRef) { + ) -> types::MethodId { let id = types::Method::alloc( &mut self.state.db, self.module.id, @@ -574,18 +590,17 @@ impl<'a> GenerateDropper<'a> { kind, ); - let self_type = types::TypeId::ClassInstance( - types::ClassInstance::for_instance_self_type( + let self_type = + types::TypeId::ClassInstance(types::ClassInstance::rigid( &mut self.state.db, self.class.id, &types::TypeBounds::new(), - ), - ); + )); let receiver = types::TypeRef::Mut(self_type); id.set_receiver(&mut self.state.db, receiver); - id.set_self_type(&mut self.state.db, self_type); - (id, receiver) + id.set_return_type(&mut self.state.db, types::TypeRef::nil()); + id } fn add_method(&mut self, name: &str, id: types::MethodId, method: Method) { @@ -632,7 +647,7 @@ impl<'a> DefineConstants<'a> { let val = match n.value { hir::ConstExpression::Int(ref n) => Constant::Int(n.value), hir::ConstExpression::String(ref n) => { - Constant::String(n.value.clone()) + Constant::String(Rc::new(n.value.clone())) } hir::ConstExpression::Float(ref n) => { Constant::Float(n.value) @@ -661,22 +676,30 @@ impl<'a> DefineConstants<'a> { match node { hir::ConstExpression::Int(ref n) => Constant::Int(n.value), hir::ConstExpression::String(ref n) => { - Constant::String(n.value.clone()) + Constant::String(Rc::new(n.value.clone())) } hir::ConstExpression::Float(ref n) => Constant::Float(n.value), hir::ConstExpression::Binary(ref n) => self.binary(n), - hir::ConstExpression::ConstantRef(ref n) => { - let id = if let types::ConstantKind::Constant(id) = n.kind { - id - } else { - unreachable!() - }; - - self.mir.constants.get(&id).cloned().unwrap() - } - hir::ConstExpression::Array(ref n) => Constant::Array( + hir::ConstExpression::ConstantRef(ref n) => match n.kind { + types::ConstantKind::Constant(id) => { + self.mir.constants.get(&id).cloned().unwrap() + } + types::ConstantKind::Builtin(id) => match id { + types::BuiltinConstant::Arch => Constant::String(Rc::new( + self.state.config.target.arch_name().to_string(), + )), + types::BuiltinConstant::Os => Constant::String(Rc::new( + self.state.config.target.os_name().to_string(), + )), + types::BuiltinConstant::Abi => Constant::String(Rc::new( + self.state.config.target.abi_name().to_string(), + )), + }, + _ => unreachable!(), + }, + hir::ConstExpression::Array(ref n) => Constant::Array(Rc::new( n.values.iter().map(|n| self.expression(n)).collect(), - ), + )), hir::ConstExpression::Invalid(_) => unreachable!(), } } @@ -750,10 +773,10 @@ impl<'a> DefineConstants<'a> { } if let Some(val) = res { - Constant::String(val) + Constant::String(Rc::new(val)) } else { self.const_expr_error(&left, op, &right, loc); - Constant::String(String::new()) + Constant::String(Rc::new(String::new())) } } Constant::Array(_) => { @@ -958,14 +981,16 @@ impl<'a> LowerToMir<'a> { let mut class = Class::new(id); - GenerateDropper { - state: self.state, - mir: self.mir, - module: self.module, - class: &mut class, - location: node.location, + if !id.kind(self.db()).is_extern() { + GenerateDropper { + state: self.state, + mir: self.mir, + module: self.module, + class: &mut class, + location: node.location, + } + .run(); } - .run(); class.add_methods(&methods); self.mir.add_methods(methods); @@ -999,9 +1024,8 @@ impl<'a> LowerToMir<'a> { node: hir::DefineModuleMethod, ) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1014,9 +1038,8 @@ impl<'a> LowerToMir<'a> { node: hir::DefineInstanceMethod, ) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1026,9 +1049,8 @@ impl<'a> LowerToMir<'a> { fn define_async_method(&mut self, node: hir::DefineAsyncMethod) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1041,9 +1063,8 @@ impl<'a> LowerToMir<'a> { node: hir::DefineStaticMethod, ) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1058,11 +1079,12 @@ impl<'a> LowerToMir<'a> { ) -> Method { let id = node.method_id.unwrap(); let variant_id = node.variant_id.unwrap(); - let mut method = Method::new(id); - let loc = self.mir.add_location(self.module.id, id, node.location); + let loc = self.mir.add_location(node.location); + let mut method = Method::new(id, loc); let fields = class.enum_fields(self.db()); + let bounds = TypeBounds::new(); let ins = types::TypeRef::Owned(types::TypeId::ClassInstance( - types::ClassInstance::for_static_self_type(self.db_mut(), class), + types::ClassInstance::rigid(self.db_mut(), class, &bounds), )); let mut lower = LowerMethod::new(self.state, self.mir, self.module, &mut method); @@ -1077,14 +1099,18 @@ impl<'a> LowerToMir<'a> { lower.current_block_mut().allocate(ins_reg, class, loc); lower.current_block_mut().int_literal(tag_reg, tag_val, loc); - lower.current_block_mut().set_field(ins_reg, tag_field, tag_reg, loc); + lower + .current_block_mut() + .set_field(ins_reg, class, tag_field, tag_reg, loc); for (arg, field) in id.arguments(lower.db()).into_iter().zip(fields.into_iter()) { let reg = *lower.variable_mapping.get(&arg.variable).unwrap(); - lower.current_block_mut().set_field(ins_reg, field, reg, loc); + lower + .current_block_mut() + .set_field(ins_reg, class, field, reg, loc); lower.mark_register_as_moved(reg); } @@ -1214,7 +1240,7 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.partially_move_self_if_field(reg); self.drop_all_registers(); - self.return_or_throw(reg, false, location); + self.return_register(reg, location); return; } @@ -1245,7 +1271,7 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.partially_move_self_if_field(reg); self.drop_all_registers(); - self.return_or_throw(reg, false, loc); + self.return_register(reg, loc); } } @@ -1258,7 +1284,10 @@ impl<'a> LowerMethod<'a> { // define the register. This is OK because the type-checker disallows // the use of `self` in these cases. let self_reg = if self.method.id.is_instance_method(self.db()) { - Some(self.new_self(self.method.id.receiver(self.db()))) + let reg = self.new_self(self.method.id.receiver(self.db())); + + self.method.arguments.push(reg); + Some(reg) } else { None }; @@ -1268,6 +1297,7 @@ impl<'a> LowerMethod<'a> { for arg in self.method.id.arguments(self.db()) { let reg = self.new_variable(arg.variable); + self.method.arguments.push(reg); self.variable_mapping.insert(arg.variable, reg); args.push(reg); } @@ -1302,13 +1332,13 @@ impl<'a> LowerMethod<'a> { // Within a closure, explicit and implicit references to `self` should // use the _captured_ `self` (i.e. point to the outer `self` value), not // the closure itself. - let outer_self = self.field_register(field, field_type, location); - let inner_self = self.surrounding_type_register; + let captured = self.field_register(field, field_type, location); + let closure = self.surrounding_type_register; + let class = self.register_type(closure).class_id(self.db()).unwrap(); self.current_block_mut() - .get_field(outer_self, inner_self, field, location); - - self.self_register = outer_self; + .get_field(captured, closure, class, field, location); + self.self_register = captured; } fn warn_unreachable(&mut self) { @@ -1320,7 +1350,7 @@ impl<'a> LowerMethod<'a> { } if let Some(first) = block.instructions.first() { - let loc = self.mir.location(first.location()).range.clone(); + let loc = self.mir.location(first.location()).clone(); let file = self.module.id.file(&self.state.db); self.state.diagnostics.unreachable(file, loc); @@ -1362,7 +1392,6 @@ impl<'a> LowerMethod<'a> { hir::Expression::AssignSetter(n) => self.assign_setter(*n), hir::Expression::AssignVariable(n) => self.assign_variable(*n), hir::Expression::ReplaceVariable(n) => self.replace_variable(*n), - hir::Expression::AsyncCall(n) => self.async_call(*n), hir::Expression::Break(n) => self.break_expression(*n), hir::Expression::BuiltinCall(n) => self.builtin_call(*n), hir::Expression::Call(n) => self.call(*n), @@ -1373,7 +1402,6 @@ impl<'a> LowerMethod<'a> { hir::Expression::FieldRef(n) => self.field(*n), hir::Expression::Float(n) => self.float_literal(*n), hir::Expression::IdentifierRef(n) => self.identifier(*n), - hir::Expression::Index(n) => self.index_expression(*n), hir::Expression::ClassLiteral(n) => self.class_literal(*n), hir::Expression::Int(n) => self.int_literal(*n), hir::Expression::Loop(n) => self.loop_expression(*n), @@ -1392,7 +1420,7 @@ impl<'a> LowerMethod<'a> { hir::Expression::Tuple(n) => self.tuple_literal(*n), hir::Expression::TypeCast(n) => self.type_cast(*n), hir::Expression::Recover(n) => self.recover_expression(*n), - hir::Expression::Invalid(_) => unreachable!(), + hir::Expression::Try(n) => self.try_expression(*n), } } @@ -1490,7 +1518,7 @@ impl<'a> LowerMethod<'a> { self.state.diagnostics.moved_variable_in_loop( &name, self.file(), - self.mir.location(loc).range.clone(), + self.mir.location(loc).clone(), ); } } @@ -1555,11 +1583,17 @@ impl<'a> LowerMethod<'a> { } fn class_literal(&mut self, node: hir::ClassLiteral) -> RegisterId { + self.check_inferred(node.resolved_type, &node.location); + let ins = self.new_register(node.resolved_type); - let id = node.class_id.unwrap(); + let class = node.class_id.unwrap(); let loc = self.add_location(node.location); - self.current_block_mut().allocate(ins, id, loc); + if class.kind(self.db()).is_async() { + self.current_block_mut().spawn(ins, class, loc); + } else { + self.current_block_mut().allocate(ins, class, loc); + } for field in node.fields { let id = field.field_id.unwrap(); @@ -1567,13 +1601,15 @@ impl<'a> LowerMethod<'a> { let val = self.input_expression(field.value, Some(exp)); let loc = self.add_location(field.location); - self.current_block_mut().set_field(ins, id, val, loc); + self.current_block_mut().set_field(ins, class, id, val, loc); } ins } fn tuple_literal(&mut self, node: hir::TupleLiteral) -> RegisterId { + self.check_inferred(node.resolved_type, &node.location); + let tup = self.new_register(node.resolved_type); let id = node.class_id.unwrap(); let loc = self.add_location(node.location); @@ -1587,20 +1623,14 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(val.location().clone()); let reg = self.input_expression(val, Some(exp)); - self.current_block_mut().set_field(tup, field, reg, loc); + self.current_block_mut().set_field(tup, id, field, reg, loc); } tup } fn array_literal(&mut self, node: hir::ArrayLiteral) -> RegisterId { - if !node.resolved_type.is_inferred(self.db()) { - self.state.diagnostics.cant_infer_type( - types::format_type(self.db(), node.resolved_type), - self.file(), - node.location.clone(), - ); - } + self.check_inferred(node.resolved_type, &node.location); let vals: Vec = node .values @@ -1623,7 +1653,7 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.location); let reg = self.new_register(node.resolved_type); - self.current_block_mut().allocate_array(reg, vals, loc); + self.current_block_mut().array(reg, vals, loc); reg } @@ -1701,7 +1731,12 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.location); let reg = self.new_register(node.resolved_type); - self.current_block_mut().strings(reg, vals, loc); + self.current_block_mut().call_builtin( + reg, + types::BuiltinFunction::StringConcat, + vals, + loc, + ); reg } } @@ -1724,10 +1759,9 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.name.location); let reg = match node.kind { types::CallKind::Call(info) => { - self.verify_call_info(&info, &node.location); + self.check_inferred(info.returns, &node.location); let returns = info.returns; - let throws = info.throws; let rec = if info.receiver.is_explicit() { node.receiver.map(|expr| self.expression(expr)) } else { @@ -1735,30 +1769,30 @@ impl<'a> LowerMethod<'a> { }; let args = self.call_arguments(&info, node.arguments); + let result = self.handle_call(info, rec, args, loc); - self.handle_call(info, rec, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + self.reduce_call(returns, loc); + + if returns.is_never(self.db()) { + self.add_current_block(); + } + + result } types::CallKind::GetField(info) => { + self.check_inferred(info.variable_type, &node.location); + let rec = self.expression(node.receiver.unwrap()); let reg = self.new_field(info.id, info.variable_type); - self.current_block_mut().get_field(reg, rec, info.id, loc); + self.current_block_mut() + .get_field(reg, rec, info.class, info.id, loc); reg } - types::CallKind::ClosureCall(info) => { - self.verify_inferred_type(info.returns, &node.location); - - if !info.throws.is_inferred(self.db()) { - self.state.diagnostics.cant_infer_throw_type( - self.file(), - node.location.clone(), - ); - } + types::CallKind::CallClosure(info) => { + self.check_inferred(info.returns, &node.location); let returns = info.returns; - let throws = info.throws; let rec = self.expression(node.receiver.unwrap()); let mut args = Vec::new(); @@ -1772,9 +1806,16 @@ impl<'a> LowerMethod<'a> { } } - self.current_block_mut().call_closure(rec, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + let res = self.new_register(returns); + + self.current_block_mut().call_closure(res, rec, args, loc); + self.reduce_call(returns, loc); + + if returns.is_never(self.db()) { + self.add_current_block(); + } + + res } _ => { unreachable!() @@ -1791,7 +1832,8 @@ impl<'a> LowerMethod<'a> { receiver: Option, arguments: Vec, location: LocationId, - ) { + ) -> RegisterId { + let result = self.new_register(info.returns); let mut rec = match info.receiver { types::Receiver::Explicit => receiver.unwrap(), types::Receiver::Implicit => { @@ -1803,7 +1845,7 @@ impl<'a> LowerMethod<'a> { self.state.diagnostics.implicit_receiver_moved( &name, self.file(), - self.mir.location(location).range.clone(), + self.mir.location(location).clone(), ); } @@ -1813,9 +1855,9 @@ impl<'a> LowerMethod<'a> { // Static methods can't move, be dynamic or async, so we can // skip the code that comes after this. self.current_block_mut() - .call_static(id, info.id, arguments, location); + .call_static(result, id, info.id, arguments, location); - return; + return result; } }; @@ -1824,15 +1866,17 @@ impl<'a> LowerMethod<'a> { } if info.id.is_async(self.db()) { - self.current_block_mut() - .send_and_wait(rec, info.id, arguments, location); + self.current_block_mut().send(rec, info.id, arguments, location); + self.current_block_mut().nil_literal(result, location); } else if info.dynamic { self.current_block_mut() - .call_dynamic(rec, info.id, arguments, location); + .call_dynamic(result, rec, info.id, arguments, location); } else { self.current_block_mut() - .call_virtual(rec, info.id, arguments, location); + .call_instance(result, rec, info.id, arguments, location); } + + result } fn call_arguments( @@ -1861,21 +1905,7 @@ impl<'a> LowerMethod<'a> { args } - fn verify_call_info( - &mut self, - info: &types::CallInfo, - location: &SourceLocation, - ) { - self.verify_inferred_type(info.returns, location); - - if !info.throws.is_inferred(self.db()) { - self.state - .diagnostics - .cant_infer_throw_type(self.file(), location.clone()); - } - } - - fn verify_inferred_type( + fn check_inferred( &mut self, typ: types::TypeRef, location: &SourceLocation, @@ -1885,7 +1915,7 @@ impl<'a> LowerMethod<'a> { } self.state.diagnostics.cant_infer_type( - types::format_type(self.db(), typ), + format_type(self.db(), typ), self.file(), location.clone(), ); @@ -1911,91 +1941,11 @@ impl<'a> LowerMethod<'a> { self.input_register(reg, typ, expected, loc) } - fn async_call(&mut self, node: hir::AsyncCall) -> RegisterId { - let loc = self.add_location(node.name.location); - let info = node.info.unwrap(); - - self.verify_call_info(&info, &node.location); - - let rec = self.expression(node.receiver); - let args = self.call_arguments(&info, node.arguments); - let reg = self.new_register(info.returns); - - self.current_block_mut().send_async(reg, rec, info.id, args, loc); - self.current_block_mut().reduce_call(loc); - reg - } - - fn handle_result( - &mut self, - else_node: Option, - returns: types::TypeRef, - throws: types::TypeRef, - location: LocationId, - ) -> RegisterId { - // We create the register first so its state is mapped to the block the - // call is performed in, not a block we may branch to based on the - // result. - let ret_reg = self.new_untracked_register(returns); - - if let Some(node) = else_node { - let before_else = self.current_block; - let ok_block = self.add_block(); - let else_block = self.add_block(); - let after_else = self.add_block(); - let else_loc = self.add_location(node.location); - - self.add_edge(before_else, else_block); - self.add_edge(before_else, ok_block); - self.add_edge(ok_block, after_else); - - self.current_block = else_block; - - self.enter_scope(); - - let else_var = self.new_register(throws); - - self.current_block_mut().move_result(else_var, else_loc); - self.else_argument(node.argument, else_var, else_loc); - - let else_res = self.body(node.body, else_loc); - - self.mark_register_as_moved(else_res); - self.exit_scope(); - - if self.in_connected_block() { - self.mark_register_as_moved(else_res); - self.current_block_mut() - .move_register(ret_reg, else_res, else_loc); - self.add_edge(self.current_block, after_else); - } - - self.current_block = after_else; - - self.block_mut(before_else) - .branch_result(ok_block, else_block, location); - - self.block_mut(ok_block).move_result(ret_reg, location); - self.scope.created.push(ret_reg); - - return ret_reg; - } - - self.current_block_mut().move_result(ret_reg, location); - self.scope.created.push(ret_reg); - - if returns.is_never(self.db()) { - self.add_current_block(); - } - - ret_reg - } - fn assign_setter(&mut self, node: hir::AssignSetter) -> RegisterId { let entered = self.enter_call_scope(); let reg = match node.kind { types::CallKind::Call(info) => { - self.verify_call_info(&info, &node.location); + self.check_inferred(info.returns, &node.location); let loc = self.add_location(node.location); let exp = self.expected_argument_type(info.id, 0); @@ -2007,11 +1957,15 @@ impl<'a> LowerMethod<'a> { let args = vec![self.input_expression(node.value, Some(exp))]; let returns = info.returns; - let throws = info.throws; + let result = self.handle_call(info, rec, args, loc); + + self.reduce_call(returns, loc); + + if returns.is_never(self.db()) { + self.add_current_block(); + } - self.handle_call(info, rec, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + result } types::CallKind::SetField(info) => { let rec = self.expression(node.receiver); @@ -2019,7 +1973,8 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.location); let arg = self.input_expression(node.value, Some(exp)); - self.current_block_mut().set_field(rec, info.id, arg, loc); + self.current_block_mut() + .set_field(rec, info.class, info.id, arg, loc); self.get_nil(loc) } _ => unreachable!(), @@ -2046,13 +2001,14 @@ impl<'a> LowerMethod<'a> { let &field = self.variable_fields.get(&id).unwrap(); let ® = self.field_mapping.get(&field).unwrap(); let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); if self.should_drop_register(reg) { - self.current_block_mut().get_field(reg, rec, field, loc); + self.current_block_mut().get_field(reg, rec, class, field, loc); self.drop_register(reg, loc); } - self.current_block_mut().set_field(rec, field, val, loc); + self.current_block_mut().set_field(rec, class, field, val, loc); } self.get_nil(loc) @@ -2077,9 +2033,10 @@ impl<'a> LowerMethod<'a> { } else { let &field = self.variable_fields.get(&id).unwrap(); let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); - self.current_block_mut().get_field(old_val, rec, field, loc); - self.current_block_mut().set_field(rec, field, new_val, loc); + self.current_block_mut().get_field(old_val, rec, class, field, loc); + self.current_block_mut().set_field(rec, class, field, new_val, loc); } old_val @@ -2093,25 +2050,26 @@ impl<'a> LowerMethod<'a> { if let Some(®) = self.field_mapping.get(&id) { let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); if self.should_drop_register(reg) { - self.current_block_mut().get_field(reg, rec, id, loc); + self.current_block_mut().get_field(reg, rec, class, id, loc); self.drop_register(reg, loc); } self.update_register_state(reg, RegisterState::Available); - self.current_block_mut().set_field(rec, id, new_val, loc); + self.current_block_mut().set_field(rec, class, id, new_val, loc); } else { let reg = self.new_register(node.resolved_type); let rec = self.self_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); // Closures capture `self` as a whole, so we can't end up with a // case where we try to drop an already dropped value here. - self.current_block_mut().get_field(reg, rec, id, loc); + self.current_block_mut().get_field(reg, rec, class, id, loc); self.drop_register(reg, loc); - self.mark_register_as_moved(reg); - self.current_block_mut().set_field(rec, id, new_val, loc); + self.current_block_mut().set_field(rec, class, id, new_val, loc); }; self.get_nil(loc) @@ -2130,143 +2088,40 @@ impl<'a> LowerMethod<'a> { (self.self_register, self.self_register) }; + let class = self.register_type(rec).class_id(self.db()).unwrap(); + self.check_if_moved(check_reg, &node.field.name, &node.field.location); - self.current_block_mut().get_field(old_val, rec, id, loc); - self.current_block_mut().set_field(rec, id, new_val, loc); + self.current_block_mut().get_field(old_val, rec, class, id, loc); + self.current_block_mut().set_field(rec, class, id, new_val, loc); old_val } - fn index_expression(&mut self, node: hir::Index) -> RegisterId { - let info = node.info.unwrap(); - - self.verify_inferred_type(info.returns, &node.location); - - let entered = self.enter_call_scope(); - let exp = self.expected_argument_type(info.id, 0); - let loc = self.add_location(node.location); - let rec = self.expression(node.receiver); - let args = vec![self.input_expression(node.index, Some(exp))]; - let reg = self.new_register(info.returns); - let block = self.current_block_mut(); - - if info.dynamic { - block.call_dynamic(rec, info.id, args, loc); - } else { - block.call_virtual(rec, info.id, args, loc); - } - - block.reduce_call(loc); - block.move_result(reg, loc); - - self.exit_call_scope(entered, reg); - reg - } - fn builtin_call(&mut self, node: hir::BuiltinCall) -> RegisterId { let loc = self.add_location(node.location); let info = node.info.unwrap(); let returns = info.returns; - let throws = info.throws; // Builtin calls don't take ownership of arguments, nor do we need/want // to modify reference counts. As such we use a simplified approach to // passing arguments (compared to regular method calls). - let mut args = + let args: Vec<_> = node.arguments.into_iter().map(|n| self.expression(n)).collect(); - match info.id.kind(self.db()) { - types::BuiltinFunctionKind::Function(bif) => { - self.current_block_mut().call_builtin(bif, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) - } - types::BuiltinFunctionKind::Instruction(op) if op.writes() => { - let reg = self.new_register(returns); - let block = self.current_block_mut(); - let mut regs = vec![reg]; - - regs.append(&mut args); - block.raw_instruction(op, regs, loc); - reg - } - types::BuiltinFunctionKind::Instruction(Opcode::Panic) => { - let op = Opcode::Panic; - - self.current_block_mut().raw_instruction(op, args, loc); - self.add_current_block(); - self.new_register(types::TypeRef::Never) - } - types::BuiltinFunctionKind::Instruction(op) => { - self.current_block_mut().raw_instruction(op, args, loc); + match info.id { + types::BuiltinFunction::Moved => { + self.mark_register_as_moved(args[0]); self.get_nil(loc) } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::FutureGet, - ) => { - let op = Opcode::FutureGet; - - self.current_block_mut().raw_instruction(op, args, loc); - self.handle_result(node.else_block, returns, throws, loc) - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::FutureGetFor, - ) => { - let op = Opcode::FutureGetFor; + name => { + let reg = self.new_register(returns); - self.current_block_mut().raw_instruction(op, args, loc); - self.handle_result(node.else_block, returns, throws, loc) - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::StringClone, - ) => { - let kind = CloneKind::String; - let reg = self.new_register(types::TypeRef::string()); - let src = args[0]; - - self.current_block_mut().clone(kind, reg, src, loc); - reg - } - types::BuiltinFunctionKind::Macro(types::CompilerMacro::Moved) => { - let reg = args[0]; + self.current_block_mut().call_builtin(reg, name, args, loc); + self.reduce_call(returns, loc); - self.mark_register_as_moved(reg); - reg - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::PanicThrown, - ) => { - let reg = self.new_register(types::TypeRef::string()); - let arg = args[0]; - let mid = self - .register_type(arg) - .type_id(self.db(), self.method.id.self_type(self.db())) - .ok() - .and_then(|id| { - id.method(self.db(), types::TO_STRING_METHOD) - }) - .unwrap(); - - self.current_block_mut().call_virtual( - arg, - mid, - Vec::new(), - loc, - ); - self.current_block_mut().reduce_call(loc); - self.current_block_mut().move_result(reg, loc); - self.current_block_mut().raw_instruction( - Opcode::Panic, - vec![reg], - loc, - ); - self.get_nil(loc) - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::Strings, - ) => { - let reg = self.new_register(types::TypeRef::string()); + if returns.is_never(self.db()) { + self.add_current_block(); + } - self.current_block_mut().strings(reg, args, loc); reg } } @@ -2282,64 +2137,153 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.drop_all_registers(); - self.return_or_throw(reg, false, loc); + self.return_register(reg, loc); self.add_current_block(); self.new_register(types::TypeRef::Never) } - fn throw_expression(&mut self, node: hir::Throw) -> RegisterId { + fn try_expression(&mut self, node: hir::Try) -> RegisterId { let loc = self.add_location(node.location); - let reg = self.output_expression(node.value); + let reg = self.expression(node.expression); + let class = self.register_type(reg).class_id(self.db()).unwrap(); + let tag_reg = self.new_untracked_register(types::TypeRef::int()); + let tag_field = + class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); + let val_field = class.enum_fields(self.db())[0]; + let ok_block = self.add_block(); + let err_block = self.add_block(); + let after_block = self.add_block(); + let mut blocks = vec![BlockId(0), BlockId(0)]; + let ret_reg = self.new_untracked_register(node.return_type); + let err_tag = self.new_untracked_register(types::TypeRef::int()); - self.mark_register_as_moved(reg); - self.drop_all_registers(); - self.return_or_throw(reg, true, loc); - self.add_current_block(); - self.new_register(types::TypeRef::Never) - } + self.add_edge(self.current_block, ok_block); + self.add_edge(self.current_block, err_block); + self.add_edge(ok_block, after_block); - fn return_or_throw( - &mut self, - register: RegisterId, - throw: bool, - location: LocationId, - ) { - if self.method.id.is_async(self.db()) { - let write_reg = self.new_register(types::TypeRef::boolean()); - let before_id = self.current_block; + self.mark_register_as_moved(reg); + self.current_block_mut().get_field(tag_reg, reg, class, tag_field, loc); + + let out_reg = match node.kind { + types::ThrowKind::Unknown => unreachable!(), + types::ThrowKind::Option(typ) => { + let some_id = class + .variant(self.db(), OPTION_SOME) + .unwrap() + .id(self.db()); + let none_id = class + .variant(self.db(), OPTION_NONE) + .unwrap() + .id(self.db()); + let ok_reg = self.new_untracked_register(typ); + + blocks[some_id as usize] = ok_block; + blocks[none_id as usize] = err_block; + + self.current_block_mut().switch(tag_reg, blocks, loc); + + // The block to jump to for a Some. + self.block_mut(ok_block) + .get_field(ok_reg, reg, class, val_field, loc); + self.block_mut(ok_block).free(reg, loc); + self.block_mut(ok_block).goto(after_block, loc); + + // The block to jump to for a None + self.current_block = err_block; + + self.current_block_mut().allocate(ret_reg, class, loc); + self.current_block_mut().int_literal( + err_tag, + none_id as _, + loc, + ); + self.current_block_mut() + .set_field(ret_reg, class, tag_field, err_tag, loc); + self.current_block_mut().free(reg, loc); - if throw { + self.drop_all_registers(); + self.current_block_mut().return_value(ret_reg, loc); + ok_reg + } + types::ThrowKind::Result(ok_typ, err_typ) => { + let ok_id = + class.variant(self.db(), RESULT_OK).unwrap().id(self.db()); + let err_id = class + .variant(self.db(), RESULT_ERROR) + .unwrap() + .id(self.db()); + let ok_reg = self.new_untracked_register(ok_typ); + let err_val = self.new_untracked_register(err_typ); + + blocks[ok_id as usize] = ok_block; + blocks[err_id as usize] = err_block; + + self.current_block_mut().switch(tag_reg, blocks, loc); + + // The block to jump to for an Ok. + self.block_mut(ok_block) + .get_field(ok_reg, reg, class, val_field, loc); + self.block_mut(ok_block).free(reg, loc); + self.block_mut(ok_block).goto(after_block, loc); + + // The block to jump to for an Error. + self.current_block = err_block; + + self.current_block_mut().allocate(ret_reg, class, loc); + self.current_block_mut().int_literal(err_tag, err_id as _, loc); self.current_block_mut() - .throw_async_value(write_reg, register, location); - } else { + .get_field(err_val, reg, class, val_field, loc); + self.current_block_mut() + .set_field(ret_reg, class, tag_field, err_tag, loc); self.current_block_mut() - .return_async_value(write_reg, register, location); + .set_field(ret_reg, class, val_field, err_val, loc); + self.current_block_mut().free(reg, loc); + + self.drop_all_registers(); + self.current_block_mut().return_value(ret_reg, loc); + ok_reg } + }; - if !self.register_type(register).is_permanent(self.db()) { - let drop_id = self.add_block(); - let after_id = self.add_block(); + self.current_block = after_block; + self.scope.created.push(out_reg); + out_reg + } - self.add_edge(before_id, drop_id); - self.add_edge(before_id, after_id); - self.add_edge(drop_id, after_id); + fn throw_expression(&mut self, node: hir::Throw) -> RegisterId { + let loc = self.add_location(node.location); + let reg = self.expression(node.value); + let class = self.db().class_in_module(RESULT_MODULE, RESULT_CLASS); + let err_id = + class.variant(self.db(), RESULT_ERROR).unwrap().id(self.db()); + let tag_field = + class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); + let val_field = class.enum_fields(self.db())[0]; + let result_reg = self.new_register(node.return_type); + let tag_reg = self.new_register(types::TypeRef::int()); - self.current_block_mut() - .branch(write_reg, after_id, drop_id, location); + self.current_block_mut().allocate(result_reg, class, loc); + self.current_block_mut().int_literal(tag_reg, err_id as _, loc); + self.current_block_mut() + .set_field(result_reg, class, tag_field, tag_reg, loc); + self.current_block_mut() + .set_field(result_reg, class, val_field, reg, loc); - self.current_block = drop_id; + self.mark_register_as_moved(reg); + self.mark_register_as_moved(result_reg); + self.drop_all_registers(); - self.drop_register(register, location); - self.current_block_mut().goto(after_id, location); + self.current_block_mut().return_value(result_reg, loc); - self.current_block = after_id; - } + self.add_current_block(); + self.new_register(types::TypeRef::Never) + } + fn return_register(&mut self, register: RegisterId, location: LocationId) { + if self.method.id.is_async(self.db()) { let terminate = self.method.id.is_main(self.db()); self.current_block_mut().finish(terminate, location); - } else if throw { - self.current_block_mut().throw_value(register, location); } else { self.current_block_mut().return_value(register, location); } @@ -2366,21 +2310,22 @@ impl<'a> LowerMethod<'a> { fn increment( &mut self, value: hir::Expression, - value_type: types::TypeRef, + return_type: types::TypeRef, location: SourceLocation, ) -> RegisterId { let loc = self.add_location(location); let val = self.expression(value); + let val_type = self.register_type(val); - if value_type.is_value_type(self.db()) { - let reg = self.clone_value_type(val, value_type, false, loc); + if val_type.is_value_type(self.db()) { + let reg = self.clone_value_type(val, return_type, false, loc); self.mark_register_as_available(reg); reg } else { - let reg = self.new_register(value_type); + let reg = self.new_register(return_type); - self.current_block_mut().increment(reg, val, loc); + self.current_block_mut().reference(reg, val, loc); reg } } @@ -2442,8 +2387,8 @@ impl<'a> LowerMethod<'a> { let input_reg = self.input_expression(node.expression, None); let input_type = self.register_type(input_reg); - // The result is untracked as otherwise an explicit return/throw/etc may - // drop it before we write to it. + // The result is untracked as otherwise an explicit return may drop it + // before we write to it. let output_reg = self.new_untracked_register(node.resolved_type); let mut rows = Vec::new(); @@ -2478,8 +2423,8 @@ impl<'a> LowerMethod<'a> { rows.push(pmatch::Row::new(vec![col], guard, body)); } - let stype = self.self_type(); - let compiler = pmatch::Compiler::new(self.state, stype, vars); + let bounds = self.method.id.bounds(self.db()).clone(); + let compiler = pmatch::Compiler::new(self.state, vars, bounds); let result = compiler.compile(rows); if result.missing { @@ -2701,7 +2646,19 @@ impl<'a> LowerMethod<'a> { self.add_drop_flag(target, loc); if state.increment.contains(&source) { - self.current_block_mut().increment(target, source, loc); + let typ = self.register_type(source); + + if typ.is_value_type(self.db()) { + let copy = + self.clone_value_type(source, typ, false, loc); + + self.mark_register_as_moved(copy); + self.current_block_mut() + .move_register(target, copy, loc); + } else { + self.current_block_mut() + .reference(target, source, loc); + } } else { self.current_block_mut() .move_register(target, source, loc); @@ -2879,8 +2836,12 @@ impl<'a> LowerMethod<'a> { let val_reg = self.new_untracked_register(types::TypeRef::string()); self.block_mut(test_block).string_literal(val_reg, val, loc); - self.block_mut(test_block) - .string_eq(res_reg, test_reg, val_reg, loc); + self.block_mut(test_block).call_builtin( + res_reg, + types::BuiltinFunction::StringEq, + vec![test_reg, val_reg], + loc, + ); let ok_block = self.decision(state, case.node, test_block, registers.clone()); @@ -2932,8 +2893,12 @@ impl<'a> LowerMethod<'a> { let val_reg = self.new_untracked_register(val_type); self.block_mut(test_block).int_literal(val_reg, val, loc); - self.block_mut(test_block) - .int_eq(res_reg, test_reg, val_reg, loc); + self.block_mut(test_block).call_builtin( + res_reg, + types::BuiltinFunction::IntEq, + vec![test_reg, val_reg], + loc, + ); test_block } @@ -2974,9 +2939,12 @@ impl<'a> LowerMethod<'a> { for (arg, field) in case.arguments.into_iter().zip(fields.into_iter()) { let reg = state.registers[arg.0]; + let class = + self.register_type(test_reg).class_id(self.db()).unwrap(); state.mark_as_increment(reg); - self.block_mut(parent_block).get_field(reg, test_reg, field, loc); + self.block_mut(parent_block) + .get_field(reg, test_reg, class, field, loc); registers.push(reg); } @@ -2999,19 +2967,21 @@ impl<'a> LowerMethod<'a> { registers.push(test_reg); let test_type = self.register_type(test_reg); - let class = test_type.class_id(self.db(), self.self_type()).unwrap(); + let class = test_type.class_id(self.db()).unwrap(); let tag_reg = self.new_untracked_register(types::TypeRef::int()); let tag_field = class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); let member_fields = class.enum_fields(self.db()); let mut member_regs = Vec::new(); - self.block_mut(test_block).get_field(tag_reg, test_reg, tag_field, loc); + self.block_mut(test_block) + .get_field(tag_reg, test_reg, class, tag_field, loc); for &field in &member_fields { let reg = self.new_untracked_register(types::TypeRef::Any); - self.block_mut(test_block).get_field(reg, test_reg, field, loc); + self.block_mut(test_block) + .get_field(reg, test_reg, class, field, loc); member_regs.push(reg); } @@ -3040,38 +3010,10 @@ impl<'a> LowerMethod<'a> { self.decision(state, case.node, block, case_registers); } - self.block_mut(test_block).jump_table(tag_reg, blocks, loc); + self.block_mut(test_block).switch(tag_reg, blocks, loc); test_block } - fn else_argument( - &mut self, - node: Option, - value: RegisterId, - else_location: LocationId, - ) { - let (id, loc) = match node { - Some(hir::BlockArgument { - variable_id: Some(id), - location: loc, - .. - }) => (id, self.add_location(loc)), - _ => { - // If no argument is given or it's a wildcard, we just drop the - // thrown value right away. - self.drop_register(value, else_location); - return; - } - }; - - let reg = self.new_variable(id); - - self.add_drop_flag(reg, loc); - self.current_block_mut().move_register(reg, value, loc); - self.variable_mapping.insert(id, reg); - self.mark_register_as_moved(value); - } - fn identifier(&mut self, node: hir::IdentifierRef) -> RegisterId { let loc = self.add_location(node.location.clone()); @@ -3084,11 +3026,10 @@ impl<'a> LowerMethod<'a> { } types::IdentifierKind::Method(info) => { let entered = self.enter_call_scope(); - let reg = self.new_register(info.returns); + let ret = info.returns; + let reg = self.handle_call(info, None, Vec::new(), loc); - self.handle_call(info, None, Vec::new(), loc); - self.current_block_mut().reduce_call(loc); - self.current_block_mut().move_result(reg, loc); + self.reduce_call(ret, loc); self.exit_call_scope(entered, reg); reg } @@ -3097,14 +3038,15 @@ impl<'a> LowerMethod<'a> { self.state.diagnostics.implicit_receiver_moved( &node.name, self.file(), - self.mir.location(loc).range.clone(), + self.mir.location(loc).clone(), ); } let rec = self.self_register; let reg = self.new_field(info.id, info.variable_type); - self.current_block_mut().get_field(reg, rec, info.id, loc); + self.current_block_mut() + .get_field(reg, rec, info.class, info.id, loc); reg } types::IdentifierKind::Unknown => unreachable!(), @@ -3121,6 +3063,7 @@ impl<'a> LowerMethod<'a> { }; let rec = self.self_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); let name = &node.name; let check_loc = &node.location; @@ -3137,7 +3080,7 @@ impl<'a> LowerMethod<'a> { } } - self.current_block_mut().get_field(reg, rec, id, loc); + self.current_block_mut().get_field(reg, rec, class, id, loc); reg } @@ -3153,15 +3096,14 @@ impl<'a> LowerMethod<'a> { types::ConstantKind::Method(info) => { let entered = self.enter_call_scope(); let loc = self.add_location(node.location); - let reg = self.new_register(info.returns); + let ret = info.returns; + let reg = self.handle_call(info, None, Vec::new(), loc); - self.handle_call(info, None, Vec::new(), loc); - self.current_block_mut().reduce_call(loc); - self.current_block_mut().move_result(reg, loc); + self.reduce_call(ret, loc); self.exit_call_scope(entered, reg); reg } - types::ConstantKind::Unknown => unreachable!(), + _ => unreachable!(), } } @@ -3173,13 +3115,15 @@ impl<'a> LowerMethod<'a> { } fn closure(&mut self, node: hir::Closure) -> RegisterId { + self.check_inferred(node.resolved_type, &node.location); + let module = self.module.id; let closure_id = node.closure_id.unwrap(); let moving = closure_id.is_moving(self.db()); let class_id = types::Class::alloc( self.db_mut(), format!("Closure{}", closure_id.0), - types::ClassKind::Regular, + types::ClassKind::Closure, types::Visibility::Private, module, ); @@ -3189,7 +3133,7 @@ impl<'a> LowerMethod<'a> { module, types::CALL_METHOD.to_string(), types::Visibility::Public, - types::MethodKind::Instance, + types::MethodKind::Mutable, ); let gen_class_ins = @@ -3197,11 +3141,8 @@ impl<'a> LowerMethod<'a> { let call_rec_type = types::TypeRef::Mut(gen_class_ins); let returns = closure_id.return_type(self.db()); - let throws = closure_id.throw_type(self.db()); - method_id.set_self_type(self.db_mut(), gen_class_ins); method_id.set_receiver(self.db_mut(), call_rec_type); - method_id.set_throw_type(self.db_mut(), throws); method_id.set_return_type(self.db_mut(), returns); for arg in closure_id.arguments(self.db()) { @@ -3266,7 +3207,13 @@ impl<'a> LowerMethod<'a> { let val = self.input_register(self_reg, captured_as, None, loc); - self.current_block_mut().set_field(gen_class_reg, field, val, loc); + self.current_block_mut().set_field( + gen_class_reg, + class_id, + field, + val, + loc, + ); method_id.set_field_type(self.db_mut(), name, field, captured_as); captured_self_field = Some((field, exposed_as)); @@ -3304,7 +3251,13 @@ impl<'a> LowerMethod<'a> { let val = self.input_register(raw, captured_as, None, loc); - self.current_block_mut().set_field(gen_class_reg, field, val, loc); + self.current_block_mut().set_field( + gen_class_reg, + class_id, + field, + val, + loc, + ); field_index += 1; @@ -3314,7 +3267,7 @@ impl<'a> LowerMethod<'a> { if field_index >= FIELDS_LIMIT { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!( "Closures can't capture more than {} variables", FIELDS_LIMIT @@ -3325,7 +3278,7 @@ impl<'a> LowerMethod<'a> { } let mut mir_class = Class::new(class_id); - let mut mir_method = Method::new(method_id); + let mut mir_method = Method::new(method_id, loc); let mut lower = LowerMethod::new( self.state, self.mir, @@ -3354,7 +3307,6 @@ impl<'a> LowerMethod<'a> { self.mir.methods.insert(method_id, mir_method); self.mir.classes.insert(class_id, mir_class); self.module.classes.push(class_id); - self.mir.closure_classes.insert(closure_id, class_id); gen_class_reg } @@ -3369,8 +3321,10 @@ impl<'a> LowerMethod<'a> { let &field = self.variable_fields.get(&id).unwrap(); let ® = self.field_mapping.get(&field).unwrap(); let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); - self.current_block_mut().get_field(reg, rec, field, location); + self.current_block_mut() + .get_field(reg, rec, class, field, location); reg } } @@ -3393,7 +3347,7 @@ impl<'a> LowerMethod<'a> { } fn add_current_block(&mut self) -> BlockId { - self.current_block = self.method.body.add_block(); + self.current_block = self.add_block(); self.current_block } @@ -3416,11 +3370,7 @@ impl<'a> LowerMethod<'a> { } fn in_connected_block(&self) -> bool { - self.block_is_connected(self.current_block) - } - - fn block_is_connected(&self, block: BlockId) -> bool { - self.method.body.is_connected(block) + self.method.body.is_connected(self.current_block) } /// Returns the register to use for an output expression (`return` or @@ -3451,11 +3401,11 @@ impl<'a> LowerMethod<'a> { } // When returning/throwing a field or `self` as a reference, we must - // increment the reference count. + // return a new reference. if self.register_kind(reg).is_field_or_self() { let res = self.new_register(typ); - self.current_block_mut().increment(res, reg, loc); + self.current_block_mut().reference(res, reg, loc); return res; } @@ -3521,18 +3471,14 @@ impl<'a> LowerMethod<'a> { return; } - let loc = self.mir.location(location).range.clone(); + let loc = self.mir.location(location).clone(); self.state.diagnostics.error( - DiagnosticId::InvalidMove, + DiagnosticId::Moved, format!( "This value can't be moved out of '{}', \ as it defines a custom destructor", - types::format_type_with_self( - self.db(), - stype, - self.surrounding_type() - ), + format_type(self.db(), self.surrounding_type()), ), self.file(), loc, @@ -3589,20 +3535,36 @@ impl<'a> LowerMethod<'a> { } } + // Value types are always passed as a new value, whether the receiving + // argument is owned or a reference. + // + // This ensures that if we pass the value to generic code, it can freely + // add references to it (if the value is boxed), without this affecting + // our current code (i.e. by said reference outliving the input value). + if register_type.is_value_type(self.db()) + && !register_type.use_atomic_reference_counting(self.db()) + { + return self.clone_value_type( + register, + register_type, + true, + location, + ); + } + if register_type.is_owned_or_uni(self.db()) { - match expected { - // Owned values passed to references are implicitly passed as - // references. - Some(exp) if !exp.is_owned_or_uni(self.db()) => { + if let Some(exp) = expected { + // Regular owned values passed to references are implicitly + // passed as references. + if !exp.is_owned_or_uni(self.db()) { let typ = register_type.cast_according_to(exp, self.db()); let reg = self.new_register(typ); self.mark_register_as_moved(reg); - self.current_block_mut().increment(reg, register, location); + self.current_block_mut().reference(reg, register, location); return reg; } - _ => {} } self.check_field_move(register, location); @@ -3627,25 +3589,6 @@ impl<'a> LowerMethod<'a> { return register; } - // References of value types passed to owned values should be cloned. - // This allows passing e.g. `ref Int` to something that expects `Int`, - // without the need for an explicit clone. - if register_type.is_value_type(self.db()) - && expected.map_or(false, |v| v.is_owned_or_uni(self.db())) - { - // In this case we force cloning, so expressions such as - // `foo(vals[0])` pass a clone instead of passing the returned ref - // directly. If we were to pass the ref directly, `foo` might drop - // it thinking its an owned value, then fail because a ref still - // exists. - return self.clone_value_type( - register, - register_type, - true, - location, - ); - } - // For reference types we only need to increment if they originate from // a variable or field, as regular registers can't be referred to more // than once. @@ -3654,7 +3597,7 @@ impl<'a> LowerMethod<'a> { { let reg = self.new_register(register_type); - self.current_block_mut().increment(reg, register, location); + self.current_block_mut().reference(reg, register, location); self.mark_register_as_moved(reg); return reg; @@ -3691,20 +3634,35 @@ impl<'a> LowerMethod<'a> { } let reg = self.new_register(typ); - let class = typ.class_id(self.db(), self.self_type()).unwrap(); - let kind = if class.kind(self.db()).is_async() { - CloneKind::Process - } else { - match class.0 { - types::INT_ID => CloneKind::Int, - types::FLOAT_ID => CloneKind::Float, - types::STRING_ID => CloneKind::String, - _ => CloneKind::Other, + let class = typ.class_id(self.db()).unwrap(); + + match class.0 { + types::INT_ID => { + self.current_block_mut().clone( + CloneKind::Int, + reg, + source, + location, + ); } - }; + types::FLOAT_ID => { + self.current_block_mut().clone( + CloneKind::Float, + reg, + source, + location, + ); + } + _ if class.kind(self.db()).is_extern() => { + self.current_block_mut().move_register(reg, source, location); + } + _ => { + self.current_block_mut() + .increment_atomic(reg, source, location); + } + } self.mark_register_as_moved(reg); - self.current_block_mut().clone(kind, reg, source, location); reg } @@ -3951,7 +3909,10 @@ impl<'a> LowerMethod<'a> { register: RegisterId, location: LocationId, ) { - self.current_block_mut().get_field(register, receiver, field, location); + let class = self.register_type(receiver).class_id(self.db()).unwrap(); + + self.current_block_mut() + .get_field(register, receiver, class, field, location); self.unconditional_drop_register(register, location); } @@ -4072,7 +4033,7 @@ impl<'a> LowerMethod<'a> { } fn register_kind(&self, register: RegisterId) -> RegisterKind { - self.register_kinds[register.0] + self.register_kinds[register.0 as usize] } fn register_is_available(&mut self, register: RegisterId) -> bool { @@ -4206,9 +4167,7 @@ impl<'a> LowerMethod<'a> { } fn add_location(&mut self, range: SourceLocation) -> LocationId { - let module = self.module.id; - - self.mir.add_location(module, self.method.id, range) + self.mir.add_location(range) } fn last_location(&self) -> LocationId { @@ -4228,7 +4187,7 @@ impl<'a> LowerMethod<'a> { } fn self_type(&self) -> types::TypeId { - self.method.id.self_type(self.db()) + self.method.id.receiver_id(self.db()) } fn surrounding_type(&self) -> types::TypeRef { @@ -4238,6 +4197,17 @@ impl<'a> LowerMethod<'a> { fn in_closure(&self) -> bool { self.self_register != self.surrounding_type_register } + + fn reduce_call(&mut self, returns: types::TypeRef, location: LocationId) { + // If the method never returns there's no point in generating a + // reduction. Doing this can also break the LLVM code generation + // process. + if returns.is_never(self.db()) { + return; + } + + self.current_block_mut().reduce_call(location); + } } /// A compiler pass that cleans up basic blocks. @@ -4256,22 +4226,23 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { let blocks = &method.body.blocks; let mut new_blocks = Vec::new(); let mut id_map = vec![BlockId(0); blocks.len()]; + let mut valid = Vec::with_capacity(blocks.len()); for (index, block) in blocks.iter().enumerate() { - if block.instructions.is_empty() { + if block.instructions.is_empty() + || !method.body.is_connected(BlockId(index)) + { + // Empty and unreachable blocks are useless, so we get rid of + // them here. continue; } id_map[index] = BlockId(new_blocks.len()); - + valid.push((index, block)); new_blocks.push(Block::new()); } - for (index, block) in blocks.iter().enumerate() { - if block.instructions.is_empty() { - continue; - } - + for (index, block) in valid { let block_id = id_map[index]; new_blocks[block_id.0].instructions = block.instructions.clone(); @@ -4288,16 +4259,27 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { vec![ok, err] } - Instruction::BranchResult(ins) => { - let ok = id_map[find_successor(blocks, ins.ok).0]; - let err = id_map[find_successor(blocks, ins.error).0]; + Instruction::DecrementAtomic(ins) => { + let ok = id_map[find_successor(blocks, ins.if_true).0]; + let err = + id_map[find_successor(blocks, ins.if_false).0]; - ins.ok = ok; - ins.error = err; + ins.if_true = ok; + ins.if_false = err; vec![ok, err] } - Instruction::JumpTable(ins) => { + Instruction::Switch(ins) => { + for index in 0..ins.blocks.len() { + let old_id = ins.blocks[index]; + + ins.blocks[index] = + id_map[find_successor(blocks, old_id).0]; + } + + ins.blocks.clone() + } + Instruction::SwitchKind(ins) => { for index in 0..ins.blocks.len() { let old_id = ins.blocks[index]; @@ -4365,6 +4347,15 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { /// /// For example, a `drop()` instruction used on a type parameter is expanded /// into a conditional drop or decrement. +/// +/// This pass exists because when initially generating MIR, we might not have +/// enough information to generate the correct drop logic (e.g. we're depending +/// on a class that has yet to be lowered to MIR). +/// +/// This pass should be run after applying any optimisation on MIR, that way we +/// can generate the most ideal drop code. For example, inlining a method may +/// influence how certain values are dropped. Running this pass at the end means +/// we don't have to undo some of the work this pass does. pub(crate) struct ExpandDrop<'a> { db: &'a types::Database, method: &'a mut Method, @@ -4409,17 +4400,14 @@ impl<'a> ExpandDrop<'a> { let typ = self.method.registers.value_type(val); let mut succ = Vec::new(); let after_id = self.add_block(); - let stype = self.method.id.self_type(self.db); swap(&mut succ, &mut self.block_mut(block_id).successors); if typ.use_reference_counting(self.db) { self.drop_reference(block_id, after_id, val, typ, loc); - } else if typ.use_atomic_reference_counting(self.db, stype) { + } else if typ.use_atomic_reference_counting(self.db) { self.drop_atomic(block_id, after_id, val, loc); - } else if typ.is_trait_instance(self.db) - || typ.is_self_type(self.db) - { + } else if typ.is_trait_instance(self.db) { self.drop_owned_trait_or_self( block_id, after_id, @@ -4460,19 +4448,19 @@ impl<'a> ExpandDrop<'a> { value: RegisterId, location: LocationId, ) { - let stype = self.method.id.self_type(self.db); let drop_id = self.add_block(); - let check_reg = self.method.registers.alloc(types::TypeRef::boolean()); let check = self.block_mut(before_id); - check.decrement_atomic(check_reg, value, location); - check.branch(check_reg, drop_id, after_id, location); + check.decrement_atomic(value, drop_id, after_id, location); - if self.method.registers.value_type(value).is_string(self.db, stype) { + if self.method.registers.value_type(value).is_string(self.db) { // Strings can be dropped directly instead of going through the // dropper. - self.block_mut(drop_id).raw_instruction( - Opcode::StringDrop, + let reg = self.method.registers.alloc(types::TypeRef::nil()); + + self.block_mut(drop_id).call_builtin( + reg, + types::BuiltinFunction::StringDrop, vec![value], location, ); @@ -4498,49 +4486,28 @@ impl<'a> ExpandDrop<'a> { value_type: types::TypeRef, location: LocationId, ) { - let stype = self.method.id.self_type(self.db); - - if value_type.use_atomic_reference_counting(self.db, stype) { - let reg = self.method.registers.alloc(types::TypeRef::boolean()); - - self.block_mut(before_id).decrement_atomic(reg, value, location); + if value_type.use_atomic_reference_counting(self.db) { + self.drop_atomic(before_id, after_id, value, location); + } else if value_type.is_value_type(self.db) { + self.block_mut(before_id).free(value, location); self.block_mut(before_id).goto(after_id, location); - - self.method.body.add_edge(before_id, after_id); - } else if value_type.is_type_parameter(self.db) - || value_type.is_self_type(self.db) - { - let atomic_id = self.add_block(); - let ref_id = self.add_block(); - let kind_reg = self.method.registers.alloc(types::TypeRef::int()); - - self.block_mut(before_id).ref_kind(kind_reg, value, location); - self.block_mut(before_id).jump_table( - kind_reg, - vec![after_id, ref_id, atomic_id, after_id], - location, - ); - - self.block_mut(ref_id).decrement(value, location); - self.block_mut(ref_id).goto(after_id, location); - - let decr_reg = - self.method.registers.alloc(types::TypeRef::boolean()); - - self.block_mut(atomic_id) - .decrement_atomic(decr_reg, value, location); - self.block_mut(atomic_id).goto(after_id, location); - - self.method.body.add_edge(ref_id, after_id); - self.method.body.add_edge(atomic_id, after_id); - self.method.body.add_edge(before_id, atomic_id); - self.method.body.add_edge(before_id, ref_id); self.method.body.add_edge(before_id, after_id); - } else { + } else if value_type.class_id(self.db).is_some() { self.block_mut(before_id).decrement(value, location); self.block_mut(before_id).goto(after_id, location); - self.method.body.add_edge(before_id, after_id); + } else { + // If the value is typed as a type parameter or trait, it may be + // passed a value type or a type that uses atomic reference + // counting. As such we fall back to a runtime check for such cases. + // + // Disabling the use of a dropper here ensures that _if_ the value + // is a value type (in which case it's treated as an owned value), + // we simply free it, as running its dropper is redundant in that + // case. + self.drop_with_runtime_check( + before_id, after_id, value, false, location, + ); } } @@ -4554,20 +4521,21 @@ impl<'a> ExpandDrop<'a> { ) { let atomic_id = self.add_block(); let owned_id = self.add_block(); - let kind_reg = self.method.registers.alloc(types::TypeRef::int()); + let value_id = self.add_block(); - self.block_mut(before_id).ref_kind(kind_reg, value, location); - self.block_mut(before_id).jump_table( - kind_reg, - vec![owned_id, after_id, atomic_id, after_id], + self.block_mut(before_id).switch_kind( + value, + vec![owned_id, after_id, atomic_id, after_id, value_id, value_id], location, ); self.drop_owned(owned_id, after_id, value, dropper, location); self.drop_atomic(atomic_id, after_id, value, location); + self.drop_value_type(value_id, after_id, value, location); self.method.body.add_edge(before_id, atomic_id); self.method.body.add_edge(before_id, owned_id); + self.method.body.add_edge(before_id, value_id); self.method.body.add_edge(before_id, after_id); } @@ -4586,12 +4554,22 @@ impl<'a> ExpandDrop<'a> { { self.call_dropper(before_id, value, location); } else { - self.block_mut(before_id).check_refs(value, location); self.block_mut(before_id).free(value, location); } self.block_mut(before_id).goto(after_id, location); + self.method.body.add_edge(before_id, after_id); + } + fn drop_value_type( + &mut self, + before_id: BlockId, + after_id: BlockId, + value: RegisterId, + location: LocationId, + ) { + self.block_mut(before_id).free(value, location); + self.block_mut(before_id).goto(after_id, location); self.method.body.add_edge(before_id, after_id); } @@ -4606,12 +4584,11 @@ impl<'a> ExpandDrop<'a> { let ref_id = self.add_block(); let atomic_id = self.add_block(); let owned_id = self.add_block(); - let kind_reg = self.method.registers.alloc(types::TypeRef::int()); + let value_id = self.add_block(); - self.block_mut(before_id).ref_kind(kind_reg, value, location); - self.block_mut(before_id).jump_table( - kind_reg, - vec![owned_id, ref_id, atomic_id, after_id], + self.block_mut(before_id).switch_kind( + value, + vec![owned_id, ref_id, atomic_id, after_id, value_id, value_id], location, ); @@ -4620,11 +4597,13 @@ impl<'a> ExpandDrop<'a> { self.drop_owned(owned_id, after_id, value, dropper, location); self.drop_atomic(atomic_id, after_id, value, location); + self.drop_value_type(value_id, after_id, value, location); self.method.body.add_edge(before_id, ref_id); self.method.body.add_edge(ref_id, after_id); self.method.body.add_edge(before_id, atomic_id); self.method.body.add_edge(before_id, owned_id); + self.method.body.add_edge(before_id, value_id); self.method.body.add_edge(before_id, after_id); } @@ -4634,24 +4613,25 @@ impl<'a> ExpandDrop<'a> { value: RegisterId, location: LocationId, ) { - if let Some(class) = self - .method - .registers - .value_type(value) - .class_id(self.db, self.method.id.self_type(self.db)) - { + let typ = self.method.registers.value_type(value); + let reg = self.method.registers.alloc(types::TypeRef::nil()); + + if let Some(class) = typ.class_id(self.db) { // If the type of the receiver is statically known to be a class, we // can just call the dropper directly. let method = class.method(self.db, types::DROPPER_METHOD).unwrap(); - self.block_mut(block).call_virtual( + self.block_mut(block).call_instance( + reg, value, method, Vec::new(), location, ); - } else { - self.block_mut(block).call_dropper(value, location); + } else if !typ.is_any(self.db) { + // Values of type `Any` could be, well, anything, and as such it's + // not safe to drop them. + self.block_mut(block).call_dropper(reg, value, location); } self.block_mut(block).reduce_call(location); @@ -4666,6 +4646,158 @@ impl<'a> ExpandDrop<'a> { } } +/// A compiler pass that expands the reference() instruction into the correct +/// instruction to create an alias. +pub(crate) struct ExpandReference<'a> { + db: &'a types::Database, + method: &'a mut Method, +} + +impl<'a> ExpandReference<'a> { + pub(crate) fn run_all(db: &'a types::Database, mir: &'a mut Mir) { + for method in mir.methods.values_mut() { + ExpandReference { db, method }.run(); + } + } + + pub(crate) fn run(mut self) { + let mut block_idx = 0; + + while block_idx < self.method.body.blocks.len() { + let block_id = BlockId(block_idx); + + if let Some(ins_idx) = self + .block_mut(block_id) + .instructions + .iter() + .position(|ins| matches!(ins, Instruction::Reference(_))) + { + let (ins, remaining_ins) = { + let block = self.block_mut(block_id); + + if let Instruction::Reference(ins) = + block.instructions.remove(ins_idx) + { + (ins, block.instructions.split_off(ins_idx)) + } else { + unreachable!() + } + }; + + let loc = ins.location; + let reg = ins.register; + let val = ins.value; + let typ = self.method.registers.value_type(val); + let mut succ = Vec::new(); + let after_id = self.add_block(); + + swap(&mut succ, &mut self.block_mut(block_id).successors); + + if let Some(class) = typ.class_id(self.db) { + self.increment_class( + block_id, after_id, reg, val, class, loc, + ); + } else { + self.increment_with_runtime_check( + block_id, after_id, reg, val, loc, + ); + } + + for succ_id in succ { + self.method.body.remove_predecessor(succ_id, block_id); + self.method.body.add_edge(after_id, succ_id); + } + + self.block_mut(after_id).instructions = remaining_ins; + } + + block_idx += 1; + } + } + + fn increment_class( + &mut self, + block_id: BlockId, + after_id: BlockId, + register: RegisterId, + value: RegisterId, + class: types::ClassId, + location: LocationId, + ) { + if class.is_atomic(self.db) { + self.block_mut(block_id) + .increment_atomic(register, value, location); + } else { + self.block_mut(block_id).increment(register, value, location); + } + + self.block_mut(block_id).goto(after_id, location); + self.method.body.add_edge(block_id, after_id); + } + + fn increment_with_runtime_check( + &mut self, + block_id: BlockId, + after_id: BlockId, + register: RegisterId, + value: RegisterId, + location: LocationId, + ) { + let normal_id = self.add_block(); + let atomic_id = self.add_block(); + let int_id = self.add_block(); + let float_id = self.add_block(); + let perm_id = self.add_block(); + + self.block_mut(block_id).switch_kind( + value, + vec![normal_id, normal_id, atomic_id, perm_id, int_id, float_id], + location, + ); + + self.block_mut(normal_id).increment(register, value, location); + self.block_mut(normal_id).goto(after_id, location); + + self.block_mut(atomic_id).increment_atomic(register, value, location); + self.block_mut(atomic_id).goto(after_id, location); + + self.block_mut(int_id).clone(CloneKind::Int, register, value, location); + self.block_mut(int_id).goto(after_id, location); + + self.block_mut(float_id).clone( + CloneKind::Float, + register, + value, + location, + ); + self.block_mut(float_id).goto(after_id, location); + + self.block_mut(perm_id).move_register(register, value, location); + self.block_mut(perm_id).goto(after_id, location); + + self.method.body.add_edge(block_id, normal_id); + self.method.body.add_edge(block_id, atomic_id); + self.method.body.add_edge(block_id, after_id); + self.method.body.add_edge(block_id, int_id); + self.method.body.add_edge(block_id, float_id); + self.method.body.add_edge(block_id, perm_id); + + self.method.body.add_edge(normal_id, after_id); + self.method.body.add_edge(atomic_id, after_id); + self.method.body.add_edge(int_id, after_id); + self.method.body.add_edge(float_id, after_id); + self.method.body.add_edge(perm_id, after_id); + } + + fn block_mut(&mut self, id: BlockId) -> &mut Block { + &mut self.method.body.blocks[id.0] + } + + fn add_block(&mut self) -> BlockId { + self.method.body.add_block() + } +} + fn find_successor(blocks: &[Block], old_id: BlockId) -> BlockId { let mut id = old_id; diff --git a/compiler/src/mir/pattern_matching.rs b/compiler/src/mir/pattern_matching.rs index 26d4b02b9..010685e31 100644 --- a/compiler/src/mir/pattern_matching.rs +++ b/compiler/src/mir/pattern_matching.rs @@ -22,11 +22,94 @@ use crate::hir; use crate::mir::{BlockId, Constant, Mir}; use crate::state::State; use std::collections::{HashMap, HashSet}; +use types::resolve::TypeResolver; use types::{ - ClassInstance, ClassKind, Database, FieldId, TypeContext, TypeId, TypeRef, - VariableId, VariantId, BOOLEAN_ID, INT_ID, STRING_ID, + ClassInstance, ClassKind, Database, FieldId, TypeArguments, TypeBounds, + TypeId, TypeRef, VariableId, VariantId, BOOLEAN_ID, INT_ID, STRING_ID, }; +fn add_missing_patterns( + db: &Database, + node: &Decision, + terms: &mut Vec, + missing: &mut HashSet, +) { + match node { + Decision::Success(_) => {} + Decision::Fail => { + let mut mapping = HashMap::new(); + + // At this point the terms stack looks something like this: + // `[term, term + arguments, term, ...]`. To construct a pattern + // name from this stack, we first map all variables to their + // term indexes. This is needed because when a term defines + // arguments, the terms for those arguments don't necessarily + // appear in order in the term stack. + // + // This mapping is then used when (recursively) generating a + // pattern name. + for (index, step) in terms.iter().enumerate() { + mapping.insert(&step.variable, index); + } + + let name = terms + .first() + .map(|term| term.pattern_name(terms, &mapping)) + .unwrap_or_else(|| "_".to_string()); + + missing.insert(name); + } + Decision::Guard(_, _, fallback) => { + add_missing_patterns(db, fallback, terms, missing); + } + Decision::Switch(var, cases, fallback) => { + for case in cases { + match &case.constructor { + Constructor::True => { + let name = "true".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::False => { + let name = "false".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::Int(_) | Constructor::String(_) => { + let name = "_".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::Class(_) => { + let name = "_".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::Tuple(_) => { + let name = String::new(); + let args = case.arguments.clone(); + + terms.push(Term::new(*var, name, args)); + } + Constructor::Variant(variant) => { + let args = case.arguments.clone(); + let name = variant.name(db).clone(); + + terms.push(Term::new(*var, name, args)); + } + } + + add_missing_patterns(db, &case.node, terms, missing); + terms.pop(); + } + + if let Some(node) = fallback { + add_missing_patterns(db, node, terms, missing); + } + } + } +} + /// A binding to define as part of a pattern. #[derive(Clone, Eq, PartialEq, Debug)] pub(crate) enum Binding { @@ -131,7 +214,7 @@ impl Pattern { for pat in n.values { let field = pat.field_id.unwrap(); - let index = field.index(db) as usize; + let index = field.index(db); args[index] = Pattern::from_hir(db, mir, pat.pattern); fields[index] = field; @@ -142,7 +225,9 @@ impl Pattern { hir::Pattern::Constant(n) => match n.kind { types::ConstantPatternKind::String(id) => { match mir.constants.get(&id) { - Some(Constant::String(v)) => Pattern::String(v.clone()), + Some(Constant::String(v)) => { + Pattern::String(v.as_ref().clone()) + } _ => unreachable!(), } } @@ -359,7 +444,7 @@ impl Match { let mut names = HashSet::new(); let mut steps = Vec::new(); - self.add_missing_patterns(db, &self.tree, &mut steps, &mut names); + add_missing_patterns(db, &self.tree, &mut steps, &mut names); let mut missing: Vec = names.into_iter().collect(); @@ -367,89 +452,6 @@ impl Match { missing.sort(); missing } - - fn add_missing_patterns( - &self, - db: &Database, - node: &Decision, - terms: &mut Vec, - missing: &mut HashSet, - ) { - match node { - Decision::Success(_) => {} - Decision::Fail => { - let mut mapping = HashMap::new(); - - // At this point the terms stack looks something like this: - // `[term, term + arguments, term, ...]`. To construct a pattern - // name from this stack, we first map all variables to their - // term indexes. This is needed because when a term defines - // arguments, the terms for those arguments don't necessarily - // appear in order in the term stack. - // - // This mapping is then used when (recursively) generating a - // pattern name. - for (index, step) in terms.iter().enumerate() { - mapping.insert(&step.variable, index); - } - - let name = terms - .first() - .map(|term| term.pattern_name(terms, &mapping)) - .unwrap_or_else(|| "_".to_string()); - - missing.insert(name); - } - Decision::Guard(_, _, fallback) => { - self.add_missing_patterns(db, fallback, terms, missing); - } - Decision::Switch(var, cases, fallback) => { - for case in cases { - match &case.constructor { - Constructor::True => { - let name = "true".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::False => { - let name = "false".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::Int(_) | Constructor::String(_) => { - let name = "_".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::Class(_) => { - let name = "_".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::Tuple(_) => { - let name = String::new(); - let args = case.arguments.clone(); - - terms.push(Term::new(*var, name, args)); - } - Constructor::Variant(variant) => { - let args = case.arguments.clone(); - let name = variant.name(db).clone(); - - terms.push(Term::new(*var, name, args)); - } - } - - self.add_missing_patterns(db, &case.node, terms, missing); - terms.pop(); - } - - if let Some(node) = fallback { - self.add_missing_patterns(db, node, terms, missing); - } - } - } - } } /// State to use for creating pattern matching variables. @@ -485,9 +487,6 @@ impl Variables { pub(crate) struct Compiler<'a> { state: &'a mut State, - /// The type of `Self` in the scope the `match` expression resides in. - self_type: TypeId, - /// The basic blocks that are reachable in the match expression. /// /// If a block isn't in this list it means its pattern is redundant. @@ -498,20 +497,23 @@ pub(crate) struct Compiler<'a> { /// The pattern matching variables/temporaries. variables: Variables, + + /// Type bounds to apply to types produced by patterns. + bounds: TypeBounds, } impl<'a> Compiler<'a> { pub(crate) fn new( state: &'a mut State, - self_type: TypeId, variables: Variables, + bounds: TypeBounds, ) -> Self { Self { state, reachable: HashSet::new(), missing: false, variables, - self_type, + bounds, } } @@ -799,17 +801,15 @@ impl<'a> Compiler<'a> { return types.into_iter().map(|t| self.new_variable(t)).collect(); } - let mut ctx = TypeContext::with_arguments( - self.self_type, - instance.type_arguments(self.db()).clone(), - ); + let args = TypeArguments::for_class(self.db_mut(), instance); types .into_iter() .map(|raw_type| { - let inferred = raw_type - .inferred(self.db_mut(), &mut ctx, false) - .cast_according_to(source_variable_type, self.db()); + let inferred = + TypeResolver::new(&mut self.state.db, &args, &self.bounds) + .resolve(raw_type) + .cast_according_to(source_variable_type, self.db()); self.new_variable(inferred) }) @@ -818,7 +818,7 @@ impl<'a> Compiler<'a> { fn variable_type(&mut self, variable: &Variable) -> Type { let typ = variable.value_type(&self.variables); - let type_id = typ.type_id(self.db(), self.self_type).unwrap(); + let type_id = typ.type_id(self.db()).unwrap(); let class_ins = if let TypeId::ClassInstance(ins) = type_id { ins } else { @@ -851,7 +851,7 @@ impl<'a> Compiler<'a> { Type::Finite(cons) } - ClassKind::Regular => { + ClassKind::Regular | ClassKind::Extern => { let fields = class_id.fields(self.db()); let args = fields .iter() @@ -877,9 +877,7 @@ impl<'a> Compiler<'a> { Vec::new(), )]) } - // Async classes can't be used in class patterns, so we should - // never encounter an async class here. - ClassKind::Async => unreachable!(), + _ => unreachable!(), }, } } @@ -900,7 +898,7 @@ mod tests { use similar_asserts::assert_eq; use types::module_name::ModuleName; use types::{ - Class, ClassId, ClassInstance, ClassKind, Module, TypeId, + Class, ClassInstance, ClassKind, Module, TypeId, Variable as VariableType, Visibility, }; @@ -930,10 +928,7 @@ mod tests { } fn compiler(state: &mut State) -> Compiler { - let self_type = - TypeId::ClassInstance(ClassInstance::new(ClassId::int())); - - Compiler::new(state, self_type, Variables::new()) + Compiler::new(state, Variables::new(), TypeBounds::new()) } fn success(block: BlockId) -> Decision { diff --git a/compiler/src/mir/printer.rs b/compiler/src/mir/printer.rs index 040cf5968..4f0f4f196 100644 --- a/compiler/src/mir/printer.rs +++ b/compiler/src/mir/printer.rs @@ -23,7 +23,7 @@ pub(crate) fn to_dot(db: &Database, mir: &Mir, methods: &[&Method]) -> String { buffer.push_str("node[fontname=\"monospace\", fontsize=10];\n"); buffer.push_str("edge[fontname=\"monospace\", fontsize=10];\n"); - let rec_name = match method.id.self_type(db) { + let rec_name = match method.id.receiver_id(db) { TypeId::Class(id) => id.name(db), TypeId::Trait(id) => id.name(db), TypeId::ClassInstance(ins) => ins.instance_of().name(db), @@ -74,7 +74,7 @@ pub(crate) fn to_dot(db: &Database, mir: &Mir, methods: &[&Method]) -> String { buffer, "{}{}", ins.format(db).replace('>', ">").replace('<', "<"), - mir.location(ins.location()).range.line_range.start(), + mir.location(ins.location()).line_range.start(), ); } diff --git a/compiler/src/modules_parser.rs b/compiler/src/modules_parser.rs index 37cf3ffb3..a3881c2d1 100644 --- a/compiler/src/modules_parser.rs +++ b/compiler/src/modules_parser.rs @@ -67,9 +67,9 @@ impl<'a> ModulesParser<'a> { } for name in &self.state.config.implicit_imports { - // Implicitly imported modules are always part of libstd, so we + // Implicitly imported modules are always part of std, so we // don't need to search through all the source paths. - let path = self.state.config.libstd.join(name.to_path()); + let path = self.state.config.std.join(name.to_path()); scheduled.insert(name.clone()); pending.push((name.clone(), path)); @@ -105,8 +105,7 @@ impl<'a> ModulesParser<'a> { } } - let mut result: Vec = - modules.into_iter().map(|(_, v)| v).collect(); + let mut result: Vec = modules.into_values().collect(); // We sort the modules so we process them in a deterministic order, // resulting in diagnostics being produced in a deterministic order. @@ -253,7 +252,7 @@ mod tests { let mut state = State::new(Config::new()); - state.config.libstd = temp_dir(); + state.config.std = temp_dir(); state.config.implicit_imports = vec![ModuleName::new("parsing2d")]; let mut pass = ModulesParser::new(&mut state); diff --git a/compiler/src/presenters.rs b/compiler/src/presenters.rs index a3ab53fbc..4fe07fee7 100644 --- a/compiler/src/presenters.rs +++ b/compiler/src/presenters.rs @@ -8,7 +8,7 @@ pub(crate) trait Presenter { fn present(&self, diagnostics: &Diagnostics); } -/// Prints diagnostics in a compact text form, optionally enabling the use of +/// Print diagnostics in a compact text form, optionally enabling the use of /// colors. /// /// The resulting output looks like this: @@ -92,7 +92,7 @@ impl Presenter for TextPresenter { } } -/// Prints diagnostics in JSON. +/// A type that presents diagnostics as JSON. pub(crate) struct JSONPresenter {} impl JSONPresenter { diff --git a/compiler/src/symbol_names.rs b/compiler/src/symbol_names.rs new file mode 100644 index 000000000..5d28c7948 --- /dev/null +++ b/compiler/src/symbol_names.rs @@ -0,0 +1,84 @@ +//! Mangled symbol names for native code. +use crate::mir::Mir; +use std::collections::HashMap; +use types::{ClassId, ConstantId, Database, MethodId, ModuleId}; + +pub(crate) const SYMBOL_PREFIX: &str = "_I"; + +/// A cache of mangled symbol names. +pub(crate) struct SymbolNames { + pub(crate) classes: HashMap, + pub(crate) methods: HashMap, + pub(crate) constants: HashMap, + pub(crate) setup_functions: HashMap, +} + +impl SymbolNames { + pub(crate) fn new(db: &Database, mir: &Mir) -> Self { + let mut classes = HashMap::new(); + let mut methods = HashMap::new(); + let mut constants = HashMap::new(); + let mut setup_functions = HashMap::new(); + + for module_index in 0..mir.modules.len() { + let module = &mir.modules[module_index]; + let mod_name = module.id.name(db).as_str(); + + for &class in &module.classes { + let is_mod = class.kind(db).is_module(); + let class_name = format!( + "{}T_{}::{}", + SYMBOL_PREFIX, + mod_name, + class.name(db) + ); + + classes.insert(class, class_name); + + for &method in &mir.classes[&class].methods { + let name = if is_mod { + // This ensures that methods such as + // `std::process.sleep` aren't formatted as + // `std::process::std::process.sleep`. This in turn + // makes stack traces easier to read. + format!( + "{}M_{}.{}", + SYMBOL_PREFIX, + mod_name, + method.name(db) + ) + } else { + format!( + "{}M_{}::{}.{}", + SYMBOL_PREFIX, + mod_name, + class.name(db), + method.name(db) + ) + }; + + methods.insert(method, name); + } + } + } + + for id in mir.constants.keys() { + let mod_name = id.module(db).name(db).as_str(); + let name = id.name(db); + + constants.insert( + *id, + format!("{}C_{}::{}", SYMBOL_PREFIX, mod_name, name), + ); + } + + for &id in mir.modules.keys() { + let name = + format!("{}M_{}::$setup", SYMBOL_PREFIX, id.name(db).as_str()); + + setup_functions.insert(id, name); + } + + Self { classes, methods, constants, setup_functions } + } +} diff --git a/compiler/src/target.rs b/compiler/src/target.rs new file mode 100644 index 000000000..8e3b4357e --- /dev/null +++ b/compiler/src/target.rs @@ -0,0 +1,294 @@ +use std::fmt; + +/// The supported CPU architectures. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum Architecture { + Amd64, + Arm64, +} + +impl Architecture { + pub(crate) fn from_str(input: &str) -> Option { + match input { + "amd64" => Some(Architecture::Amd64), + "arm64" => Some(Architecture::Arm64), + _ => None, + } + } + + pub(crate) fn native() -> Architecture { + if cfg!(target_arch = "x86_64") { + Architecture::Amd64 + } else if cfg!(target_arch = "aarch64") { + Architecture::Arm64 + } else { + panic!("The host architecture isn't supported"); + } + } +} + +/// The supported operating systems. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum OperatingSystem { + Freebsd, + Linux, + Mac, + Openbsd, +} + +impl OperatingSystem { + pub(crate) fn from_str(input: &str) -> Option { + match input { + "freebsd" => Some(OperatingSystem::Freebsd), + "linux" => Some(OperatingSystem::Linux), + "mac" => Some(OperatingSystem::Mac), + "openbsd" => Some(OperatingSystem::Openbsd), + _ => None, + } + } + + pub(crate) fn native() -> OperatingSystem { + if cfg!(target_os = "freebsd") { + OperatingSystem::Freebsd + } else if cfg!(target_os = "linux") { + OperatingSystem::Linux + } else if cfg!(target_os = "macos") { + OperatingSystem::Mac + } else if cfg!(target_os = "openbsd") { + OperatingSystem::Openbsd + } else { + panic!("The host operating system isn't supported"); + } + } +} + +/// The ABI to target. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum Abi { + Native, + Gnu, + Msvc, +} + +impl Abi { + pub(crate) fn from_str(input: &str) -> Option { + match input { + "native" => Some(Abi::Native), + "gnu" => Some(Abi::Gnu), + "msvc" => Some(Abi::Msvc), + _ => None, + } + } + + pub(crate) fn native() -> Abi { + if cfg!(target_env = "gnu") { + Abi::Gnu + } else if cfg!(target_env = "msvc") { + Abi::Msvc + } else { + Abi::Native + } + } +} + +/// A type describing the compile target, such as the operating system and +/// architecture. +#[derive(Eq, PartialEq, Debug)] +pub struct Target { + pub(crate) arch: Architecture, + pub(crate) os: OperatingSystem, + pub(crate) abi: Abi, +} + +impl Target { + /// Parses a target from a string. + /// + /// If the target is invalid, a None is returned. + pub(crate) fn from_str(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)?; + let abi = iter.next().and_then(Abi::from_str)?; + + Some(Target { arch, os, abi }) + } + + /// Returns the target for the current platform. + pub fn native() -> Target { + Target { + arch: Architecture::native(), + os: OperatingSystem::native(), + abi: Abi::native(), + } + } + + /// Returns a String describing the target using the LLVM triple format. + pub(crate) fn llvm_triple(&self) -> String { + let arch = match self.arch { + Architecture::Amd64 => "x86_64", + Architecture::Arm64 => "aarch64", + }; + + let os = match self.os { + OperatingSystem::Freebsd => "unknown-freebsd", + OperatingSystem::Mac => "apple-darwin", + OperatingSystem::Openbsd => "unknown-openbsd", + OperatingSystem::Linux => "unknown-linux-gnu", + }; + + format!("{}-{}", arch, os) + } + + pub(crate) fn arch_name(&self) -> &'static str { + match self.arch { + Architecture::Amd64 => "amd64", + Architecture::Arm64 => "arm64", + } + } + + pub(crate) fn os_name(&self) -> &'static str { + match self.os { + OperatingSystem::Freebsd => "freebsd", + OperatingSystem::Mac => "mac", + OperatingSystem::Openbsd => "openbsd", + OperatingSystem::Linux => "linux", + } + } + + pub(crate) fn abi_name(&self) -> &'static str { + match self.abi { + Abi::Native => match self.os { + OperatingSystem::Linux => "gnu", + _ => "native", + }, + Abi::Gnu => "gnu", + Abi::Msvc => "msvc", + } + } + + pub(crate) fn is_native(&self) -> bool { + self == &Target::native() + } +} + +impl fmt::Display for Target { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!( + fmt, + "{}-{}-{}", + self.arch_name(), + self.os_name(), + self.abi_name() + ) + } +} + +#[cfg(test)] +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!( + OperatingSystem::from_str("freebsd"), + Some(OperatingSystem::Freebsd) + ); + assert_eq!( + OperatingSystem::from_str("openbsd"), + Some(OperatingSystem::Openbsd) + ); + assert_eq!( + OperatingSystem::from_str("linux"), + Some(OperatingSystem::Linux) + ); + assert_eq!( + OperatingSystem::from_str("mac"), + Some(OperatingSystem::Mac) + ); + assert_eq!(OperatingSystem::from_str("bla"), None); + } + + #[test] + fn test_architecture_from_str() { + assert_eq!(Architecture::from_str("amd64"), Some(Architecture::Amd64)); + assert_eq!(Architecture::from_str("arm64"), Some(Architecture::Arm64)); + assert_eq!(Architecture::from_str("bla"), None); + } + + #[test] + fn test_target_from_str() { + assert_eq!( + Target::from_str("amd64-freebsd-native"), + Some(target( + Architecture::Amd64, + OperatingSystem::Freebsd, + Abi::Native + )) + ); + assert_eq!( + Target::from_str("arm64-linux-gnu"), + Some( + target(Architecture::Arm64, OperatingSystem::Linux, Abi::Gnu,) + ) + ); + + 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); + } + + #[test] + fn test_target_host() { + let target = Target::native(); + + assert_eq!(target.arch, Architecture::native()); + assert_eq!(target.os, OperatingSystem::native()); + } + + #[test] + fn test_target_llvm_triple() { + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Linux, Abi::Native) + .llvm_triple(), + "x86_64-unknown-linux-gnu" + ); + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native) + .llvm_triple(), + "x86_64-unknown-freebsd" + ); + assert_eq!( + target(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + .llvm_triple(), + "aarch64-apple-darwin" + ); + } + + #[test] + fn test_target_to_string() { + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Linux, Abi::Native) + .to_string(), + "amd64-linux-gnu" + ); + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native) + .to_string(), + "amd64-freebsd-native" + ); + assert_eq!( + target(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + .to_string(), + "arm64-mac-native" + ); + } + + #[test] + fn test_target_is_native() { + assert!(Target::native().is_native()); + } +} diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index 3c7723acf..c226a6364 100644 --- a/compiler/src/type_check/define_types.rs +++ b/compiler/src/type_check/define_types.rs @@ -3,15 +3,17 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; use crate::type_check::{ - CheckTypeSignature, DefineAndCheckTypeSignature, DefineTypeSignature, - Rules, TypeScope, + define_type_bounds, CheckTypeSignature, DefineAndCheckTypeSignature, + DefineTypeSignature, Rules, TypeScope, }; use std::path::PathBuf; +use types::check::TypeChecker; +use types::format::format_type; use types::{ - format_type, Class, ClassId, ClassInstance, ClassKind, Constant, Database, - ModuleId, Symbol, Trait, TraitId, TraitImplementation, TypeBounds, - TypeContext, TypeId, TypeParameter, TypeRef, Visibility, ENUM_TAG_FIELD, - ENUM_TAG_INDEX, FIELDS_LIMIT, MAIN_CLASS, VARIANTS_LIMIT, + Class, ClassId, ClassInstance, ClassKind, Constant, Database, ModuleId, + Symbol, Trait, TraitId, TraitImplementation, TypeId, TypeRef, Visibility, + ENUM_TAG_FIELD, ENUM_TAG_INDEX, FIELDS_LIMIT, MAIN_CLASS, OPTION_CLASS, + OPTION_MODULE, RESULT_CLASS, RESULT_MODULE, VARIANTS_LIMIT, }; /// The maximum number of members a single variant can store. We subtract one as @@ -64,7 +66,7 @@ impl<'a> DefineTypes<'a> { hir::ClassKind::Builtin => { if !self.module.is_std(self.db()) { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, "Builtin classes can only be defined in 'std' modules", self.file(), node.location.clone(), @@ -76,7 +78,7 @@ impl<'a> DefineTypes<'a> { id } else { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!("'{}' isn't a valid builtin class", name), self.file(), node.location.clone(), @@ -243,68 +245,22 @@ impl<'a> ImplementTraits<'a> { return; } - let mut bounds = TypeBounds::new(); - let rules = Rules::default(); - - for bound in &mut node.bounds { - let name = &bound.name.name; - - let param = - if let Some(id) = class_id.type_parameter(self.db(), name) { - id - } else { - self.state.diagnostics.undefined_symbol( - name, - self.file(), - bound.name.location.clone(), - ); - - continue; - }; - - if bounds.get(param).is_some() { - self.state.diagnostics.error( - DiagnosticId::InvalidBound, - format!( - "Bounds are already defined for type parameter '{}'", - name - ), - self.file(), - bound.location.clone(), - ); - - continue; - } - - let mut reqs = param.requirements(self.db()); - let scope = - TypeScope::new(self.module, TypeId::Class(class_id), None); - - let mut definer = DefineTypeSignature::new( - self.state, - self.module, - &scope, - rules, + if class_id.kind(self.db()).is_extern() { + self.state.diagnostics.error( + DiagnosticId::InvalidImplementation, + "Traits can't be implemented for extern classes", + self.file(), + node.location.clone(), ); - - for req in &mut bound.requirements { - if let Some(ins) = definer.as_trait_instance(req) { - reqs.push(ins); - } - } - - let name = param.name(self.db()).clone(); - let new_param = TypeParameter::alloc(self.db_mut(), name); - - new_param.add_requirements(self.db_mut(), reqs); - bounds.set(param, new_param); } - let class_ins = ClassInstance::for_instance_self_type( - self.db_mut(), + let bounds = define_type_bounds( + self.state, + self.module, class_id, - &bounds, + &mut node.bounds, ); + let class_ins = ClassInstance::rigid(self.db_mut(), class_id, &bounds); let scope = TypeScope::with_bounds( self.module, TypeId::ClassInstance(class_ins), @@ -312,6 +268,7 @@ impl<'a> ImplementTraits<'a> { &bounds, ); + let rules = Rules::default(); let mut definer = DefineTypeSignature::new(self.state, self.module, &scope, rules); @@ -451,13 +408,7 @@ impl<'a> CheckTraitImplementations<'a> { fn implement_trait(&mut self, node: &hir::ImplementTrait) { let class_ins = node.class_instance.unwrap(); let trait_ins = node.trait_instance.unwrap(); - let self_type = TypeId::ClassInstance(class_ins); - let mut checker = CheckTypeSignature::new( - self.state, - self.module, - self_type, - Rules::default(), - ); + let mut checker = CheckTypeSignature::new(self.state, self.module); checker.check_type_name(&node.trait_name); @@ -467,15 +418,10 @@ impl<'a> CheckTraitImplementations<'a> { } } - let mut context = TypeContext::new(self_type); - for req in trait_ins.instance_of().required_traits(self.db()) { - if !class_ins.type_check_with_trait_instance( - self.db_mut(), - req, - &mut context, - true, - ) { + let mut checker = TypeChecker::new(self.db()); + + if !checker.class_implements_trait(class_ins, req) { self.state.diagnostics.error( DiagnosticId::MissingTrait, format!( @@ -497,10 +443,31 @@ impl<'a> CheckTraitImplementations<'a> { fn db(&self) -> &Database { &self.state.db } +} - fn db_mut(&mut self) -> &mut Database { - &mut self.state.db - } +/// A compiler pass that defines the fields for the runtime's result type. +pub(crate) fn define_runtime_result_fields(state: &mut State) -> bool { + let class = ClassId::result(); + let module = class.module(&state.db); + + class.new_field( + &mut state.db, + "tag".to_string(), + 0, + TypeRef::int(), + Visibility::Public, + module, + ); + class.new_field( + &mut state.db, + "value".to_string(), + 1, + TypeRef::Any, + Visibility::Public, + module, + ); + + true } /// A compiler pass that defines the fields in a class. @@ -577,7 +544,7 @@ impl<'a> DefineFields<'a> { if id >= FIELDS_LIMIT { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!( "Classes can't define more than {} fields", FIELDS_LIMIT @@ -621,25 +588,9 @@ impl<'a> DefineFields<'a> { ) .define_type(&mut node.value_type); - match typ { - TypeRef::OwnedSelf | TypeRef::RefSelf => { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "'Self' can't be used here as it prevents \ - creating instances of '{}'", - format_type(self.db(), scope.self_type), - ), - self.file(), - node.value_type.location().clone(), - ); - } - _ => {} - } - if !class_id.is_public(self.db()) && vis == Visibility::Public { self.state.diagnostics.error( - DiagnosticId::InvalidField, + DiagnosticId::InvalidSymbol, "Public fields can't be defined for private types", self.file(), node.location.clone(), @@ -717,6 +668,10 @@ impl<'a> DefineTypeParameters<'a> { } else { let pid = id.new_type_parameter(self.db_mut(), name.clone()); + if param.mutable { + pid.set_mutable(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -737,6 +692,10 @@ impl<'a> DefineTypeParameters<'a> { } else { let pid = id.new_type_parameter(self.db_mut(), name.clone()); + if param.mutable { + pid.set_mutable(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -854,41 +813,18 @@ impl<'a> CheckTypeParameters<'a> { for expr in module.expressions.iter_mut() { match expr { hir::TopLevelExpression::Class(ref node) => { - self.check_class(node); + self.check_type_parameters(&node.type_parameters); } hir::TopLevelExpression::Trait(ref node) => { - self.check_trait(node); + self.check_type_parameters(&node.type_parameters); } _ => {} } } } - fn check_class(&mut self, node: &hir::DefineClass) { - let id = node.class_id.unwrap(); - let self_type = TypeId::Class(id); - - self.check_type_parameters(&node.type_parameters, self_type); - } - - fn check_trait(&mut self, node: &hir::DefineTrait) { - let id = node.trait_id.unwrap(); - let self_type = TypeId::Trait(id); - - self.check_type_parameters(&node.type_parameters, self_type); - } - - fn check_type_parameters( - &mut self, - nodes: &Vec, - self_type: TypeId, - ) { - let mut checker = CheckTypeSignature::new( - self.state, - self.module, - self_type, - Rules { allow_self_type: false, ..Default::default() }, - ); + fn check_type_parameters(&mut self, nodes: &Vec) { + let mut checker = CheckTypeSignature::new(self.state, self.module); for node in nodes { for req in &node.requirements { @@ -923,8 +859,15 @@ impl<'a> InsertPrelude<'a> { self.add_class(ClassId::boolean()); self.add_class(ClassId::nil()); self.add_class(ClassId::byte_array()); + self.add_class(ClassId::channel()); + + self.import_class(OPTION_MODULE, OPTION_CLASS); + + // $Result is used by try/throw to prevent name conflicts, while Result + // is meant to be used by developers. + self.import_class_as(RESULT_MODULE, RESULT_CLASS, "$Result"); + self.import_class(RESULT_MODULE, RESULT_CLASS); - self.import_class("std::option", "Option"); self.import_class("std::map", "Map"); self.import_method("std::process", "panic"); } @@ -945,6 +888,20 @@ impl<'a> InsertPrelude<'a> { self.add_class(id); } + fn import_class_as(&mut self, module: &str, class: &str, alias: &str) { + let id = self.state.db.class_in_module(module, class); + + if self.module.symbol_exists(self.db(), alias) { + return; + } + + self.module.new_symbol( + self.db_mut(), + alias.to_string(), + Symbol::Class(id), + ); + } + fn import_method(&mut self, module: &str, method: &str) { let mod_id = self.state.db.module(module); let method_id = if let Some(id) = mod_id.method(self.db(), method) { @@ -1140,7 +1097,7 @@ mod tests { use ast::parser::Parser; use std::fmt::Write as _; use types::module_name::ModuleName; - use types::{ClassId, ConstantId, TraitId, TraitInstance}; + use types::{ClassId, ConstantId, TraitId, TraitInstance, TypeBounds}; fn get_trait(db: &Database, module: ModuleId, name: &str) -> TraitId { if let Some(Symbol::Trait(id)) = module.symbol(db, name) { @@ -1204,7 +1161,7 @@ mod tests { assert!(DefineTypes::run_all(&mut state, &mut modules)); - let id = ClassId(17); + let id = ClassId(18); assert_eq!(state.diagnostics.iter().count(), 0); assert_eq!(class_expr(&modules[0]).class_id, Some(id)); @@ -1224,7 +1181,7 @@ mod tests { assert!(DefineTypes::run_all(&mut state, &mut modules)); - let id = ClassId(17); + let id = ClassId(18); assert_eq!(state.diagnostics.iter().count(), 0); assert_eq!(class_expr(&modules[0]).class_id, Some(id)); @@ -1739,7 +1696,7 @@ mod tests { let diag = state.diagnostics.iter().next().unwrap(); - assert_eq!(diag.id(), DiagnosticId::InvalidClass); + assert_eq!(diag.id(), DiagnosticId::InvalidType); } #[test] @@ -1753,7 +1710,7 @@ mod tests { let error = state.diagnostics.iter().next().unwrap(); - assert_eq!(error.id(), DiagnosticId::InvalidType); + assert_eq!(error.id(), DiagnosticId::InvalidSymbol); assert_eq!(error.location(), &cols(27, 30)); } diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs index d32656e54..4cadffb45 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -7,25 +7,19 @@ use ast::source_location::SourceLocation; use std::cell::Cell; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; +use types::check::{Environment, TypeChecker}; +use types::format::{format_type, format_type_with_arguments}; +use types::resolve::TypeResolver; use types::{ - format_type, format_type_with_context, format_type_with_self, Block, - BuiltinCallInfo, BuiltinFunctionKind, CallInfo, CallKind, ClassId, - ClassInstance, Closure, ClosureCallInfo, ClosureId, CompilerMacro, - ConstantKind, ConstantPatternKind, Database, FieldId, FieldInfo, - IdentifierKind, MethodId, MethodLookup, MethodSource, ModuleId, Receiver, - Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeContext, - TypeId, TypeRef, Variable, VariableId, CALL_METHOD, STRING_MODULE, - TO_STRING_TRAIT, + Block, BuiltinCallInfo, CallInfo, CallKind, ClassId, ClassInstance, + Closure, ClosureCallInfo, ClosureId, ConstantKind, ConstantPatternKind, + Database, FieldId, FieldInfo, IdentifierKind, MethodId, MethodKind, + MethodLookup, MethodSource, ModuleId, Receiver, Symbol, ThrowKind, TraitId, + TraitInstance, TypeArguments, TypeBounds, TypeId, TypeRef, Variable, + VariableId, CALL_METHOD, }; const IGNORE_VARIABLE: &str = "_"; - -const INDEX_METHOD: &str = "index"; -const INDEX_MUT_METHOD: &str = "index_mut"; -const INDEX_MODULE: &str = "std::index"; -const INDEX_TRAIT: &str = "Index"; -const INDEX_MUT_TRAIT: &str = "IndexMut"; - const STRING_LITERAL_LIMIT: usize = u32::MAX as usize; const CONST_ARRAY_LIMIT: usize = u16::MAX as usize; @@ -50,7 +44,7 @@ impl<'a> Pattern<'a> { } /// A collection of variables defined in a lexical scope. -pub struct VariableScope { +struct VariableScope { /// The variables defined in this scope. variables: HashMap, } @@ -98,9 +92,6 @@ enum ScopeKind { struct LexicalScope<'a> { kind: ScopeKind, - /// The throw type of the surrounding block. - throw_type: TypeRef, - /// The return type of the surrounding block. return_type: TypeRef, @@ -134,17 +125,12 @@ struct LexicalScope<'a> { } impl<'a> LexicalScope<'a> { - fn method( - self_type: TypeRef, - return_type: TypeRef, - throw_type: TypeRef, - ) -> Self { + fn method(self_type: TypeRef, return_type: TypeRef) -> Self { Self { kind: ScopeKind::Method, variables: VariableScope::new(), surrounding_type: self_type, return_type, - throw_type, parent: None, in_closure: false, break_in_loop: Cell::new(false), @@ -155,7 +141,6 @@ impl<'a> LexicalScope<'a> { Self { kind, surrounding_type: self.surrounding_type, - throw_type: self.throw_type, return_type: self.return_type, variables: VariableScope::new(), parent: Some(self), @@ -233,8 +218,14 @@ struct MethodCall { /// The method that's called. method: MethodId, - /// A context for type-checking and substituting types. - context: TypeContext, + /// The base type arguments to use for type-checking. + type_arguments: TypeArguments, + + /// A union of the type bounds of the surrounding and the called method. + /// + /// These bounds are to be used when inferring types, such as the return + /// type. + bounds: TypeBounds, /// The type of the method's receiver. receiver: TypeRef, @@ -247,32 +238,51 @@ struct MethodCall { /// If input/output types should be limited to sendable types. require_sendable: bool, + + /// Arguments of which we need to check if they are sendable. + check_sendable: Vec<(TypeRef, SourceLocation)>, + + /// The resolved return type of the call. + return_type: TypeRef, } impl MethodCall { fn new( - state: &State, + state: &mut State, module: ModuleId, + surrounding_scope: Option<(TypeId, MethodId)>, receiver: TypeRef, receiver_id: TypeId, method: MethodId, ) -> Self { - let mut args = TypeArguments::new(); - - // The method call needs access to the type arguments of the receiver. - // So given a `pop -> T` method for `Array[Int]`, we want to be able to - // map `T` to `Int`. Since a TypeContext only has a single - // `TypeArguments` structure (to simplify lookups), we copy the - // arguments of the receiver into this temporary collection of - // arguments. - match receiver_id { - TypeId::ClassInstance(ins) => { - ins.copy_type_arguments_into(&state.db, &mut args); - } - TypeId::TraitInstance(ins) => { - ins.copy_type_arguments_into(&state.db, &mut args); + // When checking arguments we need access to the type arguments of the + // receiver, along with any type arguments introduced by the method + // itself. + let mut type_arguments = receiver.type_arguments(&state.db); + + // Type parameters may be reused between arguments and throw/return + // types, so we need to ensure all references resolve into the same + // types, hence we create type placeholders here. + for param in method.type_parameters(&state.db).into_iter() { + type_arguments.assign( + param, + TypeRef::placeholder(&mut state.db, Some(param)), + ); + } + + // Static methods may use/return type parameters of the surrounding + // type, so we also need to create placeholders for those. + if method.kind(&state.db) == MethodKind::Static { + if let TypeId::Class(class) = receiver_id { + if class.is_generic(&state.db) { + for param in class.type_parameters(&state.db) { + type_arguments.assign( + param, + TypeRef::placeholder(&mut state.db, Some(param)), + ); + } + } } - _ => {} } // When a method is implemented through a trait, it may depend on type @@ -301,59 +311,56 @@ impl MethodCall { // We only need to do this when the receiver is a class (and thus the // method has a source). If the receiver is a trait, we'll end up using // its inherited type arguments when inferring a type parameter. - match method.source(&state.db) { - MethodSource::BoundedImplementation(ins) - | MethodSource::Implementation(ins) => { - ins.copy_type_arguments_into(&state.db, &mut args); - } - _ => {} + if let MethodSource::Implementation(ins, _) = method.source(&state.db) { + ins.copy_type_arguments_into(&state.db, &mut type_arguments); } let require_sendable = receiver.require_sendable_arguments(&state.db) && !method.is_moving(&state.db); + let bounds = if let Some((self_id, self_method)) = surrounding_scope { + // When calling a method on `self`, we need to take any surrounding + // bounds into account when resolving types. + if self_id == receiver_id { + self_method.bounds(&state.db).union(method.bounds(&state.db)) + } else { + self_method.bounds(&state.db).clone() + } + } else { + TypeBounds::new() + }; + Self { module, method, + bounds, receiver, - context: TypeContext::with_arguments(receiver_id, args), + type_arguments, arguments: 0, named_arguments: HashSet::new(), require_sendable, + check_sendable: Vec::new(), + return_type: TypeRef::Unknown, } } - fn check_bounded_implementation( + fn check_type_bounds( &mut self, state: &mut State, location: &SourceLocation, ) { - if let MethodSource::BoundedImplementation(trait_ins) = - self.method.source(&state.db) - { - if self.receiver_id().implements_trait_instance( - &mut state.db, - trait_ins, - &mut self.context, - ) { - return; - } - - let method_name = self.method.name(&state.db).clone(); - let rec_name = format_type_with_context( - &state.db, - &self.context, - self.receiver_id(), - ); - let trait_name = - format_type_with_context(&state.db, &self.context, trait_ins); + let bounds = self.method.bounds(&state.db); + let args = self.type_arguments.clone(); + let mut scope = Environment::new(args.clone(), args); + let mut checker = TypeChecker::new(&state.db); + if !checker.check_bounds(bounds, &mut scope) { state.diagnostics.error( DiagnosticId::InvalidSymbol, format!( - "The method '{}' exists for type '{}', \ - but requires this type to implement trait '{}'", - method_name, rec_name, trait_name + "The method '{}' exists but isn't available because \ + one or more type parameter bounds aren't met", + self.method.name(&state.db), ), self.module.file(&state.db), location.clone(), @@ -361,23 +368,21 @@ impl MethodCall { } } - fn check_argument_count( + fn check_arguments( &mut self, state: &mut State, location: &SourceLocation, ) { let expected = self.method.number_of_arguments(&state.db); - if self.arguments == expected { - return; + if self.arguments != expected { + state.diagnostics.incorrect_call_arguments( + self.arguments, + expected, + self.module.file(&state.db), + location.clone(), + ); } - - state.diagnostics.incorrect_call_arguments( - self.arguments, - expected, - self.module.file(&state.db), - location.clone(), - ); } fn check_mutability( @@ -395,7 +400,11 @@ impl MethodCall { "The method '{}' takes ownership of its receiver, \ but '{}' isn't an owned value", name, - format_type_with_context(&state.db, &self.context, rec) + format_type_with_arguments( + &state.db, + &self.type_arguments, + rec + ) ), self.module.file(&state.db), location.clone(), @@ -411,7 +420,11 @@ impl MethodCall { "The method '{}' requires a mutable receiver, \ but '{}' isn't mutable", name, - format_type_with_context(&state.db, &self.context, rec) + format_type_with_arguments( + &state.db, + &self.type_arguments, + rec + ) ), self.module.file(&state.db), location.clone(), @@ -428,143 +441,90 @@ impl MethodCall { ) { let given = argument.cast_according_to(expected, &state.db); - if self.require_sendable && !given.is_sendable(&state.db) { - state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "The receiver ('{}') of this call requires sendable \ - arguments, but '{}' isn't sendable", - format_type_with_context( - &state.db, - &self.context, - self.receiver - ), - format_type_with_context(&state.db, &self.context, given), - ), + if self.require_sendable { + self.check_sendable.push((given, location.clone())); + } + + let mut scope = Environment::new( + given.type_arguments(&state.db), + self.type_arguments.clone(), + ); + + if !TypeChecker::new(&state.db).run(given, expected, &mut scope) { + state.diagnostics.type_error( + format_type_with_arguments(&state.db, &scope.left, given), + format_type_with_arguments(&state.db, &scope.right, expected), self.module.file(&state.db), location.clone(), ); } + } - if given.type_check(&mut state.db, expected, &mut self.context, true) { + fn check_sendable(&mut self, state: &mut State, location: &SourceLocation) { + if !self.require_sendable { return; } - state.diagnostics.type_error( - format_type_with_context(&state.db, &self.context, given), - format_type_with_context(&state.db, &self.context, expected), - self.module.file(&state.db), - location.clone(), - ); - } + // It's safe to pass `ref T` as an argument if all arguments and `self` + // are immutable, as this prevents storing of the `ref T` in `self`, + // thus violating the uniqueness constraints. + let ref_safe = self.method.is_immutable(&state.db) + && self.check_sendable.iter().all(|(typ, _)| { + typ.is_sendable(&state.db) || typ.is_ref(&state.db) + }); - fn update_receiver_type_arguments( - &mut self, - state: &mut State, - surrounding_type: TypeRef, - ) { - // We don't update the type arguments of `self`, as that messes up - // future method calls acting on the same type. - match surrounding_type { - TypeRef::Owned(id) | TypeRef::Ref(id) - if id == self.receiver_id() => + for (given, loc) in &self.check_sendable { + if given.is_sendable(&state.db) + || (given.is_ref(&state.db) && ref_safe) { - return; + continue; } - TypeRef::OwnedSelf | TypeRef::RefSelf => return, - _ => {} - } - - let db = &mut state.db; - let args = &self.context.type_arguments; - // As part of the method call we use a temporary collection of type - // arguments. Once the call is done, newly assigned type parameters that - // belong to the receiver need to be copied into the receiver's type - // arguments. This ensures that future method calls observe the newly - // assigned type parameters. - match self.receiver_id() { - TypeId::ClassInstance(ins) => ins.copy_new_arguments_from(db, args), - TypeId::TraitInstance(ins) => ins.copy_new_arguments_from(db, args), - _ => {} - } - } - - fn throw_type( - &mut self, - state: &mut State, - location: &SourceLocation, - ) -> TypeRef { - let typ = self.method.throw_type(&state.db).inferred( - &mut state.db, - &mut self.context, - false, - ); - - if !self.output_type_is_sendable(state, typ) { - let name = self.method.name(&state.db); + let targs = &self.type_arguments; - state.diagnostics.unsendable_throw_type( - name, - format_type_with_context(&state.db, &self.context, typ), + state.diagnostics.unsendable_argument( + format_type_with_arguments(&state.db, targs, *given), self.module.file(&state.db), - location.clone(), + loc.clone(), ); } - typ - } - - fn return_type( - &mut self, - state: &mut State, - location: &SourceLocation, - ) -> TypeRef { - let typ = self.method.return_type(&state.db).inferred( - &mut state.db, - &mut self.context, - false, - ); - - if !self.output_type_is_sendable(state, typ) { - let name = self.method.name(&state.db); + // If `self` and all arguments are immutable, we allow owned return + // values provided they don't contain any references. This is safe + // because `self` can't have references to it (because it's immutable), + // we can't "leak" a reference through the arguments (because they too + // are immutable), and the returned value can't refer to `self` because + // we don't allow references anywhere in the type or its sub types. + let ret_sendable = if ref_safe { + self.return_type.is_sendable_output(&state.db) + } else { + self.return_type.is_sendable(&state.db) + }; + if !ret_sendable { state.diagnostics.unsendable_return_type( - name, - format_type_with_context(&state.db, &self.context, typ), + format_type_with_arguments( + &state.db, + &self.type_arguments, + self.return_type, + ), self.module.file(&state.db), location.clone(), ); } - - typ - } - - fn receiver_id(&self) -> TypeId { - self.context.self_type } - fn output_type_is_sendable(&self, state: &State, typ: TypeRef) -> bool { - if !self.require_sendable { - return true; - } + fn resolve_return_type(&mut self, state: &mut State) -> TypeRef { + let raw = self.method.return_type(&state.db); + let typ = TypeResolver::new( + &mut state.db, + &self.type_arguments, + &self.bounds, + ) + .resolve(raw); - // If a method is immutable and doesn't define any arguments, any - // returned or thrown value that is sendable must have been created as - // part of the call, and thus can't have any outside references pointing - // to it. This allows such methods to return/throw owned values, while - // still allowing the use of such methods on unique receivers. - // - // Note that this check still enforces sendable sub values, meaning it's - // invalid to return e.g. `Array[ref Thing]`, as `ref Thing` isn't - // sendable. - if self.method.number_of_arguments(&state.db) == 0 - && self.method.is_immutable(&state.db) - { - typ.is_sendable_output(&state.db) - } else { - typ.is_sendable(&state.db) - } + self.return_type = typ; + typ } } @@ -577,19 +537,36 @@ pub(crate) struct DefineConstants<'a> { impl<'a> DefineConstants<'a> { pub(crate) fn run_all( state: &'a mut State, - modules: &mut Vec, + modules: &mut [hir::Module], ) -> bool { - for module in modules { - DefineConstants { state, module: module.module_id }.run(module); + // Regular constants must be defined first such that complex constants + // (e.g. `A + B` or `[A, B]`) can refer to them, regardless of the order + // in which modules are processed. + for module in modules.iter_mut() { + DefineConstants { state, module: module.module_id } + .run(module, true); + } + + for module in modules.iter_mut() { + DefineConstants { state, module: module.module_id } + .run(module, false); } !state.diagnostics.has_errors() } - fn run(mut self, module: &mut hir::Module) { + fn run(mut self, module: &mut hir::Module, simple_only: bool) { for expression in module.expressions.iter_mut() { - if let hir::TopLevelExpression::Constant(ref mut n) = expression { - self.define_constant(n); + let node = if let hir::TopLevelExpression::Constant(ref mut node) = + expression + { + node + } else { + continue; + }; + + if node.value.is_simple_literal() == simple_only { + self.define_constant(node); } } } @@ -649,13 +626,12 @@ impl<'a> Expressions<'a> { } fn define_class(&mut self, node: &mut hir::DefineClass) { - let bounds = TypeBounds::new(); let id = node.class_id.unwrap(); let num_methods = id.number_of_methods(self.db()); if num_methods > METHODS_IN_CLASS_LIMIT { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!( "The number of methods defined in this class ({}) \ exceeds the maximum of {} methods", @@ -674,7 +650,7 @@ impl<'a> Expressions<'a> { self.define_async_method(n); } hir::ClassExpression::InstanceMethod(ref mut n) => { - self.define_instance_method(n, &bounds); + self.define_instance_method(n); } hir::ClassExpression::StaticMethod(ref mut n) => { self.define_static_method(n); @@ -685,12 +661,10 @@ impl<'a> Expressions<'a> { } fn reopen_class(&mut self, node: &mut hir::ReopenClass) { - let bounds = TypeBounds::new(); - for node in &mut node.body { match node { hir::ReopenClassExpression::InstanceMethod(ref mut n) => { - self.define_instance_method(n, &bounds) + self.define_instance_method(n) } hir::ReopenClassExpression::StaticMethod(ref mut n) => { self.define_static_method(n) @@ -703,8 +677,6 @@ impl<'a> Expressions<'a> { } fn define_trait(&mut self, node: &mut hir::DefineTrait) { - let bounds = TypeBounds::new(); - self.verify_type_parameter_requirements(&node.type_parameters); self.verify_required_traits( &node.requirements, @@ -713,34 +685,25 @@ impl<'a> Expressions<'a> { for node in &mut node.body { if let hir::TraitExpression::InstanceMethod(ref mut n) = node { - self.define_instance_method(n, &bounds); + self.define_instance_method(n); } } } fn implement_trait(&mut self, node: &mut hir::ImplementTrait) { - let class_id = node.class_instance.unwrap().instance_of(); - let trait_id = node.trait_instance.unwrap().instance_of(); - let bounds = class_id - .trait_implementation(self.db(), trait_id) - .map(|i| i.bounds.clone()) - .unwrap(); - for n in &mut node.body { - self.define_instance_method(n, &bounds); + self.define_instance_method(n); } } fn define_module_method(&mut self, node: &mut hir::DefineModuleMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let bounds = TypeBounds::new(); let returns = method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -762,23 +725,16 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } - fn define_instance_method( - &mut self, - node: &mut hir::DefineInstanceMethod, - bounds: &TypeBounds, - ) { + fn define_instance_method(&mut self, node: &mut hir::DefineInstanceMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let bounds = method.bounds(self.db()).clone(); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let returns = - method.return_type(self.db()).as_rigid_type(self.db_mut(), bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -786,14 +742,14 @@ impl<'a> Expressions<'a> { scope.variables.add_variable(arg.name, arg.variable); } - self.define_field_types(receiver, method, bounds); + self.define_field_types(receiver, method, &bounds); let mut checker = CheckMethodBody::new( self.state, self.module, method, stype, - bounds, + &bounds, ); checker.expressions_with_return( @@ -802,20 +758,15 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } fn define_async_method(&mut self, node: &mut hir::DefineAsyncMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let bounds = TypeBounds::new(); - let returns = - method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + let returns = TypeRef::nil(); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -839,20 +790,16 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } fn define_static_method(&mut self, node: &mut hir::DefineStaticMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let bounds = TypeBounds::new(); let returns = method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -874,8 +821,6 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } fn define_field_types( @@ -886,9 +831,11 @@ impl<'a> Expressions<'a> { ) { for field in receiver.fields(self.db()) { let name = field.name(self.db()).clone(); - let typ = field - .value_type(self.db()) - .as_rigid_type(self.db_mut(), bounds); + let raw_type = field.value_type(self.db()); + let args = TypeArguments::new(); + let typ = TypeResolver::new(self.db_mut(), &args, bounds) + .with_rigid(true) + .resolve(raw_type); method.set_field_type(self.db_mut(), name, field, typ); } @@ -969,14 +916,11 @@ impl<'a> Expressions<'a> { struct CheckConstant<'a> { state: &'a mut State, module: ModuleId, - module_type: TypeRef, } impl<'a> CheckConstant<'a> { fn new(state: &'a mut State, module: ModuleId) -> Self { - let module_type = TypeRef::Owned(TypeId::Module(module)); - - Self { state, module, module_type } + Self { state, module } } fn expression(&mut self, node: &mut hir::ConstExpression) -> TypeRef { @@ -1028,17 +972,23 @@ impl<'a> CheckConstant<'a> { return TypeRef::Error; }; - let mod_type = self.module_type; - let mut call = - MethodCall::new(self.state, self.module, left, left_id, method); + let mut call = MethodCall::new( + self.state, + self.module, + None, + left, + left_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); self.positional_argument(&mut call, &mut node.right); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, mod_type); + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - node.resolved_type = call.return_type(self.state, &node.location); + node.resolved_type = call.return_type; node.resolved_type } @@ -1078,13 +1028,18 @@ impl<'a> CheckConstant<'a> { TypeRef::Error } _ => { - self.state.diagnostics.undefined_symbol( - name, - self.file(), - node.location.clone(), - ); + if let Some(cons) = self.db().builtin_constant(name) { + node.kind = ConstantKind::Builtin(cons); + cons.return_type() + } else { + self.state.diagnostics.undefined_symbol( + name, + self.file(), + node.location.clone(), + ); - TypeRef::Error + TypeRef::Error + } } } } @@ -1097,15 +1052,13 @@ impl<'a> CheckConstant<'a> { .collect::>(); if types.len() > 1 { - let stype = TypeId::Module(self.module); let &first = types.first().unwrap(); - let mut ctx = TypeContext::new(stype); for (&typ, node) in types[1..].iter().zip(node.values[1..].iter()) { - if !typ.type_check(self.db_mut(), first, &mut ctx, true) { + if !TypeChecker::check(self.db(), typ, first) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, typ), - format_type_with_context(self.db(), &ctx, first), + format_type(self.db(), typ), + format_type(self.db(), first), self.file(), node.location().clone(), ); @@ -1128,11 +1081,7 @@ impl<'a> CheckConstant<'a> { // Mutating constant arrays isn't safe, so they're typed as `ref // Array[T]` instead of `Array[T]`. let ary = TypeRef::Ref(TypeId::ClassInstance( - ClassInstance::generic_with_types( - self.db_mut(), - ClassId::array(), - types, - ), + ClassInstance::with_types(self.db_mut(), ClassId::array(), types), )); node.resolved_type = ary; @@ -1145,14 +1094,13 @@ impl<'a> CheckConstant<'a> { name: &str, location: &SourceLocation, ) -> Option<(TypeId, MethodId)> { - let stype = TypeId::Module(self.module); - let rec_id = match receiver.type_id(self.db(), stype) { + let rec_id = match receiver.type_id(self.db()) { Ok(id) => id, Err(TypeRef::Error) => return None, Err(typ) => { self.state.diagnostics.undefined_method( name, - format_type_with_self(self.db(), stype, typ), + format_type(self.db(), typ), self.file(), location.clone(), ); @@ -1173,7 +1121,7 @@ impl<'a> CheckConstant<'a> { MethodLookup::InstanceOnStatic => { self.state.diagnostics.invalid_instance_call( name, - format_type_with_self(self.db(), stype, receiver), + format_type(self.db(), receiver), self.file(), location.clone(), ); @@ -1181,7 +1129,7 @@ impl<'a> CheckConstant<'a> { MethodLookup::StaticOnInstance => { self.state.diagnostics.invalid_static_call( name, - format_type_with_self(self.db(), stype, receiver), + format_type(self.db(), receiver), self.file(), location.clone(), ); @@ -1189,7 +1137,7 @@ impl<'a> CheckConstant<'a> { MethodLookup::None => { self.state.diagnostics.undefined_method( name, - format_type_with_self(self.db(), stype, receiver), + format_type(self.db(), receiver), self.file(), location.clone(), ); @@ -1238,14 +1186,11 @@ struct CheckMethodBody<'a> { /// The surrounding method. method: MethodId, - /// The type for `Self`, excluding ownership. + /// The type ID of the receiver of the surrounding method. self_type: TypeId, /// Any bounds to apply to type parameters. bounds: &'a TypeBounds, - - /// If a value is thrown from this body. - thrown: bool, } impl<'a> CheckMethodBody<'a> { @@ -1256,23 +1201,7 @@ impl<'a> CheckMethodBody<'a> { self_type: TypeId, bounds: &'a TypeBounds, ) -> Self { - Self { state, module, method, self_type, bounds, thrown: false } - } - - fn check_if_throws( - &mut self, - expected: TypeRef, - location: &SourceLocation, - ) { - if !expected.is_present(self.db()) || self.thrown { - return; - } - - self.state.diagnostics.missing_throw( - self.fmt(expected), - self.file(), - location.clone(), - ); + Self { state, module, method, self_type, bounds } } fn expressions( @@ -1310,21 +1239,20 @@ impl<'a> CheckMethodBody<'a> { fallback_location: &SourceLocation, ) { let typ = self.last_expression_type(nodes, scope); - let mut ctx = TypeContext::new(self.self_type); - if returns.is_nil(self.db(), self.self_type) { + if returns.is_nil(self.db()) { // When the return type is `Nil` (explicit or not), we just ignore // whatever value is returned. return; } - if !typ.type_check(self.db_mut(), returns, &mut ctx, true) { + if !TypeChecker::check(self.db(), typ, returns) { let loc = nodes.last().map(|n| n.location()).unwrap_or(fallback_location); self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, typ), - format_type_with_context(self.db(), &ctx, returns), + format_type(self.db(), typ), + format_type(self.db(), returns), self.file(), loc.clone(), ); @@ -1354,7 +1282,6 @@ impl<'a> CheckMethodBody<'a> { hir::Expression::ReplaceVariable(ref mut n) => { self.replace_variable(n, scope) } - hir::Expression::AsyncCall(ref mut n) => self.async_call(n, scope), hir::Expression::Break(ref n) => self.break_expression(n, scope), hir::Expression::BuiltinCall(ref mut n) => { self.builtin_call(n, scope) @@ -1377,7 +1304,6 @@ impl<'a> CheckMethodBody<'a> { self.class_literal(n, scope) } hir::Expression::Int(ref mut n) => self.int_literal(n, scope), - hir::Expression::Invalid(_) => TypeRef::Error, hir::Expression::Loop(ref mut n) => self.loop_expression(n, scope), hir::Expression::Match(ref mut n) => { self.match_expression(n, scope) @@ -1404,9 +1330,7 @@ impl<'a> CheckMethodBody<'a> { hir::Expression::Nil(ref mut n) => self.nil_literal(n), hir::Expression::Tuple(ref mut n) => self.tuple_literal(n, scope), hir::Expression::TypeCast(ref mut n) => self.type_cast(n, scope), - hir::Expression::Index(ref mut n) => { - self.index_expression(n, scope) - } + hir::Expression::Try(ref mut n) => self.try_expression(n, scope), } } @@ -1417,11 +1341,17 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let typ = self.expression(node, scope); + if typ.is_uni(self.db()) { + // This ensures that value types such as `uni T` aren't implicitly + // converted to `T`. + return typ; + } + if typ.is_value_type(self.db()) { - typ.as_owned(self.db()) - } else { - typ + return typ.as_owned(self.db()); } + + typ } fn argument_expression( @@ -1429,13 +1359,13 @@ impl<'a> CheckMethodBody<'a> { expected_type: TypeRef, node: &mut hir::Expression, scope: &mut LexicalScope, - context: &mut TypeContext, + type_arguments: &TypeArguments, ) -> TypeRef { match node { hir::Expression::Closure(ref mut n) => { let expected = expected_type - .closure_id(self.db(), self.self_type) - .map(|f| (f, expected_type, context)); + .closure_id(self.db()) + .map(|f| (f, expected_type, type_arguments)); self.closure(n, expected, scope) } @@ -1485,17 +1415,14 @@ impl<'a> CheckMethodBody<'a> { match value { hir::StringValue::Expression(v) => { let val = self.call(v, scope); - let stype = self.self_type; - if val != TypeRef::Error - && !val.is_string(self.db(), self.self_type) - { + if val != TypeRef::Error && !val.is_string(self.db()) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "Expected a 'String', 'ref String' or \ 'mut String', found '{}' instead", - format_type_with_self(self.db(), stype, val) + format_type(self.db(), val) ), self.file(), v.location.clone(), @@ -1527,13 +1454,12 @@ impl<'a> CheckMethodBody<'a> { if types.len() > 1 { let &first = types.first().unwrap(); - let mut ctx = TypeContext::new(self.self_type); for (&typ, node) in types[1..].iter().zip(node.values[1..].iter()) { - if !typ.type_check(self.db_mut(), first, &mut ctx, true) { + if !TypeChecker::check(self.db(), typ, first) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, typ), - format_type_with_context(self.db(), &ctx, first), + format_type(self.db(), typ), + format_type(self.db(), first), self.file(), node.location().clone(), ); @@ -1552,11 +1478,8 @@ impl<'a> CheckMethodBody<'a> { ); } - let ins = ClassInstance::generic_with_types( - self.db_mut(), - ClassId::array(), - types, - ); + let ins = + ClassInstance::with_types(self.db_mut(), ClassId::array(), types); let ary = TypeRef::Owned(TypeId::ClassInstance(ins)); node.value_type = @@ -1582,11 +1505,7 @@ impl<'a> CheckMethodBody<'a> { }; let tuple = TypeRef::Owned(TypeId::ClassInstance( - ClassInstance::generic_with_types( - self.db_mut(), - class, - types.clone(), - ), + ClassInstance::with_types(self.db_mut(), class, types.clone()), )); node.class_id = Some(class); @@ -1633,14 +1552,7 @@ impl<'a> CheckMethodBody<'a> { }; let require_send = class.kind(self.db()).is_async(); - let ins = if class.is_generic(self.db()) { - ClassInstance::generic_with_placeholders(self.db_mut(), class) - } else { - ClassInstance::new(class) - }; - - let mut ctx = - TypeContext::for_class_instance(self.db(), self.self_type, ins); + let ins = ClassInstance::empty(self.db_mut(), class); let mut assigned = HashSet::new(); for field in &mut node.fields { @@ -1662,7 +1574,7 @@ impl<'a> CheckMethodBody<'a> { && class.module(self.db()) != self.module { self.state.diagnostics.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!("The field '{}' is private", name), self.file(), node.location.clone(), @@ -1672,12 +1584,16 @@ impl<'a> CheckMethodBody<'a> { let expected = field_id.value_type(self.db()); let value = self.expression(&mut field.value, scope); let value_casted = value.cast_according_to(expected, self.db()); + let checker = TypeChecker::new(self.db()); + let mut env = Environment::new( + value_casted.type_arguments(self.db()), + ins.type_arguments(self.db()).clone(), + ); - if !value_casted.type_check(self.db_mut(), expected, &mut ctx, true) - { + if !checker.run(value_casted, expected, &mut env) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, value), - format_type_with_context(self.db(), &ctx, expected), + format_type_with_arguments(self.db(), &env.left, value), + format_type_with_arguments(self.db(), &env.right, expected), self.file(), field.value.location().clone(), ); @@ -1686,7 +1602,7 @@ impl<'a> CheckMethodBody<'a> { if require_send && !value.is_sendable(self.db()) { self.state.diagnostics.unsendable_field_value( name, - format_type_with_context(self.db(), &ctx, value), + format_type(self.db(), value), self.file(), field.value.location().clone(), ); @@ -1720,8 +1636,6 @@ impl<'a> CheckMethodBody<'a> { ); } - ins.copy_new_arguments_from(self.db_mut(), &ctx.type_arguments); - node.class_id = Some(class); node.resolved_type = TypeRef::Owned(TypeId::ClassInstance(ins)); node.resolved_type @@ -1783,7 +1697,7 @@ impl<'a> CheckMethodBody<'a> { if !value_type.allow_assignment(self.db()) { self.state.diagnostics.cant_assign_type( - &format_type_with_self(self.db(), self.self_type, value_type), + &format_type(self.db(), value_type), self.file(), node.value.location().clone(), ); @@ -1791,23 +1705,15 @@ impl<'a> CheckMethodBody<'a> { let var_type = if let Some(tnode) = node.value_type.as_mut() { let exp_type = self.type_signature(tnode, self.self_type); - let mut typ_ctx = TypeContext::new(self.self_type); let value_casted = value_type.cast_according_to(exp_type, self.db()); - if !value_casted.type_check( - self.db_mut(), - exp_type, - &mut typ_ctx, - true, - ) { - let stype = self.self_type; - + if !TypeChecker::check(self.db(), value_casted, exp_type) { self.state.diagnostics.type_error( - format_type_with_self(self.db(), stype, value_type), - format_type_with_self(self.db(), stype, exp_type), + format_type(self.db(), value_type), + format_type(self.db(), exp_type), self.file(), - node.location.clone(), + node.value.location().clone(), ); } @@ -1887,19 +1793,11 @@ impl<'a> CheckMethodBody<'a> { ) { let var_type = if let Some(tnode) = node.value_type.as_mut() { let exp_type = self.type_signature(tnode, self.self_type); - let mut typ_ctx = TypeContext::new(self.self_type); - - if !value_type.type_check( - self.db_mut(), - exp_type, - &mut typ_ctx, - true, - ) { - let stype = self.self_type; + if !TypeChecker::check(self.db(), value_type, exp_type) { self.state.diagnostics.pattern_type_error( - format_type_with_self(self.db(), stype, value_type), - format_type_with_self(self.db(), stype, exp_type), + format_type(self.db(), value_type), + format_type(self.db(), exp_type), self.file(), node.location.clone(), ); @@ -1925,17 +1823,16 @@ impl<'a> CheckMethodBody<'a> { } if let Some(existing) = pattern.variable_scope.variable(&name) { - let mut ctx = TypeContext::new(self.self_type); let ex_type = existing.value_type(self.db()); - if !var_type.type_check(self.db_mut(), ex_type, &mut ctx, true) { + if !TypeChecker::check(self.db(), var_type, ex_type) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "The type of this variable is defined as '{}' \ in another pattern, but here its type is '{}'", - format_type_with_context(self.db(), &ctx, ex_type), - format_type_with_context(self.db(), &ctx, var_type), + format_type(self.db(), ex_type), + format_type(self.db(), var_type), ), self.file(), node.location.clone(), @@ -1977,20 +1874,14 @@ impl<'a> CheckMethodBody<'a> { ) { let name = &node.name; - if let Some(ins) = - value_type.as_enum_instance(self.db(), self.self_type) - { + if let Some(ins) = value_type.as_enum_instance(self.db()) { let variant = if let Some(v) = ins.instance_of().variant(self.db(), name) { v } else { self.state.diagnostics.undefined_variant( name, - format_type_with_self( - self.db(), - self.self_type, - value_type, - ), + format_type(self.db(), value_type), self.file(), node.location.clone(), ); @@ -2020,22 +1911,17 @@ impl<'a> CheckMethodBody<'a> { let exp_type = match symbol { Ok(Some(Symbol::Constant(id))) => { let typ = id.value_type(self.db()); - let cid = typ.class_id(self.db(), self.self_type).unwrap(); - node.kind = if cid == ClassId::int() { + node.kind = if typ.is_int(self.db()) { ConstantPatternKind::Int(id) - } else if cid == ClassId::string() { + } else if typ.is_string(self.db()) { ConstantPatternKind::String(id) } else { self.state.diagnostics.error( DiagnosticId::InvalidPattern, format!( "Expected a 'String' or 'Int', found '{}' instead", - format_type_with_self( - self.db(), - self.self_type, - typ - ), + format_type(self.db(), typ), ), self.file(), node.location.clone(), @@ -2070,14 +1956,10 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut typ_ctx = TypeContext::new(self.self_type); - - if !value_type.type_check(self.db_mut(), exp_type, &mut typ_ctx, true) { - let self_type = self.self_type; - + if !TypeChecker::check(self.db(), value_type, exp_type) { self.state.diagnostics.pattern_type_error( - format_type_with_self(self.db(), self_type, value_type), - format_type_with_self(self.db(), self_type, exp_type), + format_type(self.db(), value_type), + format_type(self.db(), exp_type), self.file(), node.location.clone(), ); @@ -2110,11 +1992,7 @@ impl<'a> CheckMethodBody<'a> { format!( "This pattern expects a tuple, \ but the input type is '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + format_type(self.db(), value_type), ), self.file(), node.location.clone(), @@ -2174,7 +2052,10 @@ impl<'a> CheckMethodBody<'a> { | TypeRef::Uni(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::Ref(TypeId::ClassInstance(ins)) - if ins.instance_of().kind(self.db()).is_regular() => + if ins + .instance_of() + .kind(self.db()) + .allow_pattern_matching() => { ins } @@ -2182,13 +2063,9 @@ impl<'a> CheckMethodBody<'a> { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( - "This pattern expects a regular class instance, \ - but the input type is '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + "A regular or extern class instance is expected, \ + but the input type is an instance of type '{}'", + format_type(self.db(), value_type), ), self.file(), node.location.clone(), @@ -2207,11 +2084,7 @@ impl<'a> CheckMethodBody<'a> { format!( "The type '{}' can't be destructured as it defines \ a custom destructor", - format_type_with_self( - self.db(), - self.self_type, - value_type - ) + format_type(self.db(), value_type) ), self.file(), node.location.clone(), @@ -2228,8 +2101,7 @@ impl<'a> CheckMethodBody<'a> { } let immutable = value_type.is_ref(self.db()); - let mut ctx = - TypeContext::for_class_instance(self.db(), self.self_type, ins); + let args = TypeArguments::for_class(self.db(), ins); for node in &mut node.values { let name = &node.field.name; @@ -2240,11 +2112,7 @@ impl<'a> CheckMethodBody<'a> { DiagnosticId::InvalidSymbol, format!( "The type '{}' doesn't define the field '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + format_type(self.db(), value_type), name ), self.file(), @@ -2255,10 +2123,12 @@ impl<'a> CheckMethodBody<'a> { continue; }; - let field_type = field - .value_type(self.db()) - .inferred(self.db_mut(), &mut ctx, immutable) - .cast_according_to(value_type, self.db()); + let raw_type = field.value_type(self.db()); + let field_type = + TypeResolver::new(&mut self.state.db, &args, self.bounds) + .with_immutable(immutable) + .resolve(raw_type) + .cast_according_to(value_type, self.db()); node.field_id = Some(field); @@ -2302,7 +2172,6 @@ impl<'a> CheckMethodBody<'a> { input_type: TypeRef, location: &SourceLocation, ) { - let mut typ_ctx = TypeContext::new(self.self_type); let compare = if input_type.is_owned_or_uni(self.db()) { input_type } else { @@ -2311,17 +2180,14 @@ impl<'a> CheckMethodBody<'a> { input_type.as_owned(self.db()) }; - if !compare.type_check(self.db_mut(), pattern_type, &mut typ_ctx, true) - { - let self_type = self.self_type; - + if !TypeChecker::check(self.db(), compare, pattern_type) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "The type of this pattern is '{}', \ but the input type is '{}'", - format_type_with_self(self.db(), self_type, pattern_type), - format_type_with_self(self.db(), self_type, input_type), + format_type(self.db(), pattern_type), + format_type(self.db(), input_type), ), self.file(), location.clone(), @@ -2340,9 +2206,7 @@ impl<'a> CheckMethodBody<'a> { return; } - let ins = if let Some(ins) = - value_type.as_enum_instance(self.db(), self.self_type) - { + let ins = if let Some(ins) = value_type.as_enum_instance(self.db()) { ins } else { self.state.diagnostics.error( @@ -2350,11 +2214,7 @@ impl<'a> CheckMethodBody<'a> { format!( "This pattern expects an enum class, \ but the input type is '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + format_type(self.db(), value_type), ), self.file(), node.location.clone(), @@ -2372,7 +2232,7 @@ impl<'a> CheckMethodBody<'a> { } else { self.state.diagnostics.undefined_variant( name, - format_type_with_self(self.db(), self.self_type, value_type), + format_type(self.db(), value_type), self.file(), node.location.clone(), ); @@ -2396,20 +2256,18 @@ impl<'a> CheckMethodBody<'a> { } let immutable = value_type.is_ref(self.db()); - let mut ctx = TypeContext::new(self.self_type); - - ins.copy_type_arguments_into(self.db(), &mut ctx.type_arguments); + let args = TypeArguments::for_class(self.db(), ins); + let bounds = self.bounds; for (patt, member) in node.values.iter_mut().zip(members.into_iter()) { - let typ = member - .inferred(self.db_mut(), &mut ctx, immutable) + let typ = TypeResolver::new(self.db_mut(), &args, bounds) + .with_immutable(immutable) + .resolve(member) .cast_according_to(value_type, self.db()); self.pattern(patt, typ, pattern); } - ins.copy_new_arguments_from(self.db_mut(), &ctx.type_arguments); - node.variant_id = Some(variant); } @@ -2549,19 +2407,18 @@ impl<'a> CheckMethodBody<'a> { if !val_type.allow_assignment(self.db()) { self.state.diagnostics.cant_assign_type( - &format_type_with_self(self.db(), self.self_type, val_type), + &format_type(self.db(), val_type), self.file(), value_node.location().clone(), ); } let var_type = var.value_type(self.db()); - let mut ctx = TypeContext::new(self.self_type); - if !val_type.type_check(self.db_mut(), var_type, &mut ctx, true) { + if !TypeChecker::check(self.db(), val_type, var_type) { self.state.diagnostics.type_error( - format_type_with_self(self.db(), self.self_type, val_type), - format_type_with_self(self.db(), self.self_type, var_type), + format_type(self.db(), val_type), + format_type(self.db(), var_type), self.file(), location.clone(), ); @@ -2575,7 +2432,7 @@ impl<'a> CheckMethodBody<'a> { fn closure( &mut self, node: &mut hir::Closure, - mut expected: Option<(ClosureId, TypeRef, &mut TypeContext)>, + mut expected: Option<(ClosureId, TypeRef, &TypeArguments)>, scope: &mut LexicalScope, ) -> TypeRef { let self_type = self.self_type; @@ -2585,33 +2442,22 @@ impl<'a> CheckMethodBody<'a> { .map_or(false, |(id, _, _)| id.is_moving(self.db())); let closure = Closure::alloc(self.db_mut(), moving); - let throw_type = if let Some(n) = node.throw_type.as_mut() { - self.type_signature(n, self_type) - } else { - let db = &mut self.state.db; - - expected - .as_mut() - .map(|(id, _, context)| { - id.throw_type(db).inferred(db, *context, false) - }) - .unwrap_or_else(|| TypeRef::placeholder(self.db_mut())) - }; - + let bounds = self.bounds; let return_type = if let Some(n) = node.return_type.as_mut() { self.type_signature(n, self_type) } else { - let db = &mut self.state.db; + let db = self.db_mut(); expected .as_mut() - .map(|(id, _, context)| { - id.return_type(db).inferred(db, *context, false) + .map(|(id, _, targs)| { + let raw = id.return_type(db); + + TypeResolver::new(db, targs, bounds).resolve(raw) }) - .unwrap_or_else(|| TypeRef::placeholder(self.db_mut())) + .unwrap_or_else(|| TypeRef::placeholder(db, None)) }; - closure.set_throw_type(self.db_mut(), throw_type); closure.set_return_type(self.db_mut(), return_type); let surrounding_type = @@ -2625,7 +2471,6 @@ impl<'a> CheckMethodBody<'a> { kind: ScopeKind::Closure(closure), surrounding_type, return_type, - throw_type, variables: VariableScope::new(), parent: Some(scope), in_closure: true, @@ -2637,15 +2482,16 @@ impl<'a> CheckMethodBody<'a> { let typ = if let Some(n) = arg.value_type.as_mut() { self.type_signature(n, self.self_type) } else { - let db = &mut self.state.db; + let db = self.db_mut(); expected .as_mut() - .and_then(|(id, _, context)| { - id.positional_argument_input_type(db, index) - .map(|t| t.inferred(db, context, false)) + .and_then(|(id, _, targs)| { + id.positional_argument_input_type(db, index).map(|t| { + TypeResolver::new(db, targs, bounds).resolve(t) + }) }) - .unwrap_or_else(|| TypeRef::placeholder(db)) + .unwrap_or_else(|| TypeRef::placeholder(db, None)) }; let var = @@ -2661,14 +2507,6 @@ impl<'a> CheckMethodBody<'a> { &node.location, ); - self.check_if_throws(throw_type, &node.location); - - if let TypeRef::Placeholder(id) = throw_type { - if id.value(self.db()).is_none() { - closure.set_throw_type(self.db_mut(), TypeRef::Never); - } - } - node.resolved_type = match expected.as_ref() { // If a closure is immediately passed to a `uni fn`, and we don't // capture any variables, we can safely infer the closure as unique. @@ -2701,7 +2539,7 @@ impl<'a> CheckMethodBody<'a> { let module = self.module; let (rec, rec_id, rec_kind, method) = { let rec = scope.surrounding_type; - let rec_id = rec.type_id(self.db(), self.self_type).unwrap(); + let rec_id = rec.type_id(self.db()).unwrap(); match rec_id.lookup_method(self.db(), name, module, false) { MethodLookup::Ok(method) => { @@ -2778,22 +2616,27 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut call = MethodCall::new(self.state, module, rec, rec_id, method); + let mut call = MethodCall::new( + self.state, + module, + Some((self.self_type, self.method)), + rec, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - call.check_argument_count(self.state, &node.location); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.check_missing_try(name, throws, &node.location); + let returns = call.return_type; node.kind = ConstantKind::Method(CallInfo { id: method, receiver: rec_kind, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); @@ -2819,9 +2662,8 @@ impl<'a> CheckMethodBody<'a> { } let (rec, rec_id, rec_kind, method) = { - let stype = self.self_type; let rec = scope.surrounding_type; - let rec_id = rec.type_id(self.db(), stype).unwrap(); + let rec_id = rec.type_id(self.db()).unwrap(); match rec_id.lookup_method(self.db(), name, module, true) { MethodLookup::Ok(method) => { @@ -2852,21 +2694,6 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } _ => { - if let Some((field, raw_type)) = self.field_type(name) { - let typ = self.field_reference( - raw_type, - scope, - &node.location, - ); - - node.kind = IdentifierKind::Field(FieldInfo { - id: field, - variable_type: typ, - }); - - return typ; - } - if let Some(Symbol::Module(id)) = module.symbol(self.db(), name) { @@ -2907,22 +2734,26 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut call = MethodCall::new(self.state, module, rec, rec_id, method); + let mut call = MethodCall::new( + self.state, + module, + Some((self.self_type, self.method)), + rec, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - call.check_argument_count(self.state, &node.location); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - self.check_missing_try(name, throws, &node.location); + call.check_type_bounds(self.state, &node.location); + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); + let returns = call.return_type; node.kind = IdentifierKind::Method(CallInfo { id: method, receiver: rec_kind, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); @@ -3004,7 +2835,7 @@ impl<'a> CheckMethodBody<'a> { if !val_type.allow_assignment(self.db()) { self.state.diagnostics.cant_assign_type( - &format_type_with_self(self.db(), self.self_type, val_type), + &format_type(self.db(), val_type), self.file(), value_node.location().clone(), ); @@ -3022,13 +2853,10 @@ impl<'a> CheckMethodBody<'a> { return None; }; - let stype = self.self_type; - let mut ctx = TypeContext::new(stype); - - if !val_type.type_check(self.db_mut(), var_type, &mut ctx, true) { + if !TypeChecker::check(self.db(), val_type, var_type) { self.state.diagnostics.type_error( - format_type_with_self(self.db(), stype, val_type), - format_type_with_self(self.db(), stype, var_type), + format_type(self.db(), val_type), + format_type(self.db(), var_type), self.file(), location.clone(), ); @@ -3136,8 +2964,8 @@ impl<'a> CheckMethodBody<'a> { let lhs = self.expression(&mut node.left, scope); let rhs = self.expression(&mut node.right, scope); - self.require_boolean(lhs, self.self_type, node.left.location()); - self.require_boolean(rhs, self.self_type, node.right.location()); + self.require_boolean(lhs, node.left.location()); + self.require_boolean(rhs, node.right.location()); node.resolved_type = TypeRef::boolean(); node.resolved_type @@ -3151,8 +2979,8 @@ impl<'a> CheckMethodBody<'a> { let lhs = self.expression(&mut node.left, scope); let rhs = self.expression(&mut node.right, scope); - self.require_boolean(lhs, self.self_type, node.left.location()); - self.require_boolean(rhs, self.self_type, node.right.location()); + self.require_boolean(lhs, node.left.location()); + self.require_boolean(rhs, node.right.location()); node.resolved_type = TypeRef::boolean(); node.resolved_type @@ -3174,19 +3002,17 @@ impl<'a> CheckMethodBody<'a> { } let expected = scope.return_type; - let mut ctx = TypeContext::new(self.self_type); - if !returned.type_check(self.db_mut(), expected, &mut ctx, true) { + if !TypeChecker::check(self.db(), returned, expected) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, returned), - format_type_with_context(self.db(), &ctx, expected), + format_type(self.db(), returned), + format_type(self.db(), expected), self.file(), node.location.clone(), ); } node.resolved_type = returned; - TypeRef::Never } @@ -3195,30 +3021,41 @@ impl<'a> CheckMethodBody<'a> { node: &mut hir::Throw, scope: &mut LexicalScope, ) -> TypeRef { - let mut thrown = self.expression(&mut node.value, scope); - let expected = scope.throw_type; - let mut ctx = TypeContext::new(self.self_type); + let expr = self.expression(&mut node.value, scope); - if scope.in_recover() && thrown.is_owned(self.db()) { - thrown = thrown.as_uni(self.db()); + if expr.is_error(self.db()) { + return expr; } - if expected.is_never(self.db()) { - self.state + let ret_type = scope.return_type; + + node.return_type = ret_type; + + match ret_type.throw_kind(self.db()) { + ThrowKind::Unknown | ThrowKind::Option(_) => self + .state .diagnostics - .throw_not_allowed(self.file(), node.location.clone()); - } else if !thrown.type_check(self.db_mut(), expected, &mut ctx, true) { - self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, thrown), - format_type_with_context(self.db(), &ctx, expected), - self.file(), - node.location.clone(), - ); + .throw_not_available(self.file(), node.location.clone()), + ThrowKind::Result(ret_ok, ret_err) => { + node.resolved_type = + if scope.in_recover() && expr.is_owned(self.db()) { + expr.as_uni(self.db()) + } else { + expr + }; + + if !TypeChecker::check(self.db(), expr, ret_err) { + self.state.diagnostics.invalid_throw( + ThrowKind::Result(ret_ok, expr) + .throw_type_name(self.db(), ret_ok), + format_type(self.db(), ret_type), + self.file(), + node.location.clone(), + ); + } + } } - node.resolved_type = thrown; - self.thrown = true; - TypeRef::Never } @@ -3243,7 +3080,7 @@ impl<'a> CheckMethodBody<'a> { let mut scope = new_scope.inherit(ScopeKind::Regular); let typ = self.expression(guard, &mut scope); - self.require_boolean(typ, self.self_type, guard.location()); + self.require_boolean(typ, guard.location()); } if let Some(expected) = rtype { @@ -3278,9 +3115,9 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let expr = self.expression(&mut node.value, scope); - if !expr.is_owned_or_uni(self.db()) { + if !expr.allow_as_ref(self.db()) { self.state.diagnostics.error( - DiagnosticId::InvalidRef, + DiagnosticId::InvalidType, format!( "A 'ref T' can't be created from a value of type '{}'", self.fmt(expr) @@ -3288,6 +3125,8 @@ impl<'a> CheckMethodBody<'a> { self.file(), node.location.clone(), ); + + return TypeRef::Error; } node.resolved_type = if expr.is_value_type(self.db()) { @@ -3306,9 +3145,9 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let expr = self.expression(&mut node.value, scope); - if !expr.is_owned_or_uni(self.db()) { + if !expr.allow_as_mut(self.db()) { self.state.diagnostics.error( - DiagnosticId::InvalidRef, + DiagnosticId::InvalidType, format!( "A 'mut T' can't be created from a value of type '{}'", self.fmt(expr) @@ -3350,7 +3189,7 @@ impl<'a> CheckMethodBody<'a> { last_type.as_owned(db) } else { self.state.diagnostics.error( - DiagnosticId::InvalidRef, + DiagnosticId::InvalidType, format!( "Values of type '{}' can't be recovered", self.fmt(last_type) @@ -3371,152 +3210,76 @@ impl<'a> CheckMethodBody<'a> { node: &mut hir::AssignSetter, scope: &mut LexicalScope, ) -> TypeRef { - let loc = &node.location; let (receiver, allow_type_private) = self.call_receiver(&mut node.receiver, scope); let value = self.expression(&mut node.value, scope); let setter = node.name.name.clone() + "="; let module = self.module; - let rec_id = if let Some(id) = self.receiver_id(receiver, loc) { - id - } else { - return TypeRef::Error; - }; - - let method = - match rec_id.lookup_method( - self.db(), - &setter, - module, - allow_type_private, - ) { - MethodLookup::Ok(id) => id, - MethodLookup::Private => { - self.private_method_call(&setter, loc); - - return TypeRef::Error; - } - MethodLookup::InstanceOnStatic => { - self.invalid_instance_call(&setter, receiver, loc); + let rec_id = + if let Some(id) = self.receiver_id(receiver, &node.location) { + id + } else { + return TypeRef::Error; + }; - return TypeRef::Error; - } - MethodLookup::StaticOnInstance => { - self.invalid_static_call(&setter, receiver, loc); + let method = match rec_id.lookup_method( + self.db(), + &setter, + module, + allow_type_private, + ) { + MethodLookup::Ok(id) => id, + MethodLookup::Private => { + self.private_method_call(&setter, &node.location); - return TypeRef::Error; - } - MethodLookup::None => { - let field_name = &node.name.name; + return TypeRef::Error; + } + MethodLookup::InstanceOnStatic => { + self.invalid_instance_call(&setter, receiver, &node.location); - if let TypeId::ClassInstance(ins) = rec_id { - if let Some(field) = - ins.instance_of().field(self.db(), field_name) - { - if !field.is_visible_to(self.db(), module) { - self.state.diagnostics.private_field( - field_name, - self.file(), - loc.clone(), - ); - } - - if !receiver.allow_mutating() { - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!( - "Can't assign a new value to field '{}', \ - as its receiver is immutable", - field_name, - ), - self.module.file(self.db()), - loc.clone(), - ); - } - - if node.else_block.is_some() { - self.state - .diagnostics - .never_throws(self.file(), loc.clone()); - } - - let mut ctx = TypeContext::for_class_instance( - self.db(), - self.self_type, - ins, - ); - let var_type = field - .value_type(self.db()) - .inferred(self.db_mut(), &mut ctx, false); - let value = - value.cast_according_to(var_type, self.db()); - - if !value.type_check( - self.db_mut(), - var_type, - &mut ctx, - true, - ) { - self.state.diagnostics.type_error( - self.fmt(value), - self.fmt(var_type), - self.file(), - loc.clone(), - ); - } - - if receiver.require_sendable_arguments(self.db()) - && !value.is_sendable(self.db()) - { - self.state.diagnostics.unsendable_field_value( - field_name, - self.fmt(value), - self.file(), - loc.clone(), - ); - } - - node.kind = CallKind::SetField(FieldInfo { - id: field, - variable_type: var_type, - }); - - return types::TypeRef::nil(); - } - } + return TypeRef::Error; + } + MethodLookup::StaticOnInstance => { + self.invalid_static_call(&setter, receiver, &node.location); + return TypeRef::Error; + } + MethodLookup::None => { + return if self.assign_field_with_receiver( + node, receiver, rec_id, value, scope, + ) { + TypeRef::nil() + } else { self.state.diagnostics.undefined_method( &setter, self.fmt(receiver), self.file(), - loc.clone(), + node.location.clone(), ); - return TypeRef::Error; - } - }; + TypeRef::Error + }; + } + }; - let mut call = - MethodCall::new(self.state, self.module, receiver, rec_id, method); + let loc = &node.location; + let mut call = MethodCall::new( + self.state, + self.module, + Some((self.self_type, self.method)), + receiver, + rec_id, + method, + ); call.check_mutability(self.state, loc); - call.check_bounded_implementation(self.state, loc); + call.check_type_bounds(self.state, loc); self.positional_argument(&mut call, 0, &mut node.value, scope); - call.check_argument_count(self.state, loc); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state.diagnostics.never_throws(self.file(), loc.clone()); - } + call.check_arguments(self.state, loc); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(&setter, throws, loc); - } + let returns = call.return_type; let rec_info = Receiver::class_or_explicit(self.db(), receiver); @@ -3524,13 +3287,111 @@ impl<'a> CheckMethodBody<'a> { id: method, receiver: rec_info, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); returns } + fn assign_field_with_receiver( + &mut self, + node: &mut hir::AssignSetter, + receiver: TypeRef, + receiver_id: TypeId, + value: TypeRef, + scope: &mut LexicalScope, + ) -> bool { + let name = &node.name.name; + + // When using `self.field = value`, none of the below is applicable, nor + // do we need to calculate the field type as it's already cached. + if receiver_id == self.self_type { + return if let Some((field, typ)) = self.check_field_assignment( + name, + &mut node.value, + &node.name.location, + scope, + ) { + node.kind = CallKind::SetField(FieldInfo { + class: receiver.class_id(self.db()).unwrap(), + id: field, + variable_type: typ, + }); + + true + } else { + false + }; + } + + let (ins, field) = if let TypeId::ClassInstance(ins) = receiver_id { + if let Some(field) = ins.instance_of().field(self.db(), name) { + (ins, field) + } else { + return false; + } + } else { + return false; + }; + + if !field.is_visible_to(self.db(), self.module) { + self.state.diagnostics.private_field( + name, + self.file(), + node.location.clone(), + ); + } + + if !receiver.allow_mutating() { + self.state.diagnostics.error( + DiagnosticId::InvalidCall, + format!( + "Can't assign a new value to field '{}', as its receiver \ + is immutable", + name, + ), + self.module.file(self.db()), + node.location.clone(), + ); + } + + let targs = TypeArguments::for_class(self.db(), ins); + let raw_type = field.value_type(self.db()); + let bounds = self.bounds; + let var_type = + TypeResolver::new(self.db_mut(), &targs, bounds).resolve(raw_type); + + let value = value.cast_according_to(var_type, self.db()); + + if !TypeChecker::check(self.db(), value, var_type) { + self.state.diagnostics.type_error( + self.fmt(value), + self.fmt(var_type), + self.file(), + node.location.clone(), + ); + } + + if receiver.require_sendable_arguments(self.db()) + && !value.is_sendable(self.db()) + { + self.state.diagnostics.unsendable_field_value( + name, + self.fmt(value), + self.file(), + node.location.clone(), + ); + } + + node.kind = CallKind::SetField(FieldInfo { + class: ins.instance_of(), + id: field, + variable_type: var_type, + }); + + true + } + fn call( &mut self, node: &mut hir::Call, @@ -3539,7 +3400,7 @@ impl<'a> CheckMethodBody<'a> { if let Some((rec, allow_type_private)) = node.receiver.as_mut().map(|r| self.call_receiver(r, scope)) { - if let Some(closure) = rec.closure_id(self.db(), self.self_type) { + if let Some(closure) = rec.closure_id(self.db()) { self.call_closure(rec, closure, node, scope) } else { self.call_with_receiver(rec, node, scope, allow_type_private) @@ -3567,6 +3428,17 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } + if !receiver.allow_mutating() { + self.state.diagnostics.error( + DiagnosticId::InvalidCall, + "Closures can only be called using owned or mutable references", + self.file(), + node.location.clone(), + ); + + return TypeRef::Error; + } + let num_given = node.arguments.len(); let num_exp = closure.number_of_arguments(self.db()); @@ -3581,7 +3453,7 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } - let mut ctx = TypeContext::new(self.self_type); + let targs = TypeArguments::new(); let mut exp_args = Vec::new(); for (index, arg_node) in node.arguments.iter_mut().enumerate() { @@ -3603,13 +3475,13 @@ impl<'a> CheckMethodBody<'a> { }; let given = self - .argument_expression(exp, arg_expr_node, scope, &mut ctx) + .argument_expression(exp, arg_expr_node, scope, &targs) .cast_according_to(exp, self.db()); - if !given.type_check(self.db_mut(), exp, &mut ctx, true) { + if !TypeChecker::check(self.db(), given, exp) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, given), - format_type_with_context(self.db(), &ctx, exp), + format_type(self.db(), given), + format_type(self.db(), exp), self.file(), arg_expr_node.location().clone(), ); @@ -3618,33 +3490,17 @@ impl<'a> CheckMethodBody<'a> { exp_args.push(exp); } - let throws = closure - .throw_type(self.db()) - .as_rigid_type(&mut self.state.db, self.bounds) - .inferred(self.db_mut(), &mut ctx, false); - - let returns = closure - .return_type(self.db()) - .as_rigid_type(&mut self.state.db, self.bounds) - .inferred(self.db_mut(), &mut ctx, false); + let returns = { + let raw = closure.return_type(self.db()); - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state - .diagnostics - .never_throws(self.file(), node.location.clone()); - } - - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(CALL_METHOD, throws, &node.location); - } + TypeResolver::new(&mut self.state.db, &targs, self.bounds) + .resolve(raw) + }; - node.kind = CallKind::ClosureCall(ClosureCallInfo { + node.kind = CallKind::CallClosure(ClosureCallInfo { id: closure, expected_arguments: exp_args, returns, - throws, }); returns @@ -3657,141 +3513,88 @@ impl<'a> CheckMethodBody<'a> { scope: &mut LexicalScope, allow_type_private: bool, ) -> TypeRef { - let name = &node.name.name; - let loc = &node.location; - let module = self.module; - let rec_id = if let Some(id) = self.receiver_id(receiver, loc) { - id - } else { - return TypeRef::Error; - }; + let rec_id = + if let Some(id) = self.receiver_id(receiver, &node.location) { + id + } else { + return TypeRef::Error; + }; let method = match rec_id.lookup_method( self.db(), - name, - module, + &node.name.name, + self.module, allow_type_private, ) { MethodLookup::Ok(id) => id, MethodLookup::Private => { - self.private_method_call(name, loc); + self.private_method_call(&node.name.name, &node.location); return TypeRef::Error; } MethodLookup::InstanceOnStatic => { - self.invalid_instance_call(name, receiver, loc); + self.invalid_instance_call( + &node.name.name, + receiver, + &node.location, + ); return TypeRef::Error; } MethodLookup::StaticOnInstance => { - self.invalid_static_call(name, receiver, loc); + self.invalid_static_call( + &node.name.name, + receiver, + &node.location, + ); return TypeRef::Error; } MethodLookup::None if node.arguments.is_empty() => { - if let TypeId::ClassInstance(ins) = rec_id { - if let Some(field) = - ins.instance_of().field(self.db(), name) - { - if !field.is_visible_to(self.db(), module) { - self.state.diagnostics.private_field( - &node.name.name, - self.file(), - node.location.clone(), - ); - } - - if node.else_block.is_some() { - self.state - .diagnostics - .never_throws(self.file(), loc.clone()); - } - - let mut ctx = TypeContext::new(self.self_type); - let db = self.db_mut(); - let raw_typ = field.value_type(db); - - ins.type_arguments(db) - .copy_into(&mut ctx.type_arguments); - - let mut returns = if raw_typ.is_owned_or_uni(db) { - let typ = raw_typ.inferred(db, &mut ctx, false); - - if receiver.is_ref(db) { - typ.as_ref(db) - } else { - typ.as_mut(db) - } - } else { - raw_typ.inferred(db, &mut ctx, receiver.is_ref(db)) - }; - - returns = returns.value_type_as_owned(self.db()); - - if receiver.require_sendable_arguments(self.db()) - && !returns.is_sendable(self.db()) - { - self.state.diagnostics.unsendable_field( - name, - self.fmt(returns), - self.file(), - node.location.clone(), - ); - } - - node.kind = CallKind::GetField(FieldInfo { - id: field, - variable_type: returns, - }); - - return returns; - } + if let Some(typ) = + self.field_with_receiver(node, receiver, rec_id, scope) + { + return typ; } self.state.diagnostics.undefined_method( - name, + &node.name.name, self.fmt(receiver), self.file(), - loc.clone(), + node.location.clone(), ); return TypeRef::Error; } MethodLookup::None => { self.state.diagnostics.undefined_method( - name, + &node.name.name, self.fmt(receiver), self.file(), - loc.clone(), + node.location.clone(), ); return TypeRef::Error; } }; - let mut call = - MethodCall::new(self.state, module, receiver, rec_id, method); + let mut call = MethodCall::new( + self.state, + self.module, + Some((self.self_type, self.method)), + receiver, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state - .diagnostics - .never_throws(self.file(), node.location.clone()); - } + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(name, throws, &node.location); - } + let returns = call.return_type; let rec_info = Receiver::class_or_explicit(self.db(), receiver); @@ -3799,7 +3602,6 @@ impl<'a> CheckMethodBody<'a> { id: method, receiver: rec_info, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); @@ -3812,10 +3614,9 @@ impl<'a> CheckMethodBody<'a> { scope: &mut LexicalScope, ) -> TypeRef { let name = &node.name.name; - let stype = self.self_type; let module = self.module; let rec = scope.surrounding_type; - let rec_id = rec.type_id(self.db(), stype).unwrap(); + let rec_id = rec.type_id(self.db()).unwrap(); let (rec_info, rec, rec_id, method) = match rec_id.lookup_method(self.db(), name, module, true) { MethodLookup::Ok(method) => { @@ -3876,176 +3677,112 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut call = - MethodCall::new(self.state, self.module, rec, rec_id, method); + let mut call = MethodCall::new( + self.state, + self.module, + Some((self.self_type, self.method)), + rec, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state - .diagnostics - .never_throws(self.file(), node.location.clone()); - } + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(name, throws, &node.location); - } + let returns = call.return_type; node.kind = CallKind::Call(CallInfo { id: method, receiver: rec_info, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); returns } - fn async_call( + fn field_with_receiver( &mut self, - node: &mut hir::AsyncCall, + node: &mut hir::Call, + receiver: TypeRef, + receiver_id: TypeId, scope: &mut LexicalScope, - ) -> TypeRef { - let allow_type_private = node.receiver.is_self(); - let rec_type = self.expression(&mut node.receiver, scope); + ) -> Option { let name = &node.name.name; - let (rec_id, method) = if let Some(found) = self.lookup_method( - rec_type, - name, - &node.location, - allow_type_private, - ) { - found - } else { - return TypeRef::Error; - }; - if !matches!( - rec_id, - TypeId::ClassInstance(ins) - if ins.instance_of().kind(self.db()).is_async() - ) { - let rec_name = - format_type_with_self(self.db(), self.self_type, rec_type); + if receiver_id == self.self_type { + return if let Some((field, raw_type)) = self.field_type(name) { + let typ = self.field_reference(raw_type, scope, &node.location); - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!("'{}' isn't an async type", rec_name), - self.file(), - node.receiver.location().clone(), - ); + node.kind = CallKind::GetField(FieldInfo { + class: receiver.class_id(self.db()).unwrap(), + id: field, + variable_type: typ, + }); - return TypeRef::Error; + Some(typ) + } else { + self.state.diagnostics.undefined_method( + name, + self.fmt(receiver), + self.file(), + node.location.clone(), + ); + + None + }; } - if !method.is_async(self.db()) { - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!("'{}' isn't an async method", name), + let (ins, field) = if let TypeId::ClassInstance(ins) = receiver_id { + ins.instance_of().field(self.db(), name).map(|field| (ins, field)) + } else { + None + }?; + + if !field.is_visible_to(self.db(), self.module) { + self.state.diagnostics.private_field( + &node.name.name, self.file(), - node.name.location.clone(), + node.location.clone(), ); - - return TypeRef::Error; } - let mut call = - MethodCall::new(self.state, self.module, rec_type, rec_id, method); - - call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); + let raw_type = field.value_type(self.db_mut()); + let immutable = receiver.is_ref(self.db_mut()); + let args = ins.type_arguments(self.db_mut()).clone(); + let bounds = self.bounds; + let mut returns = TypeResolver::new(self.db_mut(), &args, bounds) + .with_immutable(immutable) + .resolve(raw_type); - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - let fut_class = ClassId::future(); - let mut fut_args = TypeArguments::new(); - let fut_params = fut_class.type_parameters(self.db()); - - fut_args.assign(fut_params[0], returns); - fut_args.assign(fut_params[1], throws); + if returns.is_value_type(self.db_mut()) { + returns = returns.as_owned(self.db_mut()); + } else if !immutable && raw_type.is_owned_or_uni(self.db_mut()) { + returns = returns.as_mut(self.db_mut()); + } - let throws = TypeRef::Never; - let returns = TypeRef::Owned(TypeId::ClassInstance( - ClassInstance::generic(self.db_mut(), fut_class, fut_args), - )); + if receiver.require_sendable_arguments(self.db()) + && !returns.is_sendable(self.db()) + { + self.state.diagnostics.unsendable_field( + name, + self.fmt(returns), + self.file(), + node.location.clone(), + ); + } - node.info = Some(CallInfo { - id: method, - receiver: Receiver::Explicit, - returns, - throws, - dynamic: rec_id.use_dynamic_dispatch(), + node.kind = CallKind::GetField(FieldInfo { + id: field, + class: ins.instance_of(), + variable_type: returns, }); - returns - } - - fn try_else_block( - &mut self, - node: &mut hir::ElseBlock, - returns: TypeRef, - throws: TypeRef, - scope: &mut LexicalScope, - ) { - let mut new_scope = scope.inherit(ScopeKind::Regular); - - if let Some(var_def) = node.argument.as_mut() { - let typ = if let Some(n) = var_def.value_type.as_mut() { - let mut typ_ctx = TypeContext::new(self.self_type); - let exp_type = self.type_signature(n, self.self_type); - - if !throws.type_check( - self.db_mut(), - exp_type, - &mut typ_ctx, - true, - ) { - let self_type = self.self_type; - - self.state.diagnostics.type_error( - format_type_with_self(self.db(), self_type, throws), - format_type_with_self(self.db(), self_type, exp_type), - self.file(), - node.location.clone(), - ); - } - - exp_type - } else { - throws - }; - - if var_def.name.name != IGNORE_VARIABLE { - let var = new_scope.variables.new_variable( - self.db_mut(), - var_def.name.name.clone(), - typ, - false, - ); - - var_def.variable_id = Some(var); - } - } - - self.expressions_with_return( - returns, - &mut node.body, - &mut new_scope, - &node.location, - ); + Some(returns) } fn builtin_call( @@ -4053,11 +3790,9 @@ impl<'a> CheckMethodBody<'a> { node: &mut hir::BuiltinCall, scope: &mut LexicalScope, ) -> TypeRef { - let args: Vec = node - .arguments - .iter_mut() - .map(|n| self.expression(n, scope)) - .collect(); + for n in &mut node.arguments { + self.expression(n, scope); + } let id = if let Some(id) = self.db().builtin_function(&node.name.name) { id @@ -4071,43 +3806,9 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; }; - // `try!` is desugared into `try x else (err) _INKO.panic_thrown(err)`. - // This way we don't need to duplicate a lot of the `try` logic for - // `try!`. We handle this case explicitly here to provide better error - // message in case a thrown type doesn't implement ToString. - if let BuiltinFunctionKind::Macro(CompilerMacro::PanicThrown) = - id.kind(self.db()) - { - let trait_id = - self.db().trait_in_module(STRING_MODULE, TO_STRING_TRAIT); - let arg = args[0]; + let returns = id.return_type(); - if !arg.implements_trait_id(self.db(), trait_id, self.self_type) { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "This expression may panic with a value of type '{}', \ - but this type doesn't implement '{}::{}'", - format_type_with_self(self.db(), self.self_type, arg), - STRING_MODULE, - TO_STRING_TRAIT - ), - self.file(), - node.location.clone(), - ); - } - } - - let returns = id.return_type(self.db()); - let throws = id.throw_type(self.db()); - - if let Some(block) = node.else_block.as_mut() { - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(&node.name.name, throws, &node.location); - } - - node.info = Some(BuiltinCallInfo { id, returns, throws }); + node.info = Some(BuiltinCallInfo { id, returns }); returns } @@ -4118,16 +3819,39 @@ impl<'a> CheckMethodBody<'a> { scope: &mut LexicalScope, ) -> TypeRef { let expr_type = self.expression(&mut node.value, scope); - let cast_type = self.type_signature(&mut node.cast_to, self.self_type); - let mut ctx = TypeContext::new(self.self_type); - if !expr_type.allow_cast_to(self.db_mut(), cast_type, &mut ctx) { + let rules = Rules { + type_parameters_as_rigid: true, + type_parameters_as_owned: true, + ..Default::default() + }; + let type_scope = TypeScope::with_bounds( + self.module, + self.self_type, + Some(self.method), + self.bounds, + ); + + let cast_type = DefineAndCheckTypeSignature::new( + self.state, + self.module, + &type_scope, + rules, + ) + .define_type(&mut node.cast_to); + + // Casting to/from Any is dangerous but necessary to make the standard + // library work. + if !expr_type.is_any(self.db()) + && !cast_type.is_any(self.db()) + && !TypeChecker::check(self.db_mut(), expr_type, cast_type) + { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "The type '{}' can't be cast to type '{}'", - format_type_with_context(self.db(), &ctx, expr_type), - format_type_with_context(self.db(), &ctx, cast_type) + format_type(self.db(), expr_type), + format_type(self.db(), cast_type) ), self.file(), node.location.clone(), @@ -4140,108 +3864,78 @@ impl<'a> CheckMethodBody<'a> { node.resolved_type } - fn index_expression( + fn try_expression( &mut self, - node: &mut hir::Index, + node: &mut hir::Try, scope: &mut LexicalScope, ) -> TypeRef { - let index = self.db().trait_in_module(INDEX_MODULE, INDEX_TRAIT); - let index_mut = - self.db().trait_in_module(INDEX_MODULE, INDEX_MUT_TRAIT); - - let stype = self.self_type; - let allow_type_private = node.receiver.is_self(); - let rec = self.expression(&mut node.receiver, scope); - let name = if rec.allow_mutating() - && rec.implements_trait_id(self.db(), index_mut, stype) - { - INDEX_MUT_METHOD - } else if rec.implements_trait_id(self.db(), index, stype) { - INDEX_METHOD - } else { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "The type '{typ}' must implement either \ - {module}::{index} or {module}::{index_mut}", - typ = format_type_with_self(self.db(), stype, rec), - module = INDEX_MODULE, - index = INDEX_TRAIT, - index_mut = INDEX_MUT_TRAIT - ), - self.file(), - node.receiver.location().clone(), - ); - - return TypeRef::Error; - }; - - let (rec_id, method) = if let Some(found) = - self.lookup_method(rec, name, &node.location, allow_type_private) - { - found - } else { - return TypeRef::Error; - }; - - let mut call = - MethodCall::new(self.state, self.module, rec, rec_id, method); - - call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - self.positional_argument(&mut call, 0, &mut node.index, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); + let expr = self.expression(&mut node.expression, scope); - let returns = call.return_type(self.state, &node.location); + if expr.is_error(self.db()) { + return expr; + } - node.info = Some(CallInfo { - id: method, - receiver: Receiver::Explicit, - returns, - throws: TypeRef::Never, - dynamic: rec_id.use_dynamic_dispatch(), - }); + let recovery = scope.in_recover(); + let expr_kind = expr.throw_kind(self.db()); + let ret_type = scope.return_type; + let ret_kind = ret_type.throw_kind(self.db()); - returns - } + node.return_type = ret_type; + node.kind = + if recovery { expr_kind.as_uni(self.db()) } else { expr_kind }; - fn lookup_method( - &mut self, - receiver: TypeRef, - name: &str, - location: &SourceLocation, - allow_type_private: bool, - ) -> Option<(TypeId, MethodId)> { - let rec_id = self.receiver_id(receiver, location)?; + match (expr_kind, ret_kind) { + (ThrowKind::Option(some), ThrowKind::Option(_)) => { + // If the value is a None, then `try` produces a new `None`, so + // no type-checking is necessary in this case. + return some; + } + ( + ThrowKind::Result(ok, expr_err), + ThrowKind::Result(ret_ok, ret_err), + ) => { + if TypeChecker::check(self.db(), expr_err, ret_err) { + return ok; + } - match rec_id.lookup_method( - self.db(), - name, - self.module, - allow_type_private, - ) { - MethodLookup::Ok(id) => return Some((rec_id, id)), - MethodLookup::Private => { - self.private_method_call(name, location); + self.state.diagnostics.invalid_throw( + expr_kind.throw_type_name(self.db(), ret_ok), + format_type(self.db(), ret_type), + self.file(), + node.location.clone(), + ); } - MethodLookup::InstanceOnStatic => { - self.invalid_instance_call(name, receiver, location); + (ThrowKind::Unknown, _) => { + self.state.diagnostics.invalid_try( + format_type(self.db(), expr), + self.file(), + node.expression.location().clone(), + ); } - MethodLookup::StaticOnInstance => { - self.invalid_static_call(name, receiver, location); + (_, ThrowKind::Unknown) => { + self.state + .diagnostics + .try_not_available(self.file(), node.location.clone()); } - MethodLookup::None => { - self.state.diagnostics.undefined_method( - name, - self.fmt(receiver), + (ThrowKind::Option(_), ThrowKind::Result(ret_ok, _)) => { + self.state.diagnostics.invalid_throw( + expr_kind.throw_type_name(self.db(), ret_ok), + format_type(self.db(), ret_type), self.file(), - location.clone(), + node.location.clone(), + ); + } + (ThrowKind::Result(_, _), ThrowKind::Option(ok)) => { + self.state.diagnostics.invalid_throw( + expr_kind.throw_type_name(self.db(), ok), + format_type(self.db(), ret_type), + self.file(), + node.location.clone(), ); } } - None + TypeRef::Error } fn receiver_id( @@ -4249,7 +3943,7 @@ impl<'a> CheckMethodBody<'a> { receiver: TypeRef, location: &SourceLocation, ) -> Option { - match receiver.type_id(self.db(), self.self_type) { + match receiver.type_id(self.db()) { Ok(id) => Some(id), Err(TypeRef::Error) => None, Err(TypeRef::Placeholder(_)) => { @@ -4353,7 +4047,7 @@ impl<'a> CheckMethodBody<'a> { expected, node, scope, - &mut call.context, + &call.type_arguments, ); call.check_argument(self.state, given, expected, node.location()); @@ -4381,7 +4075,7 @@ impl<'a> CheckMethodBody<'a> { expected, &mut node.value, scope, - &mut call.context, + &call.type_arguments, ); if call.named_arguments.contains(name) { @@ -4420,27 +4114,6 @@ impl<'a> CheckMethodBody<'a> { } } - fn check_missing_try( - &mut self, - name: &str, - throws: TypeRef, - location: &SourceLocation, - ) { - if throws.is_present(self.db()) { - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!( - "The method '{}' may throw a value of type '{}', \ - but the 'try' keyword is missing", - name, - format_type_with_self(self.db(), self.self_type, throws), - ), - self.file(), - location.clone(), - ); - } - } - fn check_if_self_is_allowed( &mut self, scope: &LexicalScope, @@ -4453,13 +4126,8 @@ impl<'a> CheckMethodBody<'a> { } } - fn require_boolean( - &mut self, - typ: TypeRef, - self_type: TypeId, - location: &SourceLocation, - ) { - if typ == TypeRef::Error || typ.is_bool(self.db(), self.self_type) { + fn require_boolean(&mut self, typ: TypeRef, location: &SourceLocation) { + if typ == TypeRef::Error || typ.is_bool(self.db()) { return; } @@ -4468,7 +4136,7 @@ impl<'a> CheckMethodBody<'a> { format!( "Expected a 'Bool', 'ref Bool' or 'mut Bool', \ found '{}' instead", - format_type_with_self(self.db(), self_type, typ), + format_type(self.db(), typ), ), self.file(), location.clone(), @@ -4531,14 +4199,7 @@ impl<'a> CheckMethodBody<'a> { scope: &LexicalScope, location: &SourceLocation, ) -> TypeRef { - let typ = if scope.in_closure && raw_type.is_owned_or_uni(self.db()) { - // Closures capture `self` as a whole, instead of capturing - // individual fields. If `self` is owned, this means we have to - // expose fields as references; not owned values. - raw_type.as_mut(self.db()) - } else { - raw_type.cast_according_to(scope.surrounding_type, self.db()) - }; + let typ = raw_type.cast_according_to(scope.surrounding_type, self.db()); if scope.in_recover() && !typ.is_sendable(self.db()) { self.state.diagnostics.unsendable_type_in_recover( @@ -4565,7 +4226,7 @@ impl<'a> CheckMethodBody<'a> { } fn fmt(&self, typ: TypeRef) -> String { - format_type_with_self(self.db(), self.self_type, typ) + format_type(self.db(), typ) } fn invalid_static_call( diff --git a/compiler/src/type_check/imports.rs b/compiler/src/type_check/imports.rs index 5ed17e9c3..2aed965b7 100644 --- a/compiler/src/type_check/imports.rs +++ b/compiler/src/type_check/imports.rs @@ -110,7 +110,7 @@ impl<'a> DefineImportedTypes<'a> { ); } else if symbol.is_private(self.db()) { self.state.diagnostics.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!( "The symbol '{}' is private and can't be imported", name @@ -678,7 +678,7 @@ mod tests { let error = state.diagnostics.iter().next().unwrap(); - assert_eq!(error.id(), DiagnosticId::PrivateSymbol); + assert_eq!(error.id(), DiagnosticId::InvalidSymbol); assert_eq!(error.file(), &PathBuf::from("test.inko")); assert_eq!(error.location(), &cols(3, 3)); } diff --git a/compiler/src/type_check/methods.rs b/compiler/src/type_check/methods.rs index 8511aaf57..b28b2e3ed 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -2,15 +2,17 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; -use crate::type_check::{DefineAndCheckTypeSignature, Rules, TypeScope}; +use crate::type_check::{ + define_type_bounds, DefineAndCheckTypeSignature, Rules, TypeScope, +}; use ast::source_location::SourceLocation; -use bytecode::{BuiltinFunction as BIF, Opcode}; use std::path::PathBuf; +use types::check::{Environment, TypeChecker}; +use types::format::{format_type, format_type_with_arguments}; use types::{ - format_type, Block, BuiltinFunction, BuiltinFunctionKind, ClassId, - ClassInstance, CompilerMacro, Database, Method, MethodId, MethodKind, - MethodSource, ModuleId, Symbol, TraitId, TraitInstance, TypeBounds, - TypeContext, TypeId, TypeRef, Visibility, DROP_METHOD, MAIN_CLASS, + Block, ClassId, ClassInstance, Database, Method, MethodId, MethodKind, + MethodSource, ModuleId, Symbol, TraitId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeRef, Visibility, DROP_METHOD, MAIN_CLASS, MAIN_METHOD, }; @@ -83,6 +85,10 @@ trait MethodDefiner { param_node.name.name.clone(), ); + if param_node.mutable { + pid.set_mutable(self.db_mut()); + } + param_node.type_parameter_id = Some(pid); } } @@ -162,7 +168,9 @@ trait MethodDefiner { let file = self.file(); let loc = node.location.clone(); - self.state_mut().diagnostics.unsendable_type(name, file, loc); + self.state_mut() + .diagnostics + .unsendable_async_type(name, file, loc); } let var_type = arg_type.as_rigid_type( @@ -179,32 +187,6 @@ trait MethodDefiner { } } - fn define_throw_type( - &mut self, - node: Option<&mut hir::Type>, - method: MethodId, - rules: Rules, - scope: &TypeScope, - ) { - let typ = if let Some(node) = node { - let typ = self.type_check(node, rules, scope); - - if method.is_async(self.db()) && !typ.is_sendable(self.db()) { - let name = format_type(self.db(), typ); - let file = self.file(); - let loc = node.location().clone(); - - self.state_mut().diagnostics.unsendable_type(name, file, loc); - } - - typ - } else { - TypeRef::Never - }; - - method.set_throw_type(self.db_mut(), typ); - } - fn define_return_type( &mut self, node: Option<&mut hir::Type>, @@ -220,7 +202,9 @@ trait MethodDefiner { let file = self.file(); let loc = node.location().clone(); - self.state_mut().diagnostics.unsendable_type(name, file, loc); + self.state_mut() + .diagnostics + .unsendable_async_type(name, file, loc); } typ @@ -375,13 +359,17 @@ impl<'a> DefineMethods<'a> { for expr in &mut node.body { match expr { hir::ClassExpression::AsyncMethod(ref mut node) => { - self.define_async_method(class_id, node); + self.define_async_method(class_id, node, TypeBounds::new()); } hir::ClassExpression::StaticMethod(ref mut node) => { self.define_static_method(class_id, node) } hir::ClassExpression::InstanceMethod(ref mut node) => { - self.define_instance_method(class_id, node); + self.define_instance_method( + class_id, + node, + TypeBounds::new(), + ); } hir::ClassExpression::Variant(ref mut node) => { self.define_variant_method(class_id, node); @@ -461,16 +449,32 @@ impl<'a> DefineMethods<'a> { } }; + if class_id.kind(self.db()).is_extern() { + self.state.diagnostics.error( + DiagnosticId::InvalidImplementation, + "Methods can't be defined for extern classes", + self.file(), + node.location.clone(), + ); + } + + let bounds = define_type_bounds( + self.state, + self.module, + class_id, + &mut node.bounds, + ); + for expr in &mut node.body { match expr { hir::ReopenClassExpression::InstanceMethod(ref mut n) => { - self.define_instance_method(class_id, n); + self.define_instance_method(class_id, n, bounds.clone()); } hir::ReopenClassExpression::StaticMethod(ref mut n) => { self.define_static_method(class_id, n); } hir::ReopenClassExpression::AsyncMethod(ref mut n) => { - self.define_async_method(class_id, n); + self.define_async_method(class_id, n, bounds.clone()); } } } @@ -484,11 +488,9 @@ impl<'a> DefineMethods<'a> { let method = node.method_id.unwrap(); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::new(self.module, self_type, Some(method)); let rules = Rules { - allow_self_type: false, allow_private_types: method.is_private(self.db()), ..Default::default() }; @@ -504,7 +506,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -519,9 +520,12 @@ impl<'a> DefineMethods<'a> { node: &mut hir::DefineStaticMethod, ) { let receiver = TypeRef::Owned(TypeId::Class(class_id)); - let self_type = TypeId::ClassInstance( - ClassInstance::for_static_self_type(self.db_mut(), class_id), - ); + let bounds = TypeBounds::new(); + let self_type = TypeId::ClassInstance(ClassInstance::rigid( + self.db_mut(), + class_id, + &bounds, + )); let module = self.module; let method = Method::alloc( self.db_mut(), @@ -532,7 +536,6 @@ impl<'a> DefineMethods<'a> { ); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::new(self.module, self_type, Some(method)); let rules = Rules { @@ -552,7 +555,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -573,6 +575,7 @@ impl<'a> DefineMethods<'a> { &mut self, class_id: ClassId, node: &mut hir::DefineInstanceMethod, + mut bounds: TypeBounds, ) { let async_class = class_id.kind(self.db()).is_async(); @@ -605,6 +608,10 @@ impl<'a> DefineMethods<'a> { kind, ); + if !method.is_mutable(self.db()) { + bounds.make_immutable(self.db_mut()); + } + // Regular instance methods on an `async class` must be private to the // class itself. if async_class && method.is_public(self.db()) { @@ -623,17 +630,14 @@ impl<'a> DefineMethods<'a> { || method.is_private(self.db()), ..Default::default() }; - let bounds = TypeBounds::new(); - let self_type = - TypeId::ClassInstance(ClassInstance::for_instance_self_type( - self.db_mut(), - class_id, - &bounds, - )); + let self_type = TypeId::ClassInstance(ClassInstance::rigid( + self.db_mut(), + class_id, + &bounds, + )); let receiver = receiver_type(self_type, node.kind); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::with_bounds( self.module, @@ -648,7 +652,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -662,6 +665,7 @@ impl<'a> DefineMethods<'a> { &node.location, ); + method.set_bounds(self.db_mut(), bounds); node.method_id = Some(method); } @@ -669,6 +673,7 @@ impl<'a> DefineMethods<'a> { &mut self, class_id: ClassId, node: &mut hir::DefineAsyncMethod, + mut bounds: TypeBounds, ) { let self_id = TypeId::Class(class_id); let module = self.module; @@ -685,6 +690,10 @@ impl<'a> DefineMethods<'a> { kind, ); + if !method.is_mutable(self.db()) { + bounds.make_immutable(self.db_mut()); + } + if !class_id.kind(self.db()).is_async() { let file = self.file(); @@ -699,18 +708,16 @@ impl<'a> DefineMethods<'a> { self.define_type_parameters(&mut node.type_parameters, method, self_id); let rules = Rules { - allow_self_type: true, allow_private_types: class_id.is_private(self.db()) || method.is_private(self.db()), ..Default::default() }; let bounds = TypeBounds::new(); - let self_type = - TypeId::ClassInstance(ClassInstance::for_instance_self_type( - self.db_mut(), - class_id, - &bounds, - )); + let self_type = TypeId::ClassInstance(ClassInstance::rigid( + self.db_mut(), + class_id, + &bounds, + )); let receiver = if node.mutable { TypeRef::Mut(self_type) } else { @@ -718,7 +725,7 @@ impl<'a> DefineMethods<'a> { }; method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); + method.set_return_type(self.db_mut(), TypeRef::nil()); let scope = TypeScope::with_bounds( self.module, @@ -733,13 +740,16 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); - self.define_return_type( - node.return_type.as_mut(), - method, - rules, - &scope, - ); + + if node.return_type.is_some() { + self.state.diagnostics.error( + DiagnosticId::InvalidMethod, + "Async methods can't return values", + self.file(), + node.location.clone(), + ); + } + self.add_method_to_class( method, class_id, @@ -747,6 +757,7 @@ impl<'a> DefineMethods<'a> { &node.location, ); + method.set_bounds(self.db_mut(), bounds); node.method_id = Some(method); } @@ -774,7 +785,7 @@ impl<'a> DefineMethods<'a> { ..Default::default() }; let bounds = TypeBounds::new(); - let self_type = TypeId::TraitInstance(TraitInstance::for_self_type( + let self_type = TypeId::TraitInstance(TraitInstance::rigid( self.db_mut(), trait_id, &bounds, @@ -782,7 +793,6 @@ impl<'a> DefineMethods<'a> { let receiver = receiver_type(self_type, node.kind); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::with_bounds( self.module, @@ -797,7 +807,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -843,7 +852,7 @@ impl<'a> DefineMethods<'a> { ..Default::default() }; let bounds = TypeBounds::new(); - let self_type = TypeId::TraitInstance(TraitInstance::for_self_type( + let self_type = TypeId::TraitInstance(TraitInstance::rigid( self.db_mut(), trait_id, &bounds, @@ -851,7 +860,6 @@ impl<'a> DefineMethods<'a> { let receiver = receiver_type(self_type, node.kind); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::with_bounds( self.module, @@ -866,7 +874,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -922,10 +929,23 @@ impl<'a> DefineMethods<'a> { let stype = TypeId::Class(class_id); let rec = TypeRef::Owned(stype); + let ret = if class_id.is_generic(self.db()) { + let args = class_id + .type_parameters(self.db()) + .into_iter() + .map(|param| TypeRef::Infer(TypeId::TypeParameter(param))) + .collect(); + + ClassInstance::with_types(self.db_mut(), class_id, args) + } else { + ClassInstance::new(class_id) + }; method.set_receiver(self.db_mut(), rec); - method.set_self_type(self.db_mut(), stype); - method.set_return_type(self.db_mut(), TypeRef::OwnedSelf); + method.set_return_type( + self.db_mut(), + TypeRef::Owned(TypeId::ClassInstance(ret)), + ); class_id.add_method(self.db_mut(), name, method); node.method_id = Some(method); @@ -970,8 +990,10 @@ impl<'a> CheckMainMethod<'a> { let mod_id = self.db().module(main_mod); - if let Some(method) = self.main_method(mod_id) { + if let Some((class, method)) = self.main_method(mod_id) { method.set_main(self.db_mut()); + self.db_mut().set_main_method(method); + self.db_mut().set_main_class(class); true } else { self.state.diagnostics.error( @@ -989,7 +1011,7 @@ impl<'a> CheckMainMethod<'a> { } } - fn main_method(&self, mod_id: ModuleId) -> Option { + fn main_method(&self, mod_id: ModuleId) -> Option<(ClassId, MethodId)> { let class = if let Some(Symbol::Class(class_id)) = mod_id.symbol(self.db(), MAIN_CLASS) { @@ -998,8 +1020,6 @@ impl<'a> CheckMainMethod<'a> { return None; }; - let stype = TypeId::ClassInstance(ClassInstance::new(class)); - if !class.kind(self.db()).is_async() { return None; } @@ -1008,10 +1028,9 @@ impl<'a> CheckMainMethod<'a> { if method.kind(self.db()) == MethodKind::Async && method.number_of_arguments(self.db()) == 0 - && method.throw_type(self.db()).is_never(self.db()) - && method.return_type(self.db()).is_nil(self.db(), stype) + && method.return_type(self.db()).is_nil(self.db()) { - Some(method) + Some((class, method)) } else { None } @@ -1088,14 +1107,13 @@ impl<'a> ImplementTraitMethods<'a> { ); } - let bounded = !node.bounds.is_empty(); let bounds = class_id .trait_implementation(self.db(), trait_id) .map(|i| i.bounds.clone()) .unwrap(); for expr in &mut node.body { - self.implement_method(expr, class_ins, trait_ins, bounded, &bounds); + self.implement_method(expr, class_ins, trait_ins, bounds.clone()); } for req in trait_id.required_methods(self.db()) { @@ -1126,7 +1144,7 @@ impl<'a> ImplementTraitMethods<'a> { continue; } - let source = MethodSource::implementation(bounded, trait_ins); + let source = MethodSource::Implementation(trait_ins, method); let name = method.name(self.db()).clone(); let copy = method.copy_method(self.db_mut()); @@ -1140,8 +1158,7 @@ impl<'a> ImplementTraitMethods<'a> { node: &mut hir::DefineInstanceMethod, class_instance: ClassInstance, trait_instance: TraitInstance, - bounded: bool, - bounds: &TypeBounds, + mut bounds: TypeBounds, ) { let name = &node.name.name; let original = if let Some(method) = @@ -1176,6 +1193,10 @@ impl<'a> ImplementTraitMethods<'a> { method_kind(node.kind), ); + if !method.is_mutable(self.db()) { + bounds.make_immutable(self.db_mut()); + } + self.define_type_parameters( &mut node.type_parameters, method, @@ -1191,18 +1212,17 @@ impl<'a> ImplementTraitMethods<'a> { }; let receiver = receiver_type(self_type, node.kind); - method.set_self_type(self.db_mut(), self_type); method.set_receiver(self.db_mut(), receiver); method.set_source( self.db_mut(), - MethodSource::implementation(bounded, trait_instance), + MethodSource::Implementation(trait_instance, original), ); let scope = TypeScope::with_bounds( self.module, self_type, Some(method), - bounds, + &bounds, ); self.define_type_parameter_requirements( @@ -1212,7 +1232,6 @@ impl<'a> ImplementTraitMethods<'a> { ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -1220,24 +1239,19 @@ impl<'a> ImplementTraitMethods<'a> { &scope, ); - let mut check_ctx = TypeContext::new(self_type); + let targs = TypeArguments::for_trait(self.db(), trait_instance); + let mut env = Environment::new(targs.clone(), targs); - if trait_instance.instance_of().is_generic(self.db()) { - trait_instance - .type_arguments(self.db()) - .copy_into(&mut check_ctx.type_arguments); - } - - if !method.type_check(self.db_mut(), original, &mut check_ctx) { + if !TypeChecker::new(self.db()).check_method(method, original, &mut env) + { let file = self.file(); - let expected = format_type(self.db(), original); + let lhs = format_type_with_arguments(self.db(), &env.left, method); + let rhs = + format_type_with_arguments(self.db(), &env.right, original); self.state_mut().diagnostics.error( DiagnosticId::InvalidMethod, - format!( - "This method isn't compatible with the method '{}'", - expected - ), + format!("The method '{}' isn't compatible with '{}'", lhs, rhs), file, node.location.clone(), ); @@ -1251,6 +1265,8 @@ impl<'a> ImplementTraitMethods<'a> { method.mark_as_destructor(self.db_mut()); } + method.set_bounds(self.db_mut(), bounds); + self.add_method_to_class( method, class_instance.instance_of(), @@ -1275,287 +1291,3 @@ impl<'a> MethodDefiner for ImplementTraitMethods<'a> { self.module } } - -/// A compiler pass that defines all built-in function signatures. -/// -/// This is just a regular function as we don't need to traverse any modules. -/// -/// We set these functions up separately as some of them depend on certain types -/// (e.g. `Array`) being set up correctly. -pub(crate) fn define_builtin_functions(state: &mut State) -> bool { - let db = &mut state.db; - let nil = TypeRef::nil(); - let int = TypeRef::int(); - let float = TypeRef::float(); - let string = TypeRef::string(); - let any = TypeRef::Any; - let never = TypeRef::Never; - let boolean = TypeRef::boolean(); - let byte_array = TypeRef::byte_array(); - let string_array = TypeRef::array(db, string); - - // All the functions provided by the VM. - let vm = vec![ - (BIF::ChildProcessDrop, any, never), - (BIF::ChildProcessSpawn, any, int), - (BIF::ChildProcessStderrClose, nil, never), - (BIF::ChildProcessStderrRead, int, int), - (BIF::ChildProcessStdinClose, nil, never), - (BIF::ChildProcessStdinFlush, nil, int), - (BIF::ChildProcessStdinWriteBytes, int, int), - (BIF::ChildProcessStdinWriteString, int, int), - (BIF::ChildProcessStdoutClose, nil, never), - (BIF::ChildProcessStdoutRead, int, int), - (BIF::ChildProcessTryWait, int, int), - (BIF::ChildProcessWait, int, int), - (BIF::EnvArguments, string_array, never), - (BIF::EnvExecutable, string, int), - (BIF::EnvGet, string, never), - (BIF::EnvGetWorkingDirectory, string, int), - (BIF::EnvHomeDirectory, string, never), - (BIF::EnvPlatform, int, never), - (BIF::EnvSetWorkingDirectory, nil, int), - (BIF::EnvTempDirectory, string, never), - (BIF::EnvVariables, string_array, never), - (BIF::FFIFunctionAttach, any, never), - (BIF::FFIFunctionCall, any, never), - (BIF::FFIFunctionDrop, nil, never), - (BIF::FFILibraryDrop, nil, never), - (BIF::FFILibraryOpen, any, never), - (BIF::FFIPointerAddress, int, never), - (BIF::FFIPointerAttach, any, never), - (BIF::FFIPointerFromAddress, any, never), - (BIF::FFIPointerRead, any, never), - (BIF::FFIPointerWrite, nil, never), - (BIF::FFITypeAlignment, int, never), - (BIF::FFITypeSize, int, never), - (BIF::DirectoryCreate, nil, int), - (BIF::DirectoryCreateRecursive, nil, int), - (BIF::DirectoryList, string_array, int), - (BIF::DirectoryRemove, nil, int), - (BIF::DirectoryRemoveRecursive, nil, int), - (BIF::FileCopy, int, int), - (BIF::FileDrop, nil, never), - (BIF::FileFlush, nil, int), - (BIF::FileOpenAppendOnly, any, int), - (BIF::FileOpenReadAppend, any, int), - (BIF::FileOpenReadOnly, any, int), - (BIF::FileOpenReadWrite, any, int), - (BIF::FileOpenWriteOnly, any, int), - (BIF::FileRead, int, int), - (BIF::FileRemove, nil, int), - (BIF::FileSeek, int, int), - (BIF::FileSize, int, int), - (BIF::FileWriteBytes, int, int), - (BIF::FileWriteString, int, int), - (BIF::PathAccessedAt, float, int), - (BIF::PathCreatedAt, float, int), - (BIF::PathExists, boolean, never), - (BIF::PathIsDirectory, boolean, never), - (BIF::PathIsFile, boolean, never), - (BIF::PathModifiedAt, float, int), - (BIF::HasherDrop, nil, never), - (BIF::HasherNew, any, never), - (BIF::HasherToHash, int, never), - (BIF::HasherWriteInt, nil, never), - (BIF::ProcessStacktraceDrop, nil, never), - (BIF::ProcessCallFrameLine, int, never), - (BIF::ProcessCallFrameName, string, never), - (BIF::ProcessCallFramePath, string, never), - (BIF::ProcessStacktrace, any, never), - (BIF::RandomBytes, byte_array, never), - (BIF::RandomFloat, float, never), - (BIF::RandomFloatRange, float, never), - (BIF::RandomIntRange, int, never), - (BIF::RandomInt, int, never), - (BIF::SocketAcceptIp, any, int), - (BIF::SocketAcceptUnix, any, int), - (BIF::SocketAddressPairAddress, string, never), - (BIF::SocketAddressPairDrop, nil, never), - (BIF::SocketAddressPairPort, int, never), - (BIF::SocketAllocateIpv4, any, int), - (BIF::SocketAllocateIpv6, any, int), - (BIF::SocketAllocateUnix, any, int), - (BIF::SocketBind, nil, int), - (BIF::SocketConnect, nil, int), - (BIF::SocketDrop, nil, never), - (BIF::SocketGetBroadcast, boolean, int), - (BIF::SocketGetKeepalive, boolean, int), - (BIF::SocketGetLinger, float, int), - (BIF::SocketGetNodelay, boolean, int), - (BIF::SocketGetOnlyV6, boolean, int), - (BIF::SocketGetRecvSize, int, int), - (BIF::SocketGetReuseAddress, boolean, int), - (BIF::SocketGetReusePort, boolean, int), - (BIF::SocketGetSendSize, int, int), - (BIF::SocketGetTtl, int, int), - (BIF::SocketListen, nil, int), - (BIF::SocketLocalAddress, any, int), - (BIF::SocketPeerAddress, any, int), - (BIF::SocketRead, int, int), - (BIF::SocketReceiveFrom, any, int), - (BIF::SocketSendBytesTo, int, int), - (BIF::SocketSendStringTo, int, int), - (BIF::SocketSetBroadcast, nil, int), - (BIF::SocketSetKeepalive, nil, int), - (BIF::SocketSetLinger, nil, int), - (BIF::SocketSetNodelay, nil, int), - (BIF::SocketSetOnlyV6, nil, int), - (BIF::SocketSetRecvSize, nil, int), - (BIF::SocketSetReuseAddress, nil, int), - (BIF::SocketSetReusePort, nil, int), - (BIF::SocketSetSendSize, nil, int), - (BIF::SocketSetTtl, nil, int), - (BIF::SocketShutdownRead, nil, int), - (BIF::SocketShutdownReadWrite, nil, int), - (BIF::SocketShutdownWrite, nil, int), - (BIF::SocketTryClone, any, int), - (BIF::SocketWriteBytes, int, int), - (BIF::SocketWriteString, int, int), - (BIF::StderrFlush, nil, int), - (BIF::StderrWriteBytes, int, int), - (BIF::StderrWriteString, int, int), - (BIF::StdinRead, int, int), - (BIF::StdoutFlush, nil, int), - (BIF::StdoutWriteBytes, int, int), - (BIF::StdoutWriteString, int, int), - (BIF::TimeMonotonic, int, never), - (BIF::TimeSystem, float, never), - (BIF::TimeSystemOffset, int, never), - (BIF::StringToLower, string, never), - (BIF::StringToUpper, string, never), - (BIF::StringToByteArray, byte_array, never), - (BIF::StringToFloat, float, never), - (BIF::StringToInt, int, never), - (BIF::ByteArrayDrainToString, string, never), - (BIF::ByteArrayToString, string, never), - (BIF::CpuCores, int, never), - (BIF::StringCharacters, any, never), - (BIF::StringCharactersNext, any, never), - (BIF::StringCharactersDrop, nil, never), - (BIF::StringConcatArray, string, never), - (BIF::ArrayReserve, nil, never), - (BIF::ArrayCapacity, int, never), - (BIF::ProcessStacktraceLength, int, never), - (BIF::FloatToBits, int, never), - (BIF::FloatFromBits, float, never), - (BIF::RandomNew, any, never), - (BIF::RandomFromInt, any, never), - (BIF::RandomDrop, nil, never), - (BIF::StringSliceBytes, string, never), - (BIF::ByteArraySlice, byte_array, never), - (BIF::ByteArrayAppend, nil, never), - (BIF::ByteArrayCopyFrom, int, never), - (BIF::ByteArrayResize, nil, never), - ]; - - // Regular VM instructions exposed directly to the standard library. These - // are needed to implement core functionality, such as integer addition. - let instructions = vec![ - (Opcode::ArrayClear, nil), - (Opcode::ArrayGet, any), - (Opcode::ArrayLength, int), - (Opcode::ArrayPop, any), - (Opcode::ArrayPush, nil), - (Opcode::ArrayRemove, any), - (Opcode::ArraySet, any), - (Opcode::ArrayDrop, nil), - (Opcode::ByteArrayAllocate, byte_array), - (Opcode::ByteArrayClear, nil), - (Opcode::ByteArrayClone, byte_array), - (Opcode::ByteArrayEquals, boolean), - (Opcode::ByteArrayGet, int), - (Opcode::ByteArrayLength, int), - (Opcode::ByteArrayPop, int), - (Opcode::ByteArrayPush, nil), - (Opcode::ByteArrayRemove, int), - (Opcode::ByteArraySet, int), - (Opcode::ByteArrayDrop, nil), - (Opcode::Exit, never), - (Opcode::FloatAdd, float), - (Opcode::FloatCeil, float), - (Opcode::FloatClone, float), - (Opcode::FloatDiv, float), - (Opcode::FloatEq, boolean), - (Opcode::FloatFloor, float), - (Opcode::FloatGe, boolean), - (Opcode::FloatGt, boolean), - (Opcode::FloatIsInf, boolean), - (Opcode::FloatIsNan, boolean), - (Opcode::FloatLe, boolean), - (Opcode::FloatLt, boolean), - (Opcode::FloatMod, float), - (Opcode::FloatMul, float), - (Opcode::FloatRound, float), - (Opcode::FloatSub, float), - (Opcode::FloatToInt, int), - (Opcode::FloatToString, string), - (Opcode::FutureDrop, any), - (Opcode::GetNil, nil), - (Opcode::GetUndefined, any), - (Opcode::IntAdd, int), - (Opcode::IntBitAnd, int), - (Opcode::IntBitOr, int), - (Opcode::IntBitXor, int), - (Opcode::IntClone, int), - (Opcode::IntDiv, int), - (Opcode::IntEq, boolean), - (Opcode::IntGe, boolean), - (Opcode::IntGt, boolean), - (Opcode::IntLe, boolean), - (Opcode::IntLt, boolean), - (Opcode::IntMod, int), - (Opcode::IntMul, int), - (Opcode::IntShl, int), - (Opcode::IntShr, int), - (Opcode::IntSub, int), - (Opcode::IntPow, int), - (Opcode::IntToFloat, float), - (Opcode::IntToString, string), - (Opcode::ObjectEq, boolean), - (Opcode::Panic, never), - (Opcode::StringByte, int), - (Opcode::StringEq, boolean), - (Opcode::StringSize, int), - (Opcode::StringDrop, nil), - (Opcode::IsUndefined, boolean), - (Opcode::ProcessSuspend, nil), - (Opcode::FuturePoll, any), - (Opcode::IntBitNot, int), - (Opcode::IntRotateLeft, int), - (Opcode::IntRotateRight, int), - (Opcode::IntWrappingAdd, int), - (Opcode::IntWrappingSub, int), - (Opcode::IntWrappingMul, int), - (Opcode::IntUnsignedShr, int), - ]; - - let macros = vec![ - (CompilerMacro::FutureGet, any, any), - (CompilerMacro::FutureGetFor, any, any), - (CompilerMacro::StringClone, string, never), - (CompilerMacro::Moved, any, never), - (CompilerMacro::PanicThrown, never, never), - (CompilerMacro::Strings, string, never), - ]; - - for (id, returns, throws) in vm.into_iter() { - let kind = BuiltinFunctionKind::Function(id); - - BuiltinFunction::alloc(db, kind, id.name(), returns, throws); - } - - for (opcode, returns) in instructions.into_iter() { - let kind = BuiltinFunctionKind::Instruction(opcode); - - BuiltinFunction::alloc(db, kind, opcode.name(), returns, never); - } - - for (mac, returns, throws) in macros.into_iter() { - let kind = BuiltinFunctionKind::Macro(mac); - - BuiltinFunction::alloc(db, kind, mac.name(), returns, throws); - } - - true -} diff --git a/compiler/src/type_check/mod.rs b/compiler/src/type_check/mod.rs index 7791061c4..9351a266b 100644 --- a/compiler/src/type_check/mod.rs +++ b/compiler/src/type_check/mod.rs @@ -3,10 +3,12 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; use std::path::PathBuf; +use types::check::TypeChecker; +use types::format::format_type; use types::{ - format_type, Block, ClassId, ClassInstance, Closure, Database, MethodId, - ModuleId, Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, - TypeContext, TypeId, TypeParameterId, TypeRef, + Block, ClassId, ClassInstance, Closure, Database, MethodId, ModuleId, + Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, + TypeParameter, TypeParameterId, TypeRef, }; pub(crate) mod define_types; @@ -67,20 +69,33 @@ impl<'a> TypeScope<'a> { } pub(crate) fn symbol(&self, db: &Database, name: &str) -> Option { - self.method - .and_then(|id| id.named_type(db, name)) - .or_else(|| self.self_type.named_type(db, name)) - .or_else(|| self.module.symbol(db, name)) + if let Some(id) = self.method { + if let Some(sym) = id.named_type(db, name) { + return Some(sym); + } + + match self.self_type.named_type(db, name) { + Some(Symbol::TypeParameter(pid)) => { + if let Some(bound) = id.bounds(db).get(pid) { + Some(Symbol::TypeParameter(bound)) + } else { + Some(Symbol::TypeParameter(pid)) + } + } + None => self.module.symbol(db, name), + sym => sym, + } + } else { + self.self_type + .named_type(db, name) + .or_else(|| self.module.symbol(db, name)) + } } } /// Rules to apply when defining and checking the types of type signatures. #[derive(Copy, Clone)] pub(crate) struct Rules { - /// When set to `true`, allows the use of the `Self` type in type - /// signatures. - pub(crate) allow_self_type: bool, - /// When set to `true`, type parameters are defined as owned values; rather /// than allowing both owned values and references. pub(crate) type_parameters_as_owned: bool, @@ -95,7 +110,6 @@ pub(crate) struct Rules { impl Default for Rules { fn default() -> Self { Self { - allow_self_type: true, type_parameters_as_owned: false, type_parameters_as_rigid: false, allow_private_types: true, @@ -216,7 +230,7 @@ impl<'a> DefineTypeSignature<'a> { node.resolved_type = if let Some(symbol) = symbol { if !self.rules.allow_private_types && symbol.is_private(self.db()) { self.state.diagnostics.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!( "'{}' is private, but private types can't be used here", name @@ -268,12 +282,6 @@ impl<'a> DefineTypeSignature<'a> { return TypeRef::Error; } } - "Self" => match kind { - RefKind::Owned => TypeRef::OwnedSelf, - RefKind::Mut => TypeRef::MutSelf, - RefKind::Uni => TypeRef::UniSelf, - _ => TypeRef::RefSelf, - }, "Any" => match kind { RefKind::Owned => TypeRef::Any, RefKind::Ref | RefKind::Mut => TypeRef::RefAny, @@ -320,7 +328,7 @@ impl<'a> DefineTypeSignature<'a> { let types = node.values.iter_mut().map(|n| self.define_type(n)).collect(); - let ins = TypeId::ClassInstance(ClassInstance::generic_with_types( + let ins = TypeId::ClassInstance(ClassInstance::with_types( self.db_mut(), class, types, @@ -395,7 +403,22 @@ impl<'a> DefineTypeSignature<'a> { RefKind::Owned => TypeRef::Infer(type_id), RefKind::Uni => TypeRef::Uni(type_id), RefKind::Ref => TypeRef::Ref(type_id), - RefKind::Mut => TypeRef::Mut(type_id), + RefKind::Mut => { + if !param_id.is_mutable(self.db()) { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + format!( + "The type 'mut {name}' is invalid, as '{name}' \ + might be immutable at runtime", + name = id.name(self.db()), + ), + self.file(), + node.location.clone(), + ); + } + + TypeRef::Mut(type_id) + } } } @@ -412,14 +435,6 @@ impl<'a> DefineTypeSignature<'a> { block.new_anonymous_argument(self.db_mut(), typ); } - let throw_type = if let Some(type_node) = node.throw_type.as_mut() { - self.define_type(type_node) - } else { - TypeRef::Never - }; - - block.set_throw_type(self.db_mut(), throw_type); - let return_type = if let Some(type_node) = node.return_type.as_mut() { self.define_type(type_node) } else { @@ -497,18 +512,11 @@ impl<'a> DefineTypeSignature<'a> { pub(crate) struct CheckTypeSignature<'a> { state: &'a mut State, module: ModuleId, - self_type: TypeId, - rules: Rules, } impl<'a> CheckTypeSignature<'a> { - pub(crate) fn new( - state: &'a mut State, - module: ModuleId, - self_type: TypeId, - rules: Rules, - ) -> Self { - Self { state, module, self_type, rules } + pub(crate) fn new(state: &'a mut State, module: ModuleId) -> Self { + Self { state, module } } pub(crate) fn check(&mut self, node: &hir::Type) { @@ -536,16 +544,6 @@ impl<'a> CheckTypeSignature<'a> { } _ => {} }, - TypeRef::OwnedSelf | TypeRef::RefSelf - if !self.rules.allow_self_type => - { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - "Self types can't be used here", - self.file(), - node.location.clone(), - ); - } _ => {} } } @@ -635,8 +633,6 @@ impl<'a> CheckTypeSignature<'a> { arguments: Vec<(TypeParameterId, TypeRef)>, allow_any: bool, ) { - let mut context = TypeContext::new(self.self_type); - for ((param, arg), node) in arguments.into_iter().zip(node.arguments.iter()) { @@ -650,11 +646,9 @@ impl<'a> CheckTypeSignature<'a> { ); } - if !arg.is_compatible_with_type_parameter( - self.db_mut(), - param, - &mut context, - ) { + let exp = TypeRef::Infer(TypeId::TypeParameter(param)); + + if !TypeChecker::check(self.db(), arg, exp) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( @@ -676,10 +670,6 @@ impl<'a> CheckTypeSignature<'a> { self.check(node); } - if let Some(node) = node.throw_type.as_ref() { - self.check(node); - } - if let Some(node) = node.return_type.as_ref() { self.check(node); } @@ -698,10 +688,6 @@ impl<'a> CheckTypeSignature<'a> { fn db(&self) -> &Database { &self.state.db } - - fn db_mut(&mut self) -> &mut Database { - &mut self.state.db - } } /// A visitor that combines `DefineTypeSignature` and `CheckTypeSignature`. @@ -731,13 +717,7 @@ impl<'a> DefineAndCheckTypeSignature<'a> { ) .define_type(node); - CheckTypeSignature::new( - self.state, - self.module, - self.scope.self_type, - self.rules, - ) - .check(node); + CheckTypeSignature::new(self.state, self.module).check(node); typ } @@ -755,19 +735,76 @@ impl<'a> DefineAndCheckTypeSignature<'a> { .as_trait_instance(node); if ins.is_some() { - CheckTypeSignature::new( - self.state, - self.module, - self.scope.self_type, - self.rules, - ) - .check_type_name(node); + CheckTypeSignature::new(self.state, self.module) + .check_type_name(node); } ins } } +pub(crate) fn define_type_bounds( + state: &mut State, + module: ModuleId, + class: ClassId, + nodes: &mut [hir::TypeBound], +) -> TypeBounds { + let mut bounds = TypeBounds::new(); + + for bound in nodes { + let name = &bound.name.name; + let param = if let Some(id) = class.type_parameter(&state.db, name) { + id + } else { + state.diagnostics.undefined_symbol( + name, + module.file(&state.db), + bound.name.location.clone(), + ); + + continue; + }; + + if bounds.get(param).is_some() { + state.diagnostics.error( + DiagnosticId::DuplicateSymbol, + format!( + "Bounds are already defined for type parameter '{}'", + name + ), + module.file(&state.db), + bound.location.clone(), + ); + + continue; + } + + let mut reqs = param.requirements(&state.db); + let new_param = TypeParameter::alloc(&mut state.db, name.clone()); + + for req in &mut bound.requirements { + let rules = Rules::default(); + let scope = TypeScope::new(module, TypeId::Class(class), None); + let mut definer = + DefineTypeSignature::new(state, module, &scope, rules); + + if let Some(ins) = definer.as_trait_instance(req) { + reqs.push(ins); + } + } + + if bound.mutable { + new_param.set_mutable(&mut state.db); + } + + new_param.set_original(&mut state.db, param); + new_param.add_requirements(&mut state.db, reqs); + bounds.set(param, new_param); + } + + bounds +} + #[cfg(test)] mod tests { use super::*; @@ -1050,7 +1087,7 @@ mod tests { let error = state.diagnostics.iter().next().unwrap(); - assert_eq!(error.id(), DiagnosticId::PrivateSymbol); + assert_eq!(error.id(), DiagnosticId::InvalidSymbol); } #[test] @@ -1112,7 +1149,6 @@ mod tests { let module = module_type(&mut state, "foo"); let mut node = hir::Type::Closure(Box::new(hir::ClosureType { arguments: Vec::new(), - throw_type: None, return_type: None, location: cols(1, 1), resolved_type: TypeRef::Unknown, @@ -1176,8 +1212,7 @@ mod tests { DefineTypeSignature::new(&mut state, module, &scope, rules) .define_type(&mut node); - CheckTypeSignature::new(&mut state, module, instance_a, rules) - .check(&node); + CheckTypeSignature::new(&mut state, module).check(&node); assert!(state.diagnostics.has_errors()); @@ -1253,8 +1288,7 @@ mod tests { DefineTypeSignature::new(&mut state, module, &scope, rules) .define_type(&mut node); - CheckTypeSignature::new(&mut state, module, instance_a, rules) - .check(&node); + CheckTypeSignature::new(&mut state, module).check(&node); assert!(state.diagnostics.has_errors()); diff --git a/deny.toml b/deny.toml index 74e4d8346..994570708 100644 --- a/deny.toml +++ b/deny.toml @@ -14,9 +14,8 @@ allow = [ "MIT", "Apache-2.0", "MPL-2.0", - "ISC", - "CC0-1.0", "BSD-3-Clause", + "Unicode-DFS-2016", ] copyleft = "deny" allow-osi-fsf-free = "neither" diff --git a/docs/source/getting-started/installation.md b/docs/source/getting-started/installation.md index b31afacaa..37bd77ef5 100644 --- a/docs/source/getting-started/installation.md +++ b/docs/source/getting-started/installation.md @@ -15,7 +15,7 @@ Unix compatibility layer such as [MSYS2][msys2]. - A 64-bits little-endian platform - A CPU with AES-NI support -- Rust 1.62 or newer +- Rust 1.63 or newer Inko's package manager (ipm) also required Git to be installed, and the `git` executable to be available in your PATH. @@ -152,15 +152,15 @@ and the `ipm` executable in `target/release/ipm` (or `target/debug/ipm` for debug builds). By default Inko uses the standard library provided in the Git repository, -located in `libstd/src`. If you wish to use a different directory, set the -`INKO_LIBSTD` environment variable to a path of your choosing. For example: +located in `std/src`. If you wish to use a different directory, set the +`INKO_STD` environment variable to a path of your choosing. For example: ```bash -INKO_LIBSTD=/tmp/libstd/src cargo build --release +INKO_STD=/tmp/std/src cargo build --release ``` This builds Inko such that it uses the standard library located at -`/tmp/libstd/src`. +`/tmp/std/src`. When building from source you can set certain feature flags to customise the installation. These flags are specified like so: diff --git a/docs/source/getting-started/ivm.md b/docs/source/getting-started/ivm.md index 06e87f2b8..44510654d 100644 --- a/docs/source/getting-started/ivm.md +++ b/docs/source/getting-started/ivm.md @@ -7,7 +7,7 @@ additional system dependencies. ## Installing -ivm itself only requires Rust 1.62 or newer, but to build Inko itself you'll +ivm itself only requires Rust 1.63 or newer, but to build Inko itself you'll need to also meet the requirements listed in the [installation guide](installation.md). diff --git a/docs/source/getting-started/pattern-matching.md b/docs/source/getting-started/pattern-matching.md index d026cba20..3ca9651e6 100644 --- a/docs/source/getting-started/pattern-matching.md +++ b/docs/source/getting-started/pattern-matching.md @@ -290,13 +290,13 @@ For references we just drop the reference before entering the pattern body: ```inko -match values[4] { +match values.get(4) { case Some(42) -> { - # This is valid because the `ref Option[Int]` returned by `values[4]` is - # dropped before we enter this body. If we didn't the line below would - # panic, because we'd try to drop the old value of `values[4]` while a + # This is valid because the `ref Option[Int]` returned by `values.get(4)` is + # dropped before we enter this body. If we didn't, the line below would + # panic, because we'd try to drop the old value of `values.get(4)` while a # reference to it still exists. - values[4] = Option.Some(0) + values.set(4, Option.Some(0)) } case _ -> nil } diff --git a/docs/source/getting-started/syntax.md b/docs/source/getting-started/syntax.md index b424884b9..9647a3153 100644 --- a/docs/source/getting-started/syntax.md +++ b/docs/source/getting-started/syntax.md @@ -734,11 +734,16 @@ async { foo.bar } ### Indexing -Indexing is done using `[]` and `[]=` like so: +Inko doesn't have dedicated indexing syntax such as `array[index]` or +`array[index] = value`, instead you'd use the methods `get`, `get_mut`, and +`set` like so: ```inko -numbers[0] -numbers[0] = 42 +let numbers = [10, 20] + +numbers.get(1) # => 20 +numbers.set(1, 30) +numbers.get(1) # => 30 ``` ### Type casts diff --git a/docs/source/guides/contributing.md b/docs/source/guides/contributing.md index 5ce155fba..b198f5d07 100644 --- a/docs/source/guides/contributing.md +++ b/docs/source/guides/contributing.md @@ -90,9 +90,9 @@ For contributing changes to Inko source code, please follow [the Inko style guide](style-guide.md). We don't have any tools yet to enforce the style guide, so this is done manually during code review. -Unit tests for Inko are located in `libstd/test` and are named `test_X.inko`, +Unit tests for Inko are located in `std/test` and are named `test_X.inko`, where `X` is the module to test. For example, the tests for `std::string` are -located in `libstd/test/std/test_string.inko`. Test modules are structured as +located in `std/test/std/test_string.inko`. Test modules are structured as follows: ```inko @@ -106,7 +106,7 @@ fn pub tests(t: mut Tests) { ``` When adding a new test module, follow this structure then add it to -`libstd/test/main.inko`, following the same style as the existing tests. +`std/test/main.inko`, following the same style as the existing tests. To run the stdlib tests: diff --git a/docs/source/guides/operators.md b/docs/source/guides/operators.md index 48324cb17..c18765d1e 100644 --- a/docs/source/guides/operators.md +++ b/docs/source/guides/operators.md @@ -18,7 +18,7 @@ class Rational { let @denominator: Int } -impl Add[Rational] for Rational { +impl Add[Rational, Rational] for Rational { fn pub +(other: ref Rational) -> Rational { Rational { @numerator = @numerator + other.numerator, diff --git a/docs/source/guides/testing.md b/docs/source/guides/testing.md index 929aae7af..0be7ba820 100644 --- a/docs/source/guides/testing.md +++ b/docs/source/guides/testing.md @@ -38,7 +38,7 @@ mirror the module hierarchy of the module they are testing. For example, the tests for the standard library are organised as follows: ``` -libstd/test/ +std/test/ ├── main.inko └── std ├── fs diff --git a/docs/source/internals/vm.md b/docs/source/internals/vm.md index 5c06ce284..1a1394ac5 100644 --- a/docs/source/internals/vm.md +++ b/docs/source/internals/vm.md @@ -226,9 +226,8 @@ booleans. This is achieved using pointer tagging. If a pointer has its lowest bit set to `1` it means the pointer is an immediate value, instead of pointing to a heap allocated object. -If the lowest two bits are set to `1` (so `xx11`), the pointer is a tagged -integer capable of storing integers up to 62 bits. 63 and 64 bits integers are -heap allocated. +If the lowest bit is set to `1` (so `xx1`), the pointer is a tagged integer +capable of storing integers up to 63 bits. 64 bits integers are heap allocated. If the lowest two bits of a pointer are set to `10`, it means the pointer is a pointer to a permanently allocated heap object, such as a string literal. @@ -241,14 +240,10 @@ unset): | Value | Pattern |:-----------------|:----------- -| Heap object | `xxxx x000` -| Permanent object | `xxxx xx10` -| Reference | `xxxx x1x0` -| Tagged integer | `xxxx xx11` -| `true` | `0000 1101` -| `false` | `0000 0101` -| `nil` | `0000 0001` -| `undefined` | `0000 1001` +| Heap object | `000` +| Permanent object | `x10` +| Reference | `1x0` +| Tagged integer | `xx1` For more information, refer to `vm/src/mem.rs`, which implements most of the memory management logic of the VM. diff --git a/inko/Cargo.toml b/inko/Cargo.toml index c94bfaef3..2f7cb0454 100644 --- a/inko/Cargo.toml +++ b/inko/Cargo.toml @@ -8,10 +8,9 @@ license = "MPL-2.0" [features] default = [] jemalloc = ["jemallocator"] -libffi-system = ["vm/libffi-system"] [dependencies] getopts = "^0.2" -jemallocator = { version = "^0.3", optional = true } -vm = { path = "../vm" } +jemallocator = { version = "^0.5", optional = true } compiler = { path = "../compiler" } +blake2 = "^0.10" diff --git a/inko/src/command.rs b/inko/src/command.rs index 7cfbad94a..6f4f79108 100644 --- a/inko/src/command.rs +++ b/inko/src/command.rs @@ -1,5 +1,7 @@ pub(crate) mod build; pub(crate) mod check; pub(crate) mod main; +pub(crate) mod pkg; +pub(crate) mod print; pub(crate) mod run; pub(crate) mod test; diff --git a/inko/src/command/build.rs b/inko/src/command/build.rs index b417e82cb..12a72a2fc 100644 --- a/inko/src/command/build.rs +++ b/inko/src/command/build.rs @@ -1,8 +1,7 @@ -//! Command for building an Inko bytecode image from a source file. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; -use compiler::config::Config as CompilerConfig; +use compiler::config::{Config, Mode, Output}; use getopts::Options; use std::path::PathBuf; @@ -12,14 +11,13 @@ Compile a source file and its dependencies into a bytecode file. Examples: - inko build # Compile src/main.inko - inko build hello.inko # Compile the file hello.inko"; + inko build # Compile src/main.inko + inko build hello.inko # Compile the file hello.inko"; -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); - options.optflag("h", "help", "Shows this help message"); - + options.optflag("h", "help", "Show this help message"); options.optopt( "f", "format", @@ -27,6 +25,19 @@ pub fn run(arguments: &[String]) -> Result { "FORMAT", ); + options.optopt( + "t", + "target", + "The target platform to compile for", + "TARGET", + ); + + options.optflag( + "", + "release", + "Enable a release build instead of a debug build", + ); + options.optopt( "o", "output", @@ -48,10 +59,18 @@ pub fn run(arguments: &[String]) -> Result { return Ok(0); } - let mut config = CompilerConfig::default(); + let mut config = Config::default(); + + if let Some(val) = matches.opt_str("f") { + config.set_presenter(&val)?; + } + + if let Some(val) = matches.opt_str("t") { + config.set_target(&val)?; + } - if let Some(format) = matches.opt_str("f") { - config.set_presenter(&format)?; + if matches.opt_present("release") { + config.mode = Mode::Release; } for path in matches.opt_strs("i") { @@ -59,12 +78,12 @@ pub fn run(arguments: &[String]) -> Result { } if let Some(path) = matches.opt_str("o") { - config.output = Some(PathBuf::from(path)); + config.output = Output::Path(PathBuf::from(path)); } let mut compiler = Compiler::new(config); let file = matches.free.get(0).map(PathBuf::from); - let result = compiler.compile_to_file(file); + let result = compiler.run(file); compiler.print_diagnostics(); diff --git a/inko/src/command/check.rs b/inko/src/command/check.rs index 3c32e8c49..f8ade4368 100644 --- a/inko/src/command/check.rs +++ b/inko/src/command/check.rs @@ -1,4 +1,3 @@ -//! Command for type-checking Inko source code. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; @@ -16,10 +15,10 @@ Examples: inko check hello.inko # Check the file hello.inko"; /// Type-checks Inko source code. -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); - options.optflag("h", "help", "Shows this help message"); + options.optflag("h", "help", "Show this help message"); options.optopt( "f", "format", diff --git a/inko/src/command/main.rs b/inko/src/command/main.rs index 2e2aa20f8..a72f4a83f 100644 --- a/inko/src/command/main.rs +++ b/inko/src/command/main.rs @@ -1,6 +1,7 @@ -//! The main entry point for the CLI. use crate::command::build; use crate::command::check; +use crate::command::pkg; +use crate::command::print; use crate::command::run; use crate::command::test; use crate::error::Error; @@ -12,12 +13,11 @@ const USAGE: &str = "Usage: inko [OPTIONS] [COMMAND | FILE] Commands: - run Compiles and runs FILE - build Compiles FILE - test Runs Inko unit tests - -If no explicit command is given, the run command is implied. Each command takes -its own set of options. + run Compile and run Inko source code directly + build Compile Inko source code + test Run Inko unit tests + print Print compiler details to STDOUT + pkg Managing of Inko packages Examples: @@ -25,16 +25,15 @@ Examples: inko run hello.inko # Same inko build hello.inko # Compiles the file into a bytecode image inko check hello.inko # Checks hello.inko for errors - inko run --help # Prints the help message for the run command"; + inko run --help # Print the help message for the run command"; -/// Runs the default CLI command. -pub fn run() -> Result { +pub(crate) fn run() -> Result { let args: Vec = env::args().collect(); let mut options = Options::new(); options.parsing_style(ParsingStyle::StopAtFirstFree); - options.optflag("h", "help", "Shows this help message"); - options.optflag("v", "version", "Prints the version number"); + options.optflag("h", "help", "Show this help message"); + options.optflag("v", "version", "Print the version number"); let matches = options.parse(&args[1..])?; @@ -44,10 +43,7 @@ pub fn run() -> Result { } if matches.opt_present("v") { - println!("inko version {}\n", env!("CARGO_PKG_VERSION")); - println!("AES-NI: {}", cfg!(target_feature = "aes")); - println!("jemalloc: {}", cfg!(feature = "jemalloc")); - + println!("inko {}", env!("CARGO_PKG_VERSION")); return Ok(0); } @@ -56,9 +52,14 @@ pub fn run() -> Result { Some("build") => build::run(&matches.free[1..]), Some("check") => check::run(&matches.free[1..]), Some("test") => test::run(&matches.free[1..]), - Some(_) => run::run(&matches.free), - None => Err(Error::generic( - "You must specify a command or input file to run".to_string(), - )), + Some("print") => print::run(&matches.free[1..]), + Some("pkg") => pkg::run(&matches.free[1..]), + Some(cmd) => { + Err(Error::generic(format!("The command '{}' is invalid", cmd))) + } + None => { + print_usage(&options, USAGE); + Ok(0) + } } } diff --git a/inko/src/command/pkg.rs b/inko/src/command/pkg.rs new file mode 100644 index 000000000..c1af1693f --- /dev/null +++ b/inko/src/command/pkg.rs @@ -0,0 +1,54 @@ +mod add; +mod init; +mod remove; +mod sync; +mod update; + +use crate::error::Error; +use crate::options::print_usage; +use getopts::Options; + +const USAGE: &str = "inko pkg [OPTIONS] [COMMAND] + +Package and dependency management for Inko. + +Commands: + + init Create a new package + add Add or update a dependency + remove Remove a dependency + sync Download and install dependencies + update Update all dependencies to the latest version + +Examples: + + inko pkg init + inko pkg add github.com/hello/world 1.2.3"; + +pub(crate) fn run(arguments: &[String]) -> Result { + let mut options = Options::new(); + + 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); + } + + match matches.free.get(0).map(|s| s.as_str()) { + Some("init") => init::run(&matches.free[1..]), + Some("add") => add::run(&matches.free[1..]), + Some("remove") => remove::run(&matches.free[1..]), + 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))) + } + None => { + print_usage(&options, USAGE); + Ok(0) + } + } +} diff --git a/inko/src/command/pkg/add.rs b/inko/src/command/pkg/add.rs new file mode 100644 index 000000000..c78c1803a --- /dev/null +++ b/inko/src/command/pkg/add.rs @@ -0,0 +1,85 @@ +use crate::error::Error; +use crate::options::print_usage; +use crate::pkg::git::Repository; +use crate::pkg::manifest::{Checksum, Manifest, Url, MANIFEST_FILE}; +use crate::pkg::util::data_dir; +use crate::pkg::version::Version; +use getopts::Options; + +const USAGE: &str = "inko pkg add [OPTIONS] [URL] [VERSION] + +Add a dependency to the current project. + +This command merely adds the dependency to the manifest. To download it along +with its dependencies, run `inko pkg sync`. + +Examples: + + inko pkg add github.com/inko-lang/example 1.2.3"; + +pub(crate) fn run(args: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(args)?; + + if matches.opt_present("h") || matches.free.is_empty() { + print_usage(&options, USAGE); + return Ok(0); + } + + if matches.free.len() != 2 { + return Err(Error::generic( + "You must specify a package and version to add".to_string(), + )); + } + + let url = matches.free.get(0).and_then(|uri| Url::parse(uri)).ok_or_else( + || Error::generic("The package URL is invalid".to_string()), + )?; + + let version = + matches.free.get(1).and_then(|uri| Version::parse(uri)).ok_or_else( + || Error::generic("The package version is invalid".to_string()), + )?; + + let dir = data_dir()?.join(url.directory_name()); + let (mut repo, fetch) = if dir.is_dir() { + (Repository::open(&dir)?, true) + } else { + (Repository::clone(&url.to_string(), &dir)?, false) + }; + + if fetch { + repo.fetch()?; + } + + let tag_name = version.tag_name(); + let tag = if let Some(tag) = repo.tag(&tag_name) { + Some(tag) + } else if fetch { + println!("Updating {}", url); + repo.fetch()?; + repo.tag(&tag_name) + } else { + None + }; + + let hash = tag.map(|t| t.target).ok_or_else(|| { + Error::generic(format!("Version {} doesn't exist", version)) + })?; + + let checksum = Checksum::new(hash); + let mut manifest = Manifest::load(&MANIFEST_FILE)?; + + if let Some(existing) = manifest.find_dependency(&url) { + existing.version = version; + existing.checksum = checksum; + } else { + manifest.add_dependency(url, version, checksum); + } + + manifest.save(&MANIFEST_FILE)?; + Ok(0) +} diff --git a/inko/src/command/pkg/init.rs b/inko/src/command/pkg/init.rs new file mode 100644 index 000000000..d3615c965 --- /dev/null +++ b/inko/src/command/pkg/init.rs @@ -0,0 +1,62 @@ +use crate::error::Error; +use crate::options::print_usage; +use crate::pkg::manifest::MANIFEST_FILE; +use getopts::Options; +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +const USAGE: &str = "inko pkg init [OPTIONS] [DIR] + +Create a new package in an existing directory + +Examples: + + inko pkg init + inko pkg init example/"; + +const TEMPLATE: &str = "\ +# This file contains your project's dependencies. For more information, refer +# to https://docs.inko-lang.org/manual/latest/getting-started/modules/"; + +pub(crate) fn run(args: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(args)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + let dir = matches.free.get(0).map(PathBuf::from).unwrap_or_else(|| { + env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) + }); + + if !dir.is_dir() { + return Err(Error::generic(format!( + "The directory {:?} doesn't exist", + dir + ))); + } + + let path = dir.join(MANIFEST_FILE); + + if path.exists() { + return Ok(0); + } + + File::create(&path) + .and_then(|mut f| f.write_all(TEMPLATE.as_bytes())) + .map(|_| 0) + .map_err(|err| { + Error::generic(format!( + "Failed to create {}: {}", + path.display(), + err + )) + }) +} diff --git a/inko/src/command/pkg/remove.rs b/inko/src/command/pkg/remove.rs new file mode 100644 index 000000000..6f611025c --- /dev/null +++ b/inko/src/command/pkg/remove.rs @@ -0,0 +1,41 @@ +use crate::error::Error; +use crate::options::print_usage; +use crate::pkg::manifest::{Manifest, Url, MANIFEST_FILE}; +use getopts::Options; + +const USAGE: &str = "inko pkg remove [OPTIONS] [URI] + +Remove a dependency from the current project. + +This command merely removes the dependency from the manifest. To also remove its +files from your project, along with any unnecessary dependencies, run +`inko pkg sync`. + +Examples: + + inko pkg remove github.com/inko-lang/example"; + +pub(crate) fn run(args: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(args)?; + + if matches.opt_present("h") || matches.free.is_empty() { + print_usage(&options, USAGE); + return Ok(0); + } + + let url = matches + .free + .get(0) + .and_then(|uri| Url::parse(uri)) + .ok_or_else(|| "The package URL is invalid".to_string())?; + + let mut manifest = Manifest::load(&MANIFEST_FILE)?; + + manifest.remove_dependency(&url); + manifest.save(&MANIFEST_FILE)?; + Ok(0) +} diff --git a/ipm/src/command/sync.rs b/inko/src/command/pkg/sync.rs similarity index 81% rename from ipm/src/command/sync.rs rename to inko/src/command/pkg/sync.rs index b230da5b0..5c43632e3 100644 --- a/ipm/src/command/sync.rs +++ b/inko/src/command/pkg/sync.rs @@ -1,8 +1,9 @@ use crate::error::Error; -use crate::git::Repository; -use crate::manifest::{Dependency, Manifest, Url, MANIFEST_FILE}; -use crate::util::{cp_r, data_dir, usage, DEP_DIR}; -use crate::version::{select, Version}; +use crate::options::print_usage; +use crate::pkg::git::Repository; +use crate::pkg::manifest::{Dependency, Manifest, Url, MANIFEST_FILE}; +use crate::pkg::util::{cp_r, data_dir, DEP_DIR}; +use crate::pkg::version::{select, Version}; use getopts::Options; use std::collections::HashMap; use std::collections::HashSet; @@ -16,14 +17,14 @@ const SRC_DIR: &str = "src"; /// The dependant string to use for the root project. const ROOT_DEPENDANT: &str = "Your project"; -const USAGE: &str = "ipm sync [OPTIONS] +const USAGE: &str = "inko pkg sync [OPTIONS] -Installs all necessary dependencies into your project, and removes dependencies -no longer in use. +Install all necessary dependencies, and remove dependencies that are no longer +needed. Examples: - ipm sync"; + inko pkg sync"; #[derive(Clone)] enum Dependant { @@ -37,7 +38,7 @@ struct Package { dependency: Dependency, } -pub(crate) fn run(args: &[String]) -> Result<(), Error> { +pub(crate) fn run(args: &[String]) -> Result { let mut options = Options::new(); options.optflag("h", "help", "Show this help message"); @@ -45,19 +46,20 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { let matches = options.parse(args)?; if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); + print_usage(&options, USAGE); + return Ok(0); } println!("Updating package cache"); let packages = download_packages()?; let versions = select_versions(&packages)?; - let dep_dir = PathBuf::from(DEP_DIR); + let dep_dir = PathBuf::from(DEP_DIR).join(SRC_DIR); remove_dependencies(&dep_dir)?; println!("Installing"); - install_packages(packages, versions, &dep_dir) + install_packages(packages, versions, &dep_dir)?; + Ok(0) } fn download_packages() -> Result, Error> { @@ -112,7 +114,6 @@ fn download_dependency( Some(tag) } else if fetch { println!(" Updating {}", dependency.url); - repo.fetch()?; repo.tag(&tag_name) } else { @@ -120,21 +121,21 @@ fn download_dependency( }; let tag = tag.ok_or_else(|| { - error!( + format!( "The version {} of package {} doesn't exist", dependency.version, url ) })?; repo.checkout(&tag.target).map_err(|err| { - error!( + format!( "Failed to checkout tag {} of package {}: {}", tag_name, url, err ) })?; if tag.target != dependency.checksum.to_string() { - fail!( + format!( "The checksum of {} version {} didn't match. The checksum that is expected is: @@ -153,10 +154,7 @@ maintainer to ensure this is expected. DO NOT PROCEED BLINDLY, as you may be including unexpected or even malicious changes.", - url, - dependency.version, - dependency.checksum, - tag.target + url, dependency.version, dependency.checksum, tag.target ); } @@ -170,19 +168,19 @@ changes.", } } -fn select_versions(packages: &[Package]) -> Result, Error> { - match select(packages.iter().map(|p| &p.dependency)) { - Ok(versions) => Ok(versions), - Err(url) => Err(conflicting_versions_error(url, packages)), - } +fn select_versions( + packages: &[Package], +) -> Result, String> { + select(packages.iter().map(|p| &p.dependency)) + .map_err(|url| conflicting_versions_error(url, packages)) } -fn remove_dependencies(directory: &Path) -> Result<(), Error> { +fn remove_dependencies(directory: &Path) -> Result<(), String> { if directory.is_dir() { println!("Removing existing ./{}", DEP_DIR); - remove_dir_all(&directory).map_err(|err| { - error!("Failed to remove the existing ./{}: {}", DEP_DIR, err) + remove_dir_all(directory).map_err(|err| { + format!("Failed to remove the existing ./{}: {}", DEP_DIR, err) })?; } @@ -193,7 +191,7 @@ fn install_packages( packages: Vec, versions: Vec<(Url, Version)>, directory: &Path, -) -> Result<(), Error> { +) -> Result<(), String> { let repos = packages .into_iter() .map(|pkg| (pkg.dependency.url, pkg.repository)) @@ -207,7 +205,7 @@ fn install_packages( let tag = repo.tag(&tag_name).unwrap(); repo.checkout(&tag.target).map_err(|err| { - error!("Failed to check out {}: {}", tag_name, err) + format!("Failed to check out {}: {}", tag_name, err) })?; cp_r(&repo.path.join(SRC_DIR), directory)?; @@ -216,7 +214,7 @@ fn install_packages( Ok(()) } -fn conflicting_versions_error(url: Url, packages: &[Package]) -> Error { +fn conflicting_versions_error(url: Url, packages: &[Package]) -> String { let reqs: Vec<_> = packages .iter() .filter_map(|pkg| { @@ -238,7 +236,7 @@ fn conflicting_versions_error(url: Url, packages: &[Package]) -> Error { }) .collect(); - error!( + format!( "\ The dependency graph contains conflicting major version requirements for the \ package {}. diff --git a/ipm/src/command/update.rs b/inko/src/command/pkg/update.rs similarity index 78% rename from ipm/src/command/update.rs rename to inko/src/command/pkg/update.rs index d7b92a49a..e5a4241ac 100644 --- a/ipm/src/command/update.rs +++ b/inko/src/command/pkg/update.rs @@ -1,11 +1,14 @@ use crate::error::Error; -use crate::git::Repository; -use crate::manifest::{Checksum, Dependency, Manifest, Url, MANIFEST_FILE}; -use crate::util::{data_dir, usage}; -use crate::version::Version; +use crate::options::print_usage; +use crate::pkg::git::Repository; +use crate::pkg::manifest::{ + Checksum, Dependency, Manifest, Url, MANIFEST_FILE, +}; +use crate::pkg::util::data_dir; +use crate::pkg::version::Version; use getopts::Options; -const USAGE: &str = "ipm update [OPTIONS] [PACKAGE] +const USAGE: &str = "inko pkg update [OPTIONS] [PACKAGE] Update the version requirements of one or more packages to the latest compatible version. This command only updates the entries in the package manifest. @@ -15,11 +18,11 @@ update them to the latest major version, use the -m/--major flag. Examples: - ipm update - ipm update github.com/inko-lang/example - ipm update github.com/inko-lang/example --major"; + inko pkg update + inko pkg update github.com/inko-lang/example + inko pkg update github.com/inko-lang/example --major"; -pub(crate) fn run(args: &[String]) -> Result<(), Error> { +pub(crate) fn run(args: &[String]) -> Result { let mut options = Options::new(); options.optflag("h", "help", "Show this help message"); @@ -28,8 +31,8 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { let matches = options.parse(args)?; if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); + print_usage(&options, USAGE); + return Ok(0); } let major = matches.opt_present("m"); @@ -40,7 +43,10 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { if let Some(dep) = manifest.find_dependency(&url) { vec![dep] } else { - fail!("The package {} isn't listed in {}", url, MANIFEST_FILE); + return Err(Error::generic(format!( + "The package {} isn't listed in {}", + url, MANIFEST_FILE + ))); } } else { manifest.dependencies_mut() @@ -60,7 +66,10 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { let tag_names = repo.version_tag_names(); if tag_names.is_empty() { - fail!("The package {} doesn't have any versions", dep.url); + return Err(Error::generic(format!( + "The package {} doesn't have any versions", + dep.url + ))); } let mut candidates = version_candidates(dep, tag_names, major); @@ -83,7 +92,8 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { dep.checksum = Checksum::new(tag.target); } - manifest.save(&MANIFEST_FILE) + manifest.save(&MANIFEST_FILE)?; + Ok(0) } fn version_candidates( diff --git a/inko/src/command/print.rs b/inko/src/command/print.rs new file mode 100644 index 000000000..f5680d139 --- /dev/null +++ b/inko/src/command/print.rs @@ -0,0 +1,49 @@ +use crate::error::Error; +use crate::options::print_usage; +use compiler::config::Config; +use compiler::target::Target; +use getopts::Options; + +const USAGE: &str = "Usage: inko print [OPTIONS] [ARGS] + +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 + +Examples: + + inko print target # Print the target to STDOUT"; + +pub(crate) fn run(arguments: &[String]) -> Result { + let mut options = Options::new(); + + 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); + } + + match matches.free.get(0).map(|s| s.as_str()) { + Some("target") => { + println!("{}", Target::native()); + Ok(0) + } + Some("runtime") => { + println!("{}", Config::new().runtime.display()); + Ok(0) + } + Some(val) => Err(Error::generic(format!( + "'{}' isn't a valid value to print", + val + ))), + None => Err(Error::generic( + "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 3a38455e9..a9f00ea73 100644 --- a/inko/src/command/run.rs +++ b/inko/src/command/run.rs @@ -1,41 +1,37 @@ -//! Command for compiling and running Inko source code or bytecode images. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; -use compiler::config::{Config as CompilerConfig, IMAGE_EXT}; +use compiler::config::{Config, Mode}; use getopts::{Options, ParsingStyle}; +use std::env::temp_dir; +use std::fs::{create_dir, remove_dir_all}; use std::path::PathBuf; -use vm::config::Config; -use vm::image::Image; -use vm::machine::Machine; +use std::process::Command; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; -const USAGE: &str = "Usage: inko run [OPTIONS] [FILE] +const USAGE: &str = "Usage: inko run [OPTIONS] [FILE] [ARGS] -Compile a source file and its dependencies into a bytecode file, then run it. +Compile a source file and its dependencies into an executable, then run it. -If the file is a source file (its extension is .inko), the file is first -compiled into a bytecode file. If the file is a bytecode file (its extension is -.ibi), the file is run directly. +Running source files is meant for development and scripting purposes, as it +requires compiling source code from scratch every time. When distributing or +deploying your Inko software, you should build it ahead of time using the +\"inko build\" command. -Running source files is meant for development and scripting purposes, and comes -with the overhead of having to run the compiler. For production environments -it's best to compile and run your program separately. For example: +When running files directly, the executable is built in release mode. - inko build hello.inko -o hello.ibi # Produces ./hello.ibi - inko run hello.ibi # Run the bytecode file +Arguments passed _after_ the file to run are passed to the resulting executable. Examples: - inko run hello.inko # Compile and runs the file hello.inko - inko run hello.ibi # Run the bytecode file directly"; + inko run hello.inko # Compile and run the file hello.inko + inko run hello.inko --foo # Passes --foo to the resulting executable"; -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); options.parsing_style(ParsingStyle::StopAtFirstFree); - - options.optflag("h", "help", "Shows this help message"); - + options.optflag("h", "help", "Show this help message"); options.optopt( "f", "format", @@ -57,27 +53,10 @@ pub fn run(arguments: &[String]) -> Result { return Ok(0); } - let input = matches.free.get(0); + let mut config = Config::default(); let arguments = if matches.free.len() > 1 { &matches.free[1..] } else { &[] }; - match input { - Some(input) if input.ends_with(IMAGE_EXT) => { - let config = Config::from_env(); - let image = Image::load_file(config, input).map_err(|e| { - Error::generic(format!( - "Failed to parse bytecode image {}: {}", - input, e - )) - })?; - - return Machine::boot(image, arguments).map_err(Error::generic); - } - _ => {} - } - - let mut config = CompilerConfig::default(); - if let Some(format) = matches.opt_str("f") { config.set_presenter(&format)?; } @@ -86,23 +65,52 @@ pub fn run(arguments: &[String]) -> Result { config.sources.add(path.into()); } + let time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_else(|_| Duration::from_secs(0)) + .as_secs(); + let build_dir = temp_dir().join(format!("inko-run-{}", time)); + + if !build_dir.is_dir() { + create_dir(&build_dir).map_err(|err| { + Error::generic(format!( + "Failed to create {}: {}", + build_dir.display(), + err + )) + })?; + } + + config.build = build_dir.clone(); + config.mode = Mode::Release; + let mut compiler = Compiler::new(config); - let result = compiler.compile_to_memory(input.map(PathBuf::from)); + let file = matches.free.get(0).map(PathBuf::from); + let result = compiler.run(file); compiler.print_diagnostics(); - // This ensures we don't keep the compiler instance around beyond this - // point, as we don't need it from now on. - drop(compiler); - match result { - Ok(bytes) => { - let config = Config::from_env(); - let image = Image::load_bytes(config, bytes).map_err(|e| { - Error::generic(format!("Failed to parse bytecode image: {}", e)) - })?; - - Machine::boot(image, arguments).map_err(Error::generic) + Ok(exe) => { + let status = Command::new(exe) + .args(arguments) + .spawn() + .and_then(|mut child| child.wait()) + .map_err(|err| { + Error::generic(format!( + "Failed to run the executable: {}", + err + )) + }) + .map(|status| status.code().unwrap_or(0)); + + if build_dir.is_dir() { + // If this fails that dosen't matter because temporary files are + // removed upon shutdown anyway. + let _ = remove_dir_all(&build_dir); + } + + status } Err(CompileError::Invalid) => Ok(1), Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), diff --git a/inko/src/command/test.rs b/inko/src/command/test.rs index 3b80a5a5f..b53dbcf6a 100644 --- a/inko/src/command/test.rs +++ b/inko/src/command/test.rs @@ -1,29 +1,26 @@ -//! Command for running unit tests. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; -use compiler::config::Config as CompilerConfig; +use compiler::config::{Config, Output}; use getopts::Options; -use vm::config::Config; -use vm::image::Image; -use vm::machine::Machine; +use std::process::Command; const USAGE: &str = "Usage: inko test [OPTIONS] Compiles and runs unit tests -This command adds your tests directory to the module load path, and runs the -`main.inko` file that resides in this tests directory. +This command compiles your unit tests in ./test, then runs the resulting test +executable. Examples: inko test # Runs all unit tests in ./test"; /// Compiles and runs Inko unit tests. -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); - options.optflag("h", "help", "Shows this help message"); + options.optflag("h", "help", "Show this help message"); let matches = options.parse(arguments)?; @@ -32,34 +29,33 @@ pub fn run(arguments: &[String]) -> Result { return Ok(0); } - let arguments = &matches.free; - let mut config = CompilerConfig::default(); + let mut config = Config::default(); let input = config.main_test_module(); - let tests = config.tests.clone(); - if !tests.is_dir() { + if !config.tests.is_dir() { return Err(Error::generic(format!( "The tests directory {:?} doesn't exist", - tests + config.tests ))); } - config.sources.add(tests); + config.sources.add(config.tests.clone()); + config.output = Output::File("inko-tests".to_string()); let mut compiler = Compiler::new(config); - let result = compiler.compile_to_memory(Some(input)); + let result = compiler.run(Some(input)); compiler.print_diagnostics(); match result { - Ok(bytes) => { - let config = Config::from_env(); - let image = Image::load_bytes(config, bytes).map_err(|e| { - Error::generic(format!("Failed to parse bytecode image: {}", e)) - })?; - - Machine::boot(image, arguments).map_err(Error::generic) - } + Ok(exe) => Command::new(exe) + .args(matches.free) + .spawn() + .and_then(|mut child| child.wait()) + .map_err(|err| { + Error::generic(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)), } diff --git a/inko/src/error.rs b/inko/src/error.rs index b9abe7a2e..1fc44e78c 100644 --- a/inko/src/error.rs +++ b/inko/src/error.rs @@ -3,16 +3,16 @@ use getopts::Fail; use std::io; /// An error produced by a subprocess. -pub struct Error { +pub(crate) struct Error { /// The exit code to use. - pub status: i32, + pub(crate) status: i32, /// An error message to print to STDERR. - pub message: Option, + pub(crate) message: Option, } impl Error { - pub fn generic(message: String) -> Self { + pub(crate) fn generic(message: String) -> Self { Error { status: 1, message: Some(message) } } } diff --git a/inko/src/main.rs b/inko/src/main.rs index ea9573d2e..a6f334b0d 100644 --- a/inko/src/main.rs +++ b/inko/src/main.rs @@ -9,6 +9,7 @@ static A: jemallocator::Jemalloc = jemallocator::Jemalloc; mod command; mod error; mod options; +mod pkg; use crate::command::main; use std::process::exit; diff --git a/inko/src/options.rs b/inko/src/options.rs index 6f4b6dcf0..5be7d4480 100644 --- a/inko/src/options.rs +++ b/inko/src/options.rs @@ -2,7 +2,7 @@ use getopts::Options; /// Prints a usage message for a set of CLI options. -pub fn print_usage(options: &Options, brief: &str) { +pub(crate) fn print_usage(options: &Options, brief: &str) { let out = options.usage_with_format(|opts| { format!( "{}\n\nOptions:\n\n{}\n", diff --git a/ipm/src/git.rs b/inko/src/pkg/git.rs similarity index 78% rename from ipm/src/git.rs rename to inko/src/pkg/git.rs index fcc3d52c4..3e9cee279 100644 --- a/ipm/src/git.rs +++ b/inko/src/pkg/git.rs @@ -1,4 +1,3 @@ -use crate::error::Error; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -16,29 +15,32 @@ pub(crate) struct Tag { } impl Repository { - pub(crate) fn open(path: &Path) -> Result { + pub(crate) fn open(path: &Path) -> Result { if path.is_dir() { Ok(Self { path: path.to_path_buf() }) } else { - fail!("The Git repository at {} doesn't exist", path.display()) + Err(format!( + "The Git repository at {} doesn't exist", + path.display() + )) } } - pub(crate) fn clone(url: &str, path: &Path) -> Result { + pub(crate) fn clone(url: &str, path: &Path) -> Result { run("clone", None, &[OsStr::new(url), path.as_os_str()]) - .map_err(|err| error!("Failed to clone {}: {}", url, err))?; + .map_err(|err| format!("Failed to clone {}: {}", url, err))?; Ok(Self { path: path.to_path_buf() }) } - pub(crate) fn fetch(&mut self) -> Result<(), Error> { + pub(crate) fn fetch(&mut self) -> Result<(), String> { run( "fetch", Some(self.path.as_path()), &[OsStr::new(REMOTE), OsStr::new("--tags")], ) .map_err(|err| { - error!("Failed to update {}: {}", self.path.display(), err) + format!("Failed to update {}: {}", self.path.display(), err) })?; Ok(()) @@ -66,7 +68,7 @@ impl Repository { } } - pub(crate) fn checkout(&self, name: &str) -> Result<(), Error> { + pub(crate) fn checkout(&self, name: &str) -> Result<(), String> { run("checkout", Some(self.path.as_path()), &[OsStr::new(name)])?; Ok(()) } @@ -76,7 +78,7 @@ fn run( command: &str, working_directory: Option<&Path>, arguments: &[&OsStr], -) -> Result { +) -> Result { let mut cmd = Command::new("git"); cmd.arg(command); @@ -92,8 +94,8 @@ fn run( let child = cmd .spawn() - .map_err(|err| error!("Failed to spawn 'git {}': {}", command, err))?; - let output = child.wait_with_output().map_err(|err| error!("{}", err))?; + .map_err(|err| format!("Failed to spawn 'git {}': {}", command, err))?; + let output = child.wait_with_output().map_err(|err| format!("{}", err))?; if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout) @@ -101,12 +103,10 @@ fn run( .trim() .to_string()) } else { - Err(Error::new( - String::from_utf8_lossy(&output.stderr) - .into_owned() - .trim() - .to_string(), - )) + Err(String::from_utf8_lossy(&output.stderr) + .into_owned() + .trim() + .to_string()) } } @@ -136,7 +136,7 @@ mod tests { #[test] fn test_repository_clone() { - let temp = temp_dir().join("ipm-test_repository_clone"); + let temp = temp_dir().join("inko-pkg-test_repository_clone"); let repo = Repository::clone(source_dir().to_str().unwrap(), &temp); assert!(repo.is_ok()); @@ -147,7 +147,7 @@ mod tests { #[test] fn test_repository_fetch() { - let temp = temp_dir().join("ipm-test_repository_fetch"); + let temp = temp_dir().join("inko-pkg-test_repository_fetch"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); @@ -158,7 +158,7 @@ mod tests { #[test] fn test_repository_tag() { - let temp = temp_dir().join("ipm-test_repository_tag"); + let temp = temp_dir().join("inko-pkg-test_repository_tag"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); @@ -171,7 +171,7 @@ mod tests { #[test] fn test_repository_checkout() { - let temp = temp_dir().join("ipm-test_repository_checkout"); + let temp = temp_dir().join("inko-pkg-test_repository_checkout"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); @@ -186,7 +186,8 @@ mod tests { #[test] fn test_repository_version_tag_names() { - let temp = temp_dir().join("ipm-test_repository_version_tag_names"); + let temp = + temp_dir().join("inko-pkg-test_repository_version_tag_names"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); diff --git a/ipm/src/manifest.rs b/inko/src/pkg/manifest.rs similarity index 92% rename from ipm/src/manifest.rs rename to inko/src/pkg/manifest.rs index 36900c684..a5d2fbf93 100644 --- a/ipm/src/manifest.rs +++ b/inko/src/pkg/manifest.rs @@ -1,5 +1,4 @@ -use crate::error::Error; -use crate::version::Version; +use crate::pkg::version::Version; use blake2::{digest::consts::U16, Blake2b, Digest}; use std::fmt; use std::fs::File; @@ -119,22 +118,22 @@ pub(crate) struct Manifest { } impl Manifest { - pub(crate) fn load>(path: &P) -> Result { + pub(crate) fn load>(path: &P) -> Result { let path = path.as_ref(); File::open(path) - .map_err(|e| error!("Failed to read {}: {}", path.display(), e)) + .map_err(|e| format!("Failed to read {}: {}", path.display(), e)) .and_then(|mut file| Self::parse(&mut file)) } - pub(crate) fn parse(stream: &mut R) -> Result { + pub(crate) fn parse(stream: &mut R) -> Result { let reader = BufReader::new(stream); let mut manifest = Self { entries: Vec::new() }; for (index, line) in reader.lines().enumerate() { let lnum = index + 1; let line = line.map_err(|err| { - error!("Failed to read lines from the manifest: {}", err) + format!("Failed to read lines from the manifest: {}", err) })?; let trimmed = line.trim(); @@ -152,25 +151,25 @@ impl Manifest { let chunks: Vec<_> = trimmed.split(' ').collect(); if chunks.len() != 4 { - fail!("The entry on line {} is invalid", lnum); + return Err(format!("The entry on line {} is invalid", lnum)); } // Currently this is the only action we support. if chunks[0] != "require" { - fail!( + return Err(format!( "Expected line {} to start with 'require', not '{}'", - lnum, - chunks[0] - ); + lnum, chunks[0] + )); } - let url = Url::parse(chunks[1]) - .ok_or_else(|| error!("The URI on line {} is invalid", lnum))?; + let url = Url::parse(chunks[1]).ok_or_else(|| { + format!("The URI on line {} is invalid", lnum) + })?; let version = Version::parse(chunks[2]).ok_or_else(|| { - error!("The version on line {} is invalid", lnum) + format!("The version on line {} is invalid", lnum) })?; let checksum = Checksum::parse(chunks[3]).ok_or_else(|| { - error!("The checksum on line {} is invalid", lnum) + format!("The checksum on line {} is invalid", lnum) })?; manifest.entries.push(Entry::Dependency(Dependency { @@ -232,12 +231,12 @@ impl Manifest { .collect() } - pub(crate) fn save>(&self, path: &P) -> Result<(), Error> { + pub(crate) fn save>(&self, path: &P) -> Result<(), String> { let path = path.as_ref(); File::create(path) .and_then(|mut file| file.write_all(self.to_string().as_bytes())) - .map_err(|e| error!("Failed to update {}: {}", path.display(), e)) + .map_err(|e| format!("Failed to update {}: {}", path.display(), e)) } } @@ -309,22 +308,20 @@ mod tests { assert_eq!( Manifest::parse(&mut missing_chunks.as_bytes()), - Err(Error::new("The entry on line 2 is invalid".to_string())) + Err("The entry on line 2 is invalid".to_string()) ); assert_eq!( Manifest::parse(&mut invalid_cmd.as_bytes()), - Err(Error::new( - "Expected line 2 to start with 'require', not 'bla'" - .to_string() - )) + Err("Expected line 2 to start with 'require', not 'bla'" + .to_string()) ); assert_eq!( Manifest::parse(&mut invalid_version.as_bytes()), - Err(Error::new("The version on line 1 is invalid".to_string())) + Err("The version on line 1 is invalid".to_string()) ); assert_eq!( Manifest::parse(&mut invalid_checksum.as_bytes()), - Err(Error::new("The checksum on line 1 is invalid".to_string())) + Err("The checksum on line 1 is invalid".to_string()) ); } diff --git a/inko/src/pkg/mod.rs b/inko/src/pkg/mod.rs new file mode 100644 index 000000000..2c334e345 --- /dev/null +++ b/inko/src/pkg/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod git; +pub(crate) mod manifest; +pub(crate) mod util; +pub(crate) mod version; diff --git a/inko/src/pkg/util.rs b/inko/src/pkg/util.rs new file mode 100644 index 000000000..bd037d50d --- /dev/null +++ b/inko/src/pkg/util.rs @@ -0,0 +1,88 @@ +use std::env; +use std::fs::{copy, create_dir_all, read_dir}; +use std::path::{Path, PathBuf}; + +/// The directory to install dependencies into. +pub(crate) const DEP_DIR: &str = "dep"; + +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")) + .ok_or_else(|| "No data directory could be determined".to_string()) +} + +pub(crate) fn cp_r(source: &Path, target: &Path) -> Result<(), String> { + create_dir_all(target).map_err(|e| e.to_string())?; + + let mut pending = vec![source.to_path_buf()]; + + while let Some(path) = pending.pop() { + let entries = read_dir(&path).map_err(|e| e.to_string())?; + + for entry in entries { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + + if path.is_dir() { + pending.push(path); + continue; + } + + let rel = path.strip_prefix(source).unwrap(); + let target = target.join(rel); + let dir = target.parent().unwrap(); + + create_dir_all(dir) + .map_err(|e| format!("Failed to create {:?}: {}", dir, e))?; + + if target.is_file() { + return Err(format!( + "Failed to copy {} to {} as the target file already exists", + path.display(), + target.display() + )); + } + + copy(&path, &target).map_err(|error| { + format!( + "Failed to copy {} to {}: {}", + path.to_string_lossy(), + target.to_string_lossy(), + error + ) + })?; + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + use std::fs::remove_dir_all; + + #[test] + fn test_cp_r() { + let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let temp = env::temp_dir().join("inko-pkg-test_cp_r"); + + assert!(cp_r(&src.join("src"), &temp).is_ok()); + assert!(temp.join("pkg").join("util.rs").is_file()); + + remove_dir_all(temp).unwrap(); + } +} diff --git a/ipm/src/version.rs b/inko/src/pkg/version.rs similarity index 98% rename from ipm/src/version.rs rename to inko/src/pkg/version.rs index f4e195474..2baf1596c 100644 --- a/ipm/src/version.rs +++ b/inko/src/pkg/version.rs @@ -1,4 +1,4 @@ -use crate::manifest::{Dependency, Url}; +use crate::pkg::manifest::{Dependency, Url}; use std::cmp::{Ord, Ordering}; use std::collections::HashMap; use std::fmt; @@ -131,7 +131,7 @@ pub(crate) fn select<'a>( #[cfg(test)] mod tests { use super::*; - use crate::manifest::Checksum; + use crate::pkg::manifest::Checksum; #[test] fn test_version_parse() { diff --git a/ipm/Cargo.toml b/ipm/Cargo.toml deleted file mode 100644 index 5051fb8ae..000000000 --- a/ipm/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "ipm" -version = "0.10.0" # VERSION -authors = ["Yorick Peterse "] -edition = "2021" -license = "MPL-2.0" - -[dependencies] -getopts = "^0.2" -blake2 = "^0.10" diff --git a/ipm/src/command/add.rs b/ipm/src/command/add.rs deleted file mode 100644 index 29e7e8464..000000000 --- a/ipm/src/command/add.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::error::Error; -use crate::git::Repository; -use crate::manifest::{Checksum, Manifest, Url, MANIFEST_FILE}; -use crate::util::{data_dir, usage}; -use crate::version::Version; -use getopts::Options; - -const USAGE: &str = "ipm add [OPTIONS] [URL] [VERSION] - -Add a package to the manifest in the current working directory. - -This command doesn't resolve any sub-dependencies or install the package into -your project, for that you need to run `ipm sync`. - -Examples: - - ipm add github.com/inko-lang/example 1.2.3"; - -pub(crate) fn run(args: &[String]) -> Result<(), Error> { - let mut options = Options::new(); - - options.optflag("h", "help", "Show this help message"); - - let matches = options.parse(args)?; - - if matches.opt_present("h") || matches.free.is_empty() { - usage(&options, USAGE); - return Ok(()); - } - - if matches.free.len() != 2 { - fail!("You must specify a package and version to add"); - } - - let url = matches - .free - .get(0) - .and_then(|uri| Url::parse(uri)) - .ok_or_else(|| error!("The package URL is invalid"))?; - - let version = matches - .free - .get(1) - .and_then(|uri| Version::parse(uri)) - .ok_or_else(|| error!("The package version is invalid"))?; - - let dir = data_dir()?.join(url.directory_name()); - let (mut repo, fetch) = if dir.is_dir() { - (Repository::open(&dir)?, true) - } else { - (Repository::clone(&url.to_string(), &dir)?, false) - }; - - if fetch { - repo.fetch()?; - } - - let tag_name = version.tag_name(); - let tag = if let Some(tag) = repo.tag(&tag_name) { - Some(tag) - } else if fetch { - println!("Updating {}", url); - repo.fetch()?; - repo.tag(&tag_name) - } else { - None - }; - - let hash = tag - .map(|t| t.target) - .ok_or_else(|| error!("Version {} doesn't exist", version))?; - - let checksum = Checksum::new(&hash); - let mut manifest = Manifest::load(&MANIFEST_FILE)?; - - if let Some(existing) = manifest.find_dependency(&url) { - existing.version = version; - existing.checksum = checksum; - } else { - manifest.add_dependency(url, version, checksum); - } - - manifest.save(&MANIFEST_FILE) -} diff --git a/ipm/src/command/init.rs b/ipm/src/command/init.rs deleted file mode 100644 index b17d7267a..000000000 --- a/ipm/src/command/init.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::error::Error; -use crate::manifest::MANIFEST_FILE; -use crate::util::usage; -use getopts::Options; -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -const USAGE: &str = "ipm init [OPTIONS] [DIR] - -Create a new package in an existing directory - -Examples: - - ipm init - ipm init example/"; - -const TEMPLATE: &str = "\ -# This file contains your project's dependencies. For more information, refer -# to https://docs.inko-lang.org/manual/latest/getting-started/modules/"; - -pub(crate) fn run(args: &[String]) -> Result<(), Error> { - let mut options = Options::new(); - - options.optflag("h", "help", "Show this help message"); - - let matches = options.parse(args)?; - - if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); - } - - let dir = if matches.free.is_empty() { - env::current_dir()? - } else { - PathBuf::from(&matches.free[0]) - }; - - if !dir.is_dir() { - fail!("The directory {:?} doesn't exist", dir); - } - - let path = dir.join(MANIFEST_FILE); - - if path.exists() { - Ok(()) - } else { - let mut file = File::create(&path) - .map_err(|e| error!("Failed to create {:?}: {}", path, e))?; - - file.write_all(TEMPLATE.as_bytes()) - .map_err(|e| error!("Failed to write to {:?}: {}", path, e)) - } -} diff --git a/ipm/src/command/main.rs b/ipm/src/command/main.rs deleted file mode 100644 index da2057404..000000000 --- a/ipm/src/command/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::command::add; -use crate::command::init; -use crate::command::remove; -use crate::command::sync; -use crate::command::update; -use crate::error::Error; -use crate::util::usage; -use getopts::{Options, ParsingStyle}; -use std::env::args; - -const USAGE: &str = "ipm [OPTIONS] [COMMAND] - -ipm is Inko's package manager, used to install and manage dependencies of -your project. - -Commands: - - init Create a new package - add Add or update a dependency - remove Remove a dependency - sync Download and install dependencies - update Update all dependencies to the latest version - -Examples: - - ipm init - ipm add github.com/hello/world 1.2.3"; - -pub(crate) fn run() -> Result<(), Error> { - let args: Vec<_> = args().collect(); - let mut options = Options::new(); - - options.parsing_style(ParsingStyle::StopAtFirstFree); - options.optflag("h", "help", "Show this help message"); - options.optflag("v", "version", "Print the version number"); - - let matches = options.parse(&args[1..])?; - - if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); - } - - if matches.opt_present("v") { - println!("ipm version {}", env!("CARGO_PKG_VERSION")); - return Ok(()); - } - - match matches.free.get(0).map(|s| s.as_str()) { - Some("init") => init::run(&matches.free[1..]), - Some("add") => add::run(&matches.free[1..]), - Some("remove") => remove::run(&matches.free[1..]), - Some("sync") => sync::run(&matches.free[1..]), - Some("update") => update::run(&matches.free[1..]), - Some(cmd) => fail!("The command {:?} is invalid", cmd), - None => sync::run(&[]), - } -} diff --git a/ipm/src/command/mod.rs b/ipm/src/command/mod.rs deleted file mode 100644 index 746b068d5..000000000 --- a/ipm/src/command/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub(crate) mod add; -pub(crate) mod init; -pub(crate) mod main; -pub(crate) mod remove; -pub(crate) mod sync; -pub(crate) mod update; diff --git a/ipm/src/command/remove.rs b/ipm/src/command/remove.rs deleted file mode 100644 index cd5f00fc3..000000000 --- a/ipm/src/command/remove.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::error::Error; -use crate::manifest::{Manifest, Url, MANIFEST_FILE}; -use crate::util::usage; -use getopts::Options; - -const USAGE: &str = "ipm remove [OPTIONS] [URI] - -Remove a dependency from the manifest. - -This command doesn't remove the dependency from your project, for that you need -to run `ipm sync`. - -Examples: - - ipm remove github.com/inko-lang/example"; - -pub(crate) fn run(args: &[String]) -> Result<(), Error> { - let mut options = Options::new(); - - options.optflag("h", "help", "Show this help message"); - - let matches = options.parse(args)?; - - if matches.opt_present("h") || matches.free.is_empty() { - usage(&options, USAGE); - return Ok(()); - } - - let url = matches - .free - .get(0) - .and_then(|uri| Url::parse(uri)) - .ok_or_else(|| error!("The package URL is invalid"))?; - - let mut manifest = Manifest::load(&MANIFEST_FILE)?; - - manifest.remove_dependency(&url); - manifest.save(&MANIFEST_FILE) -} diff --git a/ipm/src/error.rs b/ipm/src/error.rs deleted file mode 100644 index 5731e5483..000000000 --- a/ipm/src/error.rs +++ /dev/null @@ -1,32 +0,0 @@ -use getopts::Fail; -use std::fmt; -use std::io; - -#[derive(Debug, PartialEq, Eq)] -pub struct Error { - message: String, -} - -impl Error { - pub fn new>(message: S) -> Self { - Error { message: message.into() } - } -} - -impl From for Error { - fn from(fail: Fail) -> Self { - Error { message: fail.to_string() } - } -} - -impl From for Error { - fn from(error: io::Error) -> Self { - Error { message: error.to_string() } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.message) - } -} diff --git a/ipm/src/macros.rs b/ipm/src/macros.rs deleted file mode 100644 index 50ddc1711..000000000 --- a/ipm/src/macros.rs +++ /dev/null @@ -1,11 +0,0 @@ -macro_rules! fail { - ($message:expr $(,$arg:expr)*) => { - return Err(Error::new(format!($message $(,$arg)*))) - }; -} - -macro_rules! error { - ($message:expr $(,$arg:expr)*) => { - Error::new(format!($message $(,$arg)*)) - }; -} diff --git a/ipm/src/main.rs b/ipm/src/main.rs deleted file mode 100644 index f1c437e00..000000000 --- a/ipm/src/main.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -mod macros; - -mod command; -mod error; -mod git; -mod manifest; -mod util; -mod version; - -use crate::util::red; -use command::main; -use std::process::exit; - -fn main() { - if let Err(err) = main::run() { - eprintln!("{} {}", red("error:"), err); - exit(1); - } -} diff --git a/ipm/src/util.rs b/ipm/src/util.rs deleted file mode 100644 index 71a467fd5..000000000 --- a/ipm/src/util.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::error::Error; -use getopts::Options; -use std::env; -use std::fs::{copy, create_dir_all, read_dir}; -use std::path::{Path, PathBuf}; - -const DIR_NAME: &str = "ipm"; - -/// The directory to install dependencies into. -pub(crate) const DEP_DIR: &str = "dep"; - -fn windows_local_appdata() -> Option { - env::var_os("LOCALAPPDATA") - .filter(|v| !v.is_empty()) - .map(PathBuf::from) - .or_else(|| home_dir().map(|h| h.join("AppData").join("Local"))) -} - -fn home_dir() -> Option { - let var = if cfg!(windows) { - env::var_os("USERPROFILE") - } else { - env::var_os("HOME") - }; - - var.filter(|v| !v.is_empty()).map(PathBuf::from) -} - -pub(crate) fn usage(options: &Options, summary: &str) { - let out = options.usage_with_format(|opts| { - format!( - "{}\n\nOptions:\n\n{}", - summary, - opts.collect::>().join("\n") - ) - }); - - println!("{}", out); -} - -pub(crate) fn data_dir() -> Result { - let base = if cfg!(windows) { - windows_local_appdata() - } else 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(DIR_NAME)) - .ok_or_else(|| error!("No data directory could be determined")) -} - -pub(crate) fn cp_r(source: &Path, target: &Path) -> Result<(), Error> { - create_dir_all(target)?; - - let mut pending = vec![source.to_path_buf()]; - - while let Some(path) = pending.pop() { - let entries = read_dir(&path)?; - - for entry in entries { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - pending.push(path); - continue; - } - - let rel = path.strip_prefix(&source).unwrap(); - let target = target.join(rel); - let dir = target.parent().unwrap(); - - create_dir_all(&dir) - .map_err(|e| error!("Failed to create {:?}: {}", dir, e))?; - - if target.is_file() { - fail!( - "Failed to copy {} to {} as the target file already exists", - path.display(), - target.display() - ); - } - - copy(&path, &target).map_err(|error| { - error!( - "Failed to copy {} to {}: {}", - path.to_string_lossy(), - target.to_string_lossy(), - error - ) - })?; - } - } - - Ok(()) -} - -pub(crate) fn red>(message: S) -> String { - if cfg!(windows) { - message.into() - } else { - format!("\x1b[1m\x1b[31m{}\x1b[0m\x1b[0m", message.into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::env; - use std::fs::remove_dir_all; - - #[test] - fn test_cp_r() { - let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let temp = env::temp_dir().join("ipm-test_cp_r"); - - assert!(cp_r(&src.join("src"), &temp).is_ok()); - assert!(temp.join("util.rs").is_file()); - - remove_dir_all(temp).unwrap(); - } -} diff --git a/libstd/src/std/clone.inko b/libstd/src/std/clone.inko deleted file mode 100644 index 387b6a587..000000000 --- a/libstd/src/std/clone.inko +++ /dev/null @@ -1,7 +0,0 @@ -# Cloning of objects. - -# A type that can be cloned into a copy of itself. -trait pub Clone { - # Returns a copy of `self`. - fn pub clone -> Self -} diff --git a/libstd/src/std/ffi.inko b/libstd/src/std/ffi.inko deleted file mode 100644 index b7f91a92f..000000000 --- a/libstd/src/std/ffi.inko +++ /dev/null @@ -1,543 +0,0 @@ -# Foreign function interface for interfacing with C code. -# -# This module provides various types and methods for calling C code from Inko -# code. -# -# # Limitations -# -# * C code calling back into Inko code isn't supported -# * Only builtin types (e.g. Int and String) can be converted from/to C types -# * Memory management of C types has to be done manually (e.g. using `free()`) -# * Variadic arguments aren't supported -import std::clone::Clone -import std::cmp::Equal -import std::drop::Drop -import std::fmt::(Format, Formatter) -import std::hash::Hash -import std::index::(Index, SetIndex) -import std::int::ToInt -import std::process::(panic) -import std::set::Set - -# The address used for NULL pointers. -let NULL_ADDRESS = 0x0 - -# The size (in bytes) of an Inko object header. -let HEADER_SIZE = 16 - -# A pointer to a C value. -class pub Pointer { - # The raw pointer. - let @raw: Any - - # Returns a `Pointer` wrapping the given raw pointer. - fn pub static new(raw: Any) -> Self { - Self { @raw = raw } - } - - # Creates a pointer using the given memory address. - fn pub static from_address(address: Int) -> Self { - Self { @raw = _INKO.ffi_pointer_from_address(address) } - } - - # Returns a NULL `Pointer`. - fn pub static null -> Self { - from_address(NULL_ADDRESS) - } - - # Returns `true` if this `Pointer` is a NULL pointer. - fn pub null? -> Bool { - address == NULL_ADDRESS - } - - # Returns the address of this `Pointer`. - fn pub address -> Int { - _INKO.ffi_pointer_address(@raw) - } - - # Reads the value of a pointer into a certain type. - # - # The `type` argument specifies the `Type` of data that is being read. - # - # The `offset` argument is used to specify an offset in bytes (relative to the - # pointer) to read from. - fn pub read(type: Type, offset: Int) -> Any { - _INKO.ffi_pointer_read(@raw, type.to_int, offset) - } - - # Writes a value to the pointer. - # - # The `type` argument specifies the `Type` of data that is being written. The - # `offset` argument can be used to specify an offset in bytes (relative to - # the pointer) to write to. - fn pub mut write(type: Type, offset: Int, value: Any) { - _INKO.ffi_pointer_write(@raw, type.to_int, offset, value) - } - - # Returns the underlying raw pointer. - fn pub raw -> Any { - @raw - } - - # Returns a pointer to the raw pointer wrapped by `self` (aka a pointer - # pointer). - fn pub pointer -> Pointer { - let addr = _INKO.ffi_pointer_address(self) + HEADER_SIZE - - Pointer.from_address(addr) - } -} - -impl Clone for Pointer { - fn pub clone -> Pointer { - Pointer.from_address(address) - } -} - -impl Equal for Pointer { - # Returns `true` if `self` and the given `Pointer` point to the same memory - # address. - fn pub ==(other: ref Self) -> Bool { - address == other.address - } -} - -impl Format for Pointer { - fn pub fmt(formatter: mut Formatter) { - formatter.write("Pointer(0x{address.to_base16})") - } -} - -# A dynamically loaded C library. -class pub Library { - let @lib: Any - - # Dynamically loads a C library. - # - # The `names` argument is an `Array` of library names to use for loading the - # library. These names are used in order to find the corresponding library. - # - # The names in this `Array` can either be the library name with extension, - # such as `'libc.so.6'`, or absolute paths such as `'/usr/lib/libc.so.6'`. - fn pub static new(names: ref Array[String]) -> Option[Self] { - let lib = _INKO.ffi_library_open(names) - - if _INKO.is_undefined(lib) { - Option.None - } else { - Option.Some(Self { @lib = lib }) - } - } - - # Returns a `Pointer` pointing to the address of the given variable. - fn pub variable(name: String) -> Option[Pointer] { - let ptr = _INKO.ffi_pointer_attach(@lib, name) - - if _INKO.is_undefined(ptr) { - Option.None - } else { - Option.Some(Pointer { @raw = ptr }) - } - } - - # Loads a C function from the given library. - # - # # Panics - # - # This method panics if the argument types or return type are invalid. - fn pub function( - name: String, - arguments: Array[Type], - returns: Type - ) -> Option[Function] { - let args = arguments.into_iter.map fn (type) { type.to_int }.to_array - let func = _INKO.ffi_function_attach(@lib, name, args, returns.to_int) - - if _INKO.is_undefined(func) { - Option.None - } else { - Option.Some(Function { @func = func }) - } - } -} - -impl Drop for Library { - fn mut drop { - _INKO.ffi_library_drop(@lib) - } -} - -# A C function that can be called from Inko. -class pub Function { - let @func: Any - - # Calls the function without providing any arguments. - fn pub call0 -> Any { - _INKO.ffi_function_call(@func) - } - - # Calls the function with a single argument. - fn pub call1(arg0: ref Any) -> Any { - _INKO.ffi_function_call(@func, arg0) - } - - # Calls the function with two arguments. - fn pub call2(arg1: ref Any, arg2: ref Any) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2) - } - - # Calls the function with three arguments. - fn pub call3(arg1: ref Any, arg2: ref Any, arg3: ref Any) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3) - } - - # Calls the function with four arguments. - fn pub call4( - arg1: ref Any, arg2: ref Any, arg3: ref Any, arg4: ref Any - ) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4) - } - - # Calls the function with five arguments. - fn pub call5( - arg1: ref Any, arg2: ref Any, arg3: ref Any, arg4: ref Any, arg5: ref Any - ) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4, arg5) - } - - # Calls the function with six arguments. - fn pub call6( - arg1: ref Any, - arg2: ref Any, - arg3: ref Any, - arg4: ref Any, - arg5: ref Any, - arg6: ref Any, - ) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4, arg5, arg6) - } -} - -impl Drop for Function { - fn mut drop { - _INKO.ffi_function_drop(@func) - } -} - -# A member in a structure. -class Member { - let @name: String - let @type: Type - let @offset: Int -} - -# The members, alignment, and other information used for building a C -# structure. -class pub Layout { - # The members of this layout, mapped to their names. - let @members: Map[String, Member] - - # The size (in bytes) of this layout. - let pub @size: Int - - # The alignment to use for all fields. This equals the alignment of the - # largest field. - let pub @alignment: Int -} - -# A C structure. -class pub Struct { - let @layout: Layout - - # The pointer to the C address that contains the structure. - let pub @pointer: Pointer - - fn pub static new(pointer: Pointer, layout: Layout) -> Self { - Self { @pointer = pointer, @layout = layout } - } - - # Returns the size in bytes of this `Struct`. - fn pub size -> Int { - @layout.size - } - - # Returns the alignment of the members in this `Struct`. - fn pub alignment -> Int { - @layout.alignment - } -} - -impl Index[String, Any] for Struct { - # Reads the value of the given struct member. - # - # This method will panic if the given member does not exist. - fn pub index(index: String) -> Any { - let member = @layout.members[index] - - @pointer.read(type: member.type.clone, offset: member.offset) - } -} - -impl SetIndex[String, Any] for Struct { - # Writes a value to a struct member. - fn pub mut set_index(index: String, value: Any) { - let member = @layout.members[index] - let type = member.type.clone - let offset = member.offset - - @pointer.write(type, offset, value) - } -} - -# An object used for constructing an immutable `Layout`. -class pub LayoutBuilder { - # The names of all members, in the order they are defined in. - let @members: Array[String] - - # The types of all members, in the order they are defined in. - let @types: Array[Type] - - # A Map that tracks which members have already been defined. - let @existing: Set[String] - - # The alignment of the largest field. - let @alignment: Int - - # A boolean indicating if members should be padded or not. - let @padding: Bool - - fn pub static new -> Self { - Self { - @members = [], - @types = [], - @existing = Set.new, - @alignment = 0, - @padding = true - } - } - - # Disables the padding of members. - fn pub mut no_padding { - @padding = false - @alignment = 1 - } - - # Defines a new member. - # - # Trying to redefine a member will result in a panic. - fn pub mut field(name: String, value: Type) { - if @existing.contains?(name) { - panic("The field '{name}' is already defined") - } - - # If padding is enabled we determine the structure's alignment based on the - # alignment of the largest field. - if @padding and { value.alignment > @alignment } { - @alignment = value.alignment - } - - @existing.insert(name) - @members.push(name) - @types.push(value) - } - - # Creates a new `Layout` based on the current state of this builder. - fn pub move into_layout -> Layout { - if @padding { - into_layout_with_padding - } else { - into_layout_without_padding - } - } - - fn move into_layout_with_padding -> Layout { - let members = Map.new - let mut size = 0 - let mut offset = 0 - let mut remaining_in_hole = @alignment - let mut index = 0 - - while index < @members.length { - let name = @members[index] - let type = @types[index] - let type_align = type.alignment - - # If the value is too great to fit into the current hole, we need to add - # padding to the current hole, then start over at the next hole. - if type_align > remaining_in_hole { - let padding = @alignment - remaining_in_hole - - size += padding - offset += padding - - remaining_in_hole = @alignment - } - - members[name] = - Member { @name = name, @type = type.clone, @offset = offset } - - remaining_in_hole -= type_align - - size += type_align - offset += type_align - - if remaining_in_hole == 0 { remaining_in_hole = @alignment } - - index += 1 - } - - Layout { @members = members, @alignment = @alignment, @size = size } - } - - fn move into_layout_without_padding -> Layout { - let members = Map.new - let mut offset = 0 - let mut index = 0 - - while index < @members.length { - let name = @members[index] - let type = @types[index] - - members[name] = - Member { @name = name, @type = type.clone, @offset = offset } - - offset += type.alignment - index += 1 - } - - Layout { @members = members, @alignment = @alignment, @size = offset } - } -} - -# An identifier for a C type. -class pub enum Type { - # The C type `void`. - case Void - - # The C type `void *`. - case Pointer - - # The C type `double`. - case F64 - - # The C type `float`. - case F32 - - # The C type `char`. - case I8 - - # The C type `short`. - case I16 - - # The C type `int`. - case I32 - - # The C type `long`. - case I64 - - # The C type `unsigned char`. - case U8 - - # The C type `unsigned short`. - case U16 - - # The C type `unsigned int`. - case U32 - - # The C type `unsigned long`. - case U64 - - # The C type `size_t` - case SizeT - - # The C type `char *`. - case String - - # The C type `char *`, but for when you need a `ByteArray` instead of a - # `String`. - case ByteArray - - # Returns the size of this type in bytes. - fn pub size -> Int { - _INKO.ffi_type_size(to_int) - } - - # Returns the alignment of this type in bytes. - fn pub alignment -> Int { - _INKO.ffi_type_alignment(to_int) - } -} - -impl ToInt for Type { - fn pub to_int -> Int { - match self { - case Void -> 0 - case Pointer -> 1 - case F64 -> 2 - case F32 -> 3 - case I8 -> 4 - case I16 -> 5 - case I32 -> 6 - case I64 -> 7 - case U8 -> 8 - case U16 -> 9 - case U32 -> 10 - case U64 -> 11 - case String -> 12 - case ByteArray -> 13 - case SizeT -> 14 - } - } -} - -impl Clone for Type { - fn pub clone -> Self { - match self { - case Void -> Type.Void - case Pointer -> Type.Pointer - case F64 -> Type.F64 - case F32 -> Type.F32 - case I8 -> Type.I8 - case I16 -> Type.I16 - case I32 -> Type.I32 - case I64 -> Type.I64 - case U8 -> Type.U8 - case U16 -> Type.U16 - case U32 -> Type.U32 - case U64 -> Type.U64 - case SizeT -> Type.SizeT - case String -> Type.String - case ByteArray -> Type.ByteArray - } - } -} - -impl Equal for Type { - fn pub ==(other: ref Self) -> Bool { - to_int == other.to_int - } -} - -impl Format for Type { - fn pub fmt(formatter: mut Formatter) { - let write = match self { - case Void -> 'void' - case Pointer -> 'void*' - case F64 -> 'double' - case F32 -> 'float' - case I8 -> 'char' - case I16 -> 'short' - case I32 -> 'int' - case I64 -> 'long' - case U8 -> 'unsignhed char' - case U16 -> 'unsigned short' - case U32 -> 'unsigned int' - case U64 -> 'unsigned long' - case SizeT -> 'size_t' - case String -> 'String' - case ByteArray -> 'ByteArray' - } - - formatter.write(write) - } -} diff --git a/libstd/src/std/fs/dir.inko b/libstd/src/std/fs/dir.inko deleted file mode 100644 index 1d5520f13..000000000 --- a/libstd/src/std/fs/dir.inko +++ /dev/null @@ -1,117 +0,0 @@ -# Manipulating directories on a filesystem. -import std::fs::path::Path -import std::io::Error -import std::string::ToString - -# Creates a new empty directory at the given path. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to create the directory. -# 2. The directory already exists. -# -# # Examples -# -# Creating a directory: -# -# import std::fs::dir -# -# try! dir.create('/tmp/test') -fn pub create(path: ref ToString) !! Error { - try _INKO.directory_create(path.to_string) else (e) throw Error.from_int(e) -} - -# Creates a new empty directory at the given path, while also creating any -# intermediate directories. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to create the directory. -# -# # Examples -# -# Creating a directory: -# -# import std::fs::dir -# -# try! dir.create_all('/tmp/foo/bar/test') -fn pub create_all(path: ref ToString) !! Error { - let str = path.to_string - - try _INKO.directory_create_recursive(str) else (e) throw Error.from_int(e) -} - -# Removes the directory at the given path. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to remove the directory. -# 2. The directory is not empty. -# 3. The directory does not exist. -# -# # Examples -# -# Removing a directory: -# -# import std::fs::dir -# -# try! dir.create('/tmp/test') -# try! dir.remove('/tmp/test') -fn pub remove(path: ref ToString) !! Error { - try _INKO.directory_remove(path.to_string) else (e) throw Error.from_int(e) -} - -# Removes the directory and its contents at the given path. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to remove the directory. -# 2. The directory does not exist. -# -# # Examples -# -# Removing a directory: -# -# import std::fs::dir -# -# try! dir.create_all('/tmp/foo/bar') -# try! dir.remove_all('/tmp/foo') -fn pub remove_all(path: ref ToString) !! Error { - let str = path.to_string - - try _INKO.directory_remove_recursive(str) else (e) throw Error.from_int(e) -} - -# Returns an `Array` containing the paths to the contents of the directory. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to read the contents of the -# directory. -# 2. The path does not point to a valid directory. -# -# # Examples -# -# Listing the contents of a directory: -# -# import std::fs::dir -# -# let paths = try! dir.list('.') -# -# paths[0].to_string # => 'README.md' -fn pub list(path: ref ToString) !! Error -> Array[Path] { - let paths = - try _INKO.directory_list(path.to_string) else (e) throw Error.from_int(e) - - paths.into_iter.map fn (path) { Path.new(path) }.to_array -} diff --git a/libstd/src/std/fs/file.inko b/libstd/src/std/fs/file.inko deleted file mode 100644 index 2d7d33cbf..000000000 --- a/libstd/src/std/fs/file.inko +++ /dev/null @@ -1,261 +0,0 @@ -# Types and methods for manipulating files on a filesystem. -# -# Rather than using a single "File" type for all different file modes, Inko uses -# three separate file types: -# -# - `ReadOnlyFile`: read-only file operations -# - `WriteOnlyFile`: write-only file operations -# - `ReadWriteFile`: read-write file operations -# -# Using different types per file mode allows for a type-safe file API. -# -# Files are automatically closed when they are dropped. Any errors that may -# occur when closing a file are ignored. -import std::drop::Drop -import std::fs::path::(IntoPath, Path) -import std::io::(Error, Read, Seek, Size, Write) -import std::string::ToString - -# Removes the file for the given file path. -# -# # Examples -# -# Removing a file: -# -# import std::fs::file::(self, WriteOnlyFile) -# -# let handle = try! WriteOnlyFile.new('/tmp/test.txt') -# -# try! handle.write('hello') -# try! file.remove('/tmp/test.txt') -fn pub remove(path: ref ToString) !! Error { - try _INKO.file_remove(path.to_string) else (e) throw Error.from_int(e) -} - -# Copies a file from the source destination to the target destination, -# returning the number of copied bytes. -# -# # Examples -# -# Copying a file: -# -# import std::fs::file::(self, WriteOnlyFile) -# -# let handle = try! WriteOnlyFile.new('/tmp/test.txt') -# -# try! handle.write('hello') -# try! file.copy(from: '/tmp/test.txt', to: '/tmp/test2.txt') -fn pub copy(from: ref ToString, to: ref ToString) !! Error -> Int { - try { - _INKO.file_copy(from.to_string, to.to_string) - } else (e) { - throw Error.from_int(e) - } -} - -# A file that can only be used for reads. -class pub ReadOnlyFile { - # The path of the file. - let pub @path: Path - - # The internal file descriptor. - let @fd: Any - - # Returns a new `ReadOnlyFile`. - # - # # Examples - # - # Opening a file in read-only mode: - # - # import std::fs::file::ReadOnlyFile - # - # let handle = try! ReadOnlyFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_read_only(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } -} - -impl Drop for ReadOnlyFile { - fn mut drop { - _INKO.file_drop(@fd) - } -} - -impl Read for ReadOnlyFile { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try _INKO.file_read(@fd, into, size) else (e) throw Error.from_int(e) - } -} - -impl Seek for ReadOnlyFile { - fn pub mut seek(position: Int) !! Error -> Int { - try _INKO.file_seek(@fd, position) else (err) throw Error.from_int(err) - } -} - -impl Size for ReadOnlyFile { - fn pub size !! Error -> Int { - try _INKO.file_size(@path.to_string) else (err) throw Error.from_int(err) - } -} - -# A file that can only be used for writes. -class pub WriteOnlyFile { - # The path of the file. - let pub @path: Path - - # The internal file descriptor. - let @fd: Any - - # Opens a file in write-only mode. - # - # # Examples - # - # import std::fs::file::WriteOnlyFile - # - # let file = try! WriteOnlyFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_write_only(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } - - # Opens a file in append-only mode. - # - # # Examples - # - # import std::fs::file::WriteOnlyFile - # - # let file = try! WriteOnlyFile.append('/dev/null') - fn pub static append(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_append_only(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } -} - -impl Drop for WriteOnlyFile { - fn mut drop { - _INKO.file_drop(@fd) - } -} - -impl Write for WriteOnlyFile { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try _INKO.file_write_bytes(@fd, bytes) else (e) throw Error.from_int(e) - } - - fn pub mut write_string(string: String) !! Error -> Int { - try _INKO.file_write_string(@fd, string) else (e) throw Error.from_int(e) - } - - fn pub mut flush !! Error { - try _INKO.file_flush(@fd) else (e) throw Error.from_int(e) - } -} - -impl Seek for WriteOnlyFile { - fn pub mut seek(position: Int) !! Error -> Int { - try _INKO.file_seek(@fd, position) else (e) throw Error.from_int(e) - } -} - -# A file that can be used for both reads and writes. -class pub ReadWriteFile { - # The path of the file. - let pub @path: Path - - # The internal file descriptor. - let @fd: Any - - # Opens a file for both reading and writing: - # - # # Examples - # - # import std::fs::file::ReadWriteFile - # - # let handle = try! ReadWriteFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_read_write(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } - - # Opens a file for both reading and appending: - # - # # Examples - # - # import std::fs::file::ReadWriteFile - # - # let handle = try! ReadWriteFile.append('/dev/null') - fn pub static append(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_read_append(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } -} - -impl Drop for ReadWriteFile { - fn mut drop { - _INKO.file_drop(@fd) - } -} - -impl Read for ReadWriteFile { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try _INKO.file_read(@fd, into, size) else (e) throw Error.from_int(e) - } -} - -impl Write for ReadWriteFile { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try _INKO.file_write_bytes(@fd, bytes) else (e) throw Error.from_int(e) - } - - fn pub mut write_string(string: String) !! Error -> Int { - try _INKO.file_write_string(@fd, string) else (e) throw Error.from_int(e) - } - - fn pub mut flush !! Error { - try _INKO.file_flush(@fd) else (err) throw Error.from_int(err) - } -} - -impl Seek for ReadWriteFile { - fn pub mut seek(position: Int) !! Error -> Int { - try _INKO.file_seek(@fd, position) else (e) throw Error.from_int(e) - } -} - -impl Size for ReadWriteFile { - fn pub size !! Error -> Int { - try _INKO.file_size(@path.to_string) else (e) throw Error.from_int(e) - } -} diff --git a/libstd/src/std/index.inko b/libstd/src/std/index.inko deleted file mode 100644 index 174ce5562..000000000 --- a/libstd/src/std/index.inko +++ /dev/null @@ -1,54 +0,0 @@ -# Reading and writing of values using indexes. - -# A type that can be indexed. -trait pub Index[K, R] { - # Returns the value of the given index. - # - # # Panics - # - # This method may panic if the index doesn't exist. - fn pub index(index: K) -> R -} - -# A type that can be indexed mutably. -# -# The `index` method of the `Index` type takes its receiver as an immutable -# reference. This limits it to returning either newly created values, or -# immutable references to data stored in the receiver. -# -# The `IndexMut` trait defines the method `index_mut`, which takes its receiver -# as a mutable reference, allowing the method to return mutable references to -# data stored in the receiver. -# -# In other words: `Index` is used for when you just want to get data out of a -# type, while `IndexMut` is used if you also want to mutate that data. -trait pub IndexMut[K, R] { - # Returns a mutable reference to the index' value. - # - # # Panics - # - # This method may panic if the index doesn't exist. - fn pub mut index_mut(index: K) -> R -} - -# A type that allows writing a value to an index. -trait pub SetIndex[K, V] { - # Sets the given index to the given value. - # - # # Panics - # - # This method may panic if the index is out of bounds, though this depends on - # the type that implements this method. - fn pub mut set_index(index: K, value: V) -} - -# Checks if `index` is in the range of zero up to (but excluding) `length`. -# -# # Panics -# -# This method panics if the index is out of bounds. -fn pub bounds_check(index: Int, length: Int) { - if index >= 0 and index < length { return } - - _INKO.panic("The index {index} is out of bounds (length: {length})") -} diff --git a/libstd/src/std/process.inko b/libstd/src/std/process.inko deleted file mode 100644 index f0f8e309d..000000000 --- a/libstd/src/std/process.inko +++ /dev/null @@ -1,92 +0,0 @@ -# Lightweight Inko processes. -# -# Processes are lightweight, isolated tasks that can run concurrently. Processes -# use a type-safe form of message passing, instead of sharing memory. Futures -# are used whenever a message is sent and the sender wants to resolve the result -# at a later point in time. -import std::drop::Drop -import std::time::Duration - -# Terminates the program with an error message. -# -# A panic is an unrecoverable error meant to guard against code bugs. For -# runtime errors, use `try` and `throw` instead. -fn pub panic(message: String) -> Never { - _INKO.panic(message) -} - -# Suspends the current process for at least the given duration. -# -# The actual time the process is suspended for may be larger than the given -# duration. -# -# If the specified duration is less than or equal to zero, the process is -# rescheduled immediately. -fn pub sleep(time: ref Duration) { - _INKO.process_suspend(time.to_nanos) -} - -# Suspends the current process until one or more futures in the `pending` array -# are ready. -# -# The return value is an array containing all futures that are ready. -# -# After calling this method, `pending` only contain futures that aren't ready. -# -# If the `pending` array is empty, this method returns immediately. -# -# This method is useful when you have multiple futures to resolve, but want to -# act as soon as any futures are ready, instead of waiting until all of them are -# ready. -# -# # Performance -# -# Due to how polling works, performance of this method is `O(n)` where `n` is -# the number of futures in the `pending` array. -fn pub poll[T, E](pending: mut Array[Future[T, E]]) -> Array[Future[T, E]] { - _INKO.future_poll(pending) as Array[Future[T, E]] -} - -# The result of an asynchronous operation. -# -# A future can be resolved into its value using either `await` or `await_for`, -# both taking over ownership of the future. Both may block the calling process, -# but they won't block the OS thread the process is running on. -# -# If the message that the future belongs to threw a value, awaiting the future -# will re-throw said value. -class builtin Future[T, E] { - # Waits for and returns the result of a message. - # - # If no value is present at the time this method is called, the calling - # processes is suspended until a result is produced. - fn pub move await !! E -> T { - let val = try _INKO.future_get(self) else (e) { throw e as E } - - val as T - } - - # Waits for the result or until the timeout expires. - # - # Similar to `Future.await`, this method blocks the calling process until - # either a value is produced or the timeout expires. - # - # If the given duration is less than or equal to zero, the process is - # rescheduled (more or less, but not necessarily) immediately after this call. - fn pub move await_for(time: ref Duration) !! E -> Option[T] { - let nanos = time.to_nanos - let val = try { _INKO.future_get_for(self, nanos) } else (e) { throw e as E } - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val as T) } - } -} - -impl Drop for Future { - fn mut drop { - let result = _INKO.future_drop(self) - - if _INKO.is_undefined(result) { return } - - result as T - } -} diff --git a/libstd/src/std/stdio.inko b/libstd/src/std/stdio.inko deleted file mode 100644 index 50b174070..000000000 --- a/libstd/src/std/stdio.inko +++ /dev/null @@ -1,74 +0,0 @@ -# STDIN, STDOUT, and STDERR streams. -import std::io::(Error, Read, Write) - -# The standard input stream of the current OS process. -class pub STDIN { - # Returns a new handle to the input stream. - fn pub static new -> Self { - Self {} - } -} - -impl Read for STDIN { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try _INKO.stdin_read(into, size) else (error) throw Error.from_int(error) - } -} - -# The standard output stream of the current OS process. -# -# To make it easier to write to STDOUT, any errors produced while writing are -# ignored. -class pub STDOUT { - # Returns a new handle to the output stream. - fn pub static new -> Self { - Self {} - } -} - -impl Write for STDOUT { - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { - try _INKO.stdout_write_bytes(bytes) else 0 - } - - fn pub mut write_string(string: String) -> Int { - try _INKO.stdout_write_string(string) else 0 - } - - fn pub mut flush { - try _INKO.stdout_flush else return - } - - fn pub mut print(string: String) -> Int { - write_string(string) + write_string("\n") - } -} - -# The standard error stream of the current OS process. -# -# To make it easier to write to STDERR, any errors produced while writing are -# ignored. -class pub STDERR { - # Returns a new handle to the error stream. - fn pub static new -> Self { - Self {} - } -} - -impl Write for STDERR { - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { - try _INKO.stderr_write_bytes(bytes) else 0 - } - - fn pub mut write_string(string: String) -> Int { - try _INKO.stderr_write_string(string) else 0 - } - - fn pub mut flush { - try _INKO.stderr_flush else return - } - - fn pub mut print(string: String) -> Int { - write_string(string) + write_string("\n") - } -} diff --git a/libstd/test/helpers.inko b/libstd/test/helpers.inko deleted file mode 100644 index 772950b6c..000000000 --- a/libstd/test/helpers.inko +++ /dev/null @@ -1,114 +0,0 @@ -import std::drop::(drop) -import std::env -import std::fmt::(DefaultFormatter, Format, Formatter) -import std::fs::file::(WriteOnlyFile, remove) -import std::fs::path::Path -import std::hash::(Hash, Hasher) -import std::map::DefaultHasher -import std::sys::(Command, Stream) - -fn pub hash(value: ref Hash) -> Int { - let hasher: Hasher = DefaultHasher.new - - value.hash(hasher) - hasher.hash -} - -fn pub fmt(value: ref Format) -> String { - let fmt: Formatter = DefaultFormatter.new - - value.fmt(fmt) - fmt.into_string -} - -class pub Script { - let @id: Int - let @path: Path - let @code: String - let @cmd: Command - let @stdin: Option[String] - let @imports: Array[String] - - fn pub static new(id: Int, code: String) -> Self { - let path = env.temporary_directory.join("inko_test_script_{id}.inko") - let exe = try! env.executable - let cmd = Command.new(exe) - - cmd.argument('run') - cmd.argument('-f') - cmd.argument('plain') - cmd.argument(path.to_string) - cmd.stdin(Stream.Null) - cmd.stderr(Stream.Piped) - cmd.stdout(Stream.Piped) - - Self { - @id = id, - @path = path, - @code = code, - @cmd = cmd, - @stdin = Option.None, - @imports = ['std::stdio::(STDIN, STDERR, STDOUT)'] - } - } - - fn pub move import(module: String) -> Self { - @imports.push(module) - self - } - - fn pub move stdin(input: String) -> Self { - @stdin = Option.Some(input) - - @cmd.stdin(Stream.Piped) - self - } - - fn pub move argument(value: String) -> Self { - @cmd.argument(value) - self - } - - fn pub move variable(name: String, value: String) -> Self { - @cmd.variable(name, value) - self - } - - fn pub move run -> String { - let file = try! WriteOnlyFile.new(@path.clone) - - @imports.into_iter.each fn (mod) { - try! file.write_string("import {mod}\n") - } - - try! file.write_string( -"class async Main \{ - fn async main \{ - {@code} - } -} -" - ) - - try! file.flush - - let exe = try! env.executable - let child = try! @cmd.spawn - let bytes = ByteArray.new - - match @stdin { - case Some(input) -> try! child.stdin.write_string(input) - case _ -> 0 - } - - try! child.wait - try! child.stdout.read_all(bytes) - try! child.stderr.read_all(bytes) - - drop(file) - - try! remove(@path) - - bytes.into_string - } -} diff --git a/libstd/test/std/net/test_socket.inko b/libstd/test/std/net/test_socket.inko deleted file mode 100644 index f6d3f317a..000000000 --- a/libstd/test/std/net/test_socket.inko +++ /dev/null @@ -1,1461 +0,0 @@ -import helpers::(fmt) -import std::drop::Drop -import std::env -import std::fs::file -import std::fs::path::Path -import std::net::ip::(IpAddress, Ipv4Address, Ipv6Address) -import std::net::socket::( - Socket, SocketAddress, TcpServer, TcpClient, Type, UdpSocket, UnixAddress, - UnixDatagram, UnixServer, UnixSocket, UnixClient -) -import std::string::ToString -import std::sys -import std::test::Tests -import std::time::(Duration, Instant) - -class SocketPath { - let @path: Path - - fn static pair(id: Int) -> (Self, Self) { - let p1 = env.temporary_directory.join("inko-test-{id}-1.sock") - let p2 = env.temporary_directory.join("inko-test-{id}-2.sock") - - try file.remove(p1) else nil - try file.remove(p2) else nil - - (Self { @path = p1 }, Self { @path = p2 }) - } - - fn static new(id: Int) -> Self { - let path = env.temporary_directory.join("inko-test-{id}.sock") - - try file.remove(path) else nil - - Self { @path = path } - } -} - -impl ToString for SocketPath { - fn pub to_string -> String { - @path.to_string - } -} - -impl Drop for SocketPath { - fn mut drop { - try file.remove(@path) else nil - } -} - -fn pub tests(t: mut Tests) { - t.test('SocketAddress.new') fn (t) { - let addr = SocketAddress.new(address: '127.0.0.1', port: 1234) - - t.equal(addr.ip, Option.Some(IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)))) - t.equal(addr.port, 1234) - } - - t.test('SocketAddress.==') fn (t) { - let addr1 = SocketAddress.new(address: '127.0.0.1', port: 1234) - let addr2 = SocketAddress.new(address: '127.0.0.1', port: 4567) - - t.equal(addr1, addr1) - t.not_equal(addr1, addr2) - } - - t.test('SocketAddress.fmt') fn (t) { - t.equal( - fmt(SocketAddress.new(address: '127.0.0.1', port: 1234)), - '127.0.0.1:1234' - ) - } - - t.test('Socket.ipv4') fn (t) { - t.no_throw fn { try Socket.ipv4(Type.STREAM) } - t.no_throw fn { try Socket.ipv4(Type.DGRAM) } - } - - t.test('Socket.ipv6') fn (t) { - t.no_throw fn { try Socket.ipv6(Type.STREAM) } - t.no_throw fn { try Socket.ipv6(Type.DGRAM) } - } - - t.test('Socket.bind') fn (t) { - t.throw fn { - let sock = try! Socket.ipv4(Type.STREAM) - - try sock.bind(ip: 'foo', port: 0) - } - - t.no_throw fn { - let sock = try! Socket.ipv4(Type.STREAM) - - try sock.bind(ip: '0.0.0.0', port: 0) - } - } - - t.test('Socket.connect') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream1 = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - t.no_throw fn { - try stream1.connect(ip: addr.ip.unwrap, port: addr.port.clone) - } - - let stream2 = try! Socket.ipv4(Type.STREAM) - - t.throw fn { - # connect() may not immediately raise a "connection refused" error, due - # to connect() being non-blocking. In this case the "connection refused" - # error is raised on the next operation. - # - # Since a connect() _might_ still raise the error right away, we have to - # both connect and try to use the socket in some way. - try stream2.connect(ip: '0.0.0.0', port: 40_000) - try stream2.write_string('ping') - } - } - - t.test('Socket.listen') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.bind(ip: '0.0.0.0', port: 0) - - t.no_throw fn { try socket.listen } - } - - t.test('Socket.accept') fn (t) { - let server = try! Socket.ipv4(Type.STREAM) - let client = try! Socket.ipv4(Type.STREAM) - - try! server.bind(ip: '127.0.0.1', port: 0) - try! server.listen - - let addr = try! server.local_address - - try! client.connect(addr.address.clone, addr.port.clone) - - let connection = try! server.accept - - t.equal(try! connection.local_address, try! server.local_address) - } - - t.test('Socket.send_string_to') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - # On Windows one can not use sendto() with 0.0.0.0 being the target IP - # address, so instead we bind (and send to) 127.0.0.1. - try! socket.bind(ip: '127.0.0.1', port: 0) - - let send_to = try! socket.local_address - let buffer = ByteArray.new - - try! socket - .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) - - t.equal(try! socket.read(into: buffer, size: 4), 4) - t.equal(buffer.into_string, 'ping') - } - - t.test('Socket.send_bytes_to') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - # On Windows one can not use sendto() with 0.0.0.0 being the target IP - # address, so instead we bind (and send to) 127.0.0.1. - try! socket.bind(ip: '127.0.0.1', port: 0) - - let send_to = try! socket.local_address - let buffer = ByteArray.new - - try! socket.send_bytes_to( - bytes: 'ping'.to_byte_array, - ip: send_to.address, - port: send_to.port.clone - ) - - t.equal(try! socket.read(into: buffer, size: 4), 4) - t.equal(buffer.into_string, 'ping') - } - - t.test('Socket.receive_from') fn (t) { - let listener = try! Socket.ipv4(Type.DGRAM) - let client = try! Socket.ipv4(Type.DGRAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! client.bind(ip: '127.0.0.1', port: 0) - - let send_to = try! listener.local_address - - try! client - .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.local_address with an unbound socket') fn (t) { - if sys.windows? { - let socket = try! Socket.ipv4(Type.DGRAM) - - t.throw fn { try socket.local_address } - } else { - let socket = try! Socket.ipv4(Type.DGRAM) - let address = try! socket.local_address - - t.equal(address.address, '0.0.0.0') - t.equal(address.port, 0) - } - } - - t.test('Socket.local_address with a bound socket') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - try! socket.bind(ip: '127.0.0.1', port: 0) - - let local_address = try! socket.local_address - - t.equal(local_address.address, '127.0.0.1') - t.true(local_address.port > 0) - } - - t.test('Socket.peer_address with a disconnected socket') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - t.throw fn { try socket.peer_address } - } - - t.test('Socket.peer_address with a connected socket') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let client = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! client.connect(ip: addr.address, port: addr.port.clone) - - t.equal(try! client.peer_address, addr) - } - - t.test('Socket.ttl') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.ttl = 10 - - t.equal(try! socket.ttl, 10) - } - - t.test('Socket.only_ipv6?') fn (t) { - let socket = try! Socket.ipv6(Type.STREAM) - - try! socket.only_ipv6 = true - - t.true(try! socket.only_ipv6?) - } - - t.test('Socket.no_delay?') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.no_delay = true - - t.true(try! socket.no_delay?) - } - - t.test('Socket.broadcast?') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - try! socket.broadcast = true - - t.true(try! socket.broadcast?) - } - - t.test('Socket.linger') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - let duration = Duration.from_secs(5) - - try! socket.linger = duration - - t.equal(try! socket.linger, duration) - } - - t.test('Socket.receive_buffer_size') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.receive_buffer_size = 256 - - t.true(try! { socket.receive_buffer_size } >= 256) - } - - t.test('Socket.send_buffer_size') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.send_buffer_size = 256 - - t.true(try! { socket.send_buffer_size } >= 256) - } - - # Obtaining the TCP keepalive setting fails on Windows. See - # https://github.com/alexcrichton/socket2-rs/issues/24 for more information. - if sys.windows?.false? { - t.test('Socket.keepalive') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.keepalive = true - - t.true(try! socket.keepalive) - } - } - - t.test('Socket.reuse_adress') fn (t) { - let socket = try! Socket.ipv6(Type.DGRAM) - - try! socket.reuse_address = true - - t.true(try! socket.reuse_address) - } - - t.test('Socket.reuse_port') fn (t) { - let socket = try! Socket.ipv6(Type.DGRAM) - - try! socket.reuse_port = true - - # Windows does not support SO_REUSEPORT, so the return value is always - # `false`. - if sys.windows? { - t.false(try! socket.reuse_port) - } else { - t.true(try! socket.reuse_port) - } - } - - t.test('Socket.shutdown_read') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - try! stream.shutdown_read - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes.length, 0) - } - - t.test('Socket.shutdown_write') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('Socket.shutdown shuts down the writing half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - - t.throw fn { - try stream.shutdown - try stream.write_string('ping') - } - } - - t.test('Socket.shutdown shuts down the reading half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - try! stream.shutdown - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes.length, 0) - } - - t.test('Socket.try_clone') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - t.no_throw fn { try socket.try_clone } - } - - t.test('Socket.read') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - try! socket.bind(ip: '127.0.0.1', port: 0) - - let addr = try! socket.local_address - let bytes = ByteArray.new - - try! socket.send_string_to('ping', ip: addr.address, port: addr.port.clone) - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.write_bytes') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - - let written = try! stream.write_bytes('ping'.to_byte_array) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.write_string') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - - let written = try! stream.write_string('ping') - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.flush') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - t.equal(socket.flush, nil) - } - - t.test('UdpSocket.new') fn (t) { - t.no_throw fn { - try UdpSocket - .new(ip: IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)), port: 0) - } - - t.no_throw fn { - try! UdpSocket - .new(ip: IpAddress.V6(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0)), port: 0) - } - } - - t.test('UdpSocket.connect') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket1 = try! UdpSocket.new(ip: ip.clone, port: 0) - let socket2 = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! socket2.local_address - - t.no_throw fn { - try socket1.connect(ip: addr.address, port: addr.port.clone) - } - } - - t.test('UdpSocket.send_string_to') fn (t) { - let socket = try! UdpSocket - .new(ip: IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)), port: 0) - - let addr = try! socket.local_address - - try! socket.send_string_to('ping', ip: addr.address, port: addr.port.clone) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.send_bytes_to') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! socket.local_address - - try! socket.send_bytes_to( - 'ping'.to_byte_array, - ip: addr.address, - port: addr.port.clone - ) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.receive_from') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let listener = try! UdpSocket.new(ip: ip.clone, port: 0) - let client = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! listener.local_address - - try! client - .send_string_to('ping', ip: addr.address, port: addr.port.clone) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.local_address') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - let local_address = try! socket.local_address - - t.equal(local_address.address, '127.0.0.1') - t.true(local_address.port > 0) - } - - t.test('UdpSocket.try_clone') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - - t.no_throw fn { try socket.try_clone } - } - - t.test('UdpSocket.read') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! socket.local_address - - try! socket.send_string_to('ping', ip: addr.address, port: addr.port.clone) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.to_string, 'ping') - } - - t.test('UdpSocket.write_bytes') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let server_socket = try! UdpSocket.new(ip: ip.clone, port: 0) - let client_socket = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! server_socket.local_address - - try! client_socket.connect(ip: addr.address, port: addr.port.clone) - try! client_socket.write_bytes('ping'.to_byte_array) - - let bytes = ByteArray.new - - t.equal(try! server_socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.flush') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - - t.equal(socket.flush, nil) - } - - t.test('TcpClient.new') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - t.no_throw fn { - try TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - } - } - - t.test('TcpClient.with_timeout') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - t.no_throw fn { - try TcpClient.with_timeout( - ip: addr.ip.unwrap, - port: addr.port.clone, - timeout_after: Duration.from_secs(2) - ) - } - - t.throw fn { - # This address is unroutable and so the connect times out. - try TcpClient.with_timeout( - ip: IpAddress.v4(192, 168, 0, 0), - port: addr.port.clone, - timeout_after: Duration.from_micros(500) - ) - } - } - - t.test('TcpClient.local_address') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let local_addr = try! stream.local_address - - t.equal(local_addr.address, '127.0.0.1') - t.true(local_addr.port > 0) - } - - t.test('TcpClient.peer_address') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let peer_addr = try! stream.peer_address - - t.equal(peer_addr.address, addr.address) - t.equal(peer_addr.port, addr.port) - } - - t.test('TcpClient.read') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let bytes = ByteArray.new - let client = try! listener.accept - - try! client.write_string('ping') - try! stream.read(into: bytes, size: 4) - - t.equal(bytes.into_string, 'ping') - } - - t.test('TcpClient.write_bytes') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_bytes('ping'.to_byte_array), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('TcpClient.write_string') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_string('ping'), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('TcpClient.flush') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - t.equal(stream.flush, nil) - } - - t.test('TcpClient.shutdown_read') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown_read - - let bytes = ByteArray.new - - try! stream.read(into: bytes, size: 4) - - t.equal(bytes, ByteArray.new) - } - - t.test('TcpClient.shutdown_write') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('TcpClient.shutdown shuts down the writing half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown - - t.throw fn { try stream.write_string('ping') } - } - - t.test('TcpClient.shutdown shuts down the reading half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown - - let bytes = ByteArray.new - - let message = try! stream.read(into: bytes, size: 4) - - t.equal(bytes, ByteArray.new) - } - - t.test('TcpClient.try_clone') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let client = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - t.no_throw fn { try client.try_clone } - } - - t.test('TcpServer.new') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)) - - t.no_throw fn { try TcpServer.new(ip: ip.clone, port: 0) } - - let listener = try! TcpServer.new(ip: ip, port: 0) - let addr = try! listener.local_address - - t.no_throw fn { - try TcpServer.new(ip: addr.ip.unwrap, port: addr.port.clone) - } - } - - t.test('TcpServer.accept') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let listener = try! TcpServer.new(ip: ip, port: 0) - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let connection = try! listener.accept - - t.equal(try! connection.local_address, try! stream.peer_address) - } - - t.test('TcpServer.local_address') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let listener = try! TcpServer.new(ip: ip, port: 0) - let addr = try! listener.local_address - - t.equal(addr.address, '127.0.0.1') - t.true(addr.port > 0) - } - - t.test('TcpServer.try_clone') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let server = try! TcpServer.new(ip: ip, port: 0) - - t.no_throw fn { try server.try_clone } - } - - t.test('UnixAddress.to_path') fn (t) { - t.equal(UnixAddress.new('foo.sock').to_path, Option.Some('foo.sock'.to_path)) - t.true(UnixAddress.new("\0foo").to_path.none?) - t.true(UnixAddress.new('').to_path.none?) - } - - t.test('UnixAddress.to_string') fn (t) { - t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') - t.equal(UnixAddress.new("\0foo").to_string, "\0foo") - t.equal(UnixAddress.new('').to_string, '') - } - - t.test('UnixAddress.abstract?') fn (t) { - t.false(UnixAddress.new('').abstract?) - t.false(UnixAddress.new('foo.sock').abstract?) - t.true(UnixAddress.new("\0foo").abstract?) - } - - t.test('UnixAddress.unnamed?') fn (t) { - t.false(UnixAddress.new('foo.sock').unnamed?) - t.false(UnixAddress.new("\0foo").unnamed?) - t.true(UnixAddress.new('').unnamed?) - } - - t.test('UnixAddress.fmt') fn (t) { - t.equal(fmt(UnixAddress.new('foo.sock')), 'foo.sock') - t.equal(fmt(UnixAddress.new("\0foo")), '@foo') - t.equal(fmt(UnixAddress.new('')), 'unnamed') - } - - t.test('UnixAddress.==') fn (t) { - t.equal(UnixAddress.new('a.sock'), UnixAddress.new('a.sock')) - t.not_equal(UnixAddress.new('a.sock'), UnixAddress.new('b.sock')) - } - - t.test('UnixAddress.to_string') fn (t) { - t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') - t.equal(UnixAddress.new("\0foo").to_string, "\0foo") - t.equal(UnixAddress.new('').to_string, '') - } - - if sys.unix? { unix_tests(t) } -} - -fn unix_tests(t: mut Tests) { - t.test('UnixSocket.new') fn (t) { - t.no_throw fn { try UnixSocket.new(Type.DGRAM) } - t.no_throw fn { try UnixSocket.new(Type.STREAM) } - } - - t.test('UnixSocket.bind') fn (t) { - let socket1 = try! UnixSocket.new(Type.STREAM) - let socket2 = try! UnixSocket.new(Type.STREAM) - let path = SocketPath.new(t.id) - - t.no_throw fn { try socket1.bind(path) } - t.throw fn { try socket2.bind(path) } - - if sys.linux? { - let socket = try! UnixSocket.new(Type.STREAM) - - t.no_throw fn { try socket.bind("\0inko-test-{t.id}") } - } - } - - t.test('UnixSocket.connect') fn (t) { - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - let path = SocketPath.new(t.id) - - t.throw fn { try stream.connect(path) } - - try! listener.bind(path) - try! listener.listen - - t.no_throw fn { try stream.connect(path) } - - if sys.linux? { - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - let addr = "\0inko-test-{t.id}" - - try! listener.bind(addr) - try! listener.listen - - t.no_throw fn { try stream.connect(addr) } - } - } - - t.test('UnixSocket.listen') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.STREAM) - - try! socket.bind(path) - - t.no_throw fn { try socket.listen } - } - - t.test('UnixSocket.accept') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - - let client = try! listener.accept - - t.equal(try! client.peer_address, try! stream.local_address) - } - - t.test('UnixSocket.send_string_to') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - try! socket.send_string_to('ping', path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.send_bytes_to') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - try! socket.send_bytes_to('ping'.to_byte_array, path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.receive_from') fn (t) { - let pair = SocketPath.pair(t.id) - let listener = try! UnixSocket.new(Type.DGRAM) - let client = try! UnixSocket.new(Type.DGRAM) - - try! listener.bind(pair.0) - try! client.bind(pair.1) - try! client.send_string_to('ping', pair.0) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.local_address with an unbound socket') fn (t) { - let socket = try! UnixSocket.new(Type.DGRAM) - let address = try! socket.local_address - - t.equal(address, UnixAddress.new('')) - } - - t.test('UnixSocket.local_address with a bound socket') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - - t.equal(try! socket.local_address, UnixAddress.new(path.to_string)) - } - - t.test('UnixSocket.peer_address with a disconnected socket') fn (t) { - let socket = try! UnixSocket.new(Type.DGRAM) - - t.throw fn { try socket.peer_address } - } - - t.test('UnixSocket.peer_address with a connected socket') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let client = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! client.connect(path) - - t.equal(try! client.peer_address, try! listener.local_address) - } - - t.test('UnixSocket.read') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - try! socket.send_string_to('ping', path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.write_bytes') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - - let written = try! stream.write_bytes('ping'.to_byte_array) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.write_string') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - - let written = try! stream.write_string('ping') - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.flush') fn (t) { - let socket = try! UnixSocket.new(Type.STREAM) - - t.equal(socket.flush, nil) - } - - t.test('UnixSocket.receive_buffer_size') fn (t) { - let socket = try! UnixSocket.new(Type.STREAM) - - try! socket.receive_buffer_size = 256 - - t.true(try! { socket.receive_buffer_size } >= 256) - } - - t.test('UnixSocket.send_buffer_size') fn (t) { - let socket = try! UnixSocket.new(Type.STREAM) - - try! socket.send_buffer_size = 256 - - t.true(try! { socket.send_buffer_size } >= 256) - } - - t.test('UnixSocket.shutdown_read') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown_read - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixSocket.shutdown_write') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixSocket.shutdown shuts down the writing half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixSocket.shutdown shuts down the reading half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixSocket.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.STREAM) - - t.no_throw fn { try socket.try_clone } - } - - t.test('UnixDatagram.new') fn (t) { - let path = SocketPath.new(t.id) - - t.no_throw fn { try UnixDatagram.new(path) } - t.throw fn { try UnixDatagram.new(path) } - } - - t.test('UnixDatagram.connect') fn (t) { - let pair = SocketPath.pair(t.id) - let socket1 = try! UnixDatagram.new(pair.0) - let socket2 = try! UnixDatagram.new(pair.1) - - t.no_throw fn { try socket2.connect(pair.0) } - } - - t.test('UnixDatagram.send_to') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - try! socket.send_string_to('ping', address: path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.receive_from') fn (t) { - let pair = SocketPath.pair(t.id) - let listener = try! UnixDatagram.new(pair.0) - let client = try! UnixDatagram.new(pair.1) - - try! client.send_string_to('ping', pair.0) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.local_address') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - t.equal(try! socket.local_address, UnixAddress.new(path.to_string)) - } - - t.test('UnixDatagram.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - t.no_throw fn { try socket.try_clone } - } - - t.test('UnixDatagram.read') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - try! socket.send_string_to('ping', address: path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.write_bytes') fn (t) { - let pair = SocketPath.pair(t.id) - let server = try! UnixDatagram.new(pair.0) - let client = try! UnixDatagram.new(pair.1) - - try! client.connect(pair.0) - - let bytes = ByteArray.new - - t.equal(try! client.write_bytes('ping'.to_byte_array), 4) - t.equal(try! server.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.flush') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - t.equal(socket.flush, nil) - } - - t.test('UnixClient.new') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - t.no_throw fn { try UnixClient.new(path) } - } - - t.test('UnixClient.with_timeout') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - # Unlike IP sockets there's no unroutable address we can use, so at best we - # can assert the following doesn't time out. This means this test is mostly - # a smoke test, unlikely to actually fail unless our runtime is bugged. - t.no_throw fn { - try UnixClient - .with_timeout(address: path, timeout_after: Duration.from_secs(5)) - } - } - - t.test('UnixClient.local_address') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - t.equal(try! stream.local_address, UnixAddress.new('')) - } - - t.test('UnixClient.peer_address') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - t.equal(try! stream.peer_address, UnixAddress.new(path.to_string)) - } - - t.test('UnixClient.read') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - - try! connection.write_string('ping') - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixClient.write_bytes') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_bytes('ping'.to_byte_array), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixClient.write_string') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_string('ping'), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixClient.flush') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - t.equal(stream.flush, nil) - } - - t.test('UnixClient.shutdown_read') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown_read - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixClient.shutdown_write') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixClient.shutdown shuts down the writing half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixClient.shutdown shuts down the reading half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixClient.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let client = try! UnixClient.new(path) - - t.no_throw fn { try client.try_clone } - } - - t.test('UnixServer.new') fn (t) { - let path = SocketPath.new(t.id) - - t.no_throw fn { try UnixServer.new(path) } - } - - t.test('UnixServer.accept') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixServer.new(path) - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - - t.equal(try! connection.local_address, try! stream.peer_address) - } - - t.test('UnixServer.local_address') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixServer.new(path) - let addr = try! listener.local_address - - t.equal(addr, UnixAddress.new(path.to_string)) - } - - t.test('UnixServer.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let server = try! UnixServer.new(path) - - t.no_throw fn { try server.try_clone } - } -} diff --git a/libstd/test/std/test_env.inko b/libstd/test/std/test_env.inko deleted file mode 100644 index b274d01ce..000000000 --- a/libstd/test/std/test_env.inko +++ /dev/null @@ -1,92 +0,0 @@ -import helpers::Script -import std::env -import std::test::Tests - -fn pub tests(t: mut Tests) { - t.test('env.get') fn (t) { - let code = "STDOUT.new.write_string(env.get('INKO_TEST').unwrap_or('?'))" - let present = - Script.new(t.id, code).variable('INKO_TEST', 'foo').import('std::env').run - let missing = Script.new(t.id, code).import('std::env').run - - t.equal(present, 'foo') - t.equal(missing, '?') - } - - t.test('env.variables') fn (t) { - let code = ' - let out = STDOUT.new - let vars = env.variables - - out.print(vars["INKO_FOO"]) - out.print(vars["INKO_BAR"]) - ' - - let output = Script - .new(t.id, code) - .variable('INKO_FOO', 'foo') - .variable('INKO_BAR', 'bar') - .import('std::env') - .run - - t.equal(output, "foo\nbar\n") - } - - t.test('env.home_directory') fn (t) { - # Home directories are optional, and even if they're set the actual path may - # not exist. As such there's not really anything we can test for, other than - # asserting the path isn't empty. - match env.home_directory { - case Some(path) -> t.true(path.to_string.size > 0) - case _ -> {} - } - } - - t.test('env.working_directory') fn (t) { - let path = try! env.working_directory - - t.true(path.directory?) - } - - t.test('env.working_directory=') fn (t) { - let tmp = env.temporary_directory.to_string - let code = ' - try! env.working_directory = env.get("INKO_PWD").unwrap - - STDOUT.new.write_string(try! { env.working_directory }.to_string) - ' - - let output = Script - .new(t.id, code) - .variable('INKO_PWD', tmp.clone) - .import('std::env') - .run - - t.equal(output, tmp) - } - - t.test('env.arguments') fn (t) { - let code = ' - let out = STDOUT.new - let args = env.arguments - - out.print(args[0]) - out.print(args[1]) - ' - - let output = Script - .new(t.id, code) - .argument('foo') - .argument('bar') - .import('std::env') - .run - - t.equal(output, "foo\nbar\n") - } - - t.test('env.executable') fn (t) { - let path = try! env.executable - - t.true(path.file?) - } -} diff --git a/libstd/test/std/test_ffi.inko b/libstd/test/std/test_ffi.inko deleted file mode 100644 index 5910f0d19..000000000 --- a/libstd/test/std/test_ffi.inko +++ /dev/null @@ -1,285 +0,0 @@ -import std::drop::Drop -import std::ffi::(Function, LayoutBuilder, Library, Pointer, Struct, Type) -import std::test::Tests - -fn libc_paths -> Array[String] { - [ - # Linux, sometimes a GNU ld script - 'libc.so', - - # Linux, glibc 2.x - 'libc.so.6', - - # CI uses Ubuntu, which puts libc here - '/lib/x86_64-linux-gnu/libc.so.6', - - # Mac OS - 'libSystem.dylib', - - # Windows - 'msvcrt.dll' - ] -} - -class Mem { - let @ptr: Pointer - let @free: Function - - fn static alloc(libc: ref Library, size: Int) -> Self { - let calloc = libc - .function( - 'calloc', - arguments: [Type.SizeT, Type.SizeT], - returns: Type.Pointer - ) - .unwrap - - let free = libc - .function('free', arguments: [Type.Pointer], returns: Type.Void) - .unwrap - - let ptr = Pointer.new(calloc.call2(1, size)) - - if ptr.null? { panic("calloc(1, {size}) failed") } - - Self { @ptr = ptr, @free = free } - } -} - -impl Drop for Mem { - fn mut drop { - @free.call1(@ptr.raw) - } -} - -fn pub tests(t: mut Tests) { - t.test('Pointer.from_address') fn (t) { - t.equal(Pointer.from_address(0x4).address, 0x4) - } - - t.test('Pointer.null') fn (t) { - t.equal(Pointer.null.address, 0x0) - } - - t.test('Pointer.null?') fn (t) { - t.true(Pointer.null.null?) - } - - t.test('Pointer.read') fn (t) { - let libc = Library.new(libc_paths).unwrap - - let mem = Mem.alloc(libc, size: Type.U8.size) - - t.equal(mem.ptr.read(type: Type.U8, offset: 0) as Int, 0) - } - - t.test('Pointer.write') fn (t) { - let libc = Library.new(libc_paths).unwrap - let mem = Mem.alloc(libc, size: Type.U8.size) - - mem.ptr.write(type: Type.U8, offset: 0, value: 10) - t.equal(mem.ptr.read(type: Type.U8, offset: 0) as Int, 10) - } - - t.test('Pointer.pointer') fn (t) { - let a = Pointer.null - let b = a.pointer - - b.write(type: Type.U8, offset: 0, value: 10) - t.equal(a.address, 10) - t.not_equal(b.address, 10) - } - - t.test('Pointer.clone') fn (t) { - let ptr = Pointer.from_address(0x4) - - t.equal(ptr.clone, ptr) - } - - t.test('Pointer.==') fn (t) { - t.equal(Pointer.from_address(0x4), Pointer.from_address(0x4)) - t.not_equal(Pointer.from_address(0x4), Pointer.from_address(0x5)) - } - - t.test('Library.new') fn (t) { - t.true(Library.new(['inko-test-this-wont-exist']).none?) - t.true(Library.new(libc_paths).some?) - } - - t.test('Library.variable') fn (t) { - let libc = Library.new(libc_paths).unwrap - - t.true(libc.variable('malloc').some?) - t.true(libc.variable('inko_test_this_wont_exist').none?) - } - - t.test('Library.function') fn (t) { - let libc = Library.new(libc_paths).unwrap - - t.true( - libc - .function('malloc', arguments: [Type.SizeT], returns: Type.Pointer) - .some? - ) - - t.true( - libc.function('inko_test_invalid', arguments: [], returns: Type.U8).none? - ) - } - - t.test('Function.call0') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = - libc.function('clock', arguments: [], returns: Type.I64).unwrap - - t.true((func.call0 as Int) >= 0) - } - - t.test('Function.call1') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = - libc.function('abs', arguments: [Type.I32], returns: Type.I32).unwrap - - t.equal(func.call1(-4) as Int, 4) - } - - t.test('Function.call2') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = libc - .function( - 'strcmp', - arguments: [Type.String, Type.String], - returns: Type.I32 - ) - .unwrap - - t.equal(func.call2('foo', 'foo') as Int, 0) - } - - t.test('Function.call3') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = libc - .function( - 'strtol', - arguments: [Type.String, Type.Pointer, Type.I32], - returns: Type.I32 - ) - .unwrap - - let value = func.call3('123', Pointer.null.raw, 10) as Int - - t.equal(value, 123) - } - - t.test('Function.call4') fn (t) { - let libc = Library.new(libc_paths).unwrap - } - - t.test('Struct.pointer') fn (t) { - let builder = LayoutBuilder.new - - builder.field('foo', Type.I32) - - let layout = builder.into_layout - let pointer = Pointer.null - let struct = Struct.new(pointer: pointer.clone, layout: layout) - - t.equal(struct.pointer, pointer) - } - - t.test('Struct.index') fn (t) { - let libc = Library.new(libc_paths).unwrap - let builder = LayoutBuilder.new - - builder.field('foo', Type.I32) - builder.field('bar', Type.I64) - - let layout = builder.into_layout - let mem = Mem.alloc(libc, size: layout.size.clone) - let struct = Struct.new(pointer: mem.ptr.clone, layout: layout) - - t.equal(struct['foo'] as Int, 0) - t.equal(struct['bar'] as Int, 0) - - struct['foo'] = 4 - struct['bar'] = 8 - - t.equal(struct['foo'] as Int, 4) - t.equal(struct['bar'] as Int, 8) - } - - t.test('LayoutBuilder.into_layout with padding') fn (t) { - let builder = LayoutBuilder.new - - builder.field('foo', Type.I32) - builder.field('bar', Type.I64) - - let layout = builder.into_layout - let struct = Struct.new(pointer: Pointer.null, layout: layout) - - t.equal(struct.size, Type.I64.size * 2) - t.equal(struct.alignment, Type.I64.alignment) - } - - t.test('LayoutBuilder.into_layout without padding') fn (t) { - let builder = LayoutBuilder.new - - builder.no_padding - builder.field('foo', Type.I32) - builder.field('bar', Type.I64) - - let layout = builder.into_layout - let struct = Struct.new(pointer: Pointer.null, layout: layout) - - t.equal(struct.size, Type.I64.size + Type.I32.size) - t.equal(struct.alignment, 1) - } - - t.test('Type.size') fn (t) { - t.equal(Type.I16.size, 2) - t.equal(Type.String.size, 8) - t.equal(Type.ByteArray.size, 8) - } - - t.test('Type.alignment') fn (t) { - t.equal(Type.I16.alignment, 2) - t.equal(Type.String.alignment, 8) - t.equal(Type.ByteArray.alignment, 8) - } - - t.test('Type.to_int') fn (t) { - t.equal(Type.Void.to_int, 0) - t.equal(Type.Pointer.to_int, 1) - t.equal(Type.F64.to_int, 2) - t.equal(Type.F32.to_int, 3) - t.equal(Type.I8.to_int, 4) - t.equal(Type.I16.to_int, 5) - t.equal(Type.I32.to_int, 6) - t.equal(Type.I64.to_int, 7) - t.equal(Type.U8.to_int, 8) - t.equal(Type.U16.to_int, 9) - t.equal(Type.U32.to_int, 10) - t.equal(Type.U64.to_int, 11) - t.equal(Type.String.to_int, 12) - t.equal(Type.ByteArray.to_int, 13) - t.equal(Type.SizeT.to_int, 14) - } - - t.test('Type.clone') fn (t) { - t.equal(Type.Void.clone, Type.Void) - t.equal(Type.Pointer.clone, Type.Pointer) - t.equal(Type.F64.clone, Type.F64) - t.equal(Type.F32.clone, Type.F32) - t.equal(Type.I8.clone, Type.I8) - t.equal(Type.I16.clone, Type.I16) - t.equal(Type.I32.clone, Type.I32) - t.equal(Type.I64.clone, Type.I64) - t.equal(Type.U8.clone, Type.U8) - t.equal(Type.U16.clone, Type.U16) - t.equal(Type.U32.clone, Type.U32) - t.equal(Type.U64.clone, Type.U64) - t.equal(Type.String.clone, Type.String) - t.equal(Type.ByteArray.clone, Type.ByteArray) - t.equal(Type.SizeT.clone, Type.SizeT) - } -} diff --git a/libstd/test/std/test_json.inko b/libstd/test/std/test_json.inko deleted file mode 100644 index f4d6dfdf9..000000000 --- a/libstd/test/std/test_json.inko +++ /dev/null @@ -1,407 +0,0 @@ -# import helpers::(fmt) -import helpers::(fmt) -import std::json::(self, Error, Json, Parser) -import std::test::Tests - -fn parse(input: String) !! Error -> Json { - try Parser.new(input).parse -} - -fn parse_invalid(input: String) -> Option[String] { - try Parser.new(input).parse else (err) { return Option.Some(err.to_string) } - Option.None -} - -fn pub tests(t: mut Tests) { - t.test('Error.fmt') fn (t) { - let err = Error { @message = 'foo', @line = 1, @offset = 5 } - - t.equal(fmt(err), 'foo, on line 1 at byte offset 5') - } - - t.test('Error.to_string') fn (t) { - let err = Error { @message = 'foo', @line = 1, @offset = 5 } - - t.equal(err.to_string, 'foo, on line 1 at byte offset 5') - } - - t.test('Json.fmt') fn (t) { - let map = Map.new - - map['a'] = Json.Int(10) - - t.equal(fmt(Json.Int(42)), 'Int(42)') - t.equal(fmt(Json.Float(42.0)), 'Float(42.0)') - t.equal(fmt(Json.String('test')), 'String("test")') - t.equal(fmt(Json.Array([Json.Int(10)])), 'Array([Int(10)])') - t.equal(fmt(Json.Object(map)), 'Object({"a": Int(10)})') - t.equal(fmt(Json.Bool(true)), 'Bool(true)') - t.equal(fmt(Json.Bool(false)), 'Bool(false)') - t.equal(fmt(Json.Null), 'Null') - } - - t.test('Json.==') fn (t) { - let map1 = Map.new - let map2 = Map.new - let map3 = Map.new - - map1['a'] = Json.Int(10) - map2['a'] = Json.Int(10) - map3['a'] = Json.Int(10) - - t.equal(Json.Int(10), Json.Int(10)) - t.not_equal(Json.Int(10), Json.Int(20)) - t.not_equal(Json.Int(10), Json.Float(20.0)) - - t.equal(Json.Float(10.0), Json.Float(10.0)) - t.not_equal(Json.Float(10.0), Json.Float(20.0)) - t.not_equal(Json.Float(10.0), Json.Int(10)) - - t.equal(Json.String('foo'), Json.String('foo')) - t.not_equal(Json.String('foo'), Json.String('bar')) - t.not_equal(Json.String('foo'), Json.Int(10)) - - t.equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(10)])) - t.not_equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(20)])) - t.not_equal(Json.Array([Json.Int(10)]), Json.Int(10)) - - t.equal(Json.Object(map1), Json.Object(map2)) - t.not_equal(Json.Object(map3), Json.Object(Map.new)) - t.not_equal(Json.Object(Map.new), Json.Int(10)) - - t.equal(Json.Bool(true), Json.Bool(true)) - t.not_equal(Json.Bool(true), Json.Bool(false)) - t.not_equal(Json.Bool(true), Json.Int(10)) - - t.equal(Json.Null, Json.Null) - t.not_equal(Json.Null, Json.Int(10)) - } - - t.test('Json.to_string') fn (t) { - let map = Map.new - - map['a'] = Json.Int(1) - map['b'] = Json.Int(2) - - t.equal(Json.Int(42).to_string, '42') - t.equal(Json.Float(1.2).to_string, '1.2') - t.equal(Json.String('foo').to_string, '"foo"') - t.equal(Json.String("a\nb").to_string, '"a\nb"') - t.equal(Json.String("a\rb").to_string, '"a\rb"') - t.equal(Json.String("a\tb").to_string, '"a\tb"') - t.equal(Json.String("a\u{C}b").to_string, '"a\fb"') - t.equal(Json.String("a\u{8}b").to_string, '"a\bb"') - t.equal(Json.String('a\\b').to_string, '"a\\\\b"') - t.equal(Json.Array([]).to_string, '[]') - t.equal(Json.Array([Json.Int(1), Json.Int(2)]).to_string, '[1, 2]') - t.equal(Json.Object(map).to_string, '{"a": 1, "b": 2}') - t.equal(Json.Object(Map.new).to_string, '{}') - t.equal(Json.Bool(true).to_string, 'true') - t.equal(Json.Bool(false).to_string, 'false') - t.equal(Json.Null.to_string, 'null') - } - - t.test('Json.to_pretty_string') fn (t) { - t.equal(Json.Int(42).to_pretty_string, '42') - t.equal(Json.Float(1.2).to_pretty_string, '1.2') - t.equal(Json.String('foo').to_pretty_string, '"foo"') - t.equal(Json.String("a\nb").to_pretty_string, '"a\nb"') - t.equal(Json.String("a\rb").to_pretty_string, '"a\rb"') - t.equal(Json.String("a\tb").to_pretty_string, '"a\tb"') - t.equal(Json.String("a\u{C}b").to_pretty_string, '"a\fb"') - t.equal(Json.String("a\u{8}b").to_pretty_string, '"a\bb"') - t.equal(Json.String('a\\b').to_pretty_string, '"a\\\\b"') - t.equal(Json.Bool(true).to_pretty_string, 'true') - t.equal(Json.Bool(false).to_pretty_string, 'false') - t.equal(Json.Null.to_pretty_string, 'null') - - t.equal(Json.Array([]).to_pretty_string, '[]') - t.equal( - Json.Array([Json.Int(1), Json.Int(2)]).to_pretty_string, - '[ - 1, - 2 -]' - ) - - t.equal( - Json.Array([Json.Array([Json.Int(1), Json.Int(2)])]).to_pretty_string, - '[ - [ - 1, - 2 - ] -]' - ) - - let map1 = Map.new - let map2 = Map.new - let map3 = Map.new - - map1['a'] = Json.Int(1) - map1['b'] = Json.Int(2) - map2['a'] = Json.Array([Json.Int(1), Json.Int(2)]) - map3['a'] = Json.Int(1) - map3['b'] = Json.Object(map2) - - t.equal(Json.Object(Map.new).to_pretty_string, '{}') - t.equal( - Json.Object(map1).to_pretty_string, - '{ - "a": 1, - "b": 2 -}' - ) - - t.equal( - Json.Object(map3).to_pretty_string, - '{ - "a": 1, - "b": { - "a": [ - 1, - 2 - ] - } -}' - ) - } - - t.test('Parsing integers') fn (t) { - t.try_equal(fn { try parse('0') }, Json.Int(0)) - t.try_equal(fn { try parse('42') }, Json.Int(42)) - t.try_equal(fn { try parse(' 42') }, Json.Int(42)) - t.try_equal(fn { try parse('42 ') }, Json.Int(42)) - t.try_equal(fn { try parse("\t42") }, Json.Int(42)) - t.try_equal(fn { try parse("\r42") }, Json.Int(42)) - t.try_equal(fn { try parse('-42') }, Json.Int(-42)) - - t.throw fn { try parse('00') } - t.throw fn { try parse('10,') } - t.throw fn { try parse('-') } - t.throw fn { try parse('-01') } - t.throw fn { try parse('01') } - t.throw fn { try parse('1a') } - t.throw fn { try parse('-a') } - } - - t.test('Parsing floats') fn (t) { - t.try_equal(fn { try parse(' 1.2') }, Json.Float(1.2)) - t.try_equal(fn { try parse('1.2 ') }, Json.Float(1.2)) - t.try_equal(fn { try parse('1.2') }, Json.Float(1.2)) - t.try_equal(fn { try parse('-1.2') }, Json.Float(-1.2)) - t.try_equal(fn { try parse('1.2e+123') }, Json.Float(1.2e+123)) - t.try_equal(fn { try parse('1.2e-123') }, Json.Float(1.2e-123)) - t.try_equal(fn { try parse('1.2E+123') }, Json.Float(1.2e+123)) - t.try_equal(fn { try parse('1.2E-123') }, Json.Float(1.2e-123)) - t.try_equal(fn { try parse('-1.2E-123') }, Json.Float(-1.2e-123)) - t.try_equal(fn { try parse('0.0') }, Json.Float(0.0)) - t.try_equal(fn { try parse('0E0') }, Json.Float(0.0)) - t.try_equal(fn { try parse('0e+1') }, Json.Float(0.0)) - t.try_equal(fn { try parse('1.2E1') }, Json.Float(1.2e1)) - t.try_equal(fn { try parse('1.2e1') }, Json.Float(1.2e1)) - t.try_equal( - fn { try parse('1.7976931348623157e+310') }, - Json.Float(Float.infinity) - ) - t.try_equal( - fn { try parse('4.940656458412465441765687928682213723651e-330') }, - Json.Float(0.0) - ) - t.try_equal( - fn { try parse('-0.000000000000000000000000000000000000000000000000000000000000000000000000000001') }, - Json.Float(-1.0E-78) - ) - - # These numbers are too big for regular integers, so we promote them to - # floats. - t.try_equal( - fn { try parse('11111111111111111111111111111111111111111') }, - Json.Float(11111111111111111111111111111111111111111.0) - ) - t.try_equal( - fn { try parse('10000000000000000999') }, - Json.Float(10000000000000000999.0) - ) - - t.throw fn { try parse('00.0') } - t.throw fn { try parse('1.2e') } - t.throw fn { try parse('1.2e+') } - t.throw fn { try parse('1.2e-') } - t.throw fn { try parse('1.2E') } - t.throw fn { try parse('1.2E+') } - t.throw fn { try parse('1.2E-') } - t.throw fn { try parse('1.2E+a') } - t.throw fn { try parse('1.2E-a') } - t.throw fn { try parse('0E') } - t.throw fn { try parse('10.2,') } - - t.equal( - parse_invalid("\n1.2e"), - Option.Some( - 'One or more tokens are required, but we ran out of input, \ - on line 2 at byte offset 5' - ) - ) - } - - t.test('Parsing arrays') fn (t) { - t.try_equal(fn { try parse('[]') }, Json.Array([])) - t.try_equal(fn { try parse('[10]') }, Json.Array([Json.Int(10)])) - t.try_equal( - fn { try parse('[10, 20]') }, - Json.Array([Json.Int(10), Json.Int(20)]) - ) - - t.throw fn { try parse('[') } - t.throw fn { try parse(']') } - t.throw fn { try parse('[,10]') } - t.throw fn { try parse('[10,]') } - t.throw fn { try parse('[10') } - t.throw fn { try parse('[10,') } - t.throw fn { try parse('[10true]') } - t.throw fn { try parse('[],') } - - t.throw fn { - let parser = Parser.new('[[[[10]]]]') - - parser.max_depth = 2 - try parser.parse - } - } - - t.test('Parsing booleans') fn (t) { - t.try_equal(fn { try parse('true') }, Json.Bool(true)) - t.try_equal(fn { try parse('false') }, Json.Bool(false)) - - t.throw fn { try parse('t') } - t.throw fn { try parse('tr') } - t.throw fn { try parse('tru') } - t.throw fn { try parse('f') } - t.throw fn { try parse('fa') } - t.throw fn { try parse('fal') } - t.throw fn { try parse('fals') } - } - - t.test('Parsing null') fn (t) { - t.try_equal(fn { try parse('null') }, Json.Null) - - t.throw fn { try parse('n') } - t.throw fn { try parse('nu') } - t.throw fn { try parse('nul') } - } - - t.test('Parsing strings') fn (t) { - t.try_equal(fn { try parse('"foo"') }, Json.String('foo')) - t.try_equal(fn { try parse('"foo bar"') }, Json.String('foo bar')) - t.try_equal(fn { try parse('"foo\nbar"') }, Json.String("foo\nbar")) - t.try_equal(fn { try parse('"foo\tbar"') }, Json.String("foo\tbar")) - t.try_equal(fn { try parse('"foo\rbar"') }, Json.String("foo\rbar")) - t.try_equal(fn { try parse('"foo\bbar"') }, Json.String("foo\u{0008}bar")) - t.try_equal(fn { try parse('"foo\fbar"') }, Json.String("foo\u{000C}bar")) - t.try_equal(fn { try parse('"foo\\"bar"') }, Json.String('foo"bar')) - t.try_equal(fn { try parse('"foo\\/bar"') }, Json.String('foo/bar')) - t.try_equal(fn { try parse('"foo\\\\bar"') }, Json.String("foo\\bar")) - t.try_equal(fn { try parse('"foo\u005Cbar"') }, Json.String('foo\\bar')) - t.try_equal( - fn { try parse('"foo\\u001Fbar"') }, - Json.String("foo\u{001F}bar") - ) - t.try_equal(fn { try parse('"\uD834\uDD1E"') }, Json.String("\u{1D11E}")) - t.try_equal( - fn { try parse('"\uE000\uE000"') }, - Json.String("\u{E000}\u{E000}") - ) - - t.throw fn { try parse("\"\0\"") } - t.throw fn { try parse("\"\n\"") } - t.throw fn { try parse("\"\t\"") } - t.throw fn { try parse("\"\r\"") } - t.throw fn { try parse("\"\u{8}\"") } # \b - t.throw fn { try parse("\"\u{c}\"") } # \f - - t.throw fn { try parse('"\x42"') } - t.throw fn { try parse('"\u1"') } - t.throw fn { try parse('"\u12"') } - t.throw fn { try parse('"\u123"') } - t.throw fn { try parse('"\u{XXXX}"') } - t.throw fn { try parse('"\uD834\uE000"') } - t.throw fn { try parse('"\uD834\uZZZZ"') } - t.throw fn { try parse('"\uDFFF\uDFFF"') } - - t.throw fn { - let parser = Parser.new('"foo"') - - parser.max_string_size = 2 - try parser.parse - } - - t.equal( - parse_invalid('"a'), - Option.Some( - 'One or more tokens are required, but we ran out of input, \ - on line 1 at byte offset 2' - ) - ) - } - - t.test('Parsing objects') fn (t) { - let map1 = Map.new - let map2 = Map.new - let map3 = Map.new - let map4 = Map.new - let map5 = Map.new - - map2['a'] = Json.Int(10) - map3['a'] = Json.Int(20) - map4['a'] = Json.Int(10) - map4['b'] = Json.Int(20) - map5['a'] = Json.Int(10) - map5['b'] = Json.Int(20) - - t.try_equal(fn { try parse('{}') }, Json.Object(map1)) - t.try_equal(fn { try parse('{ "a": 10 }') }, Json.Object(map2)) - t.try_equal(fn { try parse('{"a": 10, "a": 20}') }, Json.Object(map3)) - t.try_equal(fn { try parse('{"a": 10, "b": 20}') }, Json.Object(map4)) - t.try_equal( - fn { - try parse('{ - "a": 10, - "b": 20 - }') - }, - Json.Object(map5) - ) - - t.throw fn { try parse('{') } - t.throw fn { try parse('}') } - t.throw fn { try parse('{{}}') } - t.throw fn { try parse('{"a"}') } - t.throw fn { try parse('{"a":}') } - t.throw fn { try parse('{"a":10,}') } - t.throw fn { try parse('{},') } - t.throw fn { try parse('{"a": true} "x"') } - - t.throw fn { - let parser = Parser.new('{"a": {"b": {"c": 10}}}') - - parser.max_depth = 2 - try parser.parse - } - - t.equal( - parse_invalid('{"a"}'), - Option.Some("The character '}' is unexpected, on line 1 at byte offset 4") - ) - } - - t.test('Parsing Unicode BOMs') fn (t) { - t.throw fn { try parse("\u{FEFF}10") } - t.throw fn { try parse("\u{FFFE}10") } - t.throw fn { try parse("\u{EF}\u{BB}\u{BF}10") } - } - - t.test('json.parse') fn (t) { - t.try_equal(fn { try json.parse('[10]') }, Json.Array([Json.Int(10)])) - } -} diff --git a/libstd/test/std/test_process.inko b/libstd/test/std/test_process.inko deleted file mode 100644 index 5fe53382e..000000000 --- a/libstd/test/std/test_process.inko +++ /dev/null @@ -1,54 +0,0 @@ -import std::process -import std::test::Tests -import std::time::(Duration, Instant) - -class async Proc { - fn async send_back(value: Int) -> Int { - value - } - - fn async error(value: Int) !! Int { - throw value - } - - fn async slow { - process.sleep(Duration.from_secs(5)) - } -} - -fn pub tests(t: mut Tests) { - t.test('process.sleep') fn (t) { - let start = Instant.new - - process.sleep(Duration.from_millis(10)) - t.true(start.elapsed.to_millis >= 10) - } - - t.test('process.poll') fn (t) { - let pending = [async Proc {}.send_back(42), async Proc {}.send_back(42)] - let mut ready = [] - - while pending.length > 0 { - process.poll(pending).into_iter.each fn (fut) { ready.push(fut.await) } - } - - t.equal(pending.length, 0) - t.equal(ready, [42, 42]) - } - - t.test('Future.await') fn (t) { - let proc = Proc {} - - t.equal(async { proc.send_back(1) }.await, 1) - t.throw fn move { try { async { proc.error(2) }.await } } - } - - t.test('Future.await_for') fn (t) { - let proc = Proc {} - let fast = async proc.send_back(42) - let slow = async proc.slow - - t.equal(fast.await_for(Duration.from_secs(5)), Option.Some(42)) - t.true(slow.await_for(Duration.from_millis(10)).none?) - } -} diff --git a/libstd/test/std/test_stdio.inko b/libstd/test/std/test_stdio.inko deleted file mode 100644 index e54fce392..000000000 --- a/libstd/test/std/test_stdio.inko +++ /dev/null @@ -1,81 +0,0 @@ -import helpers::Script -import std::test::Tests - -fn pub tests(t: mut Tests) { - t.test('STDIN.read') fn (t) { - let code = ' - let out = STDOUT.new - let in = STDIN.new - let bytes = ByteArray.new - - try! in.read_all(bytes) - out.write_bytes(bytes) - ' - - let output = Script.new(t.id, code).stdin('hello').run - - t.equal(output, 'hello') - } - - t.test('STDOUT.write_bytes') fn (t) { - let code = 'STDOUT.new.write_bytes("hello".to_byte_array)' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDOUT.write_string') fn (t) { - let code = 'STDOUT.new.write_string("hello")' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDOUT.print') fn (t) { - let code = ' -let out = STDOUT.new -let len = out.print("hello") - -out.print(len.to_string)' - - t.equal(Script.new(t.id, code).run, "hello\n6\n") - } - - t.test('STDOUT.flush') fn (t) { - let code = ' - let out = STDOUT.new - - out.write_string("hello") - out.flush - ' - - t.equal(Script.new(t.id, code).run, "hello") - } - - t.test('STDERR.write_bytes') fn (t) { - let code = 'STDERR.new.write_bytes("hello".to_byte_array)' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDERR.write_string') fn (t) { - let code = 'STDERR.new.write_string("hello")' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDERR.print') fn (t) { - let code = 'STDERR.new.print("hello")' - - t.equal(Script.new(t.id, code).run, "hello\n") - } - - t.test('STDERR.flush') fn (t) { - let code = ' - let out = STDERR.new - - out.write_string("hello") - out.flush - ' - - t.equal(Script.new(t.id, code).run, "hello") - } -} diff --git a/rt/Cargo.toml b/rt/Cargo.toml new file mode 100644 index 000000000..6e5515527 --- /dev/null +++ b/rt/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "rt" +version = "0.10.0" # VERSION +authors = ["Yorick Peterse "] +edition = "2021" +build = "build.rs" +license = "MPL-2.0" + +[lib] +name = "inko" +doctest = false +crate-type = ["staticlib", "lib"] + +[dependencies] +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" +backtrace = "^0.3" + +[dependencies.socket2] +version = "^0.5" +features = ["all"] diff --git a/vm/build.rs b/rt/build.rs similarity index 100% rename from vm/build.rs rename to rt/build.rs diff --git a/vm/src/arc_without_weak.rs b/rt/src/arc_without_weak.rs similarity index 99% rename from vm/src/arc_without_weak.rs rename to rt/src/arc_without_weak.rs index a0794ce9e..86154f4a0 100644 --- a/vm/src/arc_without_weak.rs +++ b/rt/src/arc_without_weak.rs @@ -20,6 +20,7 @@ pub(crate) struct Inner { } /// A thread-safe reference counted pointer. +#[repr(C)] pub(crate) struct ArcWithoutWeak { inner: NonNull>, } diff --git a/vm/src/config.rs b/rt/src/config.rs similarity index 78% rename from vm/src/config.rs rename to rt/src/config.rs index aa8595ae9..989b3e8c9 100644 --- a/vm/src/config.rs +++ b/rt/src/config.rs @@ -2,8 +2,8 @@ //! //! Various virtual machine settings that can be changed by the user, such as //! the number of threads to run. +use crate::scheduler::number_of_cores; use std::env::var; -use std::thread::available_parallelism; /// Sets a configuration field based on an environment variable. macro_rules! set_from_env { @@ -18,10 +18,6 @@ macro_rules! set_from_env { }}; } -/// The default number of reductions to consume before a process suspends -/// itself. -const DEFAULT_REDUCTIONS: u16 = 1000; - /// The default number of network poller threads to use. /// /// We default to one thread because for most setups this is probably more than @@ -31,6 +27,12 @@ const DEFAULT_NETPOLL_THREADS: u8 = 1; /// The maximum number of netpoll threads that are allowed. const MAX_NETPOLL_THREADS: u8 = 127; +/// The default size of each process' stack in bytes. +/// +/// The default size is chosen as we believe it to be large enough for most +/// cases, and to ensure foreign function calls don't overflow the stack. +const DEFAULT_STACK_SIZE: u32 = 1024 * 1024; + /// Structure containing the configuration settings for the virtual machine. pub struct Config { /// The number of process threads to run. @@ -39,6 +41,9 @@ pub struct Config { /// The number of backup process threads to spawn. pub backup_threads: u16, + /// The size of each process' stack in bytes. + pub stack_size: u32, + /// The number of network poller threads to use. /// /// While this value is stored as an u8, it's limited to a maximum of 127. @@ -46,31 +51,27 @@ pub struct Config { /// and use the value -1 to signal a file descriptor isn't registered with /// any poller. pub netpoll_threads: u8, - - /// The number of reductions a process can perform before being suspended. - pub reductions: u16, } impl Config { - pub fn new() -> Config { - let cpu_count = - available_parallelism().map(|v| v.get()).unwrap_or(1) as u16; + pub(crate) fn new() -> Config { + let cpu_count = number_of_cores() as u16; Config { process_threads: cpu_count, backup_threads: cpu_count * 4, netpoll_threads: DEFAULT_NETPOLL_THREADS, - reductions: DEFAULT_REDUCTIONS, + stack_size: DEFAULT_STACK_SIZE, } } - pub fn from_env() -> Config { + pub(crate) fn from_env() -> Config { let mut config = Config::new(); set_from_env!(config, process_threads, "PROCESS_THREADS", u16); set_from_env!(config, backup_threads, "BACKUP_THREADS", u16); - set_from_env!(config, reductions, "REDUCTIONS", u16); set_from_env!(config, netpoll_threads, "NETPOLL_THREADS", u8); + set_from_env!(config, stack_size, "STACK_SIZE", u32); config.verify(); config @@ -101,7 +102,6 @@ mod tests { let config = Config::new(); assert!(config.process_threads >= 1); - assert_eq!(config.reductions, DEFAULT_REDUCTIONS); } #[test] @@ -109,14 +109,8 @@ mod tests { let mut cfg = Config::new(); set_from_env!(cfg, process_threads, "FOO", u16); - set_from_env!(cfg, reductions, "BAR", u16); assert_eq!(cfg.process_threads, 1); - assert_eq!(cfg.reductions, DEFAULT_REDUCTIONS); - - set_from_env!(cfg, reductions, "BAZ", u16); - - assert_eq!(cfg.reductions, DEFAULT_REDUCTIONS); } #[test] diff --git a/rt/src/context.rs b/rt/src/context.rs new file mode 100644 index 000000000..5d87547f7 --- /dev/null +++ b/rt/src/context.rs @@ -0,0 +1,67 @@ +//! Context switching between thread/process stacks +//! +//! Context switching is implemented using two functions: `start` and `switch`. +//! +//! The `start` function starts a new message by calling the appropriate native +//! function. The `switch` function is used for switching from a thread to a +//! process and back. This function shouldn't be used to switch between two +//! processes directly. +//! +//! The `start` function passes the necessary arguments using a `Context` type. +//! This type is dropped after the first yield, so the native code must load its +//! components into variables/registers in order to continue using them. +use crate::process::{NativeAsyncMethod, ProcessPointer}; +use crate::state::State; +mod unix; + +/// A type storing state used when first starting a process. +/// +/// Using this type allows us to keep the assembly used for setting up a process +/// simple, as we only need to pass a small number of arguments (which all fit +/// in registers). +/// +/// This type is dropped after the first yield from a process back to its +/// thread. +#[repr(C)] +pub struct Context { + pub state: *const State, + pub process: ProcessPointer, + pub arguments: *mut u8, +} + +// These functions are defined in the inline assembly macros, found in modules +// such as context/unix/x86_64.rs. +extern "system" { + fn inko_context_init( + high: *mut *mut u8, + func: NativeAsyncMethod, + ctx: *mut u8, + ); + + fn inko_context_switch(stack: *mut *mut u8); +} + +#[inline(never)] +pub(crate) unsafe fn start( + state: &State, + mut process: ProcessPointer, + func: NativeAsyncMethod, + mut args: Vec<*mut u8>, +) { + let ctx = Context { + state: state as _, + process, + arguments: args.as_mut_ptr() as _, + }; + + inko_context_init( + &mut process.stack_pointer, + func as _, + &ctx as *const _ as _, + ); +} + +#[inline(never)] +pub(crate) unsafe fn switch(mut process: ProcessPointer) { + inko_context_switch(&mut process.stack_pointer); +} diff --git a/rt/src/context/unix.rs b/rt/src/context/unix.rs new file mode 100644 index 000000000..5db896330 --- /dev/null +++ b/rt/src/context/unix.rs @@ -0,0 +1,5 @@ +#[cfg(target_arch = "x86_64")] +mod x86_64; + +#[cfg(target_arch = "aarch64")] +mod aarch64; diff --git a/rt/src/context/unix/aarch64.rs b/rt/src/context/unix/aarch64.rs new file mode 100644 index 000000000..03478d536 --- /dev/null +++ b/rt/src/context/unix/aarch64.rs @@ -0,0 +1,66 @@ +// Taken from +// https://github.com/bytecodealliance/wasmtime/blob/95ecb7e4d440605165a74de607de5389508779be/crates/fiber/src/unix/aarch64.rs + +// X0: a pointer pointer to the new stack, replaced with the new stack. +// X1: a pointer to a function to call. +// X2: an extra data argument to pass to the function. +asm_func!( + "inko_context_init", + " + stp x29, x30, [sp, -16]! + stp x20, x19, [sp, -16]! + stp x22, x21, [sp, -16]! + stp x24, x23, [sp, -16]! + stp x26, x25, [sp, -16]! + stp x28, x27, [sp, -16]! + stp d9, d8, [sp, -16]! + stp d11, d10, [sp, -16]! + stp d13, d12, [sp, -16]! + stp d15, d14, [sp, -16]! + + // Swap the stack pointers + ldr x8, [x0] + mov x9, sp + str x9, [x0] + mov sp, x8 + + mov x0, x2 + blr x1 + " +); + +// X0: a pointer pointer to a stack to restore. +asm_func!( + "inko_context_switch", + " + stp x29, x30, [sp, -16]! + stp x20, x19, [sp, -16]! + stp x22, x21, [sp, -16]! + stp x24, x23, [sp, -16]! + stp x26, x25, [sp, -16]! + stp x28, x27, [sp, -16]! + stp d9, d8, [sp, -16]! + stp d11, d10, [sp, -16]! + stp d13, d12, [sp, -16]! + stp d15, d14, [sp, -16]! + + // Swap the stack pointers + ldr x8, [x0] + mov x9, sp + str x9, [x0] + mov sp, x8 + + ldp d15, d14, [sp], 16 + ldp d13, d12, [sp], 16 + ldp d11, d10, [sp], 16 + ldp d9, d8, [sp], 16 + ldp x28, x27, [sp], 16 + ldp x26, x25, [sp], 16 + ldp x24, x23, [sp], 16 + ldp x22, x21, [sp], 16 + ldp x20, x19, [sp], 16 + ldp x29, x30, [sp], 16 + + ret + " +); diff --git a/rt/src/context/unix/x86_64.rs b/rt/src/context/unix/x86_64.rs new file mode 100644 index 000000000..694201461 --- /dev/null +++ b/rt/src/context/unix/x86_64.rs @@ -0,0 +1,49 @@ +// RDI: a pointer pointer to the new stack, replaced with the new stack. +// RSI: a pointer to a function to call. +// RDX: an extra data argument to pass to the function. +asm_func!( + "inko_context_init", + " + push rbp + push rbx + push r12 + push r13 + push r14 + push r15 + + // Swap the stack pointers + mov r8, [rdi] + mov [rdi], rsp + mov rsp, r8 + + mov rdi, rdx + call rsi + " +); + +// RDI: a pointer pointer to a stack to restore. +asm_func!( + "inko_context_switch", + " + push rbp + push rbx + push r12 + push r13 + push r14 + push r15 + + // Swap the stack pointers + mov r8, [rdi] + mov [rdi], rsp + mov rsp, r8 + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rbp + + ret + " +); diff --git a/vm/src/immutable_string.rs b/rt/src/immutable_string.rs similarity index 80% rename from vm/src/immutable_string.rs rename to rt/src/immutable_string.rs index a13029ec3..99e55e470 100644 --- a/vm/src/immutable_string.rs +++ b/rt/src/immutable_string.rs @@ -1,7 +1,6 @@ //! Immutable strings that can be shared with C code. use std::fmt; use std::ops::Deref; -use std::os::raw::c_char; use std::str; const NULL_BYTE: u8 = 0; @@ -14,7 +13,7 @@ const NULL_BYTE: u8 = 0; /// to still store NULL bytes any where in the `ImmutableString`. #[derive(Eq, PartialEq, Hash, Clone)] pub(crate) struct ImmutableString { - bytes: Vec, + bytes: Box<[u8]>, } #[cfg_attr(feature = "cargo-clippy", allow(clippy::len_without_is_empty))] @@ -42,20 +41,10 @@ impl ImmutableString { &self.bytes[0..self.len()] } - /// Returns a C `char` pointer that can be passed to C. - pub(crate) fn as_c_char_pointer(&self) -> *const c_char { - self.bytes.as_ptr() as *const _ - } - /// Returns the number of bytes in this String. pub(crate) fn len(&self) -> usize { self.bytes.len() - 1 } - - /// Returns the number of bytes in this string, including the null byte. - pub(crate) fn len_with_null_byte(&self) -> usize { - self.bytes.len() - } } impl Deref for ImmutableString { @@ -73,7 +62,7 @@ impl From> for ImmutableString { bytes.reserve_exact(1); bytes.push(NULL_BYTE); - ImmutableString { bytes } + ImmutableString { bytes: bytes.into_boxed_slice() } } } @@ -124,15 +113,6 @@ mod tests { assert_eq!(string.as_bytes(), &[105, 110, 107, 111]); } - #[test] - fn test_as_c_char_pointer() { - let string = ImmutableString::from("inko".to_string()); - - assert!( - string.as_c_char_pointer() == string.bytes.as_ptr() as *const _ - ); - } - #[test] fn test_len() { let string = ImmutableString::from("inko".to_string()); @@ -140,18 +120,11 @@ mod tests { assert_eq!(string.len(), 4); } - #[test] - fn test_len_with_null_byte() { - let string = ImmutableString::from("inko".to_string()); - - assert_eq!(string.len_with_null_byte(), 5); - } - #[test] fn test_from_bytes() { let string = ImmutableString::from(vec![10]); - assert_eq!(string.bytes, vec![10, 0]); + assert_eq!(string.bytes.as_ref(), &[10, 0]); } #[test] diff --git a/vm/src/lib.rs b/rt/src/lib.rs similarity index 63% rename from vm/src/lib.rs rename to rt/src/lib.rs index 01eb4a253..7fe1e6ff3 100644 --- a/vm/src/lib.rs +++ b/rt/src/lib.rs @@ -6,28 +6,19 @@ pub mod macros; pub mod arc_without_weak; -pub mod builtin_functions; -pub mod chunk; pub mod config; -pub mod execution_context; -pub mod ffi; -pub mod hasher; -pub mod image; +pub mod context; pub mod immutable_string; -pub mod indexes; -pub mod instructions; -pub mod location_table; -pub mod machine; pub mod mem; +pub mod memory_map; pub mod network_poller; -pub mod numeric; -pub mod permanent_space; -pub mod platform; +pub mod page; pub mod process; -pub mod registers; -pub mod runtime_error; +pub mod result; +pub mod runtime; pub mod scheduler; pub mod socket; +pub mod stack; pub mod state; #[cfg(test)] diff --git a/vm/src/macros/mod.rs b/rt/src/macros/mod.rs similarity index 59% rename from vm/src/macros/mod.rs rename to rt/src/macros/mod.rs index a07db0a14..349d8f008 100644 --- a/vm/src/macros/mod.rs +++ b/rt/src/macros/mod.rs @@ -21,3 +21,25 @@ macro_rules! init { } }; } + +#[cfg(target_os = "macos")] +macro_rules! asm_func { + ($name: expr, $($body: tt)*) => { + std::arch::global_asm!(concat!( + ".global _", $name, "\n", + "_", $name, ":\n", + $($body)* + )); + } +} + +#[cfg(not(target_os = "macos"))] +macro_rules! asm_func { + ($name: expr, $($body: tt)*) => { + std::arch::global_asm!(concat!( + ".global ", $name, "\n", + $name, ":\n", + $($body)* + )); + } +} diff --git a/rt/src/mem.rs b/rt/src/mem.rs new file mode 100644 index 000000000..586a98efa --- /dev/null +++ b/rt/src/mem.rs @@ -0,0 +1,624 @@ +use crate::immutable_string::ImmutableString; +use crate::process::Process; +use std::alloc::{alloc, alloc_zeroed, dealloc, handle_alloc_error, Layout}; +use std::mem::{align_of, size_of, swap}; +use std::ops::Deref; +use std::ptr::drop_in_place; +use std::string::String as RustString; + +/// The alignment to use for Inko objects. +const ALIGNMENT: usize = align_of::(); + +/// The number of bits to shift for tagged integers. +const INT_SHIFT: usize = 1; + +/// The minimum integer value that can be stored as a tagged signed integer. +pub(crate) const MIN_INT: i64 = i64::MIN >> INT_SHIFT; + +/// The maximum integer value that can be stored as a tagged signed integer. +pub(crate) const MAX_INT: i64 = i64::MAX >> INT_SHIFT; + +/// The mask to use for tagged integers. +const INT_MASK: usize = 0b01; + +/// A type indicating what sort of value we're dealing with in a certain place +/// (e.g. a ref or a permanent value). +/// +/// The values of the variants are specified explicitly to make it more explicit +/// we depend on these exact values (e.g. in the compiler). +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Kind { + /// The value is a regular heap allocated, owned value. + Owned = 0, + + /// The value is a reference to a heap allocated value. + Ref = 1, + + /// The value is an owned value that uses atomic reference counting. + Atomic = 2, + + /// The value musn't be dropped until the program stops. + Permanent = 3, + + /// The value is a boxed Int. + Int = 4, + + /// The value is a boxed Float. + Float = 5, +} + +pub(crate) fn allocate(layout: Layout) -> *mut u8 { + unsafe { + let ptr = alloc(layout); + + if ptr.is_null() { + handle_alloc_error(layout); + } else { + ptr + } + } +} + +fn with_mask(ptr: *const T, mask: usize) -> *const T { + (ptr as usize | mask) as _ +} + +fn mask_is_set(ptr: *const T, mask: usize) -> bool { + (ptr as usize & mask) == mask +} + +pub(crate) unsafe fn header_of<'a, T>(ptr: *const T) -> &'a mut Header { + &mut *(ptr as *mut Header) +} + +pub(crate) fn is_tagged_int(ptr: *const T) -> bool { + mask_is_set(ptr, INT_MASK) +} + +fn fits_in_tagged_int(value: i64) -> bool { + (MIN_INT..=MAX_INT).contains(&value) +} + +pub(crate) fn tagged_int(value: i64) -> *const Int { + with_mask((value << INT_SHIFT) as *const Int, INT_MASK) +} + +pub(crate) unsafe fn free(ptr: *mut T) { + let layout = header_of(ptr).class.instance_layout(); + + dealloc(ptr as *mut u8, layout); +} + +/// The header used by heap allocated objects. +/// +/// The layout is fixed to ensure we can safely assume certain fields are at +/// certain offsets in an object, even when not knowing what type of object +/// we're dealing with. +#[repr(C)] +pub struct Header { + /// The class of the object. + pub class: ClassPointer, + + /// A flag indicating what kind of pointer/object we're dealing with. + pub kind: Kind, + + /// The number of references to the object of this header. + /// + /// If this count overflows the program terminates. In practise this should + /// never happen, as one needs _a lot_ of references to achieve this. + /// + /// We're using a u32 here instead of a u16, as the likelihood of + /// overflowing a u32 is very tiny, but overflowing a u16 is something that + /// _could_ happen (i.e. a process reference shared with many other + /// processes). + pub references: u32, +} + +impl Header { + pub(crate) fn init(&mut self, class: ClassPointer) { + self.class = class; + self.kind = Kind::Owned; + self.references = 0; + } + + pub(crate) fn init_atomic(&mut self, class: ClassPointer) { + self.class = class; + self.kind = Kind::Atomic; + + // Atomic values start with a reference count of 1, so + // `decrement_atomic()` returns the correct result for a value for which + // no extra references have been created (instead of overflowing). + self.references = 1; + } + + pub(crate) fn set_permanent(&mut self) { + self.kind = Kind::Permanent; + } + + pub(crate) fn is_permanent(&self) -> bool { + matches!(self.kind, Kind::Permanent) + } + + pub(crate) fn references(&self) -> u32 { + self.references + } +} + +/// A function bound to an object. +/// +/// Methods don't have headers as there's no need for any, as methods aren't +/// values one can pass around in Inko. +#[repr(C)] +pub struct Method { + /// The hash of this method, used when performing dynamic dispatch. + pub hash: u64, + + /// A pointer to the native function that backs this method. + pub code: extern "system" fn(), +} + +/// An Inko class. +#[repr(C)] +pub struct Class { + /// The name of the class. + pub(crate) name: RustString, + + /// The size (in bytes) of instances of this class. + pub(crate) instance_size: u32, + + /// The number of method slots this class has. + /// + /// The actual number of methods may be less than this value. + pub(crate) method_slots: u16, + + /// The methods of this class, as pointers to native functions. + /// + /// Methods are accessed frequently, and we want to do so with as little + /// indirection and as cache-friendly as possible. For this reason we use a + /// flexible array member, instead of a Vec. + /// + /// The length of this array _must_ be a power of two. + pub methods: [Method; 0], +} + +impl Class { + pub(crate) unsafe fn drop(ptr: ClassPointer) { + let layout = Self::layout(ptr.method_slots); + let raw_ptr = ptr.0; + + drop_in_place(raw_ptr); + dealloc(raw_ptr as *mut u8, layout); + } + + pub(crate) fn alloc( + name: RustString, + methods: u16, + size: u32, + ) -> ClassPointer { + let ptr = unsafe { + let layout = Self::layout(methods); + + // For classes we zero memory out, so unused method slots are set to + // zeroed memory, instead of random garbage. + let ptr = alloc_zeroed(layout) as *mut Class; + + if ptr.is_null() { + handle_alloc_error(layout); + } + + ptr + }; + let obj = unsafe { &mut *ptr }; + + init!(obj.name => name); + init!(obj.instance_size => size); + init!(obj.method_slots => methods); + + ClassPointer(ptr) + } + + /// Returns a new class for a regular object. + pub(crate) fn object( + name: RustString, + fields: u8, + methods: u16, + ) -> ClassPointer { + let size = + size_of::
() + (fields as usize * size_of::<*mut u8>()); + + Self::alloc(name, methods, size as u32) + } + + /// Returns a new class for a process. + pub(crate) fn process( + name: RustString, + fields: u8, + methods: u16, + ) -> ClassPointer { + let size = + size_of::() + (fields as usize * size_of::<*mut u8>()); + + Self::alloc(name, methods, size as u32) + } + + /// Returns the `Layout` for a class itself. + unsafe fn layout(methods: u16) -> Layout { + let size = + size_of::() + (methods as usize * size_of::()); + + Layout::from_size_align_unchecked(size, align_of::()) + } + + pub(crate) unsafe fn instance_layout(&self) -> Layout { + Layout::from_size_align_unchecked( + self.instance_size as usize, + ALIGNMENT, + ) + } +} + +/// A pointer to a class. +#[repr(transparent)] +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct ClassPointer(*mut Class); + +impl Deref for ClassPointer { + type Target = Class; + + fn deref(&self) -> &Class { + unsafe { &*(self.0 as *const Class) } + } +} + +/// A resizable array. +#[repr(C)] +pub struct Array { + pub(crate) header: Header, + pub(crate) value: Vec<*mut u8>, +} + +impl Array { + pub(crate) unsafe fn drop(ptr: *mut Self) { + drop_in_place(ptr); + } + + pub(crate) fn alloc(class: ClassPointer, value: Vec<*mut u8>) -> *mut Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + init!(obj.value => value); + ptr + } + + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: Vec<*mut u8>, + ) -> *mut Self { + let ptr = Self::alloc(class, value); + + unsafe { header_of(ptr) }.set_permanent(); + ptr + } +} + +/// A resizable array of bytes. +#[repr(C)] +pub struct ByteArray { + pub(crate) header: Header, + pub(crate) value: Vec, +} + +impl ByteArray { + pub(crate) unsafe fn drop(ptr: *mut Self) { + drop_in_place(ptr); + } + + pub(crate) fn alloc(class: ClassPointer, value: Vec) -> *mut Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + init!(obj.value => value); + ptr + } + + pub(crate) fn take_bytes(&mut self) -> Vec { + let mut bytes = Vec::new(); + + swap(&mut bytes, &mut self.value); + bytes + } +} + +/// A signed 64-bits integer. +#[repr(C)] +pub struct Int { + pub(crate) header: Header, + pub(crate) value: i64, +} + +impl Int { + pub(crate) fn new(class: ClassPointer, value: i64) -> *const Self { + if fits_in_tagged_int(value) { + tagged_int(value) + } else { + Self::boxed(class, value) + } + } + + pub(crate) fn new_permanent( + class: ClassPointer, + value: i64, + ) -> *const Self { + if fits_in_tagged_int(value) { + tagged_int(value) + } else { + Self::boxed_permanent(class, value) + } + } + + pub(crate) fn boxed(class: ClassPointer, value: i64) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.kind = Kind::Int; + init!(obj.value => value); + ptr as _ + } + + pub(crate) fn boxed_permanent( + class: ClassPointer, + value: i64, + ) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.set_permanent(); + init!(obj.value => value); + ptr as _ + } +} + +#[repr(C)] +pub struct Bool { + pub(crate) header: Header, +} + +impl Bool { + pub(crate) fn drop_and_deallocate(ptr: *const Self) { + unsafe { + drop_in_place(ptr as *mut Self); + dealloc(ptr as *mut u8, Layout::new::()); + } + } + + pub(crate) fn alloc(class: ClassPointer) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.set_permanent(); + ptr as _ + } +} + +#[repr(C)] +pub struct Nil { + pub(crate) header: Header, +} + +impl Nil { + pub(crate) fn drop_and_deallocate(ptr: *const Self) { + unsafe { + drop_in_place(ptr as *mut Self); + dealloc(ptr as *mut u8, Layout::new::()); + } + } + + pub(crate) fn alloc(class: ClassPointer) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.set_permanent(); + ptr as _ + } +} + +/// A heap allocated float. +#[repr(C)] +pub struct Float { + pub(crate) header: Header, + pub(crate) value: f64, +} + +impl Float { + pub(crate) fn alloc(class: ClassPointer, value: f64) -> *const Float { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.kind = Kind::Float; + init!(obj.value => value); + ptr as _ + } + + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: f64, + ) -> *const Float { + let ptr = Self::alloc(class, value); + + unsafe { header_of(ptr) }.set_permanent(); + ptr + } +} + +/// A heap allocated string. +/// +/// Strings use atomic reference counting as they are treated as value types, +/// and this removes the need for cloning the string's contents (at the cost of +/// atomic operations). +#[repr(C)] +pub struct String { + pub(crate) header: Header, + pub(crate) value: ImmutableString, +} + +impl String { + pub(crate) unsafe fn drop(ptr: *const Self) { + drop_in_place(ptr as *mut Self); + } + + pub(crate) unsafe fn drop_and_deallocate(ptr: *const Self) { + Self::drop(ptr); + free(ptr as *mut u8); + } + + pub(crate) unsafe fn read<'a>(ptr: *const String) -> &'a str { + (*ptr).value.as_slice() + } + + pub(crate) fn alloc( + class: ClassPointer, + value: RustString, + ) -> *const String { + Self::from_immutable_string(class, ImmutableString::from(value)) + } + + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: RustString, + ) -> *const String { + let ptr = + Self::from_immutable_string(class, ImmutableString::from(value)); + + unsafe { header_of(ptr) }.set_permanent(); + ptr + } + + pub(crate) fn from_immutable_string( + class: ClassPointer, + value: ImmutableString, + ) -> *const String { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init_atomic(class); + init!(obj.value => value); + ptr as _ + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::{align_of, size_of}; + use std::ptr::addr_of; + + extern "system" fn dummy() {} + + #[test] + fn test_header_field_offsets() { + let header = Header { + class: ClassPointer(0x7 as _), + kind: Kind::Owned, + references: 42, + }; + + let base = addr_of!(header) as usize; + + assert_eq!(addr_of!(header.class) as usize - base, 0); + assert_eq!(addr_of!(header.kind) as usize - base, 8); + assert_eq!(addr_of!(header.references) as usize - base, 12); + } + + #[test] + fn test_class_field_offsets() { + let class = Class::alloc("A".to_string(), 4, 8); + let base = class.0 as usize; + + assert_eq!(addr_of!(class.name) as usize - base, 0); + assert_eq!(addr_of!(class.instance_size) as usize - base, 24); + assert_eq!(addr_of!(class.method_slots) as usize - base, 28); + assert_eq!(addr_of!(class.methods) as usize - base, 32); + + unsafe { + Class::drop(class); + } + } + + #[test] + fn test_method_field_offsets() { + let method = Method { hash: 42, code: dummy }; + let base = addr_of!(method) as usize; + + assert_eq!(addr_of!(method.hash) as usize - base, 0); + assert_eq!(addr_of!(method.code) as usize - base, 8); + } + + #[test] + fn test_type_sizes() { + assert_eq!(size_of::
(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 40); + assert_eq!(size_of::(), 40); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 32); + } + + #[test] + fn test_type_alignments() { + assert_eq!(align_of::
(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + } + + #[test] + fn test_with_mask() { + let ptr = with_mask(0x4 as *const u8, 0b10); + + assert_eq!(ptr as usize, 0x6); + } + + #[test] + fn test_class_new() { + let class = Class::alloc("A".to_string(), 0, 24); + + assert_eq!(class.method_slots, 0); + assert_eq!(class.instance_size, 24); + + unsafe { Class::drop(class) }; + } + + #[test] + fn test_class_new_object() { + let class = Class::object("A".to_string(), 1, 0); + + assert_eq!(class.method_slots, 0); + assert_eq!(class.instance_size, 24); + + unsafe { Class::drop(class) }; + } + + #[test] + fn test_class_new_process() { + let class = Class::process("A".to_string(), 1, 0); + + assert_eq!(class.method_slots, 0); + + unsafe { Class::drop(class) }; + } +} diff --git a/rt/src/memory_map.rs b/rt/src/memory_map.rs new file mode 100644 index 000000000..0fcf8b5b0 --- /dev/null +++ b/rt/src/memory_map.rs @@ -0,0 +1,86 @@ +use crate::page::{multiple_of_page_size, page_size}; +use libc::{ + c_int, mmap, mprotect, munmap, MAP_ANON, MAP_FAILED, MAP_PRIVATE, + PROT_NONE, PROT_READ, PROT_WRITE, +}; +use std::io::{Error, Result as IoResult}; +use std::ptr::null_mut; + +/// A chunk of memory created using `mmap` and similar functions. +pub(crate) struct MemoryMap { + pub(crate) ptr: *mut u8, + pub(crate) len: usize, +} + +fn mmap_options(_stack: bool) -> c_int { + let base = MAP_PRIVATE | MAP_ANON; + + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + if _stack { + return base | libc::MAP_STACK; + } + + base +} + +impl MemoryMap { + pub(crate) fn new(size: usize, stack: bool) -> Self { + let size = multiple_of_page_size(size); + let opts = mmap_options(stack); + + let ptr = unsafe { + mmap(null_mut(), size, PROT_READ | PROT_WRITE, opts, -1, 0) + }; + + if ptr == MAP_FAILED { + panic!("mmap(2) failed: {}", Error::last_os_error()); + } + + MemoryMap { ptr: ptr as *mut u8, len: size } + } + + pub(crate) fn protect(&mut self, start: usize) -> IoResult<()> { + let res = unsafe { + mprotect(self.ptr.add(start) as _, page_size(), PROT_NONE) + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } + } +} + +impl Drop for MemoryMap { + fn drop(&mut self) { + unsafe { + munmap(self.ptr as _, self.len); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let map1 = MemoryMap::new(32, false); + let map2 = MemoryMap::new(page_size() * 3, false); + + assert_eq!(map1.len, page_size()); + assert_eq!(map2.len, page_size() * 3); + } + + #[test] + fn test_protect() { + let mut map = MemoryMap::new(page_size() * 2, false); + + assert!(map.protect(0).is_ok()); + } +} diff --git a/vm/src/network_poller.rs b/rt/src/network_poller.rs similarity index 98% rename from vm/src/network_poller.rs rename to rt/src/network_poller.rs index 4d05b2ad3..e0eb95f64 100644 --- a/vm/src/network_poller.rs +++ b/rt/src/network_poller.rs @@ -92,12 +92,13 @@ impl Worker { .filter_map(|ev| { let proc = unsafe { ProcessPointer::new(ev.key as *mut _) }; let mut state = proc.state(); + let rights = state.try_reschedule_for_io(); // A process may have also been registered with the timeout // thread (e.g. when using a timeout). As such we should // only reschedule the process if the timout thread didn't // already do this for us. - match state.try_reschedule_for_io() { + match rights { RescheduleRights::Failed => None, RescheduleRights::Acquired => Some(proc), RescheduleRights::AcquiredWithTimeout => { diff --git a/rt/src/page.rs b/rt/src/page.rs new file mode 100644 index 000000000..624d765da --- /dev/null +++ b/rt/src/page.rs @@ -0,0 +1,26 @@ +use libc::{sysconf, _SC_PAGESIZE}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); + +fn page_size_raw() -> usize { + unsafe { sysconf(_SC_PAGESIZE) as usize } +} + +pub(crate) fn page_size() -> usize { + match PAGE_SIZE.load(Ordering::Relaxed) { + 0 => { + let size = page_size_raw(); + + PAGE_SIZE.store(size, Ordering::Relaxed); + size + } + n => n, + } +} + +pub(crate) fn multiple_of_page_size(size: usize) -> usize { + let page = page_size(); + + (size + (page - 1)) & !(page - 1) +} diff --git a/rt/src/process.rs b/rt/src/process.rs new file mode 100644 index 000000000..3d7a35e2d --- /dev/null +++ b/rt/src/process.rs @@ -0,0 +1,1512 @@ +use crate::arc_without_weak::ArcWithoutWeak; +use crate::mem::{allocate, free, ClassPointer, Header}; +use crate::scheduler::process::Thread; +use crate::scheduler::timeouts::Timeout; +use crate::stack::Stack; +use backtrace; +use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; +use std::cell::UnsafeCell; +use std::collections::VecDeque; +use std::mem::{align_of, forget, size_of, ManuallyDrop}; +use std::ops::Drop; +use std::ops::{Deref, DerefMut}; +use std::ptr::{drop_in_place, null_mut, NonNull}; +use std::slice; +use std::sync::{Mutex, MutexGuard}; + +const INKO_SYMBOL_IDENTIFIER: &str = "_IM_"; + +/// The type signature for Inko's async methods defined in the native code. +/// +/// Native async methods only take a single argument: a `context::Context` that +/// contains all the necessary data. This makes it easier to pass multiple +/// values back to the native function without having to change the assembly +/// code used for context switching. +/// +/// The argument this function takes is a Context. We use an opague pointer here +/// because a Context contains a State, which isn't FFI safe. This however is +/// fine as the State type is exposed as an opague pointer and its fields are +/// never read directly from native code. +/// +/// While we can disable the lint on a per-function basis, this would require +/// annotating _a lot_ of functions, so instead we use an opague pointer here. +pub(crate) type NativeAsyncMethod = unsafe extern "system" fn(*mut u8); + +/// A single stack frame in a process' call stack. +#[repr(C)] +pub struct StackFrame { + pub name: String, + pub path: String, + pub line: i64, +} + +/// A message sent between two processes. +#[repr(C)] +pub struct Message { + /// The native function to run. + method: NativeAsyncMethod, + + /// The number of arguments of this message. + length: u8, + + /// The arguments of the message. + arguments: [*mut u8; 0], +} + +impl Message { + pub(crate) fn alloc(method: NativeAsyncMethod, length: u8) -> OwnedMessage { + unsafe { + let layout = Self::layout(length); + let raw_ptr = alloc(layout) as *mut Self; + + if raw_ptr.is_null() { + handle_alloc_error(layout); + } + + let msg = &mut *raw_ptr; + + init!(msg.method => method); + init!(msg.length => length); + + OwnedMessage(NonNull::new_unchecked(raw_ptr)) + } + } + + unsafe fn layout(length: u8) -> Layout { + let size = size_of::() + (length as usize * size_of::<*mut u8>()); + + // Messages are sent often, so we don't want the overhead of size and + // alignment checks. + Layout::from_size_align_unchecked(size, align_of::()) + } +} + +#[repr(C)] +pub(crate) struct OwnedMessage(NonNull); + +impl OwnedMessage { + pub(crate) unsafe fn from_raw(message: *mut Message) -> OwnedMessage { + OwnedMessage(NonNull::new_unchecked(message)) + } + + pub(crate) fn into_raw(mut self) -> *mut Message { + let ptr = unsafe { self.0.as_mut() }; + + forget(self); + ptr + } +} + +impl Deref for OwnedMessage { + type Target = Message; + + fn deref(&self) -> &Message { + unsafe { self.0.as_ref() } + } +} + +impl DerefMut for OwnedMessage { + fn deref_mut(&mut self) -> &mut Message { + unsafe { self.0.as_mut() } + } +} + +impl Drop for OwnedMessage { + fn drop(&mut self) { + unsafe { + let layout = Message::layout(self.0.as_ref().length); + + drop_in_place(self.0.as_ptr()); + dealloc(self.0.as_ptr() as *mut u8, layout); + } + } +} + +/// A collection of messages to be processed by a process. +struct Mailbox { + messages: VecDeque, +} + +impl Mailbox { + fn new() -> Self { + Mailbox { messages: VecDeque::new() } + } + + fn send(&mut self, message: OwnedMessage) { + self.messages.push_back(message); + } + + fn receive(&mut self) -> Option { + self.messages.pop_front() + } +} + +pub(crate) enum Task { + Resume, + Start(NativeAsyncMethod, Vec<*mut u8>), + Wait, +} + +/// The status of a process, represented as a set of bits. +pub(crate) struct ProcessStatus { + /// The bits used to indicate the status of the process. + /// + /// Multiple bits may be set in order to combine different statuses. + bits: u8, +} + +impl ProcessStatus { + /// A regular process. + const NORMAL: u8 = 0b00_0000; + + /// The main process. + const MAIN: u8 = 0b00_0001; + + /// The process is waiting for a message. + const WAITING_FOR_MESSAGE: u8 = 0b00_0010; + + /// The process is waiting for a channel. + const WAITING_FOR_CHANNEL: u8 = 0b00_0100; + + /// The process is waiting for an IO operation to complete. + const WAITING_FOR_IO: u8 = 0b00_1000; + + /// The process is simply sleeping for a certain amount of time. + const SLEEPING: u8 = 0b01_0000; + + /// The process was rescheduled after a timeout expired. + const TIMEOUT_EXPIRED: u8 = 0b10_0000; + + /// The process is running a message. + const RUNNING: u8 = 0b100_0000; + + /// The process is waiting for something, or suspended for a period of time. + const WAITING: u8 = + Self::WAITING_FOR_CHANNEL | Self::SLEEPING | Self::WAITING_FOR_IO; + + pub(crate) fn new() -> Self { + Self { bits: Self::NORMAL } + } + + fn set_main(&mut self) { + self.update_bits(Self::MAIN, true); + } + + fn is_running(&self) -> bool { + self.bit_is_set(Self::RUNNING) + } + + fn set_running(&mut self, enable: bool) { + self.update_bits(Self::RUNNING, enable); + } + + fn is_main(&self) -> bool { + self.bit_is_set(Self::MAIN) + } + + fn set_waiting_for_message(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_MESSAGE, enable); + } + + fn is_waiting_for_message(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_MESSAGE) + } + + fn set_waiting_for_channel(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_CHANNEL, enable); + } + + fn set_waiting_for_io(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_IO, enable); + } + + fn is_waiting_for_io(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_IO) + } + + fn is_waiting_for_channel(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_CHANNEL) + } + + fn is_waiting(&self) -> bool { + (self.bits & Self::WAITING) != 0 + } + + fn no_longer_waiting(&mut self) { + self.update_bits(Self::WAITING, false); + } + + fn set_timeout_expired(&mut self, enable: bool) { + self.update_bits(Self::TIMEOUT_EXPIRED, enable) + } + + fn set_sleeping(&mut self, enable: bool) { + self.update_bits(Self::SLEEPING, enable); + } + + fn timeout_expired(&self) -> bool { + self.bit_is_set(Self::TIMEOUT_EXPIRED) + } + + fn update_bits(&mut self, mask: u8, enable: bool) { + self.bits = if enable { self.bits | mask } else { self.bits & !mask }; + } + + fn bit_is_set(&self, bit: u8) -> bool { + self.bits & bit == bit + } +} + +/// An enum describing what rights a thread was given when trying to reschedule +/// a process. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum RescheduleRights { + /// The rescheduling rights were not obtained. + Failed, + + /// The rescheduling rights were obtained. + Acquired, + + /// The rescheduling rights were obtained, and the process was using a + /// timeout. + AcquiredWithTimeout, +} + +impl RescheduleRights { + pub(crate) fn are_acquired(&self) -> bool { + !matches!(self, RescheduleRights::Failed) + } +} + +/// The shared state of a process. +/// +/// This state is shared by both the process and its clients. +pub(crate) struct ProcessState { + /// The mailbox of this process. + mailbox: Mailbox, + + /// The status of the process. + status: ProcessStatus, + + /// The timeout this process is suspended with, if any. + /// + /// If missing and the process is suspended, it means the process is + /// suspended indefinitely. + timeout: Option>, +} + +impl ProcessState { + pub(crate) fn new() -> Self { + Self { + mailbox: Mailbox::new(), + status: ProcessStatus::new(), + timeout: None, + } + } + + pub(crate) fn has_same_timeout( + &self, + timeout: &ArcWithoutWeak, + ) -> bool { + self.timeout + .as_ref() + .map(|t| t.as_ptr() == timeout.as_ptr()) + .unwrap_or(false) + } + + pub(crate) fn try_reschedule_after_timeout(&mut self) -> RescheduleRights { + if !self.status.is_waiting() { + return RescheduleRights::Failed; + } + + if self.status.is_waiting_for_channel() + || self.status.is_waiting_for_io() + { + // We may be suspended for some time without actually waiting for + // anything, in that case we don't want to update the process + // status. + self.status.set_timeout_expired(true); + } + + self.status.no_longer_waiting(); + + if self.timeout.take().is_some() { + RescheduleRights::AcquiredWithTimeout + } else { + RescheduleRights::Acquired + } + } + + pub(crate) fn waiting_for_channel( + &mut self, + timeout: Option>, + ) { + self.timeout = timeout; + + self.status.set_waiting_for_channel(true); + } + + pub(crate) fn waiting_for_io( + &mut self, + timeout: Option>, + ) { + self.timeout = timeout; + + self.status.set_waiting_for_io(true); + } + + fn try_reschedule_for_message(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_message() { + return RescheduleRights::Failed; + } + + self.status.set_waiting_for_message(false); + RescheduleRights::Acquired + } + + fn try_reschedule_for_channel(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_channel() { + return RescheduleRights::Failed; + } + + self.status.set_waiting_for_channel(false); + + if self.timeout.take().is_some() { + RescheduleRights::AcquiredWithTimeout + } else { + RescheduleRights::Acquired + } + } + + pub(crate) fn try_reschedule_for_io(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_io() { + return RescheduleRights::Failed; + } + + self.status.set_waiting_for_io(false); + + if self.timeout.take().is_some() { + RescheduleRights::AcquiredWithTimeout + } else { + RescheduleRights::Acquired + } + } +} + +/// A lightweight process. +#[repr(C)] +pub struct Process { + pub header: Header, + + /// A lock acquired when running a process. + /// + /// Processes may perform operations that result in the process being + /// rescheduled by another thread. An example is when process A sends a + /// message to process B and wants to wait for it, but B reschedules A + /// before A finishes wrapping up and suspends itself. + /// + /// The run lock is meant to prevent the process from running more than + /// once, and makes implementing various aspects (e.g. sending messages) + /// easier and safe. + /// + /// This lock _is not_ used to access the shared state of a process. + /// + /// This lock is separate from the other inner fields so that native code + /// can mutate these while the lock is held, without having to explicitly + /// acquire the lock every time. + /// + /// This value is wrapped in an UnsafeCell so we can borrow it without + /// running into borrowing conflicts with methods or other fields. An + /// alternative would be to move the process-local portion into a separate + /// type and define the necessary methods on that type, instead of defining + /// them on `Process` directly. We actually used such an approach in the + /// past, but found it to be rather clunky to work with. + pub(crate) run_lock: UnsafeCell>, + + /// The current stack pointer. + /// + /// When this pointer is set to NULL it means the process no longer has an + /// associated stack. + /// + /// When this process is suspended, this pointer is the current stack + /// pointer of this process. When the process is running it instead is the + /// stack pointer of the thread that switched to running the process. + /// + /// The stack pointer is reset every time a new message is picked up. + pub(crate) stack_pointer: *mut u8, + + /// The stack memory of this process. + /// + /// This value may be absent, in which case `stack_pointer` is set to NULL. + /// We take this approach in order to keep processes as small as possible, + /// and to remove the need for unwrapping an `Option` every time we know for + /// certain a stack is present. + pub(crate) stack: ManuallyDrop, + + /// A pointer to the thread running this process. + thread: Option>, + + /// The shared state of the process. + /// + /// Multiple processes/threads may try to access this state, such as when + /// they are sending a message to this process. Access to this data doesn't + /// one obtains the run lock first. + state: Mutex, + + /// The fields of this process. + /// + /// The length of this flexible array is derived from the number of fields + /// defined in this process' class. + pub fields: [*mut u8; 0], +} + +impl Process { + pub(crate) fn drop_and_deallocate(ptr: ProcessPointer) { + unsafe { + drop_in_place(ptr.0.as_ptr()); + free(ptr.0.as_ptr()); + } + } + + pub(crate) fn alloc(class: ClassPointer, stack: Stack) -> ProcessPointer { + let ptr = allocate(unsafe { class.instance_layout() }) as *mut Self; + let obj = unsafe { &mut *ptr }; + let mut state = ProcessState::new(); + + // Processes start without any messages, so we must ensure their status + // is set accordingly. + state.status.set_waiting_for_message(true); + + obj.header.init_atomic(class); + + init!(obj.run_lock => UnsafeCell::new(Mutex::new(()))); + init!(obj.stack_pointer => stack.stack_pointer()); + init!(obj.stack => ManuallyDrop::new(stack)); + init!(obj.thread => None); + init!(obj.state => Mutex::new(state)); + + unsafe { ProcessPointer::new(ptr) } + } + + /// Returns a new Process acting as the main process. + /// + /// This process always runs on the main thread. + pub(crate) fn main( + class: ClassPointer, + method: NativeAsyncMethod, + stack: Stack, + ) -> ProcessPointer { + let mut process = Self::alloc(class, stack); + let message = Message::alloc(method, 0); + + process.set_main(); + process.send_message(message); + process + } + + pub(crate) fn set_main(&mut self) { + self.state.lock().unwrap().status.set_main(); + } + + pub(crate) fn is_main(&self) -> bool { + self.state.lock().unwrap().status.is_main() + } + + /// Suspends this process for a period of time. + pub(crate) fn suspend(&mut self, timeout: ArcWithoutWeak) { + let mut state = self.state.lock().unwrap(); + + state.timeout = Some(timeout); + state.status.set_sleeping(true); + } + + /// Sends a synchronous message to this process. + pub(crate) fn send_message( + &mut self, + message: OwnedMessage, + ) -> RescheduleRights { + let mut state = self.state.lock().unwrap(); + + state.mailbox.send(message); + state.try_reschedule_for_message() + } + + pub(crate) fn next_task(&mut self) -> Task { + let mut state = self.state.lock().unwrap(); + + if state.status.is_running() { + return Task::Resume; + } + + let message = { + if let Some(message) = state.mailbox.receive() { + message + } else { + state.status.set_waiting_for_message(true); + return Task::Wait; + } + }; + + let func = message.method; + let len = message.length as usize; + let args = unsafe { + slice::from_raw_parts(message.arguments.as_ptr(), len).to_vec() + }; + + self.stack_pointer = self.stack.stack_pointer(); + state.status.set_running(true); + Task::Start(func, args) + } + + pub(crate) fn take_stack(&mut self) -> Option { + if self.stack_pointer.is_null() { + None + } else { + self.stack_pointer = null_mut(); + + Some(unsafe { ManuallyDrop::take(&mut self.stack) }) + } + } + + /// Finishes the exection of a message, and decides what to do next with + /// this process. + /// + /// If the return value is `true`, the process should be rescheduled. + pub(crate) fn finish_message(&mut self) -> bool { + let mut state = self.state.lock().unwrap(); + + // We must clear this status so we pick up the next message when + // rescheduling the process at some point in the future. + state.status.set_running(false); + + if state.mailbox.messages.is_empty() { + state.status.set_waiting_for_message(true); + false + } else { + true + } + } + + pub(crate) fn timeout_expired(&self) -> bool { + let mut state = self.state.lock().unwrap(); + + if state.status.timeout_expired() { + state.status.set_timeout_expired(false); + true + } else { + false + } + } + + pub(crate) fn state(&self) -> MutexGuard { + self.state.lock().unwrap() + } + + /// Acquires the run lock of this process. + /// + /// We use an explicit lifetime here so the mutex guard's lifetime isn't + /// bound to `self`, allowing us to borrow it while also borrowing other + /// parts of a process. + pub(crate) fn acquire_run_lock<'a>(&self) -> MutexGuard<'a, ()> { + // Safety: the lock itself is always present, we just use UnsafeCell so + // we can borrow _just_ the lock while still being able to borrow the + // rest of the process through methods. + unsafe { (*self.run_lock.get()).lock().unwrap() } + } + + pub(crate) fn set_thread(&mut self, thread: &mut Thread) { + self.thread = Some(NonNull::from(thread)); + } + + pub(crate) fn unset_thread(&mut self) { + self.thread = None; + } + + /// Returns a mutable reference to the thread that's running this process. + /// + /// This method is unsafe as it assumes a thread is set, and the pointer + /// points to valid data. + /// + /// The lifetime of the returned reference isn't bound to `self` as doing so + /// prevents various patterns we depend on (e.g. + /// `process.thread().schedule(process)`). In addition, the reference itself + /// remains valid even when moving the process around, as a thread always + /// outlives a process. + pub(crate) unsafe fn thread<'a>(&mut self) -> &'a mut Thread { + &mut *self.thread.unwrap_unchecked().as_ptr() + } + + pub(crate) fn stacktrace(&self) -> Vec { + let mut frames = Vec::new(); + + // We don't use backtrace::trace() so we can avoid the frames introduced + // by calling this function (and any functions it may call). + let trace = backtrace::Backtrace::new(); + + for frame in trace.frames() { + backtrace::resolve(frame.ip(), |symbol| { + let name = if let Some(sym_name) = symbol.name() { + let name = sym_name.as_str().unwrap_or(""); + + // We only want to include frames for Inko source code, not + // any additional frames introduced by the runtime library + // and its dependencies. + if let Some(name) = + name.strip_prefix(INKO_SYMBOL_IDENTIFIER) + { + name.to_string() + } else { + return; + } + } else { + String::new() + }; + + let path = symbol + .filename() + .map(|v| v.to_string_lossy().into_owned()) + .unwrap_or_else(String::new); + + let line = symbol.lineno().unwrap_or(0) as i64; + + frames.push(StackFrame { name, path, line }); + }); + } + + frames.reverse(); + frames + } +} + +/// A pointer to a process. +#[repr(transparent)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct ProcessPointer(NonNull); + +unsafe impl Sync for ProcessPointer {} +unsafe impl Send for ProcessPointer {} + +impl ProcessPointer { + pub(crate) unsafe fn new(pointer: *mut Process) -> Self { + Self(NonNull::new_unchecked(pointer)) + } + + pub(crate) fn identifier(self) -> usize { + self.0.as_ptr() as usize + } + + pub(crate) fn blocking(mut self, function: impl FnOnce() -> R) -> R { + // Safety: threads are stored in processes before running them. + unsafe { self.thread().blocking(self, function) } + } +} + +impl Drop for Process { + fn drop(&mut self) { + if !self.stack_pointer.is_null() { + unsafe { + ManuallyDrop::drop(&mut self.stack); + } + } + } +} + +impl Deref for ProcessPointer { + type Target = Process; + + fn deref(&self) -> &Process { + unsafe { &*self.0.as_ptr() } + } +} + +impl DerefMut for ProcessPointer { + fn deref_mut(&mut self) -> &mut Process { + unsafe { &mut *self.0.as_mut() } + } +} + +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum SendResult { + Sent, + Full, + Reschedule(ProcessPointer), + RescheduleWithTimeout(ProcessPointer), +} + +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum ReceiveResult { + None, + Some(*mut u8), + Reschedule(*mut u8, ProcessPointer), +} + +/// The internal (synchronised) state of a channel. +pub(crate) struct ChannelState { + /// The index into the ring buffer to use for sending a new value. + send_index: usize, + + /// The index into the ring buffer to use for receiving a value. + receive_index: usize, + + /// The fixed-size ring buffer of messages. + messages: Box<[*mut u8]>, + + /// Processes waiting for a message to be sent to this channel. + waiting_for_message: Vec, + + /// Processes that tried to send a message when the channel was full. + waiting_for_space: Vec, +} + +impl ChannelState { + fn new(capacity: usize) -> ChannelState { + ChannelState { + messages: (0..capacity).map(|_| null_mut()).collect(), + send_index: 0, + receive_index: 0, + waiting_for_message: Vec::new(), + waiting_for_space: Vec::new(), + } + } + + pub(crate) fn has_messages(&self) -> bool { + !self.messages[self.receive_index].is_null() + } + + pub(crate) fn add_waiting_for_message(&mut self, process: ProcessPointer) { + self.waiting_for_message.push(process); + } + + pub(crate) fn remove_waiting_for_message( + &mut self, + process: ProcessPointer, + ) { + self.waiting_for_message.retain(|&v| v != process); + } + + fn capacity(&self) -> usize { + self.messages.len() + } + + fn is_full(&self) -> bool { + !self.messages[self.send_index].is_null() + } + + fn send(&mut self, value: *mut u8) -> bool { + if self.is_full() { + return false; + } + + let index = self.send_index; + + self.messages[index] = value; + self.send_index = self.next_index(index); + true + } + + fn receive(&mut self) -> Option<*mut u8> { + let index = self.receive_index; + let value = self.messages[index]; + + if value.is_null() { + return None; + } + + self.messages[index] = null_mut(); + self.receive_index = self.next_index(index); + Some(value) + } + + fn next_index(&self, index: usize) -> usize { + // The & operator can't be used as we don't guarantee/require message + // sizes to be a power of two. The % operator is quite expensive to use: + // a simple micro benchmark at the time of writing suggested that the % + // operator is about three times slower compared to a branch like the + // one here. + if index == self.capacity() - 1 { + 0 + } else { + index + 1 + } + } +} + +/// A multiple publisher, multiple consumer first-in-first-out channel. +/// +/// Messages are sent and received in FIFO order. However, processes waiting for +/// messages or for space to be available (in case the channel is full) aren't +/// woken up in FIFO order. Currently this uses a LIFO order, but this isn't +/// guaranteed nor should this be relied upon. +/// +/// Channels are not lock-free, and as such may perform worse compared to +/// channels found in other languages (e.g. Rust or Go). This is because in its +/// current form we favour simplicity and correctness over performance. This may +/// be improved upon in the future. +/// +/// Channels are always bounded and have a minimum capacity of 1, even if the +/// user-specified capacity is 0. When a channel is full, processes sending +/// messages are to be suspended and woken up again when space is available. +#[repr(C)] +pub struct Channel { + pub(crate) header: Header, + pub(crate) state: Mutex, +} + +impl Channel { + pub(crate) fn alloc(class: ClassPointer, capacity: usize) -> *mut Channel { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init_atomic(class); + init!(obj.state => Mutex::new(ChannelState::new(capacity))); + ptr as _ + } + + pub(crate) unsafe fn drop(ptr: *mut Channel) { + drop_in_place(ptr); + } + + pub(crate) fn send( + &self, + sender: ProcessPointer, + message: *mut u8, + ) -> SendResult { + let mut state = self.state.lock().unwrap(); + + if !state.send(message) { + state.waiting_for_space.push(sender); + return SendResult::Full; + } + + if let Some(receiver) = state.waiting_for_message.pop() { + // We don't need to keep the lock any longer than necessary. + drop(state); + + // The process may be waiting for more than one channel to receive a + // message. In this case it's possible that multiple different + // processes try to reschedule the same waiting process, so we have + // to acquire the rescheduling rights first. + match receiver.state().try_reschedule_for_channel() { + RescheduleRights::Failed => SendResult::Sent, + RescheduleRights::Acquired => SendResult::Reschedule(receiver), + RescheduleRights::AcquiredWithTimeout => { + SendResult::RescheduleWithTimeout(receiver) + } + } + } else { + SendResult::Sent + } + } + + pub(crate) fn receive( + &self, + receiver: ProcessPointer, + timeout: Option>, + ) -> ReceiveResult { + let mut state = self.state.lock().unwrap(); + + if let Some(msg) = state.receive() { + if let Some(proc) = state.waiting_for_space.pop() { + ReceiveResult::Reschedule(msg, proc) + } else { + ReceiveResult::Some(msg) + } + } else { + receiver.state().waiting_for_channel(timeout); + state.waiting_for_message.push(receiver); + ReceiveResult::None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem::tagged_int; + use crate::test::{empty_class, empty_process_class, OwnedProcess}; + use std::time::Duration; + + macro_rules! offset_of { + ($value: expr, $field: ident) => {{ + (std::ptr::addr_of!($value.$field) as usize) + .saturating_sub($value.0.as_ptr() as usize) + }}; + } + + unsafe extern "system" fn method(_ctx: *mut u8) { + // This function is used for testing the sending/receiving of messages. + } + + #[test] + fn test_type_sizes() { + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + + if cfg!(target_os = "linux") { + assert_eq!(size_of::>>(), 8); + assert_eq!(size_of::(), 112); + assert_eq!(size_of::(), 104); + } else { + assert_eq!(size_of::>>(), 16); + assert_eq!(size_of::(), 128); + assert_eq!(size_of::(), 112); + } + + assert_eq!(size_of::(), 48); + assert_eq!(size_of::>>(), 8); + assert_eq!(size_of::(), 80); + } + + #[test] + fn test_field_offsets() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let proc = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert_eq!(offset_of!(proc, header), 0); + assert_eq!( + offset_of!(proc, fields), + if cfg!(target_os = "linux") { 112 } else { 128 } + ); + } + + #[test] + fn test_process_status_new() { + let status = ProcessStatus::new(); + + assert_eq!(status.bits, 0); + } + + #[test] + fn test_process_status_set_main() { + let mut status = ProcessStatus::new(); + + status.set_main(); + + assert!(status.is_main()); + } + + #[test] + fn test_process_status_set_waiting_for_message() { + let mut status = ProcessStatus::new(); + + status.set_waiting_for_message(true); + assert!(status.is_waiting_for_message()); + + status.set_waiting_for_message(false); + assert!(!status.is_waiting_for_message()); + } + + #[test] + fn test_process_status_set_waiting_for_channel() { + let mut status = ProcessStatus::new(); + + status.set_waiting_for_channel(true); + assert!(status.is_waiting_for_channel()); + + status.set_waiting_for_channel(false); + assert!(!status.is_waiting_for_channel()); + } + + #[test] + fn test_process_status_is_waiting() { + let mut status = ProcessStatus::new(); + + status.set_sleeping(true); + assert!(status.is_waiting()); + + status.set_sleeping(false); + status.set_waiting_for_channel(true); + assert!(status.is_waiting()); + + status.no_longer_waiting(); + + assert!(!status.is_waiting_for_channel()); + assert!(!status.is_waiting()); + } + + #[test] + fn test_process_status_timeout_expired() { + let mut status = ProcessStatus::new(); + + status.set_timeout_expired(true); + assert!(status.timeout_expired()); + + status.set_timeout_expired(false); + assert!(!status.timeout_expired()); + } + + #[test] + fn test_reschedule_rights_are_acquired() { + assert!(!RescheduleRights::Failed.are_acquired()); + assert!(RescheduleRights::Acquired.are_acquired()); + assert!(RescheduleRights::AcquiredWithTimeout.are_acquired()); + } + + #[test] + fn test_process_state_has_same_timeout() { + let mut state = ProcessState::new(); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + assert!(!state.has_same_timeout(&timeout)); + + state.timeout = Some(timeout.clone()); + + assert!(state.has_same_timeout(&timeout)); + } + + #[test] + fn test_process_state_try_reschedule_after_timeout() { + let mut state = ProcessState::new(); + + assert_eq!( + state.try_reschedule_after_timeout(), + RescheduleRights::Failed + ); + + state.waiting_for_channel(None); + + assert_eq!( + state.try_reschedule_after_timeout(), + RescheduleRights::Acquired + ); + + assert!(!state.status.is_waiting_for_channel()); + assert!(!state.status.is_waiting()); + + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + state.waiting_for_channel(Some(timeout)); + + assert_eq!( + state.try_reschedule_after_timeout(), + RescheduleRights::AcquiredWithTimeout + ); + + assert!(!state.status.is_waiting_for_channel()); + assert!(!state.status.is_waiting()); + } + + #[test] + fn test_process_state_waiting_for_channel() { + let mut state = ProcessState::new(); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + state.waiting_for_channel(None); + + assert!(state.status.is_waiting_for_channel()); + assert!(state.timeout.is_none()); + + state.waiting_for_channel(Some(timeout)); + + assert!(state.status.is_waiting_for_channel()); + assert!(state.timeout.is_some()); + } + + #[test] + fn test_process_state_try_reschedule_for_message() { + let mut state = ProcessState::new(); + + assert_eq!( + state.try_reschedule_for_message(), + RescheduleRights::Failed + ); + + state.status.set_waiting_for_message(true); + + assert_eq!( + state.try_reschedule_for_message(), + RescheduleRights::Acquired + ); + assert!(!state.status.is_waiting_for_message()); + } + + #[test] + fn test_process_state_try_reschedule_for_channel() { + let mut state = ProcessState::new(); + + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::Failed + ); + + state.status.set_waiting_for_channel(true); + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::Acquired + ); + assert!(!state.status.is_waiting_for_channel()); + + state.status.set_waiting_for_channel(true); + state.timeout = Some(Timeout::with_rc(Duration::from_secs(0))); + + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::AcquiredWithTimeout + ); + assert!(!state.status.is_waiting_for_channel()); + } + + #[test] + fn test_process_new() { + let class = empty_process_class("A"); + let process = OwnedProcess::new(Process::alloc(*class, Stack::new(32))); + + assert_eq!(process.header.class, class.0); + } + + #[test] + fn test_process_main() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let process = + OwnedProcess::new(Process::main(*proc_class, method, stack)); + + assert!(process.is_main()); + } + + #[test] + fn test_process_set_main() { + let class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); + + assert!(!process.is_main()); + + process.set_main(); + assert!(process.is_main()); + } + + #[test] + fn test_process_suspend() { + let class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + process.suspend(timeout); + + assert!(process.state().timeout.is_some()); + assert!(process.state().status.is_waiting()); + } + + #[test] + fn test_process_timeout_expired() { + let class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + assert!(!process.timeout_expired()); + + process.suspend(timeout); + + assert!(!process.timeout_expired()); + assert!(!process.state().status.timeout_expired()); + } + + #[test] + fn test_process_pointer_identifier() { + let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; + + assert_eq!(ptr.identifier(), 0x4); + } + + #[test] + fn test_channel_alloc() { + let class = empty_class("Channel"); + let chan = Channel::alloc(*class, 4); + + unsafe { + let chan = &(*chan); + let state = chan.state.lock().unwrap(); + + assert_eq!(chan.header.class, *class); + assert_eq!(state.messages.len(), 4); + } + + unsafe { + Channel::drop(chan); + free(chan); + } + } + + #[test] + fn test_channel_send_empty() { + let process_class = empty_process_class("A"); + let sender = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 4); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + assert_eq!(chan.send(*sender, msg as _), SendResult::Sent); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_send_full() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + assert_eq!(chan.send(*process, msg as _), SendResult::Sent); + assert_eq!(chan.send(*process, msg as _), SendResult::Full); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_send_with_waiting() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.receive(*process, None); + + assert_eq!( + chan.send(*process, msg as _), + SendResult::Reschedule(*process) + ); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_send_with_waiting_with_timeout() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.receive(*process, Some(Timeout::with_rc(Duration::from_secs(0)))); + + assert_eq!( + chan.send(*process, msg as _), + SendResult::RescheduleWithTimeout(*process) + ); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_receive_empty() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + + assert_eq!(chan.receive(*process, None), ReceiveResult::None); + assert!(process.state().status.is_waiting_for_channel()); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_receive_with_messages() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.send(*process, msg as _); + + assert_eq!(chan.receive(*process, None), ReceiveResult::Some(msg as _)); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_receive_with_messages_with_blocked_sender() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.send(*process, msg as _); + chan.send(*process, msg as _); + + assert_eq!( + chan.receive(*process, None), + ReceiveResult::Reschedule(msg as _, *process) + ); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_message_new() { + let message = Message::alloc(method, 2); + + assert_eq!(message.length, 2); + } + + #[test] + fn test_mailbox_send() { + let mut mail = Mailbox::new(); + let msg = Message::alloc(method, 0); + + mail.send(msg); + assert!(mail.receive().is_some()); + } + + #[test] + fn test_process_send_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg = Message::alloc(method, 0); + + assert_eq!(process.send_message(msg), RescheduleRights::Acquired); + assert_eq!(process.state().mailbox.messages.len(), 1); + } + + #[test] + fn test_process_next_task_without_messages() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert!(matches!(process.next_task(), Task::Wait)); + } + + #[test] + fn test_process_next_task_with_new_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg = Message::alloc(method, 0); + + process.send_message(msg); + + assert!(matches!(process.next_task(), Task::Start(_, _))); + } + + #[test] + fn test_process_next_task_with_existing_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg1 = Message::alloc(method, 0); + let msg2 = Message::alloc(method, 0); + + process.send_message(msg1); + process.next_task(); + process.send_message(msg2); + + assert!(matches!(process.next_task(), Task::Resume)); + } + + #[test] + fn test_process_take_stack() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert!(process.take_stack().is_some()); + assert!(process.stack_pointer.is_null()); + } + + #[test] + fn test_process_finish_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert!(!process.finish_message()); + assert!(process.state().status.is_waiting_for_message()); + } + + #[test] + fn test_channel_state_send() { + let mut state = ChannelState::new(2); + + assert!(!state.is_full()); + assert_eq!(state.capacity(), 2); + + assert!(state.send(0x1 as _)); + assert!(state.send(0x2 as _)); + assert!(!state.send(0x3 as _)); + assert!(!state.send(0x4 as _)); + + assert_eq!(state.messages[0], 0x1 as _); + assert_eq!(state.messages[1], 0x2 as _); + assert!(state.is_full()); + } + + #[test] + fn test_channel_state_receive() { + let mut state = ChannelState::new(2); + + assert!(state.receive().is_none()); + + state.send(0x1 as _); + state.send(0x2 as _); + + assert!(state.is_full()); + assert_eq!(state.receive(), Some(0x1 as _)); + assert!(!state.is_full()); + + assert_eq!(state.receive(), Some(0x2 as _)); + assert_eq!(state.receive(), None); + assert!(!state.is_full()); + } + + #[test] + fn test_channel_state_has_messages() { + let mut state = ChannelState::new(2); + + assert!(!state.has_messages()); + + state.send(0x1 as _); + assert!(state.has_messages()); + + state.receive(); + assert!(!state.has_messages()); + + state.send(0x1 as _); + assert!(state.has_messages()); + } +} diff --git a/rt/src/result.rs b/rt/src/result.rs new file mode 100644 index 000000000..11d5ca467 --- /dev/null +++ b/rt/src/result.rs @@ -0,0 +1,96 @@ +use crate::mem::tagged_int; +use std::io; +use std::ptr::null_mut; + +const INVALID_INPUT: i64 = 11; +const TIMED_OUT: i64 = 13; + +const OK: i64 = 0; +const NONE: i64 = 1; +const ERROR: i64 = 2; + +/// A result type that is FFI safe and wraps a pointer. +/// +/// Various functions in the runtime library need a way to signal an OK versus +/// an error value. Some of these errors are simple IO error codes, while others +/// may be strings or something else. Rust's built-in `Result` type isn't FFI +/// safe and as such we can't use it in our runtime functions. +/// +/// This type is essentially Rust's `Result` type, minus any methods as we don't +/// use it as output and not input. The layout is fixed so generated code can +/// use it as if this type were defined in the generated code directly. +/// +/// The order of this type is and must stay fixed, as rearranging the order of +/// the variants breaks generated code (unless it too is updated accordingly). +/// +/// We're using a struct here instead of an enum as this gives us more precise +/// control over the layout, and lets us test the exact field offsets. +#[repr(C)] +#[derive(Eq, PartialEq, Debug)] +pub struct Result { + pub tag: *mut u8, + pub value: *mut u8, +} + +impl Result { + pub(crate) fn ok(value: *mut u8) -> Result { + Result { tag: tagged_int(OK) as _, value } + } + + pub(crate) fn error(value: *mut u8) -> Result { + Result { tag: tagged_int(ERROR) as _, value } + } + + pub(crate) fn none() -> Result { + Result { tag: tagged_int(NONE) as _, value: null_mut() } + } + + pub(crate) fn ok_boxed(value: T) -> Result { + Result::ok(Box::into_raw(Box::new(value)) as _) + } + + pub(crate) fn io_error(error: io::Error) -> Result { + let code = match error.kind() { + io::ErrorKind::NotFound => 1, + io::ErrorKind::PermissionDenied => 2, + io::ErrorKind::ConnectionRefused => 3, + io::ErrorKind::ConnectionReset => 4, + io::ErrorKind::ConnectionAborted => 5, + io::ErrorKind::NotConnected => 6, + io::ErrorKind::AddrInUse => 7, + io::ErrorKind::AddrNotAvailable => 8, + io::ErrorKind::BrokenPipe => 9, + io::ErrorKind::AlreadyExists => 10, + io::ErrorKind::InvalidInput => INVALID_INPUT, + io::ErrorKind::InvalidData => 12, + io::ErrorKind::TimedOut => TIMED_OUT, + io::ErrorKind::WriteZero => 14, + io::ErrorKind::Interrupted => 15, + io::ErrorKind::UnexpectedEof => 16, + _ => 0, + }; + + Self::error(tagged_int(code) as _) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + use std::ptr::addr_of; + + #[test] + fn test_memory_layout() { + assert_eq!(size_of::(), 16); + } + + #[test] + fn test_field_offsets() { + let res = Result::ok(0x4 as _); + let base = addr_of!(res) as usize; + + assert_eq!(addr_of!(res.tag) as usize - base, 0); + assert_eq!(addr_of!(res.value) as usize - base, 8); + } +} diff --git a/rt/src/runtime.rs b/rt/src/runtime.rs new file mode 100644 index 000000000..f520714d9 --- /dev/null +++ b/rt/src/runtime.rs @@ -0,0 +1,143 @@ +mod array; +mod byte_array; +mod class; +mod env; +mod float; +mod fs; +mod general; +mod helpers; +mod int; +mod process; +mod random; +mod socket; +mod stdio; +mod string; +mod sys; +mod time; + +use crate::config::Config; +use crate::mem::ClassPointer; +use crate::network_poller::Worker as NetworkPollerWorker; +use crate::process::{NativeAsyncMethod, Process}; +use crate::scheduler::{number_of_cores, pin_thread_to_core}; +use crate::stack::Stack; +use crate::state::{MethodCounts, RcState, State}; +use std::env::args_os; +use std::io::{stdout, Write as _}; +use std::process::exit as rust_exit; +use std::thread; + +#[cfg(unix)] +fn ignore_sigpipe() { + // Broken pipe errors default to terminating the entire program, making it + // impossible to handle such errors. This is especially problematic for + // sockets, as writing to a socket closed on the other end would terminate + // the program. + // + // While Rust handles this for us when compiling an executable, it doesn't + // do so when compiling it to a static library and linking it to our + // generated code, so we must handle this ourselves. + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_IGN); + } +} + +#[cfg(not(unix))] +fn ignore_sigpipe() { + // Not needed on these platforms +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_new( + counts: *mut MethodCounts, +) -> *mut Runtime { + Box::into_raw(Box::new(Runtime::new(&*counts))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_drop(runtime: *mut Runtime) { + drop(Box::from_raw(runtime)); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_start( + runtime: *mut Runtime, + class: ClassPointer, + method: NativeAsyncMethod, +) { + ignore_sigpipe(); + (*runtime).start(class, method); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_state( + runtime: *mut Runtime, +) -> *const State { + (*runtime).state.as_ptr() as _ +} + +pub(crate) fn exit(status: i32) -> ! { + // STDOUT is buffered by default, and not flushing it upon exit may result + // in parent processes not observing the output. + let _ = stdout().lock().flush(); + + rust_exit(status); +} + +/// An Inko runtime along with all its state. +#[repr(C)] +pub struct Runtime { + state: RcState, +} + +impl Runtime { + /// Returns a new `Runtime` instance. + /// + /// This method sets up the runtime and allocates the core classes, but + /// doesn't start any threads. + fn new(counts: &MethodCounts) -> Self { + let config = Config::from_env(); + let args: Vec<_> = args_os() + .skip(1) + .map(|v| v.to_string_lossy().into_owned()) + .collect(); + + Self { state: State::new(config, counts, &args) } + } + + /// Starts the runtime using the given process and method as the entry + /// point. + /// + /// This method blocks the current thread until the program terminates, + /// though this thread itself doesn't run any processes (= it just + /// waits/blocks until completion). + fn start(&self, main_class: ClassPointer, main_method: NativeAsyncMethod) { + let state = self.state.clone(); + let cores = number_of_cores(); + + thread::Builder::new() + .name("timeout".to_string()) + .spawn(move || { + pin_thread_to_core(0); + state.timeout_worker.run(&state.scheduler) + }) + .unwrap(); + + for id in 0..self.state.network_pollers.len() { + let state = self.state.clone(); + + thread::Builder::new() + .name(format!("netpoll {}", id)) + .spawn(move || { + pin_thread_to_core(1 % cores); + NetworkPollerWorker::new(id, state).run() + }) + .unwrap(); + } + + let stack = Stack::new(self.state.config.stack_size as usize); + let main_proc = Process::main(main_class, main_method, stack); + + self.state.scheduler.run(&self.state, main_proc); + } +} diff --git a/rt/src/runtime/array.rs b/rt/src/runtime/array.rs new file mode 100644 index 000000000..e9d4785f3 --- /dev/null +++ b/rt/src/runtime/array.rs @@ -0,0 +1,115 @@ +use crate::mem::{Array, Int, Nil}; +use crate::result::Result as InkoResult; +use crate::state::State; + +#[no_mangle] +pub unsafe extern "system" fn inko_array_new( + state: *const State, + capacity: i64, +) -> *mut Array { + Array::alloc((*state).array_class, Vec::with_capacity(capacity as _)) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_new_permanent( + state: *const State, + capacity: i64, +) -> *mut Array { + Array::alloc_permanent( + (*state).array_class, + Vec::with_capacity(capacity as _), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_reserve( + state: *const State, + array: *mut Array, + length: i64, +) -> *const Nil { + (*array).value.reserve_exact(length as _); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_push( + state: *const State, + array: *mut Array, + value: *mut u8, +) -> *const Nil { + (*array).value.push(value); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_pop(array: *mut Array) -> InkoResult { + (*array) + .value + .pop() + .map(|v| InkoResult::ok(v as _)) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_set( + array: *mut Array, + index: i64, + value: *mut u8, +) -> *mut u8 { + let array = &mut *array; + let index_ref = array.value.get_unchecked_mut(index as usize); + let old_value = *index_ref; + + *index_ref = value; + old_value +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_get( + array: *const Array, + index: i64, +) -> *mut u8 { + *(*array).value.get_unchecked(index as usize) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_remove( + array: *mut Array, + index: i64, +) -> *mut u8 { + (*array).value.remove(index as usize) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_length( + state: *const State, + array: *const Array, +) -> *const Int { + Int::new((*state).int_class, (*array).value.len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_capacity( + state: *const State, + array: *const Array, +) -> *const Int { + Int::new((*state).int_class, (*array).value.capacity() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_clear( + state: *const State, + array: *mut Array, +) -> *const Nil { + (*array).value.clear(); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_drop( + state: *const State, + array: *mut Array, +) -> *const Nil { + Array::drop(array); + (*state).nil_singleton +} diff --git a/rt/src/runtime/byte_array.rs b/rt/src/runtime/byte_array.rs new file mode 100644 index 000000000..0be580dfd --- /dev/null +++ b/rt/src/runtime/byte_array.rs @@ -0,0 +1,188 @@ +use crate::immutable_string::ImmutableString; +use crate::mem::{tagged_int, Bool, ByteArray, Int, Nil, String as InkoString}; +use crate::state::State; +use std::cmp::min; + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_new( + state: *const State, +) -> *mut ByteArray { + ByteArray::alloc((*state).byte_array_class, Vec::new()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_push( + state: *const State, + bytes: *mut ByteArray, + value: i64, +) -> *const Nil { + (*bytes).value.push(value as u8); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_pop( + bytes: *mut ByteArray, +) -> *const Int { + if let Some(value) = (*bytes).value.pop() { + tagged_int(value as i64) + } else { + tagged_int(-1) + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_set( + bytes: *mut ByteArray, + index: i64, + value: i64, +) -> *const Int { + let bytes = &mut (*bytes).value; + let index_ref = bytes.get_unchecked_mut(index as usize); + let old_value = *index_ref; + + *index_ref = value as u8; + tagged_int(old_value as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_get( + bytes: *mut ByteArray, + index: i64, +) -> *const Int { + tagged_int(*(*bytes).value.get_unchecked(index as usize) as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_remove( + bytes: *mut ByteArray, + index: i64, +) -> *const Int { + tagged_int((*bytes).value.remove(index as usize) as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_length( + state: *const State, + bytes: *const ByteArray, +) -> *const Int { + Int::new((*state).int_class, (*bytes).value.len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_eq( + state: *const State, + lhs: *const ByteArray, + rhs: *const ByteArray, +) -> *const Bool { + let state = &*state; + + if (*lhs).value == (*rhs).value { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_clear( + state: *const State, + bytes: *mut ByteArray, +) -> *const Nil { + (*bytes).value.clear(); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_clone( + state: *const State, + bytes: *const ByteArray, +) -> *mut ByteArray { + ByteArray::alloc((*state).byte_array_class, (*bytes).value.clone()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_drop( + state: *const State, + array: *mut ByteArray, +) -> *const Nil { + ByteArray::drop(array); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_to_string( + state: *const State, + bytes: *const ByteArray, +) -> *const InkoString { + let bytes = &(*bytes).value; + let string = ImmutableString::from_utf8(bytes.clone()); + + InkoString::from_immutable_string((*state).string_class, string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_drain_to_string( + state: *const State, + bytes: *mut ByteArray, +) -> *const InkoString { + let bytes = &mut (*bytes); + let string = ImmutableString::from_utf8(bytes.take_bytes()); + + InkoString::from_immutable_string((*state).string_class, string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_slice( + state: *const State, + bytes: *const ByteArray, + start: i64, + length: i64, +) -> *mut ByteArray { + let bytes = &*bytes; + let end = min((start + length) as usize, bytes.value.len()); + + ByteArray::alloc( + (*state).byte_array_class, + bytes.value[start as usize..end].to_vec(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_append( + state: *const State, + target: *mut ByteArray, + source: *mut ByteArray, +) -> *const Nil { + (*target).value.append(&mut (*source).value); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_copy_from( + state: *const State, + target: *mut ByteArray, + source: *mut ByteArray, + start: i64, + length: i64, +) -> *const Int { + let target = &mut *target; + let source = &mut *source; + let end = min((start + length) as usize, source.value.len()); + let slice = &source.value[start as usize..end]; + let amount = slice.len() as i64; + + target.value.extend_from_slice(slice); + Int::new((*state).int_class, amount) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_resize( + state: *const State, + bytes: *mut ByteArray, + size: i64, + filler: i64, +) -> *const Nil { + (*bytes).value.resize(size as usize, filler as u8); + (*state).nil_singleton +} diff --git a/rt/src/runtime/class.rs b/rt/src/runtime/class.rs new file mode 100644 index 000000000..228a3cda0 --- /dev/null +++ b/rt/src/runtime/class.rs @@ -0,0 +1,31 @@ +use crate::mem::{Class, ClassPointer}; +use std::{ffi::CStr, os::raw::c_char}; + +#[no_mangle] +pub unsafe extern "system" fn inko_class_object( + name: *const c_char, + fields: u8, + methods: u16, +) -> ClassPointer { + let name = + String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned(); + + Class::object(name, fields, methods) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_class_process( + name: *const c_char, + fields: u8, + methods: u16, +) -> ClassPointer { + let name = + String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned(); + + Class::process(name, fields, methods) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_class_drop(class: ClassPointer) { + Class::drop(class); +} diff --git a/rt/src/runtime/env.rs b/rt/src/runtime/env.rs new file mode 100644 index 000000000..85436f0d0 --- /dev/null +++ b/rt/src/runtime/env.rs @@ -0,0 +1,122 @@ +use crate::mem::{Array, String as InkoString}; +use crate::result::Result as InkoResult; +use crate::state::State; +use std::env; +use std::path::PathBuf; + +#[no_mangle] +pub unsafe extern "system" fn inko_env_get( + state: *const State, + name: *const InkoString, +) -> InkoResult { + let state = &(*state); + let name = InkoString::read(name); + + state + .environment + .get(name) + .cloned() + .map(|path| InkoResult::ok(path as _)) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_variables( + state: *const State, +) -> *mut Array { + let state = &*state; + let names = state + .environment + .keys() + .map(|key| { + InkoString::alloc(state.string_class, key.clone()) as *mut u8 + }) + .collect(); + + Array::alloc(state.array_class, names) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_home_directory( + state: *const State, +) -> InkoResult { + let state = &*state; + + // Rather than performing all sorts of magical incantations to get the home + // directory, we're just going to require that HOME is set. + // + // If the home is explicitly set to an empty string we still ignore it, + // because there's no scenario in which Some("") is useful. + state + .environment + .get("HOME") + .cloned() + .filter(|&path| !InkoString::read(path).is_empty()) + .map(|path| InkoResult::ok(path as _)) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_temp_directory( + state: *const State, +) -> *const InkoString { + let path = canonalize(env::temp_dir().to_string_lossy().into_owned()); + + InkoString::alloc((*state).string_class, path) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_get_working_directory( + state: *const State, +) -> InkoResult { + env::current_dir() + .map(|path| canonalize(path.to_string_lossy().into_owned())) + .map(|path| { + InkoResult::ok(InkoString::alloc((*state).string_class, path) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_set_working_directory( + state: *const State, + directory: *const InkoString, +) -> InkoResult { + let state = &*state; + let dir = InkoString::read(directory); + + env::set_current_dir(dir) + .map(|_| InkoResult::ok(state.nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_arguments( + state: *const State, +) -> *mut Array { + let state = &*state; + + Array::alloc( + state.array_class, + state.arguments.iter().map(|&v| v as _).collect(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_executable( + state: *const State, +) -> InkoResult { + env::current_exe() + .map(|path| path.to_string_lossy().into_owned()) + .map(|path| { + InkoResult::ok(InkoString::alloc((*state).string_class, path) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +fn canonalize(path: String) -> String { + PathBuf::from(&path) + .canonicalize() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or(path) +} diff --git a/rt/src/runtime/float.rs b/rt/src/runtime/float.rs new file mode 100644 index 000000000..e615dc20c --- /dev/null +++ b/rt/src/runtime/float.rs @@ -0,0 +1,116 @@ +use crate::mem::{Bool, Float, String as InkoString}; +use crate::state::State; + +/// The maximum difference between two floats for them to be considered equal, +/// as expressed in "Units in the Last Place" (ULP). +const ULP_DIFF: i64 = 1; + +#[no_mangle] +pub unsafe extern "system" fn inko_float_boxed( + state: *const State, + value: f64, +) -> *const Float { + Float::alloc((*state).float_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_boxed_permanent( + state: *const State, + value: f64, +) -> *const Float { + Float::alloc_permanent((*state).float_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_eq( + state: *const State, + left: f64, + right: f64, +) -> *const Bool { + // For float equality we use ULPs. See + // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + // for more details. + let state = &*state; + + if left == right { + // Handle cases such as `-0.0 == 0.0`. + return state.true_singleton; + } + + if left.is_sign_positive() != right.is_sign_positive() { + return state.false_singleton; + } + + if left.is_nan() || right.is_nan() { + return state.false_singleton; + } + + let left_bits = left.to_bits() as i64; + let right_bits = right.to_bits() as i64; + let diff = left_bits.wrapping_sub(right_bits); + + if (-ULP_DIFF..=ULP_DIFF).contains(&diff) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_clone( + state: *const State, + float: *const Float, +) -> *const Float { + let obj = &*float; + + if obj.header.is_permanent() { + float + } else { + Float::alloc((*state).float_class, obj.value) + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_round( + state: *const State, + float: f64, + precision: i64, +) -> *const Float { + let result = if precision == 0 { + float.round() + } else if precision <= i64::from(u32::MAX) { + let power = 10.0_f64.powi(precision as i32); + let multiplied = float * power; + + // Certain very large numbers (e.g. f64::MAX) would produce Infinity + // when multiplied with the power. In this case we just return the input + // float directly. + if multiplied.is_finite() { + multiplied.round() / power + } else { + float + } + } else { + float + }; + + Float::alloc((*state).float_class, result) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_to_string( + state: *const State, + value: f64, +) -> *const InkoString { + let string = if value.is_infinite() && value.is_sign_positive() { + "Infinity".to_string() + } else if value.is_infinite() { + "-Infinity".to_string() + } else if value.is_nan() { + "NaN".to_string() + } else { + format!("{:?}", value) + }; + + InkoString::alloc((*state).string_class, string) +} diff --git a/rt/src/runtime/fs.rs b/rt/src/runtime/fs.rs new file mode 100644 index 000000000..ee63b6165 --- /dev/null +++ b/rt/src/runtime/fs.rs @@ -0,0 +1,369 @@ +use crate::mem::{ + Array, Bool, ByteArray, Float, Int, Nil, String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::helpers::read_into; +use crate::state::State; +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Seek, SeekFrom, Write}; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[no_mangle] +pub unsafe extern "system" fn inko_file_drop( + state: *const State, + file: *mut File, +) -> *const Nil { + drop(Box::from_raw(file)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_seek( + state: *const State, + process: ProcessPointer, + file: *mut File, + offset: i64, +) -> InkoResult { + let seek = if offset < 0 { + SeekFrom::End(offset) + } else { + SeekFrom::Start(offset as u64) + }; + + process + .blocking(|| (*file).seek(seek)) + .map( + |res| InkoResult::ok(Int::new((*state).int_class, res as i64) as _), + ) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_flush( + state: *const State, + process: ProcessPointer, + file: *mut File, +) -> InkoResult { + process + .blocking(|| (*file).flush()) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_write_string( + state: *const State, + process: ProcessPointer, + file: *mut File, + input: *const InkoString, +) -> InkoResult { + process + .blocking(|| (*file).write(InkoString::read(input).as_bytes())) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_write_bytes( + state: *const State, + process: ProcessPointer, + file: *mut File, + input: *mut ByteArray, +) -> InkoResult { + process + .blocking(|| (*file).write(&(*input).value)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_copy( + state: *const State, + process: ProcessPointer, + from: *const InkoString, + to: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::copy(InkoString::read(from), InkoString::read(to))) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_size( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .map(|meta| { + InkoResult::ok(Int::new((*state).int_class, meta.len() as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_remove( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::remove_file(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_created_at( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .and_then(|meta| meta.created()) + .map(system_time_to_timestamp) + .map(|time| { + InkoResult::ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_modified_at( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .and_then(|meta| meta.modified()) + .map(system_time_to_timestamp) + .map(|time| { + InkoResult::ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_accessed_at( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .and_then(|meta| meta.accessed()) + .map(system_time_to_timestamp) + .map(|time| { + InkoResult::ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_expand( + state: *const State, + path: *const InkoString, +) -> InkoResult { + let path = InkoString::read(path); + + PathBuf::from(path) + .canonicalize() + .map(|p| p.to_string_lossy().into_owned()) + .map(|p| { + InkoResult::ok(InkoString::alloc((*state).string_class, p) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_is_file( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> *const Bool { + let state = &*state; + let meta = process.blocking(|| fs::metadata(InkoString::read(path))); + + if meta.map(|m| m.is_file()).unwrap_or(false) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_is_directory( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> *const Bool { + let state = &*state; + let meta = process.blocking(|| fs::metadata(InkoString::read(path))); + + if meta.map(|m| m.is_dir()).unwrap_or(false) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_exists( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> *const Bool { + let state = &*state; + let meta = process.blocking(|| fs::metadata(InkoString::read(path))); + + if meta.is_ok() { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_open( + process: ProcessPointer, + path: *const InkoString, + mode: i64, +) -> InkoResult { + let mut opts = OpenOptions::new(); + + match mode { + 0 => opts.read(true), // Read-only + 1 => opts.write(true).truncate(true).create(true), // Write-only + 2 => opts.append(true).create(true), // Append-only + 3 => opts.read(true).write(true).create(true), // Read-write + _ => opts.read(true).append(true).create(true), // Read-append + }; + + open_file(process, opts, path).unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_read( + state: *const State, + process: ProcessPointer, + file: *mut File, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let file = &mut *file; + let buffer = &mut (*buffer).value; + + process + .blocking(|| read_into(file, buffer, size)) + .map(|size| InkoResult::ok(Int::new((*state).int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_create( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::create_dir(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_create_recursive( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::create_dir_all(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_remove( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::remove_dir(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_remove_all( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::remove_dir_all(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_list( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + let state = &*state; + let mut paths = Vec::new(); + let entries = + match process.blocking(|| fs::read_dir(InkoString::read(path))) { + Ok(entries) => entries, + Err(err) => return InkoResult::io_error(err), + }; + + for entry in entries { + let entry = match entry { + Ok(entry) => entry, + Err(err) => return InkoResult::io_error(err), + }; + + let path = entry.path().to_string_lossy().to_string(); + let pointer = InkoString::alloc(state.string_class, path); + + paths.push(pointer as *mut u8); + } + + InkoResult::ok(Array::alloc(state.array_class, paths) as _) +} + +unsafe fn open_file( + process: ProcessPointer, + options: OpenOptions, + path: *const InkoString, +) -> Result { + process + .blocking(|| options.open(InkoString::read(path))) + .map(|file| InkoResult::ok(Box::into_raw(Box::new(file)) as _)) +} + +fn system_time_to_timestamp(time: SystemTime) -> f64 { + let duration = if time < UNIX_EPOCH { + UNIX_EPOCH.duration_since(time) + } else { + time.duration_since(UNIX_EPOCH) + }; + + duration.unwrap().as_secs_f64() +} diff --git a/rt/src/runtime/general.rs b/rt/src/runtime/general.rs new file mode 100644 index 000000000..4fc8785ed --- /dev/null +++ b/rt/src/runtime/general.rs @@ -0,0 +1,74 @@ +use crate::context; +use crate::mem::{free, header_of, is_tagged_int, ClassPointer}; +use crate::process::ProcessPointer; +use crate::runtime::exit; +use crate::runtime::process::panic; +use std::alloc::alloc; + +#[no_mangle] +pub unsafe extern "system" fn inko_exit(status: i64) { + exit(status as i32); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_check_refs( + process: ProcessPointer, + pointer: *const u8, +) { + if is_tagged_int(pointer) { + return; + } + + let header = header_of(pointer); + + if header.is_permanent() { + return; + } + + let refs = header.references(); + + if refs == 0 { + return; + } + + panic( + process, + &format!( + "can't drop a value of type '{}' as it still has {} reference(s)", + &header.class.name, refs + ), + ); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_free(pointer: *mut u8) { + if is_tagged_int(pointer) || header_of(pointer).is_permanent() { + return; + } + + free(pointer); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_reduce( + mut process: ProcessPointer, + amount: u16, +) { + let thread = process.thread(); + + thread.reductions = thread.reductions.saturating_sub(amount); + + if thread.reductions == 0 { + // Safety: the current thread is holding on to the run lock + thread.schedule(process); + context::switch(process); + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_alloc(class: ClassPointer) -> *mut u8 { + let ptr = alloc(class.instance_layout()); + + header_of(ptr).init(class); + ptr +} diff --git a/rt/src/runtime/helpers.rs b/rt/src/runtime/helpers.rs new file mode 100644 index 000000000..178fcbce5 --- /dev/null +++ b/rt/src/runtime/helpers.rs @@ -0,0 +1,16 @@ +use std::io::{self, Read}; + +/// Reads a number of bytes from a buffer into a Vec. +pub(crate) fn read_into( + stream: &mut T, + output: &mut Vec, + size: i64, +) -> Result { + let read = if size > 0 { + stream.take(size as u64).read_to_end(output)? + } else { + stream.read_to_end(output)? + }; + + Ok(read as i64) +} diff --git a/rt/src/runtime/int.rs b/rt/src/runtime/int.rs new file mode 100644 index 000000000..65950e48b --- /dev/null +++ b/rt/src/runtime/int.rs @@ -0,0 +1,74 @@ +use crate::mem::{Float, Int, String as InkoString}; +use crate::process::ProcessPointer; +use crate::runtime::process::panic; +use crate::state::State; + +#[no_mangle] +pub unsafe extern "system" fn inko_int_overflow( + process: ProcessPointer, + left: i64, + right: i64, +) -> ! { + let message = format!("Int overflowed, left: {}, right: {}", left, right); + + panic(process, &message); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_boxed( + state: *const State, + value: i64, +) -> *const Int { + Int::boxed((*state).int_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_boxed_permanent( + state: *const State, + value: i64, +) -> *const Int { + Int::boxed_permanent((*state).int_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_pow( + process: ProcessPointer, + left: i64, + right: i64, +) -> i64 { + if let Some(val) = left.checked_pow(right as u32) { + val + } else { + inko_int_overflow(process, left, right); + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_clone( + state: *const State, + int: *const Int, +) -> *const Int { + let obj = &*int; + + if obj.header.is_permanent() { + int + } else { + Int::boxed((*state).int_class, obj.value) + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_to_float( + state: *const State, + int: i64, +) -> *const Float { + Float::alloc((*state).float_class, int as f64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_to_string( + state: *const State, + int: i64, +) -> *const InkoString { + InkoString::alloc((*state).string_class, int.to_string()) +} diff --git a/rt/src/runtime/process.rs b/rt/src/runtime/process.rs new file mode 100644 index 000000000..8e9ff98f1 --- /dev/null +++ b/rt/src/runtime/process.rs @@ -0,0 +1,377 @@ +use crate::context; +use crate::mem::{Array, ClassPointer, Int, Nil, String as InkoString}; +use crate::process::{ + Channel, Message, NativeAsyncMethod, OwnedMessage, Process, ProcessPointer, + ReceiveResult, RescheduleRights, SendResult, StackFrame, +}; +use crate::result::Result as InkoResult; +use crate::runtime::exit; +use crate::scheduler::process::Action; +use crate::scheduler::timeouts::Timeout; +use crate::state::State; +use std::cmp::max; +use std::fmt::Write as _; +use std::str; +use std::time::Duration; + +const SEND_ERROR: &str = "Processes can't send messages to themselves, \ + as this could result in deadlocks"; + +/// Terminates the current program with an Inko panic (opposed to a panic +/// triggered using the `panic!` macro). +/// +/// This function is marked as cold as we expect it to be called rarely, if ever +/// (in a correct program). This should also ensure any branches leading to this +/// function are treated as unlikely. +#[inline(never)] +#[cold] +pub(crate) fn panic(process: ProcessPointer, message: &str) -> ! { + let mut buffer = String::new(); + + buffer.push_str("Stack trace (the most recent call comes last):"); + + for frame in process.stacktrace() { + let _ = if !frame.path.is_empty() && frame.line > 0 { + write!( + buffer, + "\n {}:{} in {}", + frame.path, frame.line, frame.name, + ) + } else { + write!(buffer, "\n ?? in {}", frame.name) + }; + } + + let _ = write!( + buffer, + "\nProcess '{}' ({:#x}) panicked: {}", + process.header.class.name, + process.identifier(), + message + ); + + eprintln!("{}", buffer); + + // There's no real standard across programs for exit codes. Rust uses 101 so + // for the sake of "we don't know a better value", we also use 101. + exit(101); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_panic( + process: ProcessPointer, + message: *const InkoString, +) { + let msg = &(*message).value; + + panic(process, msg); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_new( + mut process: ProcessPointer, + class: ClassPointer, +) -> ProcessPointer { + let stack = process.thread().stacks.alloc(); + + Process::alloc(class, stack) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_message_new( + method: NativeAsyncMethod, + length: u8, +) -> *mut Message { + Message::alloc(method, length).into_raw() +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_send_message( + state: *const State, + mut sender: ProcessPointer, + mut receiver: ProcessPointer, + message: *mut Message, +) { + if sender == receiver { + panic(sender, SEND_ERROR); + } + + let message = OwnedMessage::from_raw(message); + let state = &*state; + let reschedule = match receiver.send_message(message) { + RescheduleRights::AcquiredWithTimeout => { + state.timeout_worker.increase_expired_timeouts(); + true + } + RescheduleRights::Acquired => true, + RescheduleRights::Failed => false, + }; + + if reschedule { + sender.thread().schedule(receiver); + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_finish_message( + mut process: ProcessPointer, + terminate: bool, +) { + let resched = process.finish_message(); + + if terminate { + // Safety: we can't terminate the process here as that would result in + // us corrupting the current stack (= the process' stack), so instead we + // defer this until we switch back to the thread's stack. + process.thread().action = Action::Terminate; + } else if resched { + process.thread().schedule(process); + } + + context::switch(process); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_yield(mut process: ProcessPointer) { + // Safety: the current thread is holding on to the run lock + process.thread().schedule(process); + context::switch(process); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_suspend( + state: *const State, + mut process: ProcessPointer, + nanos: i64, +) -> *const Nil { + let timeout = Timeout::with_rc(Duration::from_nanos(nanos as _)); + let state = &*state; + + // Safety: the current thread is holding on to the run lock + process.suspend(timeout.clone()); + state.timeout_worker.suspend(process, timeout); + context::switch(process); + state.nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace( + process: ProcessPointer, +) -> *mut Vec { + Box::into_raw(Box::new(process.stacktrace())) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_name( + state: *const State, + trace: *const Vec, + index: i64, +) -> *const InkoString { + let val = &(*trace).get_unchecked(index as usize).name; + + InkoString::alloc((*state).string_class, val.clone()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_path( + state: *const State, + trace: *const Vec, + index: i64, +) -> *const InkoString { + let val = &(*trace).get_unchecked(index as usize).path; + + InkoString::alloc((*state).string_class, val.clone()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_line( + state: *const State, + trace: *const Vec, + index: i64, +) -> *const Int { + let val = (*trace).get_unchecked(index as usize).line; + + Int::new((*state).int_class, val) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace_length( + state: *const State, + trace: *const Vec, +) -> *const Int { + Int::new((*state).int_class, (*trace).len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace_drop( + state: *const State, + trace: *mut Vec, +) -> *const Nil { + drop(Box::from_raw(trace)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_new( + state: *const State, + capacity: i64, +) -> *mut Channel { + Channel::alloc((*state).channel_class, max(capacity, 1) as usize) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_send( + state: *const State, + mut process: ProcessPointer, + channel: *const Channel, + message: *mut u8, +) -> *const Nil { + let state = &*state; + + loop { + match (*channel).send(process, message) { + SendResult::Sent => break, + SendResult::Full => context::switch(process), + SendResult::Reschedule(receiver) => { + process.thread().schedule_global(receiver); + break; + } + SendResult::RescheduleWithTimeout(receiver) => { + state.timeout_worker.increase_expired_timeouts(); + process.thread().schedule_global(receiver); + break; + } + } + } + + state.nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_receive( + mut process: ProcessPointer, + channel: *const Channel, +) -> *const u8 { + loop { + match (*channel).receive(process, None) { + ReceiveResult::None => context::switch(process), + ReceiveResult::Some(msg) => return msg, + ReceiveResult::Reschedule(msg, sender) => { + // We schedule onto the global queue because the current process + // wants to do something with the message. + process.thread().schedule_global(sender); + return msg; + } + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_try_receive( + mut process: ProcessPointer, + channel: *const Channel, +) -> InkoResult { + match (*channel).receive(process, None) { + ReceiveResult::None => InkoResult::none(), + ReceiveResult::Some(msg) => InkoResult::ok(msg as _), + ReceiveResult::Reschedule(msg, sender) => { + // We schedule onto the global queue because the current process + // wants to do something with the message. + process.thread().schedule_global(sender); + InkoResult::ok(msg as _) + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_receive_until( + state: *const State, + mut process: ProcessPointer, + channel: *const Channel, + nanos: u64, +) -> InkoResult { + let state = &(*state); + let deadline = Timeout::from_nanos_deadline(state, nanos); + + loop { + match (*channel).receive(process, Some(deadline.clone())) { + ReceiveResult::None => { + // Safety: the current thread is holding on to the run lock + state.timeout_worker.suspend(process, deadline.clone()); + context::switch(process); + + if process.timeout_expired() { + state.timeout_worker.increase_expired_timeouts(); + return InkoResult::none(); + } + + // It's possible another process received all messages before we + // got a chance to try again. In this case we continue waiting + // for a message. + } + ReceiveResult::Some(msg) => return InkoResult::ok(msg as _), + ReceiveResult::Reschedule(msg, sender) => { + // We schedule onto the global queue because the current process + // wants to do something with the message. + process.thread().schedule_global(sender); + return InkoResult::ok(msg as _); + } + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_drop( + state: *const State, + channel: *mut Channel, +) -> *const Nil { + Channel::drop(channel); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_wait( + state: *const State, + process: ProcessPointer, + channels: *mut Array, +) -> *const Nil { + let channels = &mut *channels; + let mut guards = Vec::with_capacity(channels.value.len()); + + for &ptr in &channels.value { + let chan = &(*(ptr as *const Channel)); + let guard = chan.state.lock().unwrap(); + + if guard.has_messages() { + return (*state).nil_singleton; + } + + guards.push(guard); + } + + // We have to hold on to the process state lock until all channels are + // updated. If we don't do this, a process may write to a channel before + // observing that we want to wait for messages, thus never rescheduling our + // process. + let mut proc_state = process.state(); + + for mut guard in guards { + guard.add_waiting_for_message(process); + } + + proc_state.waiting_for_channel(None); + drop(proc_state); + + // Safety: the current thread is holding on to the run lock, so a process + // writing to one of the above channels can't reschedule us until the thread + // releases the lock. + context::switch(process); + + for &ptr in &channels.value { + let chan = &(*(ptr as *const Channel)); + + chan.state.lock().unwrap().remove_waiting_for_message(process); + } + + (*state).nil_singleton +} diff --git a/rt/src/runtime/random.rs b/rt/src/runtime/random.rs new file mode 100644 index 000000000..bc7d5774c --- /dev/null +++ b/rt/src/runtime/random.rs @@ -0,0 +1,86 @@ +use crate::mem::{ByteArray, Float, Int, Nil}; +use crate::process::ProcessPointer; +use crate::runtime::process::panic; +use crate::state::State; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +#[no_mangle] +pub unsafe extern "system" fn inko_random_int( + state: *const State, + rng: *mut StdRng, +) -> *const Int { + Int::new((*state).int_class, (*rng).gen()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_float( + state: *const State, + rng: *mut StdRng, +) -> *const Float { + Float::alloc((*state).float_class, (*rng).gen()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_int_range( + state: *const State, + rng: *mut StdRng, + min: i64, + max: i64, +) -> *const Int { + let val = if min < max { (*rng).gen_range(min..max) } else { 0 }; + + Int::new((*state).int_class, val) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_float_range( + state: *const State, + rng: *mut StdRng, + min: f64, + max: f64, +) -> *const Float { + let val = if min < max { (*rng).gen_range(min..max) } else { 0.0 }; + + Float::alloc((*state).float_class, val) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_bytes( + state: *const State, + process: ProcessPointer, + rng: *mut StdRng, + size: i64, +) -> *mut ByteArray { + let mut bytes = vec![0; size as usize]; + + if let Err(err) = (*rng).try_fill(&mut bytes[..]) { + panic(process, &err.to_string()); + } + + ByteArray::alloc((*state).byte_array_class, bytes) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_new( + mut process: ProcessPointer, +) -> *mut StdRng { + let mut seed: ::Seed = Default::default(); + + process.thread().rng.fill(&mut seed); + Box::into_raw(Box::new(StdRng::from_seed(seed))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_from_int(seed: i64) -> *mut StdRng { + Box::into_raw(Box::new(StdRng::seed_from_u64(seed as _))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_drop( + state: *const State, + rng: *mut StdRng, +) -> *const Nil { + drop(Box::from_raw(rng)); + (*state).nil_singleton +} diff --git a/rt/src/runtime/socket.rs b/rt/src/runtime/socket.rs new file mode 100644 index 000000000..fd23b60e9 --- /dev/null +++ b/rt/src/runtime/socket.rs @@ -0,0 +1,478 @@ +use crate::context; +use crate::mem::{Bool, ByteArray, Int, Nil, String as InkoString}; +use crate::network_poller::Interest; +use crate::process::ProcessPointer; +use crate::result::Result; +use crate::scheduler::timeouts::Timeout; +use crate::socket::Socket; +use crate::state::State; +use std::io::{self, Write}; + +fn blocking( + state: &State, + mut process: ProcessPointer, + socket: &mut Socket, + interest: Interest, + deadline: i64, + mut func: impl FnMut(&mut Socket) -> io::Result, +) -> io::Result { + match func(socket) { + Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} + val => return val, + } + + let poll_id = unsafe { process.thread() }.network_poller; + + // We must keep the process' state lock open until everything is registered, + // otherwise a timeout thread may reschedule the process (i.e. the timeout + // is very short) before we finish registering the socket with a poller. + { + let mut proc_state = process.state(); + + // A deadline of -1 signals that we should wait indefinitely. + if deadline >= 0 { + let time = Timeout::from_nanos_deadline(state, deadline as u64); + + proc_state.waiting_for_io(Some(time.clone())); + state.timeout_worker.suspend(process, time); + } else { + proc_state.waiting_for_io(None); + } + + socket.register(state, process, poll_id, interest)?; + } + + // Safety: the current thread is holding on to the process' run lock, so if + // the process gets rescheduled onto a different thread, said thread won't + // be able to use it until we finish this context switch. + unsafe { context::switch(process) }; + + if process.timeout_expired() { + // The socket is still registered at this point, so we have to + // deregister first. If we don't and suspend for another IO operation, + // the poller could end up rescheduling the process multiple times (as + // there are multiple events still in flight for the process). + socket.deregister(state); + return Err(io::Error::from(io::ErrorKind::TimedOut)); + } + + func(socket) +} + +fn new_address_pair(state: &State, addr: String, port: i64) -> Result { + let addr = InkoString::alloc(state.string_class, addr); + let port = Int::new(state.int_class, port); + + Result::ok_boxed((addr, port)) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_socket_new( + proto: i64, + kind: i64, +) -> Result { + let sock = match proto { + 0 => Socket::ipv4(kind), + 1 => Socket::ipv6(kind), + _ => Socket::unix(kind), + }; + + sock.map(Result::ok_boxed).unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_socket_write_string( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + input: *const InkoString, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.write(InkoString::read(input).as_bytes()) + }) + .map(|v| Result::ok(Int::new(state.int_class, v as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_socket_write_bytes( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + input: *mut ByteArray, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.write(&(*input).value) + }) + .map(|v| Result::ok(Int::new(state.int_class, v as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_read( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *mut ByteArray, + amount: i64, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { + sock.read(&mut (*buffer).value, amount as usize) + }) + .map(|size| Result::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_listen( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .listen(value as i32) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_bind( + state: *const State, + socket: *mut Socket, + address: *const InkoString, + port: i64, +) -> Result { + // POSX states that bind(2) _can_ produce EINPROGRESS, but in practise it + // seems no system out there actually does this. + (*socket) + .bind(InkoString::read(address), port as u16) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_connect( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + address: *const InkoString, + port: i64, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.connect(InkoString::read(address), port as u16) + }) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_accept( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { + sock.accept() + }) + .map(Result::ok_boxed) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_receive_from( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *mut ByteArray, + amount: i64, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { + sock.recv_from(&mut (*buffer).value, amount as _) + }) + .map(|(addr, port)| new_address_pair(state, addr, port)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_send_bytes_to( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *mut ByteArray, + address: *const InkoString, + port: i64, + deadline: i64, +) -> Result { + let state = &*state; + let addr = InkoString::read(address); + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.send_to(&(*buffer).value, addr, port as _) + }) + .map(|size| Result::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_send_string_to( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *const InkoString, + address: *const InkoString, + port: i64, + deadline: i64, +) -> Result { + let state = &*state; + let addr = InkoString::read(address); + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.send_to(InkoString::read(buffer).as_bytes(), addr, port as _) + }) + .map(|size| Result::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_shutdown_read( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .shutdown_read() + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_shutdown_write( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .shutdown_write() + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_shutdown_read_write( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .shutdown_read_write() + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_local_address( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .local_address() + .map(|(addr, port)| new_address_pair(&*state, addr, port)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_peer_address( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .peer_address() + .map(|(addr, port)| new_address_pair(&*state, addr, port)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_ttl( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_ttl(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_only_v6( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_only_v6(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_nodelay( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_nodelay(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_broadcast( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_broadcast(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_linger( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_linger(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_recv_size( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_recv_buffer_size(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_send_size( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_send_buffer_size(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_keepalive( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_keepalive(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_reuse_address( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_reuse_address(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_reuse_port( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_reuse_port(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_try_clone( + socket: *mut Socket, +) -> Result { + (*socket).try_clone().map(Result::ok_boxed).unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_drop( + state: *const State, + socket: *mut Socket, +) -> *const Nil { + drop(Box::from_raw(socket)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_address_pair_address( + pair: *const (*const InkoString, *const Int), +) -> *const InkoString { + (*pair).0 +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_address_pair_port( + pair: *const (*const InkoString, *const Int), +) -> *const Int { + (*pair).1 +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_address_pair_drop( + state: *const State, + pair: *mut (*const InkoString, *const Int), +) -> *const Nil { + drop(Box::from_raw(pair)); + (*state).nil_singleton +} diff --git a/rt/src/runtime/stdio.rs b/rt/src/runtime/stdio.rs new file mode 100644 index 000000000..8585bcd5c --- /dev/null +++ b/rt/src/runtime/stdio.rs @@ -0,0 +1,106 @@ +use crate::mem::{ByteArray, Int, Nil, String as InkoString}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::helpers::read_into; +use crate::state::State; +use std::io::Write; +use std::io::{stderr, stdin, stdout}; + +#[no_mangle] +pub unsafe extern "system" fn inko_stdout_write_string( + state: *const State, + process: ProcessPointer, + input: *const InkoString, +) -> InkoResult { + let input = InkoString::read(input).as_bytes(); + + process + .blocking(|| stdout().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stdout_write_bytes( + state: *const State, + process: ProcessPointer, + input: *mut ByteArray, +) -> InkoResult { + let input = &(*input).value; + + process + .blocking(|| stdout().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stderr_write_string( + state: *const State, + process: ProcessPointer, + input: *const InkoString, +) -> InkoResult { + let input = InkoString::read(input).as_bytes(); + + process + .blocking(|| stderr().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stderr_write_bytes( + state: *const State, + process: ProcessPointer, + input: *mut ByteArray, +) -> InkoResult { + let input = &(*input).value; + + process + .blocking(|| stderr().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stdout_flush( + state: *const State, + process: ProcessPointer, +) -> *const Nil { + let _ = process.blocking(|| stdout().flush()); + + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stderr_flush( + state: *const State, + process: ProcessPointer, +) -> *const Nil { + let _ = process.blocking(|| stderr().flush()); + + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stdin_read( + state: *const State, + process: ProcessPointer, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let buffer = &mut (*buffer).value; + + process + .blocking(|| read_into(&mut stdin(), buffer, size)) + .map(|size| InkoResult::ok(Int::new((*state).int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} diff --git a/rt/src/runtime/string.rs b/rt/src/runtime/string.rs new file mode 100644 index 000000000..ec6cbc08b --- /dev/null +++ b/rt/src/runtime/string.rs @@ -0,0 +1,257 @@ +use crate::mem::{ + tagged_int, Array, Bool, ByteArray, Float, Int, Nil, String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::process::panic; +use crate::state::State; +use std::cmp::min; +use std::slice; +use unicode_segmentation::{Graphemes, UnicodeSegmentation}; + +#[no_mangle] +pub unsafe extern "system" fn inko_string_new_permanent( + state: *const State, + bytes: *const u8, + length: usize, +) -> *const InkoString { + let bytes = slice::from_raw_parts(bytes, length).to_vec(); + let string = String::from_utf8_unchecked(bytes); + + InkoString::alloc_permanent((*state).string_class, string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_equals( + state: *const State, + left: *const InkoString, + right: *const InkoString, +) -> *const Bool { + let state = &*state; + + if InkoString::read(left) == InkoString::read(right) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_size( + state: *const State, + string: *const InkoString, +) -> *const Int { + let state = &*state; + + Int::new(state.int_class, InkoString::read(string).len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_concat( + state: *const State, + strings: *const *const InkoString, + length: i64, +) -> *const InkoString { + let slice = slice::from_raw_parts(strings, length as usize); + let mut buffer = String::new(); + + for &val in slice { + buffer.push_str(InkoString::read(val)); + } + + InkoString::alloc((*state).string_class, buffer) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_byte( + string: *const InkoString, + index: i64, +) -> *const Int { + let byte = i64::from( + *InkoString::read(string).as_bytes().get_unchecked(index as usize), + ); + + tagged_int(byte) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_drop( + state: *const State, + pointer: *const InkoString, +) -> *const Nil { + InkoString::drop(pointer); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_lower( + state: *const State, + string: *const InkoString, +) -> *const InkoString { + InkoString::alloc( + (*state).string_class, + InkoString::read(string).to_lowercase(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_upper( + state: *const State, + string: *const InkoString, +) -> *const InkoString { + InkoString::alloc( + (*state).string_class, + InkoString::read(string).to_uppercase(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_byte_array( + state: *const State, + string: *const InkoString, +) -> *mut ByteArray { + let bytes = InkoString::read(string).as_bytes().to_vec(); + + ByteArray::alloc((*state).byte_array_class, bytes) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_float( + state: *const State, + string: *const InkoString, + start: i64, + end: i64, +) -> InkoResult { + let string = InkoString::read(string); + let slice = if start >= 0 && end >= 0 { + &string[start as usize..end as usize] + } else { + string + }; + + let parsed = match slice { + "Infinity" => Ok(f64::INFINITY), + "-Infinity" => Ok(f64::NEG_INFINITY), + _ => slice.parse::(), + }; + + parsed + .map(|v| InkoResult::ok(Float::alloc((*state).float_class, v) as _)) + .unwrap_or_else(|_| InkoResult::none()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_int( + state: *const State, + process: ProcessPointer, + string: *const InkoString, + radix: i64, + start: i64, + end: i64, +) -> InkoResult { + let string = InkoString::read(string); + + if !(2..=36).contains(&radix) { + panic(process, &format!("The radix '{}' is invalid", radix)); + } + + let slice = if start >= 0 && end >= 0 { + &string[start as usize..end as usize] + } else { + string + }; + + // Rust doesn't handle parsing strings like "-0x4a3f043013b2c4d1" out of the + // box. + let parsed = if radix == 16 { + if let Some(tail) = string.strip_prefix("-0x") { + i64::from_str_radix(tail, 16).map(|v| 0_i64.wrapping_sub(v)) + } else { + i64::from_str_radix(slice, 16) + } + } else { + i64::from_str_radix(slice, radix as u32) + }; + + parsed + .map(|v| InkoResult::ok(Int::new((*state).int_class, v) as _)) + .unwrap_or_else(|_| InkoResult::none()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_characters( + string: *const InkoString, +) -> *mut u8 { + let string = InkoString::read(string); + + // Safety: a Graphemes takes a reference to a slice of bytes. The standard + // library implements a wrapper around this native type that holds on to the + // string we're iterating over, preventing the slice from being invalidated + // while this iterator still exists. + // + // Graphemes isn't FFI safe, so we have to work around this by casting it to + // a regular raw pointer. + Box::into_raw(Box::new(string.graphemes(true))) as _ +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_characters_next( + state: *const State, + iter: *mut u8, +) -> InkoResult { + let iter = &mut *(iter as *mut Graphemes); + + iter.next() + .map(|v| { + let string = + InkoString::alloc((*state).string_class, v.to_string()); + + InkoResult::ok(string as _) + }) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_characters_drop( + state: *const State, + iter: *mut u8, +) -> *const Nil { + drop(Box::from_raw(iter as *mut Graphemes)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_concat_array( + state: *const State, + array: *const Array, +) -> *const InkoString { + let array = &*array; + let mut buffer = String::new(); + + for &ptr in &array.value { + let ptr = ptr as *const InkoString; + + buffer.push_str(InkoString::read(ptr)); + } + + InkoString::alloc((*state).string_class, buffer) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_slice_bytes( + state: *const State, + string: *const InkoString, + start: i64, + length: i64, +) -> *const InkoString { + let string = InkoString::read(string); + let end = min((start + length) as usize, string.len()); + let new_string = if start < 0 || length <= 0 || start as usize >= end { + String::new() + } else { + String::from_utf8_lossy(&string.as_bytes()[start as usize..end]) + .into_owned() + }; + + InkoString::alloc((*state).string_class, new_string) +} diff --git a/rt/src/runtime/sys.rs b/rt/src/runtime/sys.rs new file mode 100644 index 000000000..9a032be86 --- /dev/null +++ b/rt/src/runtime/sys.rs @@ -0,0 +1,229 @@ +use crate::mem::{ + tagged_int, Array, ByteArray, Int, Nil, String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::helpers::read_into; +use crate::scheduler::number_of_cores; +use crate::state::State; +use std::io::Write; +use std::process::{Child, Command, Stdio}; + +fn stdio_for(value: i64) -> Stdio { + match value { + 1 => Stdio::inherit(), + 2 => Stdio::piped(), + _ => Stdio::null(), + } +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_spawn( + process: ProcessPointer, + program: *const InkoString, + args: *const Array, + env: *const Array, + stdin: i64, + stdout: i64, + stderr: i64, + directory: *const InkoString, +) -> InkoResult { + let program = InkoString::read(program); + let args = &(*args).value; + let env = &(*env).value; + let directory = InkoString::read(directory); + let mut cmd = Command::new(program); + + for &ptr in args { + cmd.arg(InkoString::read(ptr as _)); + } + + for pair in env.chunks(2) { + let key = InkoString::read(pair[0] as _); + let val = InkoString::read(pair[1] as _); + + cmd.env(key, val); + } + + cmd.stdin(stdio_for(stdin)); + cmd.stdout(stdio_for(stdout)); + cmd.stderr(stdio_for(stderr)); + + if !directory.is_empty() { + cmd.current_dir(directory); + } + + process + .blocking(|| cmd.spawn()) + .map(InkoResult::ok_boxed) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_wait( + process: ProcessPointer, + child: *mut Child, +) -> InkoResult { + process + .blocking(|| (*child).wait()) + .map(|status| tagged_int(status.code().unwrap_or(0) as i64)) + .map(|status| InkoResult::ok(status as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_try_wait( + child: *mut Child, +) -> InkoResult { + let child = &mut *child; + + child + .try_wait() + .map(|status| { + InkoResult::ok(tagged_int( + status.map(|s| s.code().unwrap_or(0)).unwrap_or(-1) as i64, + ) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdout_read( + state: *const State, + process: ProcessPointer, + child: *mut Child, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let buff = &mut (*buffer).value; + + child + .stdout + .as_mut() + .map(|stream| process.blocking(|| read_into(stream, buff, size))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stderr_read( + state: *const State, + process: ProcessPointer, + child: *mut Child, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let buff = &mut (*buffer).value; + + child + .stderr + .as_mut() + .map(|stream| process.blocking(|| read_into(stream, buff, size))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_write_bytes( + state: *const State, + process: ProcessPointer, + child: *mut Child, + input: *mut ByteArray, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let input = &(*input).value; + + child + .stdin + .as_mut() + .map(|stream| process.blocking(|| stream.write(input))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_write_string( + state: *const State, + process: ProcessPointer, + child: *mut Child, + input: *mut InkoString, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let input = InkoString::read(input); + + child + .stdin + .as_mut() + .map(|stream| process.blocking(|| stream.write(input.as_bytes()))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_flush( + state: *const State, + process: ProcessPointer, + child: *mut Child, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + + child + .stdin + .as_mut() + .map(|stream| process.blocking(|| stream.flush())) + .unwrap_or(Ok(())) + .map(|_| InkoResult::ok(state.nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdout_close( + state: *const State, + child: *mut Child, +) -> *const Nil { + (*child).stdout.take(); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stderr_close( + state: *const State, + child: *mut Child, +) -> *const Nil { + (*child).stderr.take(); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_close( + state: *const State, + child: *mut Child, +) -> *const Nil { + (*child).stdin.take(); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_drop( + state: *const State, + child: *mut Child, +) -> *const Nil { + drop(Box::from_raw(child)); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_cpu_cores() -> *const Int { + tagged_int(number_of_cores() as i64) +} diff --git a/rt/src/runtime/time.rs b/rt/src/runtime/time.rs new file mode 100644 index 000000000..4f548d024 --- /dev/null +++ b/rt/src/runtime/time.rs @@ -0,0 +1,96 @@ +use crate::mem::{Float, Int}; +use crate::state::State; +use std::mem::MaybeUninit; + +fn utc() -> f64 { + unsafe { + let mut ts = MaybeUninit::uninit(); + + if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { + panic!("clock_gettime() failed"); + } + + let ts = ts.assume_init(); + + ts.tv_sec as f64 + (ts.tv_nsec as f64 / 1_000_000_000.0) + } +} + +fn offset() -> i64 { + unsafe { + extern "C" { + fn tzset(); + } + + let ts = { + let mut ts = MaybeUninit::uninit(); + + if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { + panic!("clock_gettime() failed"); + } + + ts.assume_init() + }; + + let mut tm = MaybeUninit::uninit(); + + // localtime_r() doesn't necessarily call tzset() for us. + tzset(); + + // While localtime_r() may call setenv() internally, this is not a + // problem as Inko caches environment variables upon startup. If an FFI + // call ends up racing with the setenv() call, that's a problem for the + // FFI code. + if libc::localtime_r(&ts.tv_sec, tm.as_mut_ptr()).is_null() { + panic!("localtime_r() failed"); + } + + tm.assume_init().tm_gmtoff + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_time_monotonic( + state: *const State, +) -> *const Int { + // An i64 gives us roughly 292 years of time. That should be more than + // enough for a monotonic clock, as an Inko program is unlikely to run for + // that long. + let state = &*state; + let nanos = state.start_time.elapsed().as_nanos() as i64; + + Int::new(state.int_class, nanos) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_time_system( + state: *const State, +) -> *const Float { + Float::alloc((*state).float_class, utc()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_time_system_offset( + state: *const State, +) -> *const Int { + Int::new((*state).int_class, offset()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + #[test] + fn test_utc() { + let expected = + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64(); + let given = utc(); + + // We can't assert equality, for there may be time between the two + // function calls. We also can't assert the utc() time is greater in the + // event of clock changes. Instead we just assert the two times are + // within 5 seconds of each other, which should be sufficient. + assert!((given - expected).abs() < 5.0); + } +} diff --git a/rt/src/scheduler/mod.rs b/rt/src/scheduler/mod.rs new file mode 100644 index 000000000..a625f22cd --- /dev/null +++ b/rt/src/scheduler/mod.rs @@ -0,0 +1,30 @@ +pub mod process; +pub mod timeout_worker; +pub mod timeouts; + +use std::thread::available_parallelism; + +#[cfg(target_os = "linux")] +use { + libc::{cpu_set_t, sched_setaffinity, CPU_SET}, + std::mem::{size_of, zeroed}, +}; + +#[cfg(target_os = "linux")] +pub(crate) fn pin_thread_to_core(core: usize) { + unsafe { + let mut set: cpu_set_t = zeroed(); + + CPU_SET(core, &mut set); + sched_setaffinity(0, size_of::(), &set); + } +} + +#[cfg(not(target_os = "linux"))] +pub(crate) fn pin_thread_to_core(_core: usize) { + // Pinning is only implemented for Linux at this time. +} + +pub(crate) fn number_of_cores() -> usize { + available_parallelism().map(|v| v.into()).unwrap_or(1) +} diff --git a/vm/src/scheduler/process.rs b/rt/src/scheduler/process.rs similarity index 76% rename from vm/src/scheduler/process.rs rename to rt/src/scheduler/process.rs index 8843e4922..7b9fcfde6 100644 --- a/vm/src/scheduler/process.rs +++ b/rt/src/scheduler/process.rs @@ -1,8 +1,9 @@ //! Scheduling and execution of lightweight Inko processes. use crate::arc_without_weak::ArcWithoutWeak; -use crate::machine::Machine; -use crate::mem::{ClassPointer, MethodPointer}; -use crate::process::{Process, ProcessPointer}; +use crate::context; +use crate::process::{Process, ProcessPointer, Task}; +use crate::scheduler::{number_of_cores, pin_thread_to_core}; +use crate::stack::StackPool; use crate::state::State; use crossbeam_queue::ArrayQueue; use crossbeam_utils::atomic::AtomicCell; @@ -11,12 +12,51 @@ use rand::rngs::ThreadRng; use rand::thread_rng; use std::cmp::min; use std::collections::VecDeque; -use std::mem::size_of; +use std::mem::{size_of, swap}; use std::ops::Drop; use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU64, Ordering}; use std::sync::{Condvar, Mutex}; use std::time::{Duration, Instant}; +/// The number of reductions a process can perform before it needs to suspend +/// itself. +/// +/// The number here is derived as follows: +/// +/// - We assume a static or virtual method call takes <= 5 nsec +/// - We want to restrict processes to time slices of roughly 10 µsec +const REDUCTIONS: u16 = 2000; + +/// A type describing what a thread should do in response to a process yielding +/// back control. +/// +/// This type exists as processes may need to perform certain operations that +/// aren't safe to perform while the process is still running. For example, a +/// process may want to send a message to a receiver and wait for the result. If +/// the receiver is still running, it may end up trying to reschedule the +/// sender. If this happens while the sender is still wrapping up, all sorts of +/// things can go wrong. +/// +/// To prevent such problems, processes yield control back to the thread and let +/// the thread perform such operations using its own stack. +#[derive(Debug)] +pub(crate) enum Action { + /// The thread shouldn't do anything with the process it was running. + Ignore, + + /// The thread should terminate the process. + Terminate, +} + +impl Action { + fn take(&mut self) -> Self { + let mut old_val = Action::Ignore; + + swap(self, &mut old_val); + old_val + } +} + /// The starting capacity of the global queue. /// /// The global queue grows if needed, this capacity simply exists to reduce the @@ -88,7 +128,7 @@ struct Shared { /// The private half of a thread, used only by the OS thread this state belongs /// to. -pub(crate) struct Thread<'a> { +pub struct Thread { /// The unique ID of this thread. /// /// This is used to prevent a thread from trying to steal work from itself, @@ -100,18 +140,7 @@ pub(crate) struct Thread<'a> { work: ArcWithoutWeak>, /// The pool this thread belongs to. - pool: &'a Pool, - - /// A process to run before trying to consume any other work. - /// - /// Sometimes we reschedule a process and want to run it immediately, - /// instead of processing jobs in the local queue. For example, when sending - /// a message to a process not doing anything, running it immediately - /// reduces the latency of producing a response to the message. - /// - /// Other threads can't steal from this slot, because that would defeat its - /// purpose. - priority: Option, + pool: ArcWithoutWeak, /// A flag indicating this thread is or will become a backup thread. pub(crate) backup: bool, @@ -133,34 +162,54 @@ pub(crate) struct Thread<'a> { /// A random number generator to use for the current thread. pub(crate) rng: ThreadRng, + + /// The pool of stacks to use. + pub(crate) stacks: StackPool, + + /// A value indicating what to do with a process when it yields back to us. + /// + /// The default is to not do anything with a process after it yields back to + /// the thread. + pub(crate) action: Action, + + /// The amount of reductions left before a process needs to be suspended. + pub(crate) reductions: u16, } -impl<'a> Thread<'a> { - fn new(id: usize, network_poller: usize, pool: &'a Pool) -> Thread { +impl Thread { + fn new( + id: usize, + network_poller: usize, + pool: ArcWithoutWeak, + ) -> Thread { Self { id, work: pool.threads[id].queue.clone(), - priority: None, - pool, backup: false, blocked_at: NOT_BLOCKING, network_poller, rng: thread_rng(), + stacks: StackPool::new(pool.stack_size), + action: Action::Ignore, + reductions: REDUCTIONS, + pool, } } - fn backup(network_poller: usize, pool: &'a Pool) -> Thread { + fn backup(network_poller: usize, pool: ArcWithoutWeak) -> Thread { Self { // For backup threads the ID/queue doesn't matter, because we won't // use them until we're turned into a regular thread. id: 0, work: pool.threads[0].queue.clone(), - priority: None, - pool, backup: true, blocked_at: NOT_BLOCKING, network_poller, rng: thread_rng(), + stacks: StackPool::new(pool.stack_size), + action: Action::Ignore, + reductions: REDUCTIONS, + pool, } } @@ -180,32 +229,21 @@ impl<'a> Thread<'a> { } } - /// Schedules a process in the priority slot. - /// - /// This method shouldn't be used when the thread is to transition to a - /// backup thread, as the work might never get picked up again. - pub(crate) fn schedule_priority(&mut self, process: ProcessPointer) { - // Outside of any bugs in the VM, we should never reach this point and - // still have a value in the priority slot, so we can just set the value - // as-is. - self.priority = Some(process); - } - /// Schedules a process onto the global queue. pub(crate) fn schedule_global(&self, process: ProcessPointer) { self.pool.schedule(process); } pub(crate) fn start_blocking(&mut self) { - let epoch = self.pool.current_epoch(); - let shared = &self.pool.threads[self.id]; - // We have to push our work away before entering the blocking operation. // If we do this after returning from the blocking operation, another // thread may already be using our queue, at which point we'd slow it // down by moving its work to the global queue. self.move_work_to_global_queue(); + let epoch = self.pool.current_epoch(); + let shared = &self.pool.threads[self.id]; + self.blocked_at = epoch; shared.blocked_at.store(epoch, Ordering::Release); @@ -249,7 +287,11 @@ impl<'a> Thread<'a> { self.blocked_at = NOT_BLOCKING; } - pub(crate) fn blocking(&mut self, function: F) -> R + pub(crate) fn blocking( + &mut self, + process: ProcessPointer, + function: F, + ) -> R where F: FnOnce() -> R, { @@ -258,11 +300,29 @@ impl<'a> Thread<'a> { let res = function(); self.finish_blocking(); + + // If the closure took too long to run (e.g. an IO operation took too + // long), we have to give up running the process. If we continue running + // we could mess up whatever thread has taken over our queue/work, and + // we'd be using the OS thread even longer than we already have. + // + // We schedule onto the global queue because if another thread took over + // but found no other work, it may have gone to sleep. In that case + // scheduling onto the local queue may result in the work never getting + // picked up (e.g. if all other threads are also sleeping). + if self.backup { + // Safety: the current thread is holding on to the run lock, so + // another thread can't run the process until we finish the context + // switch. + self.schedule_global(process); + unsafe { context::switch(process) }; + } + res } fn move_work_to_global_queue(&mut self) { - let len = self.work.len() + if self.priority.is_some() { 1 } else { 0 }; + let len = self.work.len(); if len == 0 { return; @@ -270,10 +330,6 @@ impl<'a> Thread<'a> { let mut work = Vec::with_capacity(len); - if let Some(process) = self.priority.take() { - work.push(process); - } - while let Some(process) = self.work.pop() { work.push(process); } @@ -313,6 +369,13 @@ impl<'a> Thread<'a> { continue; } + // Now that we ran out of local work, we can try to shrink the stack + // if really necessary. We do this _before_ stealing global work to + // prevent the stack pool from ballooning in size. If we did this + // before going to sleep then in an active system we may never end + // up shrinking the stack pool. + self.stacks.shrink(); + if let Some(process) = self.steal_from_global() { self.run_process(state, process); continue; @@ -323,7 +386,7 @@ impl<'a> Thread<'a> { } fn next_local_process(&mut self) -> Option { - self.priority.take().or_else(|| self.work.pop()) + self.work.pop() } fn steal_from_thread(&mut self) -> Option { @@ -413,20 +476,71 @@ impl<'a> Thread<'a> { self.pool.sleeping.fetch_sub(1, Ordering::AcqRel); } - fn run_process(&mut self, state: &State, process: ProcessPointer) { - Machine::new(state).run(self, process); + /// Runs a process by calling back into the native code. + fn run_process(&mut self, state: &State, mut process: ProcessPointer) { + { + // We must acquire the run lock first to prevent running a process + // that's still wrapping up/suspending in another thread. + // + // An example of such a scenario is when process A sends a message + // to process B, wants to wait for it, but B produces the result and + // tries to reschedule A _before_ A gets a chance to finish yielding + // back to the scheduler. + // + // This is done in a sub scope such that the lock is unlocked + // automatically when we decide what action to take in response to + // the yield. + let _lock = process.acquire_run_lock(); + + match process.next_task() { + Task::Resume => { + process.set_thread(self); + unsafe { context::switch(process) } + } + Task::Start(func, args) => { + process.set_thread(self); + unsafe { context::start(state, process, func, args) } + } + Task::Wait => return, + } + + process.unset_thread(); + } + + self.reductions = REDUCTIONS; + + match self.action.take() { + Action::Terminate => { + // Process termination can't be safely done on the process' + // stack, because its memory would be dropped while we're still + // using it, hence we do that here. + if process.is_main() { + state.terminate(); + } + + if let Some(stack) = process.take_stack() { + self.stacks.add(stack); + } + + // Processes drop/free themselves as this must be deferred until + // all messages (including any destructors) have finished + // running. If we did this in a destructor we'd end up releasing + // memory of a process while still using it. + Process::drop_and_deallocate(process); + } + Action::Ignore => { + // In this case it's up to the process (or another process) to + // reschedule the process we just finished running. + } + } } } -impl<'a> Drop for Thread<'a> { +impl Drop for Thread { fn drop(&mut self) { while let Some(process) = self.work.pop() { Process::drop_and_deallocate(process); } - - if let Some(process) = self.priority.take() { - Process::drop_and_deallocate(process); - } } } @@ -666,6 +780,9 @@ struct Pool { /// The state of the process monitor thread. monitor: MonitorState, + + /// The size of each stack to allocate for a process. + stack_size: usize, } impl Pool { @@ -729,7 +846,11 @@ pub(crate) struct Scheduler { } impl Scheduler { - pub(crate) fn new(size: usize, backup: usize) -> Scheduler { + pub(crate) fn new( + size: usize, + backup: usize, + stack_size: usize, + ) -> Scheduler { let mut shared = Vec::with_capacity(size); for _ in 0..size { @@ -753,6 +874,7 @@ impl Scheduler { lock: Mutex::new(()), cvar: Condvar::new(), }, + stack_size, }); Self { primary: size, backup, pool: shared } @@ -767,7 +889,7 @@ impl Scheduler { } pub(crate) fn terminate(&self) { - let _gloabl = self.pool.global.lock().unwrap(); + let _global = self.pool.global.lock().unwrap(); let _blocked = self.pool.blocked_threads.lock().unwrap(); let _monitor = self.pool.monitor.lock.lock().unwrap(); @@ -779,18 +901,19 @@ impl Scheduler { self.pool.monitor.cvar.notify_one(); } - pub(crate) fn run( - &self, - state: &State, - class: ClassPointer, - method: MethodPointer, - ) { - let process = Process::main(class, method); + pub(crate) fn run(&self, state: &State, process: ProcessPointer) { let pollers = state.network_pollers.len(); + let cores = number_of_cores(); let _ = scope(move |s| { s.builder() .name("proc monitor".to_string()) - .spawn(move |_| Monitor::new(&*self.pool).run()) + .spawn(move |_| { + // Cores 0 and 1 are used for the timeout and network poller + // threads. Since we may be running quite often we'll pin + // this thread to a different core. + pin_thread_to_core(2 % cores); + Monitor::new(&self.pool).run() + }) .unwrap(); for id in 0..self.primary { @@ -799,7 +922,8 @@ impl Scheduler { s.builder() .name(format!("proc {}", id)) .spawn(move |_| { - Thread::new(id, poll_id, &*self.pool).run(state) + pin_thread_to_core(id % cores); + Thread::new(id, poll_id, self.pool.clone()).run(state) }) .unwrap(); } @@ -810,7 +934,8 @@ impl Scheduler { s.builder() .name(format!("backup {}", id)) .spawn(move |_| { - Thread::backup(poll_id, &*self.pool).run(state) + pin_thread_to_core(id % cores); + Thread::backup(poll_id, self.pool.clone()).run(state) }) .unwrap(); } @@ -823,19 +948,25 @@ impl Scheduler { #[cfg(test)] mod tests { use super::*; - use crate::mem::Method; + use crate::context::Context; use crate::test::{ - empty_async_method, empty_process_class, new_main_process, new_process, - setup, + empty_process_class, new_main_process, new_process, setup, }; use std::thread::sleep; + unsafe extern "system" fn method(ctx: *mut u8) { + let ctx = &mut *(ctx as *mut Context); + + ctx.process.thread().action = Action::Terminate; + context::switch(ctx.process); + } + #[test] fn test_thread_schedule() { let class = empty_process_class("A"); let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); - let mut thread = Thread::new(0, 0, &scheduler.pool); + let scheduler = Scheduler::new(1, 1, 32); + let mut thread = Thread::new(0, 0, scheduler.pool.clone()); thread.schedule(process); @@ -847,8 +978,8 @@ mod tests { fn test_thread_schedule_with_overflow() { let class = empty_process_class("A"); let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); - let mut thread = Thread::new(0, 0, &scheduler.pool); + let scheduler = Scheduler::new(1, 1, 32); + let mut thread = Thread::new(0, 0, scheduler.pool.clone()); scheduler.pool.sleeping.fetch_add(1, Ordering::AcqRel); @@ -867,93 +998,54 @@ mod tests { } } - #[test] - fn test_thread_schedule_priority() { - let class = empty_process_class("A"); - let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); - let mut thread = Thread::new(0, 0, &scheduler.pool); - - thread.schedule_priority(process); - - assert_eq!(thread.priority, Some(process)); - } - #[test] fn test_thread_run_with_local_job() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); + let mut thread = Thread::new(0, 0, state.scheduler.pool.clone()); thread.schedule(process); thread.run(&state); assert_eq!(thread.work.len(), 0); - - Method::drop_and_deallocate(main_method); - } - - #[test] - fn test_thread_run_with_priority_job() { - let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); - let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); - - thread.schedule_priority(process); - thread.run(&state); - - assert_eq!(thread.work.len(), 0); - assert!(thread.priority.is_none()); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_run_with_stolen_job() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread0 = Thread::new(0, 0, &state.scheduler.pool); - let mut thread1 = Thread::new(1, 0, &state.scheduler.pool); + let mut thread0 = Thread::new(0, 0, state.scheduler.pool.clone()); + let mut thread1 = Thread::new(1, 0, state.scheduler.pool.clone()); thread1.schedule(process); thread0.run(&state); assert_eq!(thread0.work.len(), 0); assert_eq!(thread1.work.len(), 0); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_run_with_global_job() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); + let mut thread = Thread::new(0, 0, state.scheduler.pool.clone()); state.scheduler.pool.schedule(process); thread.run(&state); assert_eq!(thread.work.len(), 0); assert!(state.scheduler.pool.global.lock().unwrap().is_empty()); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_run_as_backup() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); + let mut thread = Thread::new(0, 0, state.scheduler.pool.clone()); thread.backup = true; @@ -968,38 +1060,33 @@ mod tests { thread.work.as_ptr(), state.scheduler.pool.threads[1].queue.as_ptr() ); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_start_blocking() { let class = empty_process_class("A"); - let proc1 = new_process(*class).take_and_forget(); - let proc2 = new_process(*class).take_and_forget(); + let proc = new_process(*class).take_and_forget(); let state = setup(); let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); + let mut thread = Thread::new(0, 0, pool.clone()); pool.epoch.store(4, Ordering::Release); pool.monitor.status.store(MonitorStatus::Sleeping); - thread.schedule_priority(proc1); - thread.schedule(proc2); + thread.schedule(proc); thread.start_blocking(); assert_eq!(thread.blocked_at, 4); assert_eq!(pool.threads[0].blocked_at.load(Ordering::Acquire), 4); assert_eq!(pool.monitor.status.load(), MonitorStatus::Notified); assert!(thread.work.is_empty()); - assert!(thread.priority.is_none()); } #[test] fn test_thread_finish_blocking() { let state = setup(); let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); + let mut thread = Thread::new(0, 0, pool.clone()); thread.start_blocking(); thread.finish_blocking(); @@ -1014,42 +1101,26 @@ mod tests { assert_eq!(thread.blocked_at, NOT_BLOCKING); } - #[test] - fn test_thread_blocking() { - let state = setup(); - let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); - - thread.blocking(|| { - pool.threads[0].blocked_at.store(NOT_BLOCKING, Ordering::Release) - }); - - assert!(thread.backup); - } - #[test] fn test_thread_move_work_to_global_queue() { let class = empty_process_class("A"); - let proc1 = new_process(*class).take_and_forget(); - let proc2 = new_process(*class).take_and_forget(); + let proc = new_process(*class).take_and_forget(); let state = setup(); let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); + let mut thread = Thread::new(0, 0, pool.clone()); - thread.schedule(proc1); - thread.schedule_priority(proc2); + thread.schedule(proc); thread.move_work_to_global_queue(); assert!(thread.work.is_empty()); - assert!(thread.priority.is_none()); - assert_eq!(pool.global.lock().unwrap().len(), 2); + assert_eq!(pool.global.lock().unwrap().len(), 1); } #[test] fn test_pool_schedule_with_sleeping_thread() { let class = empty_process_class("A"); let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); scheduler.pool.sleeping.fetch_add(1, Ordering::Release); scheduler.pool.schedule(process); @@ -1059,8 +1130,8 @@ mod tests { #[test] fn test_scheduler_terminate() { - let scheduler = Scheduler::new(1, 1); - let thread = Thread::new(0, 0, &scheduler.pool); + let scheduler = Scheduler::new(1, 1, 32); + let thread = Thread::new(0, 0, scheduler.pool.clone()); scheduler.pool.sleeping.fetch_add(1, Ordering::Release); scheduler.terminate(); @@ -1076,7 +1147,7 @@ mod tests { #[test] fn test_monitor_check_threads() { - let scheduler = Scheduler::new(2, 2); + let scheduler = Scheduler::new(2, 2, 32); let mut monitor = Monitor::new(&*scheduler.pool); assert!(!monitor.check_threads()); @@ -1104,7 +1175,7 @@ mod tests { #[test] fn test_monitor_update_epoch() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let mut monitor = Monitor::new(&*scheduler.pool); assert_eq!(monitor.epoch, START_EPOCH); @@ -1118,7 +1189,7 @@ mod tests { #[test] fn test_monitor_sleep() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); let start = Instant::now(); @@ -1131,7 +1202,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_termination() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); scheduler.terminate(); @@ -1142,7 +1213,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_notification() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); let _ = scope(|s| { s.spawn(|_| monitor.deep_sleep()); @@ -1164,7 +1235,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_blocked_threads() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); scheduler.pool.threads[0].blocked_at.store(1, Ordering::Release); diff --git a/vm/src/scheduler/timeout_worker.rs b/rt/src/scheduler/timeout_worker.rs similarity index 91% rename from vm/src/scheduler/timeout_worker.rs rename to rt/src/scheduler/timeout_worker.rs index fb19a9b2d..2bcc6fa13 100644 --- a/vm/src/scheduler/timeout_worker.rs +++ b/rt/src/scheduler/timeout_worker.rs @@ -191,6 +191,7 @@ mod tests { use super::*; use crate::process::Process; use crate::scheduler::process::Scheduler; + use crate::stack::Stack; use crate::test::{empty_process_class, new_process}; #[test] @@ -224,14 +225,14 @@ mod tests { #[test] fn test_run_with_fragmented_heap() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 1024); for time in &[10_u64, 5_u64] { let timeout = Timeout::with_rc(Duration::from_secs(*time)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); } @@ -250,12 +251,12 @@ mod tests { #[test] fn test_run_with_message() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 1024); let timeout = Timeout::with_rc(Duration::from_secs(10)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); worker.run_iteration(&scheduler); @@ -265,12 +266,12 @@ mod tests { #[test] fn test_run_with_reschedule() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 1024); let timeout = Timeout::with_rc(Duration::from_secs(0)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); worker.run_iteration(&scheduler); @@ -280,11 +281,11 @@ mod tests { #[test] fn test_defragment_heap_without_fragmentation() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); let timeout = Timeout::with_rc(Duration::from_secs(1)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); worker.move_messages(); worker.handle_pending_messages(); @@ -297,13 +298,13 @@ mod tests { #[test] fn test_defragment_heap_with_fragmentation() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); for time in &[1_u64, 1_u64] { let timeout = Timeout::with_rc(Duration::from_secs(*time)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); } diff --git a/vm/src/scheduler/timeouts.rs b/rt/src/scheduler/timeouts.rs similarity index 96% rename from vm/src/scheduler/timeouts.rs rename to rt/src/scheduler/timeouts.rs index c3a6c2afe..8cac32a25 100644 --- a/vm/src/scheduler/timeouts.rs +++ b/rt/src/scheduler/timeouts.rs @@ -23,7 +23,7 @@ impl Timeout { // Our own monotonic clock is the time since the runtime epoch, which is // roughly when the program first started running, so we can safely fit // this in a Duration. - let dur = Duration::from_nanos(nanos as u64); + let dur = Duration::from_nanos(nanos); // This calculates the difference between our own monotonic clock, and // the clock of `std::time::Instant`. We can then turn that into a @@ -197,6 +197,7 @@ impl Drop for Timeouts { #[cfg(test)] mod tests { use super::*; + use crate::stack::Stack; use crate::test::{empty_process_class, new_process}; mod timeout { @@ -335,11 +336,11 @@ mod tests { #[test] fn test_remove_invalid_entries_with_valid_entries() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let mut timeouts = Timeouts::new(); let timeout = Timeout::with_rc(Duration::from_secs(10)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); timeouts.insert(process, timeout); assert_eq!(timeouts.remove_invalid_entries(), 0); @@ -377,11 +378,11 @@ mod tests { #[test] fn test_processes_to_reschedule_with_remaining_time() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let mut timeouts = Timeouts::new(); let timeout = Timeout::with_rc(Duration::from_secs(10)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); timeouts.insert(process, timeout); let (reschedule, expiration) = timeouts.processes_to_reschedule(); @@ -398,7 +399,7 @@ mod tests { let mut timeouts = Timeouts::new(); let timeout = Timeout::with_rc(Duration::from_secs(0)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); timeouts.insert(*process, timeout); let (reschedule, expiration) = timeouts.processes_to_reschedule(); diff --git a/vm/src/socket.rs b/rt/src/socket.rs similarity index 61% rename from vm/src/socket.rs rename to rt/src/socket.rs index 6236e0b27..22178c0d8 100644 --- a/vm/src/socket.rs +++ b/rt/src/socket.rs @@ -2,12 +2,11 @@ pub mod socket_address; use crate::network_poller::Interest; use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; use crate::socket::socket_address::SocketAddress; use crate::state::State; +use libc::{EINPROGRESS, EISCONN}; use socket2::{Domain, SockAddr, Socket as RawSocket, Type}; -use std::io; -use std::io::Read; +use std::io::{self, Read}; use std::mem::transmute; use std::net::Shutdown; use std::net::{IpAddr, SocketAddr}; @@ -19,57 +18,18 @@ use std::time::Duration; /// network poller. const NOT_REGISTERED: i8 = -1; -#[cfg(unix)] -use libc::{EINPROGRESS, EISCONN}; - -#[cfg(windows)] -use windows_sys::Win32::Networking::WinSock::{ - WSAEINPROGRESS as EINPROGRESS, WSAEISCONN as EISCONN, -}; - macro_rules! socket_setter { ($setter:ident, $type:ty) => { - pub(crate) fn $setter(&self, value: $type) -> Result<(), RuntimeError> { - self.inner.$setter(value)?; - - Ok(()) - } - }; -} - -macro_rules! socket_getter { - ($getter:ident, $type:ty) => { - pub(crate) fn $getter(&self) -> Result<$type, RuntimeError> { - Ok(self.inner.$getter()?) - } - }; -} - -macro_rules! socket_u32_getter { - ($getter:ident) => { - pub(crate) fn $getter(&self) -> Result { - Ok(self.inner.$getter()? as usize) + pub(crate) fn $setter(&self, value: $type) -> io::Result<()> { + self.inner.$setter(value) } }; } macro_rules! socket_duration_setter { ($setter:ident) => { - pub(crate) fn $setter(&self, value: u64) -> Result<(), RuntimeError> { - let dur = Duration::from_nanos(value); - - self.inner.$setter(Some(dur))?; - Ok(()) - } - }; -} - -macro_rules! socket_duration_getter { - ($getter:ident) => { - pub(crate) fn $getter(&self) -> Result { - let dur = self.inner.$getter()?; - - Ok(dur.map(|v| v.as_nanos() as i64).unwrap_or(0)) + pub(crate) fn $setter(&self, value: u64) -> io::Result<()> { + self.inner.$setter(Some(Duration::from_nanos(value))) } }; } @@ -78,14 +38,12 @@ macro_rules! socket_duration_getter { fn decode_sockaddr( sockaddr: SockAddr, unix: bool, -) -> Result<(String, i64), RuntimeError> { - let peer_result = if unix { +) -> Result<(String, i64), String> { + if unix { SocketAddress::Unix(sockaddr).address() } else { SocketAddress::Other(sockaddr).address() - }; - - Ok(peer_result?) + } } #[cfg(unix)] @@ -93,14 +51,15 @@ fn encode_sockaddr( address: &str, port: u16, unix: bool, -) -> Result { +) -> io::Result { if unix { - return Ok(SockAddr::unix(address)?); + return SockAddr::unix(address); } - let ip = address.parse::()?; - - Ok(SockAddr::from(SocketAddr::new(ip, port))) + address + .parse::() + .map(|ip| SockAddr::from(SocketAddr::new(ip, port))) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } #[cfg(not(unix))] @@ -108,10 +67,11 @@ fn encode_sockaddr( address: &str, port: u16, _unix: bool, -) -> Result { - let ip = address.parse::()?; - - Ok(SockAddr::from(SocketAddr::new(ip, port))) +) -> io::Result { + address + .parse::() + .map(|ip| SockAddr::from(SocketAddr::new(ip, port))) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } /// Returns a slice of the input buffer that a socket operation can write to. @@ -145,25 +105,21 @@ fn update_buffer_length_and_capacity(buffer: &mut Vec, read: usize) { buffer.shrink_to_fit(); } -fn socket_type(kind: i64) -> Result { - let kind = match kind { - 0 => Type::STREAM, - 1 => Type::DGRAM, - 2 => Type::SEQPACKET, - 3 => Type::RAW, - _ => { - return Err(RuntimeError::Panic(format!( - "{} is not a valid socket type", - kind - ))) - } - }; - - Ok(kind) +fn socket_type(kind: i64) -> io::Result { + match kind { + 0 => Ok(Type::STREAM), + 1 => Ok(Type::DGRAM), + 2 => Ok(Type::SEQPACKET), + 3 => Ok(Type::RAW), + _ => Err(io::Error::new( + io::ErrorKind::Other, + format!("{} is not a valid socket type", kind), + )), + } } /// A nonblocking socket that can be registered with a `NetworkPoller`. -pub(crate) struct Socket { +pub struct Socket { /// The raw socket. inner: RawSocket, @@ -187,7 +143,7 @@ impl Socket { domain: Domain, kind: Type, unix: bool, - ) -> Result { + ) -> io::Result { let socket = RawSocket::new(domain, kind, None)?; socket.set_nonblocking(true)?; @@ -199,80 +155,62 @@ impl Socket { }) } - pub(crate) fn ipv4(kind_int: i64) -> Result { + pub(crate) fn ipv4(kind_int: i64) -> io::Result { Self::new(Domain::IPV4, socket_type(kind_int)?, false) } - pub(crate) fn ipv6(kind_int: i64) -> Result { + pub(crate) fn ipv6(kind_int: i64) -> io::Result { Self::new(Domain::IPV6, socket_type(kind_int)?, false) } #[cfg(unix)] - pub(crate) fn unix(kind_int: i64) -> Result { + pub(crate) fn unix(kind_int: i64) -> io::Result { Self::new(Domain::UNIX, socket_type(kind_int)?, true) } #[cfg(not(unix))] - pub(crate) fn unix(_: i64) -> Result { - Err(RuntimeError::from( + pub(crate) fn unix(_: i64) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, "UNIX sockets aren't supported on this platform", )) } - pub(crate) fn bind( - &self, - address: &str, - port: u16, - ) -> Result<(), RuntimeError> { + pub(crate) fn bind(&self, address: &str, port: u16) -> io::Result<()> { let sockaddr = encode_sockaddr(address, port, self.unix)?; - self.inner.bind(&sockaddr)?; - - Ok(()) + self.inner.bind(&sockaddr) } - pub(crate) fn listen(&self, backlog: i32) -> Result<(), RuntimeError> { - self.inner.listen(backlog)?; - - Ok(()) + pub(crate) fn listen(&self, backlog: i32) -> io::Result<()> { + self.inner.listen(backlog) } - pub(crate) fn connect( - &self, - address: &str, - port: u16, - ) -> Result<(), RuntimeError> { + pub(crate) fn connect(&self, address: &str, port: u16) -> io::Result<()> { let sockaddr = encode_sockaddr(address, port, self.unix)?; match self.inner.connect(&sockaddr) { - Ok(_) => {} + Ok(_) => Ok(()), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock - || e.raw_os_error() == Some(EINPROGRESS as i32) => + || e.raw_os_error() == Some(EINPROGRESS) => { if let Ok(Some(err)) = self.inner.take_error() { // When performing a connect(), the error returned may be // WouldBlock, with the actual error being stored in - // SO_ERROR on the socket. Windows in particular seems to - // take this approach. - return Err(err.into()); + // SO_ERROR on the socket. + return Err(err); } - // On Windows a connect(2) might throw WSAEWOULDBLOCK, the - // Windows equivalent of EAGAIN/EWOULDBLOCK. Other platforms may - // also produce some error that Rust will report as WouldBlock. - return Err(RuntimeError::WouldBlock); + Err(io::Error::from(io::ErrorKind::WouldBlock)) } - Err(ref e) if e.raw_os_error() == Some(EISCONN as i32) => { + Err(ref e) if e.raw_os_error() == Some(EISCONN) => { // We may run into an EISCONN if a previous connect(2) attempt // would block. In this case we can just continue. + Ok(()) } - Err(e) => { - return Err(e.into()); - } + Err(e) => Err(e), } - - Ok(()) } pub(crate) fn register( @@ -281,7 +219,7 @@ impl Socket { process: ProcessPointer, thread_poller_id: usize, interest: Interest, - ) -> Result<(), RuntimeError> { + ) -> io::Result<()> { let existing_id = self.registered.load(Ordering::Acquire); // Once registered, the process might be rescheduled immediately if @@ -292,7 +230,7 @@ impl Socket { // // 1. Set "registered" _first_ (if necessary) // 2. Add the socket to the poller - let result = if existing_id == NOT_REGISTERED { + if existing_id == NOT_REGISTERED { let poller = &state.network_pollers[thread_poller_id]; self.registered.store(thread_poller_id as i8, Ordering::Release); @@ -302,11 +240,9 @@ impl Socket { let poller = &state.network_pollers[existing_id as usize]; poller.modify(process, &self.inner, interest) - }; - + } // *DO NOT* use "self" from here on, as the socket/process may already // be running on a different thread. - result.map_err(|e| e.into()) } pub(crate) fn deregister(&mut self, state: &State) { @@ -314,7 +250,7 @@ impl Socket { let _ = state.network_pollers[poller_id].delete(&self.inner); } - pub(crate) fn accept(&self) -> Result { + pub(crate) fn accept(&self) -> io::Result { let (socket, _) = self.inner.accept()?; // Accepted sockets don't inherit the non-blocking status of the @@ -332,7 +268,7 @@ impl Socket { &self, buffer: &mut Vec, amount: usize, - ) -> Result { + ) -> io::Result { if amount > 0 { // We don't use take(), because that only terminates if: // @@ -355,7 +291,7 @@ impl Socket { &self, buffer: &mut Vec, bytes: usize, - ) -> Result<(String, i64), RuntimeError> { + ) -> io::Result<(String, i64)> { let slice = socket_output_slice(buffer, bytes); let (read, sockaddr) = self.inner.recv_from(unsafe { transmute(slice) })?; @@ -363,6 +299,7 @@ impl Socket { update_buffer_length_and_capacity(buffer, read); decode_sockaddr(sockaddr, self.unix) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } pub(crate) fn send_to( @@ -370,34 +307,36 @@ impl Socket { buffer: &[u8], address: &str, port: u16, - ) -> Result { + ) -> io::Result { let sockaddr = encode_sockaddr(address, port, self.unix)?; - Ok(self.inner.send_to(buffer, &sockaddr)?) + self.inner.send_to(buffer, &sockaddr) } - pub(crate) fn local_address(&self) -> Result<(String, i64), RuntimeError> { + pub(crate) fn local_address(&self) -> io::Result<(String, i64)> { let sockaddr = self.inner.local_addr()?; decode_sockaddr(sockaddr, self.unix) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } - pub(crate) fn peer_address(&self) -> Result<(String, i64), RuntimeError> { + pub(crate) fn peer_address(&self) -> io::Result<(String, i64)> { let sockaddr = self.inner.peer_addr()?; decode_sockaddr(sockaddr, self.unix) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } - pub(crate) fn shutdown_read(&self) -> Result<(), RuntimeError> { - self.inner.shutdown(Shutdown::Read).map_err(|e| e.into()) + pub(crate) fn shutdown_read(&self) -> io::Result<()> { + self.inner.shutdown(Shutdown::Read) } - pub(crate) fn shutdown_write(&self) -> Result<(), RuntimeError> { - self.inner.shutdown(Shutdown::Write).map_err(|e| e.into()) + pub(crate) fn shutdown_write(&self) -> io::Result<()> { + self.inner.shutdown(Shutdown::Write) } - pub(crate) fn shutdown_read_write(&self) -> Result<(), RuntimeError> { - self.inner.shutdown(Shutdown::Both).map_err(|e| e.into()) + pub(crate) fn shutdown_read_write(&self) -> io::Result<()> { + self.inner.shutdown(Shutdown::Both) } socket_setter!(set_ttl, u32); @@ -412,46 +351,17 @@ impl Socket { socket_duration_setter!(set_linger); - socket_getter!(only_v6, bool); - socket_getter!(nodelay, bool); - socket_getter!(broadcast, bool); - socket_getter!(reuse_address, bool); - socket_getter!(keepalive, bool); - - socket_getter!(recv_buffer_size, usize); - socket_getter!(send_buffer_size, usize); - - socket_u32_getter!(ttl); - - socket_duration_getter!(linger); - #[cfg(unix)] - pub(crate) fn set_reuse_port( - &self, - reuse: bool, - ) -> Result<(), RuntimeError> { - Ok(self.inner.set_reuse_port(reuse)?) + pub(crate) fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { + self.inner.set_reuse_port(reuse) } #[cfg(not(unix))] - pub(crate) fn set_reuse_port( - &self, - _reuse: bool, - ) -> Result<(), RuntimeError> { + pub(crate) fn set_reuse_port(&self, _reuse: bool) -> io::Result<()> { Ok(()) } - #[cfg(unix)] - pub(crate) fn reuse_port(&self) -> Result { - Ok(self.inner.reuse_port()?) - } - - #[cfg(not(unix))] - pub(crate) fn reuse_port(&self) -> Result { - Ok(false) - } - - pub(crate) fn try_clone(&self) -> Result { + pub(crate) fn try_clone(&self) -> io::Result { let sock = Socket { inner: self.inner.try_clone()?, registered: AtomicI8::new(NOT_REGISTERED), @@ -497,17 +407,6 @@ mod tests { #[test] fn test_type_size() { - let socket_size = if cfg!(windows) { - // Windows uses a HANDLE type, which is a pointer. This means the - // total size is 16 bytes due to the extra flags we store in the - // socket. - 16 - } else { - // On Unix a file descriptor is a 32-bits int, so the total size is - // 8 bytes. - 8 - }; - - assert_eq!(size_of::(), socket_size); + assert_eq!(size_of::(), 8); } } diff --git a/vm/src/socket/socket_address.rs b/rt/src/socket/socket_address.rs similarity index 69% rename from vm/src/socket/socket_address.rs rename to rt/src/socket/socket_address.rs index 44aa9e6b8..037c7881d 100644 --- a/vm/src/socket/socket_address.rs +++ b/rt/src/socket/socket_address.rs @@ -4,27 +4,24 @@ use socket2::SockAddr; use { libc::sockaddr_un, std::ffi::OsStr, + std::mem::transmute, std::os::{raw::c_char, unix::ffi::OsStrExt}, }; #[cfg(unix)] #[cfg_attr(feature = "cargo-clippy", allow(clippy::uninit_assumed_init))] -fn sun_path_offset() -> usize { - use std::mem::MaybeUninit; - - let addr: sockaddr_un = unsafe { MaybeUninit::uninit().assume_init() }; - let base = &addr as *const _ as usize; - let path = &addr.sun_path as *const _ as usize; +fn sun_path_offset(addr: &sockaddr_un) -> usize { + let base = addr as *const sockaddr_un as usize; + let path = &addr.sun_path as *const c_char as usize; path - base } #[cfg(unix)] fn unix_socket_path(sockaddr: &SockAddr) -> String { - let len = sockaddr.len() as usize - sun_path_offset(); - let raw_addr = unsafe { &*(sockaddr.as_ptr() as *mut sockaddr_un) }; - let path = - unsafe { &*(&raw_addr.sun_path as *const [c_char] as *const [u8]) }; + let raw_addr = unsafe { &*(sockaddr.as_ptr() as *const sockaddr_un) }; + let len = sockaddr.len() as usize - sun_path_offset(raw_addr); + let path = unsafe { transmute::<&[c_char], &[u8]>(&raw_addr.sun_path) }; if len == 0 || (cfg!(not(target_os = "linux")) && raw_addr.sun_path[0] == 0) { @@ -73,3 +70,20 @@ impl SocketAddress { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(unix)] + fn test_unix_socket_path() { + let path1 = unix_socket_path(&SockAddr::unix("foo.sock").unwrap()); + let path2 = unix_socket_path(&SockAddr::unix("").unwrap()); + let path3 = unix_socket_path(&SockAddr::unix("\0").unwrap()); + + assert_eq!(path1, "foo.sock".to_string()); + assert_eq!(path2, String::new()); + assert_eq!(path3, String::new()); + } +} diff --git a/rt/src/stack.rs b/rt/src/stack.rs new file mode 100644 index 000000000..e2310c0eb --- /dev/null +++ b/rt/src/stack.rs @@ -0,0 +1,219 @@ +use crate::memory_map::MemoryMap; +use crate::page::page_size; +use std::collections::VecDeque; + +/// The age of a reusable stack after which we deem it too old to keep around. +/// +/// The value here is arbitrary, and chosen under the assumption it's good +/// enough to prevent excessive shrinking. +const SHRINK_AGE: u16 = 10; + +/// The minimum number of stacks we need before we'll even consider shrinking +/// a stack pool. +/// +/// The value here is arbitrary and mostly meant to avoid the shrinking overhead +/// for cases where it's pretty much just a waste of time. +const MIN_STACKS: usize = 4; + +/// A pool of `Stack` objects to reuse. +/// +/// Stacks all share the same size and can't grow beyond this size. The decision +/// to not support growable stack is for the following reasons: +/// +/// - It can mask/hide runaway recursion. +/// - It requires a prologue of sorts in every function to check if there's +/// enough stack space remaining. +/// - Depending on the backend used we may not have enough information to +/// accurately determine the amount of stack space a function needs. +/// - It's not clear when/if we would also shrink the stack, and doing this at +/// the right time is tricky. +/// - Foreign function calls would require that we either grow the stack to a +/// reasonable size, or swap the stack with a temporary stack of a larger +/// size. Both add complexity and incur a runtime cost we're not happy with. +/// - Even with a 1 MiB stack per process one can easily run millions of +/// processes and not run out of virtual memory. +/// - Processes aren't likely to use even close to the full amount of stack +/// space, so all the trouble of resizing stacks likely just isn't worth it. +/// - Fixed size stacks make it easier to reuse stacks, as we don't need to +/// divide them into size classes and pick the appropriate one based on the +/// size we need. +/// +/// The amount of reusable stacks is reduced every now and then to prevent a +/// pool from using excessive amounts of memory. +pub(crate) struct StackPool { + /// The amount of bytes to allocate for every stack, excluding guard pages. + /// + /// The actual size used may be slightly larger depending on the OS' page + /// size. + size: usize, + + /// Any stacks that can be reused. + stacks: VecDeque, + + /// The current epoch. + /// + /// This value is used to determine if reusable stacks have been sitting + /// around for too long and should be discarded. + epoch: u16, + + /// The check-in epoch for every stack. + /// + /// These values are stored separate from the stacks as we only need them + /// when the stacks are reusable. + epochs: VecDeque, +} + +impl StackPool { + pub fn new(size: usize) -> Self { + Self { + size, + stacks: VecDeque::new(), + epoch: 0, + epochs: VecDeque::new(), + } + } + + pub(crate) fn alloc(&mut self) -> Stack { + if let Some(stack) = self.stacks.pop_back() { + self.epoch = self.epoch.wrapping_add(1); + self.epochs.pop_back(); + stack + } else { + Stack::new(self.size) + } + } + + pub(crate) fn add(&mut self, stack: Stack) { + self.stacks.push_back(stack); + self.epochs.push_back(self.epoch); + } + + /// Shrinks the list of reusable stacks to at most half the current number + /// of stacks. + /// + /// Using this method we can keep the number of unused stacks under control. + /// For example, if we suddenly need many stacks but then never reuse most + /// of them, this is a waste of memory. + pub(crate) fn shrink(&mut self) { + if self.stacks.len() < MIN_STACKS { + return; + } + + let trim_size = self + .epochs + .iter() + .filter(|&&epoch| self.epoch.abs_diff(epoch) >= SHRINK_AGE) + .count(); + + // We want to shrink to at most half the size in an attempt to not + // remove too many stacks we may need later. + let max = std::cmp::min(self.stacks.len() / 2, trim_size); + + self.epochs.drain(0..max); + self.stacks.drain(0..max); + + // Update the epochs of the remaining stacks so we don't shrink too soon + // again. + self.epochs = VecDeque::from(vec![self.epoch; self.epochs.len()]); + } +} + +/// A type that represents stack memory. +/// +/// A `Stack` represents a chunk of memory of a certain size that can be used +/// as a process' stack memory. The exact implementation differs per platform. +#[repr(C)] +pub struct Stack { + mem: MemoryMap, +} + +impl Stack { + pub(crate) fn new(size: usize) -> Self { + let page = page_size(); + let mut mem = MemoryMap::new(page + size + page, true); + + // There's nothing we can do at runtime in response to the guard pages + // not being set up, so we just terminate if this ever happens. + mem.protect(0) + .and_then(|_| mem.protect(mem.len - page)) + .expect("Failed to set up the stack's guard pages"); + + Self { mem } + } + + pub(crate) fn stack_pointer(&self) -> *mut u8 { + unsafe { self.mem.ptr.add(self.mem.len - page_size()) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stack_pool_alloc() { + let mut pool = StackPool::new(page_size()); + let size = page_size(); + let stack = pool.alloc(); + + assert_eq!(stack.mem.len, size * 3); + } + + #[test] + fn test_stack_pool_alloc_with_reuse() { + let size = page_size(); + let mut pool = StackPool::new(size); + let stack = pool.alloc(); + + pool.add(stack); + assert_eq!(pool.stacks.len(), 1); + assert_eq!(pool.epochs, vec![0]); + + pool.alloc(); + assert!(pool.stacks.is_empty()); + assert!(pool.epochs.is_empty()); + + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.alloc(); + pool.alloc(); + + assert_eq!(pool.epoch, 3); + } + + #[test] + fn test_stack_pool_shrink() { + let size = page_size(); + let mut pool = StackPool::new(size); + + pool.epoch = 14; + + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.epochs[0] = 1; + pool.epochs[1] = 2; + + // Not enough stacks, so no shrinking is performed. + pool.shrink(); + + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + + pool.epochs[2] = 3; + pool.epochs[3] = 4; + pool.epochs[4] = 11; + pool.epochs[5] = 12; + + // This shrinks the pool. + pool.shrink(); + + // This doesn't shrink the pool because we updated the epochs to prevent + // excessive shrinking. + pool.shrink(); + + assert_eq!(pool.stacks.len(), 3); + assert_eq!(&pool.epochs, &[14, 14, 14]); + } +} diff --git a/rt/src/state.rs b/rt/src/state.rs new file mode 100644 index 000000000..897fbd85b --- /dev/null +++ b/rt/src/state.rs @@ -0,0 +1,229 @@ +use crate::arc_without_weak::ArcWithoutWeak; +use crate::config::Config; +use crate::mem::{ + Array, Bool, ByteArray, Class, ClassPointer, Float, Int, Nil, + String as InkoString, +}; +use crate::network_poller::NetworkPoller; +use crate::process::Channel; +use crate::scheduler::process::Scheduler; +use crate::scheduler::timeout_worker::TimeoutWorker; +use rand::{thread_rng, Rng}; +use std::collections::HashMap; +use std::env; +use std::mem::size_of; +use std::panic::RefUnwindSafe; +use std::time; + +/// Allocates a new class, returning a tuple containing the owned pointer and a +/// permanent reference pointer. +macro_rules! class { + ($name: expr, $methods: expr, $size_source: ident) => {{ + Class::alloc( + $name.to_string(), + $methods, + size_of::<$size_source>() as u32, + ) + }}; +} + +/// A reference counted State. +pub(crate) type RcState = ArcWithoutWeak; + +/// The number of methods used for the various built-in classes. +/// +/// These counts are used to determine how much memory is needed for allocating +/// the various built-in classes. +#[derive(Default, Debug)] +#[repr(C)] +pub struct MethodCounts { + pub(crate) int_class: u16, + pub(crate) float_class: u16, + pub(crate) string_class: u16, + pub(crate) array_class: u16, + pub(crate) boolean_class: u16, + pub(crate) nil_class: u16, + pub(crate) byte_array_class: u16, + pub(crate) channel_class: u16, +} + +/// The state of the Inko runtime. +#[repr(C)] +pub struct State { + // These fields are exposed to the generated code directly, hence they're + // marked as public. These fields must also come first so their offsets are + // more reliable/stable. + pub true_singleton: *const Bool, + pub false_singleton: *const Bool, + pub nil_singleton: *const Nil, + pub int_class: ClassPointer, + pub float_class: ClassPointer, + pub string_class: ClassPointer, + pub array_class: ClassPointer, + pub bool_class: ClassPointer, + pub nil_class: ClassPointer, + pub byte_array_class: ClassPointer, + pub channel_class: ClassPointer, + + /// The first randomly generated key to use for hashers. + pub hash_key0: *const Int, + + /// The second randomly generated key to use for hashers. + pub hash_key1: *const Int, + + /// The runtime's configuration. + pub(crate) config: Config, + + /// The start time of the VM (more or less). + pub(crate) start_time: time::Instant, + + /// The commandline arguments passed to an Inko program. + pub(crate) arguments: Vec<*const InkoString>, + + /// The environment variables defined when the VM started. + /// + /// We cache environment variables because C functions used through the FFI + /// (or through libraries) may call `setenv()` concurrently with `getenv()` + /// calls, which is unsound. Caching the variables also means we can safely + /// use `localtime_r()` (which internally may call `setenv()`). + pub(crate) environment: HashMap, + + /// The scheduler to use for executing Inko processes. + pub(crate) scheduler: Scheduler, + + /// A task used for handling timeouts, such as message and IO timeouts. + pub(crate) timeout_worker: TimeoutWorker, + + /// The network pollers to use for process threads. + pub(crate) network_pollers: Vec, +} + +unsafe impl Sync for State {} +impl RefUnwindSafe for State {} + +impl State { + pub(crate) fn new( + config: Config, + counts: &MethodCounts, + args: &[String], + ) -> RcState { + let int_class = class!("Int", counts.int_class, Int); + let float_class = class!("Float", counts.float_class, Float); + let string_class = class!("String", counts.string_class, InkoString); + let array_class = class!("Array", counts.array_class, Array); + let bool_class = class!("Bool", counts.boolean_class, Bool); + let nil_class = class!("Nil", counts.nil_class, Nil); + let byte_array_class = + class!("ByteArray", counts.byte_array_class, ByteArray); + let channel_class = class!("Channel", counts.channel_class, Channel); + let true_singleton = Bool::alloc(bool_class); + let false_singleton = Bool::alloc(bool_class); + let nil_singleton = Nil::alloc(nil_class); + let arguments = args + .iter() + .map(|arg| InkoString::alloc_permanent(string_class, arg.clone())) + .collect(); + + let mut rng = thread_rng(); + let hash_key0 = Int::new_permanent(int_class, rng.gen()); + let hash_key1 = Int::new_permanent(int_class, rng.gen()); + let environment = env::vars_os() + .map(|(k, v)| { + ( + k.to_string_lossy().into_owned(), + InkoString::alloc_permanent( + string_class, + v.to_string_lossy().into_owned(), + ), + ) + }) + .collect::>(); + + let scheduler = Scheduler::new( + config.process_threads as usize, + config.backup_threads as usize, + config.stack_size as usize, + ); + + let network_pollers = + (0..config.netpoll_threads).map(|_| NetworkPoller::new()).collect(); + + let state = State { + hash_key0, + hash_key1, + scheduler, + environment, + config, + start_time: time::Instant::now(), + timeout_worker: TimeoutWorker::new(), + arguments, + network_pollers, + int_class, + float_class, + string_class, + array_class, + bool_class, + nil_class, + byte_array_class, + channel_class, + true_singleton, + false_singleton, + nil_singleton, + }; + + ArcWithoutWeak::new(state) + } + + pub(crate) fn terminate(&self) { + self.scheduler.terminate(); + } +} + +impl Drop for State { + fn drop(&mut self) { + unsafe { + for &val in &self.arguments { + InkoString::drop_and_deallocate(val) + } + + for &val in self.environment.values() { + InkoString::drop_and_deallocate(val); + } + + Bool::drop_and_deallocate(self.true_singleton); + Bool::drop_and_deallocate(self.false_singleton); + Nil::drop_and_deallocate(self.nil_singleton); + + Class::drop(self.int_class); + Class::drop(self.float_class); + Class::drop(self.string_class); + Class::drop(self.array_class); + Class::drop(self.bool_class); + Class::drop(self.nil_class); + Class::drop(self.byte_array_class); + Class::drop(self.channel_class); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! offset_of { + ($value: expr, $field: ident) => {{ + (std::ptr::addr_of!($value.$field) as usize) + - (&*$value as *const _ as usize) + }}; + } + + #[test] + fn test_field_offsets() { + let config = Config::new(); + let state = State::new(config, &MethodCounts::default(), &[]); + + assert_eq!(offset_of!(state, true_singleton), 0); + assert_eq!(offset_of!(state, false_singleton), 8); + assert_eq!(offset_of!(state, nil_singleton), 16); + } +} diff --git a/vm/src/test.rs b/rt/src/test.rs similarity index 53% rename from vm/src/test.rs rename to rt/src/test.rs index 39c5ab892..53a2e1a98 100644 --- a/vm/src/test.rs +++ b/rt/src/test.rs @@ -1,13 +1,9 @@ //! Helper functions for writing unit tests. use crate::config::Config; -use crate::location_table::LocationTable; -use crate::mem::{ - Class, ClassPointer, Method, MethodPointer, Module, ModulePointer, -}; -use crate::permanent_space::{MethodCounts, PermanentSpace}; -use crate::process::{Process, ProcessPointer}; -use crate::state::{RcState, State}; -use bytecode::{Instruction, Opcode}; +use crate::mem::{Class, ClassPointer}; +use crate::process::{NativeAsyncMethod, Process, ProcessPointer}; +use crate::stack::Stack; +use crate::state::{MethodCounts, RcState, State}; use std::mem::forget; use std::ops::{Deref, DerefMut, Drop}; @@ -55,7 +51,7 @@ impl Drop for OwnedProcess { /// A class that is dropped when this pointer is dropped. #[repr(transparent)] -pub(crate) struct OwnedClass(ClassPointer); +pub(crate) struct OwnedClass(pub(crate) ClassPointer); impl OwnedClass { pub(crate) fn new(ptr: ClassPointer) -> Self { @@ -73,31 +69,9 @@ impl Deref for OwnedClass { impl Drop for OwnedClass { fn drop(&mut self) { - Class::drop(self.0); - } -} - -/// A module that is dropped when this pointer is dropped. -#[repr(transparent)] -pub(crate) struct OwnedModule(ModulePointer); - -impl OwnedModule { - pub(crate) fn new(ptr: ModulePointer) -> Self { - Self(ptr) - } -} - -impl Deref for OwnedModule { - type Target = ModulePointer; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Drop for OwnedModule { - fn drop(&mut self) { - Module::drop_and_deallocate(self.0); + unsafe { + Class::drop(self.0); + } } } @@ -109,48 +83,18 @@ pub(crate) fn setup() -> RcState { // different platforms. config.process_threads = 2; - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - State::new(config, perm, &[]) + State::new(config, &MethodCounts::default(), &[]) } pub(crate) fn new_process(class: ClassPointer) -> OwnedProcess { - OwnedProcess::new(Process::alloc(class)) + OwnedProcess::new(Process::alloc(class, Stack::new(1024))) } pub(crate) fn new_main_process( class: ClassPointer, - method: MethodPointer, + method: NativeAsyncMethod, ) -> OwnedProcess { - OwnedProcess::new(Process::main(class, method)) -} - -pub(crate) fn empty_module(class: ClassPointer) -> OwnedModule { - OwnedModule::new(Module::alloc(class)) -} - -pub(crate) fn empty_method() -> MethodPointer { - // We use Int values for the name so we don't have to worry about also - // dropping strings when dropping this Method. - Method::alloc( - 123, - 2, - vec![Instruction::new(Opcode::Return, [0; 5])], - LocationTable::new(), - Vec::new(), - ) -} - -pub(crate) fn empty_async_method() -> MethodPointer { - // We use Int values for the name so we don't have to worry about also - // dropping strings when dropping this Method. - Method::alloc( - 123, - 2, - vec![Instruction::one(Opcode::ProcessFinishTask, 1)], - LocationTable::new(), - Vec::new(), - ) + OwnedProcess::new(Process::main(class, method, Stack::new(1024))) } pub(crate) fn empty_class(name: &str) -> OwnedClass { diff --git a/scripts/llvm.sh b/scripts/llvm.sh new file mode 100755 index 000000000..ce7281dd1 --- /dev/null +++ b/scripts/llvm.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -e + +if [ "$RUNNER_OS" = "Linux" ] +then + # libclang-common is needed because of + # https://gitlab.com/taricorp/llvm-sys.rs/-/issues/16. + sudo apt-get install --yes llvm-15 llvm-15-dev libstdc++-11-dev \ + libclang-common-15-dev zlib1g-dev +elif [ "$RUNNER_OS" = "macOS" ] +then + brew install llvm@15 + echo "$(brew --prefix llvm@15)/bin" >> $GITHUB_PATH +else + echo 'RUNNER_OS must be set to a supported value' + exit 1 +fi diff --git a/libstd/src/std/array.inko b/std/src/std/array.inko similarity index 78% rename from libstd/src/std/array.inko rename to std/src/std/array.inko index d869d2b1a..7d0719c30 100644 --- a/libstd/src/std/array.inko +++ b/std/src/std/array.inko @@ -4,11 +4,21 @@ import std::cmp::(Contains, Equal) import std::drop::Drop import std::fmt::(Format, Formatter) import std::hash::(Hash, Hasher) -import std::index::(bounds_check, Index, IndexMut, SetIndex) import std::iter::(Enum, Iter) import std::option::Option import std::rand::Shuffle +# Checks if `index` is in the range of zero up to (but excluding) `length`. +# +# # Panics +# +# This method panics if the index is out of bounds. +fn pub bounds_check(index: Int, length: Int) { + if index >= 0 and index < length { return } + + _INKO.panic("The index {index} is out of bounds (length: {length})") +} + # An ordered, integer-indexed generic collection of values. # # Accessing values in an `Array` is a constant-time operation. @@ -43,12 +53,13 @@ class builtin Array[T] { # # Examples # # Array.filled(with: 0, times: 4) # => [0, 0, 0, 0] - fn pub static filled[V: Clone](with: V, times: Int) -> Array[V] { - let array = with_capacity(times) + fn pub static filled[V: Clone[V]](with: V, times: Int) -> Array[V] { + if times == 0 { return [] } - if times == 0 { return array } + let array = with_capacity(times) + let temp = ref with - (times - 1).times fn (_) { array.push(with.clone) } + (times - 1).times fn (_) { array.push(temp.clone) } array.push(with) array } @@ -106,9 +117,10 @@ class builtin Array[T] { # # array.pop # => Option.None fn pub mut pop -> Option[T] { - let val = _INKO.array_pop(self) as T - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } + match _INKO.array_pop(self) { + case { @tag = 0, @value = v } -> Option.Some(v as T) + case _ -> Option.None + } } # Removes the value at the given index, returning the removed value. @@ -131,7 +143,7 @@ class builtin Array[T] { _INKO.array_remove(self, index) as T } - # Returns an immutable reference to the value at the given index. + # Returns an optional immutable reference to the value at the given index. # # # Examples # @@ -139,46 +151,56 @@ class builtin Array[T] { # # let numbers = [10, 20] # - # numbers.get(0) # => Option.Some(ref 10) + # numbers.opt(0) # => Option.Some(ref 10) # # Retrieving a value from a non-existing index: # # let numbers = [10, 20] # - # numbers.get(5) # => Option.None - fn pub get(index: Int) -> Option[ref T] { + # numbers.opt(5) # => Option.None + fn pub opt(index: Int) -> Option[ref T] { if index < 0 or index >= length { return Option.None } - let raw = _INKO.array_get(self, index) as T - let res = ref raw - - _INKO.moved(raw) - Option.Some(res) + Option.Some((ref _INKO.array_get(self, index)) as ref T) } - # Returns a mutable reference to the value at the given index. + # Returns an immutable reference to the value at the given index. # - # # Examples + # # Panics # - # Retrieving an existing value: + # This method panics if the index is out of bounds. + # + # # Examples # # let numbers = [10, 20] # - # numbers.get_mut(0) # => Option.Some(mut 10) + # numbers.get(0) # => 10 + fn pub get(index: Int) -> ref T { + bounds_check(index, length) + (ref _INKO.array_get(self, index)) as ref T + } + + # Stores a value at the given index. # - # Retrieving a value from a non-existing index: + # If a value is already present at the given index, it's dropped before the + # new value overwrites it. # - # let numbers = [10, 20] + # # Panics # - # numbers.get_mut(5) # => Option.None - fn pub mut get_mut(index: Int) -> Option[mut T] { - if index < 0 or index >= length { return Option.None } - - let raw = _INKO.array_get(self, index) as T - let res = mut raw - - _INKO.moved(raw) - Option.Some(res) + # This method panics if the index is out of bounds. + # + # # Examples + # + # Setting an index to a value: + # + # let array = [] + # + # array.set(0, 10) + # array # => [10] + fn pub mut set(index: Int, value: T) { + bounds_check(index, length) + _INKO.array_set(self, index, value) as T + _INKO.moved(value) } # Inserts the value at the given index, returning the old value. @@ -204,13 +226,8 @@ class builtin Array[T] { # Returns an iterator that yields immutable references to the values in # `self`. - fn pub iter -> Iter[ref T, Never] { - Enum.indexed(length) fn (index) { self[index] } - } - - # Returns an iterator that yields mutable references to the values in `self`. - fn pub mut iter_mut -> Iter[mut T, Never] { - Enum.indexed(length) fn (index) { self[index] } + fn pub iter -> Iter[ref T] { + Enum.indexed(length) fn (index) { get(index) } } # Returns an `Iter` that iterates over all values in `self`, returning them @@ -243,12 +260,12 @@ class builtin Array[T] { # iter.next # => Option.Some(20) # iter.next # => Option.Some(10) # iter.next # => Option.None - fn pub reverse_iter -> Iter[ref T, Never] { + fn pub reverse_iter -> Iter[ref T] { let mut index = length - 1 Enum.new fn move { if index > -1 { - Option.Some(self[index := index - 1]) + Option.Some(get(index := index - 1)) } else { Option.None } @@ -266,7 +283,7 @@ class builtin Array[T] { # numbers.append([40, 50]) # # numbers.length # => 5 - fn pub mut append(other: Self) { + fn pub mut append(other: Array[T]) { other.into_iter.each fn (v) { push(v) } } @@ -341,91 +358,86 @@ class builtin Array[T] { } } -impl Drop for Array { - fn mut drop { - let mut index = 0 - - while index < length { _INKO.array_get(self, index := index + 1) as T } +impl Array if T: mut { + # Returns an optional mutable reference to the value at the given index. + # + # # Examples + # + # Retrieving an existing value: + # + # let numbers = [10, 20] + # + # numbers.opt_mut(0) # => Option.Some(mut 10) + # + # Retrieving a value from a non-existing index: + # + # let numbers = [10, 20] + # + # numbers.opt_mut(5) # => Option.None + fn pub mut opt_mut(index: Int) -> Option[mut T] { + if index < 0 or index >= length { return Option.None } - _INKO.array_drop(self) + Option.Some((mut _INKO.array_get(self, index)) as mut T) } -} -impl Contains[T] for Array if T: Equal { - # Returns `true` if `self` contains the given value. + # Returns a mutable reference to the value at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. # # # Examples # - # Checking if an `Array` contains a value: + # let numbers = [10, 20] # - # [10, 20, 30].contains?(10) # => true - fn pub contains?(value: ref T) -> Bool { - iter.any? fn (ours) { ours == value } - } -} - -impl Index[Int, ref T] for Array { - fn pub index(index: Int) -> ref T { + # numbers.get_mut(0) # => 10 + fn pub mut get_mut(index: Int) -> mut T { bounds_check(index, length) + (mut _INKO.array_get(self, index)) as mut T + } - let raw = _INKO.array_get(self, index) as T - let res = ref raw - - _INKO.moved(raw) - res + # Returns an iterator that yields mutable references to the values in `self`. + fn pub mut iter_mut -> Iter[mut T] { + Enum.indexed(length) fn (index) { get_mut(index) } } } -impl IndexMut[Int, mut T] for Array { - fn pub mut index_mut(index: Int) -> mut T { - bounds_check(index, length) +impl Drop for Array { + fn mut drop { + let mut index = 0 - let raw = _INKO.array_get(self, index) as T - let res = mut raw + while index < length { _INKO.array_get(self, index := index + 1) as T } - _INKO.moved(raw) - res + _INKO.array_drop(self) } } -impl SetIndex[Int, T] for Array { - # Stores a value at the given index. - # - # If a value is already present at the given index, it's dropped before the - # new value overwrites it. +impl Contains[T] for Array if T: Equal[T] { + # Returns `true` if `self` contains the given value. # # # Examples # - # Setting an index to a value: - # - # let array = [] - # - # array[0] = 10 # => 10 - # array # => [10] - # - # # Panics + # Checking if an `Array` contains a value: # - # This method panics if the index is out of bounds. - fn pub mut set_index(index: Int, value: T) { - bounds_check(index, length) - _INKO.array_set(self, index, value) as T - _INKO.moved(value) + # [10, 20, 30].contains?(10) # => true + fn pub contains?(value: ref T) -> Bool { + iter.any? fn (ours) { ours == value } } } -impl Clone for Array if T: Clone { - fn pub clone -> Self { +impl Clone[Array[T]] for Array if T: Clone[T] { + fn pub clone -> Array[T] { let copy = [] let mut index = 0 let max = length - while index < max { copy.push(self[index := index + 1].clone) } + while index < max { copy.push(get(index := index + 1).clone) } copy } } -impl Equal for Array if T: Equal { +impl Equal[Array[T]] for Array if T: Equal[T] { # Returns `true` if `self` and the given `Array` are identical. # # # Examples @@ -441,15 +453,15 @@ impl Equal for Array if T: Equal { # Comparing two arrays with the same length but with different values: # # [10, 20] == [20, 10] # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Array[T]) -> Bool { if length != other.length { return false } let mut index = 0 let max = length while index < max { - let ours = self[index] - let theirs = other[index] + let ours = get(index) + let theirs = other.get(index) if ours != theirs { return false } @@ -465,7 +477,7 @@ impl Hash for Array if T: Hash { let mut index = 0 let max = length - while index < max { self[index := index + 1].hash(hasher) } + while index < max { get(index := index + 1).hash(hasher) } } } @@ -510,7 +522,7 @@ impl Drop for IntoIter { } } -impl Iter[T, Never] for IntoIter { +impl Iter[T] for IntoIter { fn pub mut next -> Option[T] { if @index < length { Option.Some(take_next) } else { Option.None } } diff --git a/libstd/src/std/bool.inko b/std/src/std/bool.inko similarity index 86% rename from libstd/src/std/bool.inko rename to std/src/std/bool.inko index bdf0ce7c4..80442bd52 100644 --- a/libstd/src/std/bool.inko +++ b/std/src/std/bool.inko @@ -41,20 +41,20 @@ impl ToString for Bool { } } -impl Clone for Bool { - fn pub clone -> Self { +impl Clone[Bool] for Bool { + fn pub clone -> Bool { if self { true } else { false } } } -impl Equal for Bool { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Bool] for Bool { + fn pub ==(other: ref Bool) -> Bool { _INKO.object_eq(self, other) } } -impl Compare for Bool { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Bool] for Bool { + fn pub cmp(other: ref Bool) -> Ordering { to_int.cmp(other.to_int) } } diff --git a/libstd/src/std/byte_array.inko b/std/src/std/byte_array.inko similarity index 91% rename from libstd/src/std/byte_array.inko rename to std/src/std/byte_array.inko index 800ac4159..467329afb 100644 --- a/libstd/src/std/byte_array.inko +++ b/std/src/std/byte_array.inko @@ -1,10 +1,10 @@ # Arrays of bytes +import std::array::(bounds_check) import std::clone::Clone import std::cmp::(Contains, Equal) import std::drop::Drop import std::fmt::(Format, Formatter) import std::hash::(Hash, Hasher) -import std::index::(bounds_check, Index, SetIndex) import std::io::Read import std::iter::(Bytes as BytesTrait, EOF, Enum, Iter) import std::option::Option @@ -31,12 +31,12 @@ trait pub IntoByteArray { # you're better off using the `Array` type. class builtin ByteArray { # Returns a new empty `ByteArray`. - fn pub static new -> Self { - _INKO.byte_array_allocate + fn pub static new -> ByteArray { + _INKO.byte_array_new } # Returns a new `ByteArray` created from the given `Array`. - fn pub static from_array(array: ref Array[Int]) -> Self { + fn pub static from_array(array: ref Array[Int]) -> ByteArray { let bytes = ByteArray.new array.iter.each fn (v) { bytes.push(v) } @@ -86,7 +86,7 @@ class builtin ByteArray { # # a.append(b) # a # => ByteArray.from_array([10, 20]) - fn pub mut append(other: Self) { + fn pub mut append(other: ByteArray) { _INKO.byte_array_append(self, other) } @@ -125,9 +125,10 @@ class builtin ByteArray { # # bytes.pop # => Option.None fn pub mut pop -> Option[Int] { - let val = _INKO.byte_array_pop(self) - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } + match _INKO.byte_array_pop(self) { + case -1 -> Option.None + case val -> Option.Some(val) + } } # Removes the value at the given index, returning the removed value. @@ -197,19 +198,57 @@ class builtin ByteArray { # # let bytes = ByteArray.from_array([10, 20]) # - # bytes.get(0) # => Option.Some(10) + # bytes.opt(0) # => Option.Some(10) # # Retrieving a non-existing byte: # # let bytes = ByteArray.from_array([10, 20]) # - # bytes.get(5) # => Option.None - fn pub get(index: Int) -> Option[Int] { + # bytes.opt(5) # => Option.None + fn pub opt(index: Int) -> Option[Int] { if index < 0 or index >= length { return Option.None } Option.Some(_INKO.byte_array_get(self, index)) } + # Returns the byte at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. + # + # # Examples + # + # Retrieving an existing byte: + # + # let bytes = ByteArray.from_array([10, 20]) + # + # bytes[0] # => 10 + fn pub get(index: Int) -> Int { + bounds_check(index, length) + _INKO.byte_array_get(self, index) + } + + # Stores a byte at the given index, then returns it. + # + # # Panics + # + # This method panics if the index is out of bounds. + # + # # Examples + # + # Setting the value of an existing index: + # + # let bytes = ByteArray.from_array([10, 20]) + # + # bytes[0] = 30 # => 30 + # bytes[0] # => 30 + fn pub mut set(index: Int, value: Int) { + bounds_check(index, length) + _INKO.byte_array_set(self, index, value) + _INKO.moved(value) + } + # Returns the number of bytes in this `ByteArray`. # # # Examples @@ -326,56 +365,14 @@ impl Drop for ByteArray { } } -impl Index[Int, Int] for ByteArray { - # Returns the byte at the given index. - # - # # Examples - # - # Retrieving an existing byte: - # - # let bytes = ByteArray.from_array([10, 20]) - # - # bytes[0] # => 10 - # - # # Panics - # - # This method panics if the index is out of bounds. - fn pub index(index: Int) -> Int { - bounds_check(index, length) - _INKO.byte_array_get(self, index) - } -} - -impl SetIndex[Int, Int] for ByteArray { - # Stores a byte at the given index, then returns it. - # - # # Examples - # - # Setting the value of an existing index: - # - # let bytes = ByteArray.from_array([10, 20]) - # - # bytes[0] = 30 # => 30 - # bytes[0] # => 30 - # - # # Panics - # - # This method panics if the index is out of bounds. - fn pub mut set_index(index: Int, value: Int) { - bounds_check(index, length) - _INKO.byte_array_set(self, index, value) - _INKO.moved(value) - } -} - impl ToByteArray for ByteArray { - fn pub to_byte_array -> Self { + fn pub to_byte_array -> ByteArray { clone } } impl IntoByteArray for ByteArray { - fn pub move into_byte_array -> Self { + fn pub move into_byte_array -> ByteArray { self } } @@ -404,7 +401,7 @@ impl IntoString for ByteArray { } } -impl Equal for ByteArray { +impl Equal[ByteArray] for ByteArray { # Returns `True` if two `ByteArray` objects are equal to each other. # # Two `ByteArray` objects are considered equal if they have the exact same @@ -417,12 +414,12 @@ impl Equal for ByteArray { # ByteArray.from_array([10]) == ByteArray.from_array([10]) # => True # ByteArray.from_array([10]) == ByteArray.from_array([20]) # => False fn pub ==(other: ref ByteArray) -> Bool { - _INKO.byte_array_equals(self, other) + _INKO.byte_array_eq(self, other) } } -impl Clone for ByteArray { - fn pub clone -> Self { +impl Clone[ByteArray] for ByteArray { + fn pub clone -> ByteArray { _INKO.byte_array_clone(self) } } @@ -469,7 +466,7 @@ class pub Bytes { let @index: Int } -impl Iter[Int, Never] for Bytes { +impl Iter[Int] for Bytes { fn pub mut next -> Option[Int] { match next_byte { case EOF -> Option.None @@ -478,7 +475,7 @@ impl Iter[Int, Never] for Bytes { } } -impl BytesTrait[Never] for Bytes { +impl BytesTrait for Bytes { fn pub mut next_byte -> Int { if @index < @bytes.length { _INKO.byte_array_get(@bytes, @index := @index + 1) @@ -489,7 +486,7 @@ impl BytesTrait[Never] for Bytes { } impl Read for Bytes { - fn pub mut read(into: mut ByteArray, size: Int) -> Int { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Never] { let mut read = 0 while read < size { @@ -502,10 +499,10 @@ impl Read for Bytes { } } - read + Result.Ok(read) } - fn pub mut read_all(bytes: mut ByteArray) -> Int { + fn pub mut read_all(bytes: mut ByteArray) -> Result[Int, Never] { read(into: bytes, size: @bytes.length - @index) } } diff --git a/std/src/std/channel.inko b/std/src/std/channel.inko new file mode 100644 index 000000000..9dece085f --- /dev/null +++ b/std/src/std/channel.inko @@ -0,0 +1,153 @@ +# A multi-producer, multi-consumer FIFO queue. +import std::clone::Clone +import std::drop::Drop +import std::time::Instant + +# Blocks the current process until one or more of the given channels have a +# message. +# +# This method simply returns once one or more channels have a message sent to +# them. As a channel can have multiple consumers, it's possible that once this +# method returns all the specified channels are empty again. As such, it's best +# to use this method in a loop like so: +# +# loop { +# channels.iter.each fn (chan) { +# match chan.try_receive { +# case Some(val) -> ... +# case _ -> {} +# } +# } +# +# wait(channels) +# } +# +# This method is intended to be used when you have a small number of channels, +# you want to wait until one or more have a message (without busy polling), and +# you don't care which channels receive a message. +# +# # Performance +# +# This method may need to iterate over the list of channels multiple times. As +# such it's best to only use a small number of channels at a time. If you find +# yourself in need of having to wait for many (e.g. hundreds) of channels at a +# time, you should probably refactor your code in some way (e.g. receive from an +# auxillary channel instead). +# +# # Examples +# +# import std::channel::(wait) +# +# let chan1 = Channel.new(1) +# let chan2 = Channel.new(2) +# +# chan2.send(1) +# wait([chan1, chan2]) +fn pub wait[T](channels: ref Array[Channel[T]]) { + _INKO.channel_wait(channels) +} + +# A multi-producer, multi-consumer FIFO queue. +# +# Channels allow for multiple producers and consumers, uses FIFO ordering, and +# are bounded. When sending a message to a channel that's full, the sending +# process is blocked until space becomes available. +# +# Channels use atomic reference counting and are dropped (along with any pending +# messages) when the last reference to the channel is dropped. Channels are +# treated as value types and are sendable to other processes without the need +# for the `recover` expression. +class builtin Channel[T] { + # Returns a new channel that can store the given number of messages. + # + # If you specify a value less than 1, the capacity is set to 1. + fn pub static new(capacity: Int) -> Channel[uni T] { + _INKO.channel_new(capacity) as Channel[uni T] + } + + # Sends a message to the channel. + # + # If the channel is full, the current process is blocked until space is + # available in the channel. + # + # # Examples + # + # let chan = Channel.new(4) + # + # chan.send(1) + # chan.send(2) + fn pub send(value: uni T) { + _INKO.channel_send(self, value) + _INKO.moved(value) + } + + # Receives a message from the channel. + # + # This method blocks the current process until a message is delivered. + # + # # Examples + # + # let chan = Channel.new(1) + # + # chan.send(1) + # chan.receive # => 1 + fn pub receive -> uni T { + _INKO.channel_receive(self) as uni T + } + + # Receives a message from the channel without blocking the sender. + # + # If a message is availabe, it's returned as a `Some`, otherwise a `None` is + # returned. + # + # # Examples + # + # let chan = Channel.new(1) + # + # chan.try_receive # => Option.None + # chan.send(1) + # chan.try_receive # => Option.Some(1) + fn pub try_receive -> Option[uni T] { + match _INKO.channel_try_receive(self) { + case { @tag = 0, @value = v } -> Option.Some(v as uni T) + case _ -> Option.None + } + } + + # Receives a message, returning a `None` if no message is received when the + # deadline is met. + # + # import std::time::Instant + # import std::time::Duration + # + # let duration = Duration.from_secs(1) + # let chan = Channel.new(1) + # + # chan.receive_until(deadline: Instant.new + duration) # => Option.None + # chan.send(1) + # chan.receive_until(deadline: Instant.new + duration) # => Option.Some(1) + fn pub receive_until(deadline: ref Instant) -> Option[uni T] { + match _INKO.channel_receive_until(self, deadline.to_int) { + case { @tag = 0, @value = v } -> Option.Some(v as uni T) + case _ -> Option.None + } + } +} + +impl Clone[Channel[T]] for Channel { + fn pub clone -> Channel[T] { + self + } +} + +impl Drop for Channel { + fn mut drop { + loop { + match _INKO.channel_try_receive(self) { + # The value is dropped at the end of this scope. + case { @tag = 0, @value = v } -> v as T + case _ -> break + } + } + } +} diff --git a/std/src/std/clone.inko b/std/src/std/clone.inko new file mode 100644 index 000000000..dffaf13de --- /dev/null +++ b/std/src/std/clone.inko @@ -0,0 +1,7 @@ +# Cloning of objects. + +# A type that can be cloned into a new type. +trait pub Clone[T] { + # Creates a clone of `self`. + fn pub clone -> T +} diff --git a/libstd/src/std/cmp.inko b/std/src/std/cmp.inko similarity index 85% rename from libstd/src/std/cmp.inko rename to std/src/std/cmp.inko index 80bfe516c..c81619c00 100644 --- a/libstd/src/std/cmp.inko +++ b/std/src/std/cmp.inko @@ -9,8 +9,8 @@ class pub enum Ordering { case Greater } -impl Equal for Ordering { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Ordering] for Ordering { + fn pub ==(other: ref Ordering) -> Bool { match self { case Less -> match other { case Less -> true @@ -39,7 +39,7 @@ impl Format for Ordering { } # A type that can be compared to another type for a sort-order. -trait pub Compare { +trait pub Compare[T] { # Returns the ordering between `self` and the given argument. # # The returned value should be as follows: @@ -49,10 +49,10 @@ trait pub Compare { # | `a == b` | `Ordering.Equal` # | `a > b` | `Ordering.Greater` # | `a < b` | `Ordering.Less` - fn pub cmp(other: ref Self) -> Ordering + fn pub cmp(other: ref T) -> Ordering # Returns `true` if `self` is lower than the given argument. - fn pub <(other: ref Self) -> Bool { + fn pub <(other: ref T) -> Bool { match cmp(other) { case Less -> true case _ -> false @@ -60,7 +60,7 @@ trait pub Compare { } # Returns `true` if `self` is lower than or equal to the given argument. - fn pub <=(other: ref Self) -> Bool { + fn pub <=(other: ref T) -> Bool { match cmp(other) { case Less or Equal -> true case _ -> false @@ -68,7 +68,7 @@ trait pub Compare { } # Returns `true` if `self` is greater than the given argument. - fn pub >(other: ref Self) -> Bool { + fn pub >(other: ref T) -> Bool { match cmp(other) { case Greater -> true case _ -> false @@ -76,7 +76,7 @@ trait pub Compare { } # Returns `true` if `self` is equal to or greater than the given argument. - fn pub >=(other: ref Self) -> Bool { + fn pub >=(other: ref T) -> Bool { match cmp(other) { case Greater or Equal -> true case _ -> false @@ -85,7 +85,7 @@ trait pub Compare { } # A type that can be compared for equality. -trait pub Equal { +trait pub Equal[T] { # Returns `True` if `self` and the given object are equal to each other. # # This operator is used to perform structural equality. This means two objects @@ -93,10 +93,10 @@ trait pub Equal { # their structure is equal. For example, two different arrays may be # considered to have structural equality if they contain the exact same # values. - fn pub ==(other: ref Self) -> Bool + fn pub ==(other: ref T) -> Bool # Returns `True` if `self` and the given object are not equal to each other. - fn pub !=(other: ref Self) -> Bool { + fn pub !=(other: ref T) -> Bool { (self == other).false? } } @@ -114,7 +114,7 @@ trait pub Contains[T] { # import std::cmp::(min) # # min(10, 5) # => 5 -fn pub min[T: Compare](a: T, b: T) -> T { +fn pub min[T: Compare[T]](a: T, b: T) -> T { if a <= b { a } else { b } } @@ -125,6 +125,6 @@ fn pub min[T: Compare](a: T, b: T) -> T { # import std::cmp::(max) # # max(10, 5) # => 10 -fn pub max[T: Compare](a: T, b: T) -> T { +fn pub max[T: Compare[T]](a: T, b: T) -> T { if a >= b { a } else { b } } diff --git a/libstd/src/std/crypto/chacha.inko b/std/src/std/crypto/chacha.inko similarity index 87% rename from libstd/src/std/crypto/chacha.inko rename to std/src/std/crypto/chacha.inko index 169216073..526a88eb2 100644 --- a/libstd/src/std/crypto/chacha.inko +++ b/std/src/std/crypto/chacha.inko @@ -9,7 +9,6 @@ import std::crypto::cipher::Cipher import std::crypto::hash::(Hash, Hasher) import std::crypto::math::(rotate_left_u32, to_u32) import std::endian::little -import std::index::(Index, SetIndex) # The ChaCha key size in bytes. let KEY_SIZE = 256 / 8 @@ -83,14 +82,14 @@ fn pub hchacha20(key: ref ByteArray, nonce: ref ByteArray) -> ByteArray { } matrix.perform_rounds - little.write_u32(matrix.words[0], into: out, at: 0) - little.write_u32(matrix.words[1], into: out, at: 4) - little.write_u32(matrix.words[2], into: out, at: 8) - little.write_u32(matrix.words[3], into: out, at: 12) - little.write_u32(matrix.words[12], into: out, at: 16) - little.write_u32(matrix.words[13], into: out, at: 20) - little.write_u32(matrix.words[14], into: out, at: 24) - little.write_u32(matrix.words[15], into: out, at: 28) + little.write_u32(matrix.words.get(0), into: out, at: 0) + little.write_u32(matrix.words.get(1), into: out, at: 4) + little.write_u32(matrix.words.get(2), into: out, at: 8) + little.write_u32(matrix.words.get(3), into: out, at: 12) + little.write_u32(matrix.words.get(12), into: out, at: 16) + little.write_u32(matrix.words.get(13), into: out, at: 20) + little.write_u32(matrix.words.get(14), into: out, at: 24) + little.write_u32(matrix.words.get(15), into: out, at: 28) out } @@ -100,17 +99,17 @@ class Matrix { fn mut quarter_round(a: Int, b: Int, c: Int, d: Int) { let words = @words - words[a] = to_u32(words[a].wrapping_add(words[b])) - words[d] = rotate_left_u32(words[d] ^ words[a], 16) + words.set(a, to_u32(words.get(a).wrapping_add(words.get(b)))) + words.set(d, rotate_left_u32(words.get(d) ^ words.get(a), 16)) - words[c] = to_u32(words[c].wrapping_add(words[d])) - words[b] = rotate_left_u32(words[b] ^ words[c], 12) + words.set(c, to_u32(words.get(c).wrapping_add(words.get(d)))) + words.set(b, rotate_left_u32(words.get(b) ^ words.get(c), 12)) - words[a] = to_u32(words[a].wrapping_add(words[b])) - words[d] = rotate_left_u32(words[d] ^ words[a], 8) + words.set(a, to_u32(words.get(a).wrapping_add(words.get(b)))) + words.set(d, rotate_left_u32(words.get(d) ^ words.get(a), 8)) - words[c] = to_u32(words[c].wrapping_add(words[d])) - words[b] = rotate_left_u32(words[b] ^ words[c], 7) + words.set(c, to_u32(words.get(c).wrapping_add(words.get(d)))) + words.set(b, rotate_left_u32(words.get(b) ^ words.get(c), 7)) } fn mut perform_rounds { @@ -132,17 +131,13 @@ class Matrix { fn length -> Int { @words.length } -} -impl Index[Int, Int] for Matrix { - fn pub index(index: Int) -> Int { - @words[index] + fn get(index: Int) -> Int { + @words.get(index) } -} -impl SetIndex[Int, Int] for Matrix { - fn pub mut set_index(index: Int, value: Int) { - @words[index] = value + fn mut set(index: Int, value: Int) { + @words.set(index, value) } } @@ -237,7 +232,7 @@ class pub ChaCha20 { # environments you must ensure that the nonce is never reused for the same # key. Simply generating a random nonce isn't enough, as given enough time and # bad luck the same nonce may be produced. - fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> Self { + fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> ChaCha20 { if key.length != KEY_SIZE { panic("ChaCha20 key sizes must be exactly {KEY_SIZE} bytes") } @@ -246,7 +241,7 @@ class pub ChaCha20 { panic("ChaCha20 nonce sizes must be exactly {CHACHA_NONCE_SIZE} bytes") } - Self { + ChaCha20 { @matrix = Matrix { @words = [ 0x61707865, @@ -280,7 +275,7 @@ class pub ChaCha20 { if value < 0 or value > MAX_COUNTER { panic("ChaCha block counters must be between 0 and {MAX_COUNTER}") } else { - @matrix[12] = value + @matrix.set(12, value) } } @@ -292,17 +287,17 @@ class pub ChaCha20 { let tmp = Matrix { @words = Array.filled(with: 0, times: MATRIX_SIZE) } loop { - MATRIX_SIZE.times fn (i) { tmp[i] = @matrix[i] } + MATRIX_SIZE.times fn (i) { tmp.set(i, @matrix.get(i)) } tmp.perform_rounds MATRIX_SIZE.times fn (i) { - tmp[i] = to_u32(tmp[i].wrapping_add(@matrix[i])) - little.write_u32(tmp[i], into: buf, at: i * 4) + tmp.set(i, to_u32(tmp.get(i).wrapping_add(@matrix.get(i)))) + little.write_u32(tmp.get(i), into: buf, at: i * 4) } # This in itself can't overflow, as the Int type is a 64-bits signed # integer, and below we limit it to the range that fits in a 32-bits # unsigned integer. - let new_length = @matrix[12] + 1 + let new_length = @matrix.get(12) + 1 # The original implementation makes no attempt at protecting the user from # overflowing the counter, as it's unlikely to happen in the first place. @@ -315,14 +310,18 @@ class pub ChaCha20 { panic("The block counter overflowed after {MAX_COUNTER} blocks") } - @matrix[12] = new_length + @matrix.set(12, new_length) if len <= BLOCK_SIZE { - len.times fn (i) { out[offset + i] = input[offset + i] ^ buf[i] } + len.times fn (i) { + out.set(offset + i, input.get(offset + i) ^ buf.get(i)) + } return out } - BLOCK_SIZE.times fn (i) { out[offset + i] = input[offset + i] ^ buf[i] } + BLOCK_SIZE.times fn (i) { + out.set(offset + i, input.get(offset + i) ^ buf.get(i)) + } len -= BLOCK_SIZE offset += BLOCK_SIZE @@ -378,7 +377,7 @@ class pub XChaCha20 { # let nonce = rand.bytes(size: 24) # # XChaCha20.new(key, nonce) - fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> Self { + fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> XChaCha20 { if key.length != KEY_SIZE { panic("XChaCha20 key sizes must be exactly {KEY_SIZE} bytes") } @@ -389,7 +388,7 @@ class pub XChaCha20 { let sub_key = hchacha20(key, nonce.slice(start: 0, length: 16)) - Self { + XChaCha20 { @chacha = ChaCha20 { @matrix = Matrix { @words = [ diff --git a/libstd/src/std/crypto/cipher.inko b/std/src/std/crypto/cipher.inko similarity index 100% rename from libstd/src/std/crypto/cipher.inko rename to std/src/std/crypto/cipher.inko diff --git a/libstd/src/std/crypto/hash.inko b/std/src/std/crypto/hash.inko similarity index 76% rename from libstd/src/std/crypto/hash.inko rename to std/src/std/crypto/hash.inko index c937b8517..9c70bea76 100644 --- a/libstd/src/std/crypto/hash.inko +++ b/std/src/std/crypto/hash.inko @@ -3,7 +3,6 @@ import std::cmp::Equal import std::endian::big import std::endian::little import std::fmt::(Format, Formatter) -import std::index::(Index, SetIndex) import std::string::(StringBuffer, ToString) # The digits to use when converting a digest to a hexadecimal string. @@ -17,8 +16,8 @@ class pub Block { let @index: Int # Returns a new `Block` with the given size in bytes. - fn pub static new(size: Int) -> Self { - Self { @bytes = ByteArray.filled(with: 0, times: size), @index = 0 } + fn pub static new(size: Int) -> Block { + Block { @bytes = ByteArray.filled(with: 0, times: size), @index = 0 } } # Reads an unsigned 32-bits little-endian integer from the given index. @@ -42,7 +41,7 @@ class pub Block { let mut index = 0 while length > 0 { - @bytes[@index := @index + 1] = bytes[index] + @bytes.set(@index := @index + 1, bytes.get(index)) if @index == @bytes.length { transform.call @@ -62,18 +61,18 @@ class pub Block { let pad_to = @bytes.length - length_bytes if @index >= pad_to { - @bytes[@index := @index + 1] = 0x80 + @bytes.set(@index := @index + 1, 0x80) - while @index < @bytes.length { @bytes[@index := @index + 1] = 0 } + while @index < @bytes.length { @bytes.set(@index := @index + 1, 0) } transform.call @index = 0 - while @index < pad_to { @bytes[@index := @index + 1] = 0 } + while @index < pad_to { @bytes.set(@index := @index + 1, 0) } } else { - @bytes[@index := @index + 1] = 0x80 + @bytes.set(@index := @index + 1, 0x80) - while @index < pad_to { @bytes[@index := @index + 1] = 0 } + while @index < pad_to { @bytes.set(@index := @index + 1, 0) } } } @@ -91,17 +90,23 @@ class pub Block { fn pub block_index -> Int { @index } -} -impl Index[Int, Int] for Block { - fn pub index(index: Int) -> Int { - @bytes[index] + # Returns the byte at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. + fn pub get(index: Int) -> Int { + @bytes.get(index) } -} -impl SetIndex[Int, Int] for Block { - fn pub mut set_index(index: Int, value: Int) { - @bytes[index] = value + # Sets the byte at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. + fn pub mut set(index: Int, value: Int) { + @bytes.set(index, value) } } @@ -117,8 +122,8 @@ class pub Hash { let pub @bytes: ByteArray # Returns a new empty `Digest`. - fn pub static new(bytes: ByteArray) -> Self { - Self { @bytes = bytes} + fn pub static new(bytes: ByteArray) -> Hash { + Hash { @bytes = bytes} } } @@ -132,16 +137,16 @@ impl ToString for Hash { @bytes.iter.each_with_index fn (index, byte) { let hex_index = index * 2 - hex[hex_index] = HEX_DIGITS.byte(byte >> 4) - hex[hex_index + 1] = HEX_DIGITS.byte(byte & 0x0F) + hex.set(hex_index, HEX_DIGITS.byte(byte >> 4)) + hex.set(hex_index + 1, HEX_DIGITS.byte(byte & 0x0F)) } hex.into_string } } -impl Equal for Hash { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Hash] for Hash { + fn pub ==(other: ref Hash) -> Bool { @bytes == other.bytes } } @@ -163,5 +168,5 @@ trait pub Hasher { fn pub mut write(bytes: ref ByteArray) # Generate a hash based on the current state. - fn pub move finalize -> Hash + fn pub move finish -> Hash } diff --git a/libstd/src/std/crypto/math.inko b/std/src/std/crypto/math.inko similarity index 71% rename from libstd/src/std/crypto/math.inko rename to std/src/std/crypto/math.inko index ff9ebe667..60d31936c 100644 --- a/libstd/src/std/crypto/math.inko +++ b/std/src/std/crypto/math.inko @@ -6,7 +6,16 @@ # # This method panics if `amount` is greater than 32 or less than zero. fn pub rotate_left_u32(value: Int, amount: Int) -> Int { - to_u32((value << amount) | (value >> (32 - amount))) + to_u32((value << amount) | (value >>> (32 - amount))) +} + +# Rotates a 64-bits unsigned integer to the left. +# +# # Panics +# +# This method panics if `amount` is greater than 64 or less than zero. +fn pub rotate_left_u64(value: Int, amount: Int) -> Int { + (value << amount) | (value >>> (64 - amount)) } # Rotates a 32-bits unsigned integer to the right. @@ -24,7 +33,7 @@ fn pub rotate_right_u32(value: Int, amount: Int) -> Int { # # This method panics if `amount` is greater than 32 or less than zero. fn pub shift_right_u32(value: Int, amount: Int) -> Int { - to_u32(value >> amount) + to_u32(value >>> amount) } # Converts an `Int` to an unsigned 32-bits `Int`. diff --git a/libstd/src/std/crypto/md5.inko b/std/src/std/crypto/md5.inko similarity index 92% rename from libstd/src/std/crypto/md5.inko rename to std/src/std/crypto/md5.inko index 50ed930ef..6eb41f4d3 100644 --- a/libstd/src/std/crypto/md5.inko +++ b/std/src/std/crypto/md5.inko @@ -13,7 +13,7 @@ # let hasher = Md5.new # # hasher.write('hello'.to_byte_array) -# hasher.finalize.to_string # => '5d41402abc4b2a76b9719d911017c592' +# hasher.finish.to_string # => '5d41402abc4b2a76b9719d911017c592' # # You can also use `Md5.hash`: # @@ -104,12 +104,12 @@ class pub Md5 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Md5 { + Md5 { @block = Block.new(BLOCK_SIZE), @words = Array.filled(with: 0, times: 16), @size = 0, @@ -123,7 +123,7 @@ class pub Md5 { fn mut compress { let words = @words - 16.times fn (i) { words[i] = @block.read_u32_le(i * 4) } + 16.times fn (i) { words.set(i, @block.read_u32_le(i * 4)) } let mut a = @a let mut b = @b @@ -152,10 +152,13 @@ class pub Md5 { } f = to_u32( - f.wrapping_add(a).wrapping_add(words[word]).wrapping_add(TABLE[i]) + f + .wrapping_add(a) + .wrapping_add(words.get(word)) + .wrapping_add(TABLE.get(i)) ) - let temp = to_u32(b.wrapping_add(rotate_left_u32(f, SHIFTS[i]))) + let temp = to_u32(b.wrapping_add(rotate_left_u32(f, SHIFTS.get(i)))) a = d d = c @@ -177,7 +180,7 @@ impl Hasher for Md5 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(8) fn { compress } @block.write_length_le(@size * 8, at: 56) compress diff --git a/libstd/src/std/crypto/poly1305.inko b/std/src/std/crypto/poly1305.inko similarity index 96% rename from libstd/src/std/crypto/poly1305.inko rename to std/src/std/crypto/poly1305.inko index 160ca302d..41ca58c32 100644 --- a/libstd/src/std/crypto/poly1305.inko +++ b/std/src/std/crypto/poly1305.inko @@ -69,7 +69,7 @@ class pub Poly1305 { let hasher = new(key) hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new Poly1305 hasher. @@ -80,12 +80,12 @@ class pub Poly1305 { # # Panics # # This method panics if the key isn't exactly 32 bytes long. - fn pub static new(key: ref ByteArray) -> Self { + fn pub static new(key: ref ByteArray) -> Poly1305 { if key.length != KEY_SIZE { panic("Poly1305 keys must be exactly {KEY_SIZE} bytes") } - Self { + Poly1305 { @block = Block.new(BLOCK_SIZE), @r0 = little.read_u32(from: key, at: 0) & 0x0FFFFFFF, @r1 = little.read_u32(from: key, at: 4) & 0x0FFFFFFC, @@ -164,13 +164,13 @@ impl Hasher for Poly1305 { @block.write_bytes(bytes) fn { compress(final: false) } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { if @block.block_index > 0 { let mut i = @block.block_index - @block[i := i + 1] = 1 + @block.set(i := i + 1, 1) - while i < BLOCK_SIZE { @block[i := i + 1] = 0 } + while i < BLOCK_SIZE { @block.set(i := i + 1, 0) } compress(final: true) } diff --git a/libstd/src/std/crypto/sha1.inko b/std/src/std/crypto/sha1.inko similarity index 85% rename from libstd/src/std/crypto/sha1.inko rename to std/src/std/crypto/sha1.inko index 37593600e..ad6bf9910 100644 --- a/libstd/src/std/crypto/sha1.inko +++ b/std/src/std/crypto/sha1.inko @@ -10,7 +10,7 @@ # let hasher = Sha1.new # # hasher.write('hello'.to_byte_array) -# hasher.finalize.to_string # => 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d' +# hasher.finish.to_string # => 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d' # # You can also use `Sha1.hash`: # @@ -50,12 +50,12 @@ class pub Sha1 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Sha1 { + Sha1 { @block = Block.new(BLOCK_SIZE), @words = Array.filled(with: 0, times: 80), @size = 0, @@ -70,12 +70,18 @@ class pub Sha1 { fn mut compress { let words = @words - 0.until(16).iter.each fn (i) { words[i] = @block.read_u32_be(i * 4) } + 0.until(16).iter.each fn (i) { words.set(i, @block.read_u32_be(i * 4)) } 16.until(80).iter.each fn (i) { - words[i] = rotate_left_u32( - words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16], - 1 + words.set( + i, + rotate_left_u32( + words.get(i - 3) + ^ words.get(i - 8) + ^ words.get(i - 14) + ^ words.get(i - 16), + 1 + ) ) } @@ -91,7 +97,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add((b & c) | (b.not & d)) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0x5A827999) ) @@ -108,7 +114,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add(b ^ c ^ d) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0x6ED9EBA1) ) @@ -125,7 +131,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add((b & c) | (b & d) | (c & d)) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0x8F1BBCDC) ) @@ -142,7 +148,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add(b ^ c ^ d) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0xCA62C1D6) ) @@ -168,7 +174,7 @@ impl Hasher for Sha1 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(8) fn { compress } @block.write_length_be(@size * 8, at: 56) compress diff --git a/libstd/src/std/crypto/sha2.inko b/std/src/std/crypto/sha2.inko similarity index 90% rename from libstd/src/std/crypto/sha2.inko rename to std/src/std/crypto/sha2.inko index 0bfd84a48..e5610bc08 100644 --- a/libstd/src/std/crypto/sha2.inko +++ b/std/src/std/crypto/sha2.inko @@ -7,7 +7,7 @@ # let hasher = Sha256.new # # hasher.write('hello'.to_byte_array) -# hasher.finalize.to_string # => '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' +# hasher.finish.to_string # => '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' # # Or when using the `hash` static method: # @@ -102,12 +102,12 @@ class pub Sha256 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Sha256 { + Sha256 { @block = Block.new(SHA256_BLOCK_SIZE), @words = Array.filled(with: 0, times: 64), @size = 0, @@ -125,11 +125,11 @@ class pub Sha256 { fn mut compress { let words = @words - 0.until(16).iter.each fn (i) { words[i] = @block.read_u32_be(i * 4) } + 0.until(16).iter.each fn (i) { words.set(i, @block.read_u32_be(i * 4)) } 16.until(64).iter.each fn (i) { - let w15 = words[i - 15] - let w2 = words[i - 2] + let w15 = words.get(i - 15) + let w2 = words.get(i - 2) let s0 = to_u32( rotate_right_u32(w15, 7) ^ rotate_right_u32(w15, 18) @@ -142,11 +142,14 @@ class pub Sha256 { ^ shift_right_u32(w2, 10) ) - words[i] = to_u32( - words[i - 16] - .wrapping_add(s0) - .wrapping_add(words[i - 7]) - .wrapping_add(s1) + words.set( + i, + to_u32( + words.get(i - 16) + .wrapping_add(s0) + .wrapping_add(words.get(i - 7)) + .wrapping_add(s1) + ) ) } @@ -179,8 +182,8 @@ class pub Sha256 { h .wrapping_add(s1) .wrapping_add(ch) - .wrapping_add(SHA256_TABLE[i]) - .wrapping_add(words[i]) + .wrapping_add(SHA256_TABLE.get(i)) + .wrapping_add(words.get(i)) ) let temp2 = to_u32(s0.wrapping_add(maj)) @@ -212,7 +215,7 @@ impl Hasher for Sha256 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(8) fn { compress } @block.write_length_be(@size * 8, at: 56) compress @@ -256,12 +259,12 @@ class pub Sha512 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Sha512 { + Sha512 { @block = Block.new(SHA512_BLOCK_SIZE), @words = Array.filled(with: 0, times: 80), @size = 0, @@ -279,18 +282,21 @@ class pub Sha512 { fn mut compress { let words = @words - 0.until(16).iter.each fn (i) { words[i] = @block.read_i64_be(i * 8) } + 0.until(16).iter.each fn (i) { words.set(i, @block.read_i64_be(i * 8)) } 16.until(80).iter.each fn (i) { - let w15 = words[i - 15] - let w2 = words[i - 2] + let w15 = words.get(i - 15) + let w2 = words.get(i - 2) let s0 = w15.rotate_right(1) ^ w15.rotate_right(8) ^ (w15 >>> 7) let s1 = w2.rotate_right(19) ^ w2.rotate_right(61) ^ (w2 >>> 6) - words[i] = words[i - 16] - .wrapping_add(s0) - .wrapping_add(words[i - 7]) - .wrapping_add(s1) + words.set( + i, + words.get(i - 16) + .wrapping_add(s0) + .wrapping_add(words.get(i - 7)) + .wrapping_add(s1) + ) } let mut a = @a @@ -311,8 +317,8 @@ class pub Sha512 { let temp1 = h .wrapping_add(s1) .wrapping_add(ch) - .wrapping_add(SHA512_TABLE[i]) - .wrapping_add(words[i]) + .wrapping_add(SHA512_TABLE.get(i)) + .wrapping_add(words.get(i)) let temp2 = s0.wrapping_add(maj) @@ -344,7 +350,7 @@ impl Hasher for Sha512 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(16) fn { compress } # SHA512 wants a 128-bits length, but we internally maintain a 64-bits diff --git a/libstd/src/std/debug.inko b/std/src/std/debug.inko similarity index 61% rename from libstd/src/std/debug.inko rename to std/src/std/debug.inko index f19af9809..7cbf109b8 100644 --- a/libstd/src/std/debug.inko +++ b/std/src/std/debug.inko @@ -16,14 +16,14 @@ class pub StackFrame { # The line number the stack frame originates from. let pub @line: Int - fn pub static new(path: Path, name: String, line: Int) -> Self { - Self { @path = path, @name = name, @line = line } + fn pub static new(path: Path, name: String, line: Int) -> StackFrame { + StackFrame { @path = path, @name = name, @line = line } } } -impl Clone for StackFrame { - fn pub clone -> Self { - Self { @path = @path.clone, @name = @name, @line = @line } +impl Clone[StackFrame] for StackFrame { + fn pub clone -> StackFrame { + StackFrame { @path = @path.clone, @name = @name, @line = @line } } } @@ -47,24 +47,27 @@ impl Clone for StackFrame { # # fn second { # let frames = stacktrace(skip: 1) -# let frame = frames[frames.length - 1] +# let frame = frames.get(frames.length - 1) # # frame.name # => 'second' # } fn pub stacktrace(skip: Int) -> Array[StackFrame] { - let trace = _INKO.process_stacktrace(skip) - let length = _INKO.process_stacktrace_length(trace) + let trace = _INKO.process_stacktrace + let len = _INKO.process_stacktrace_length(trace) + let max = len - skip let mut index = 0 - let frames = Array.with_capacity(length) - while index < length { - let path = Path.new(_INKO.process_call_frame_path(trace, index)) - let name = _INKO.process_call_frame_name(trace, index) - let line = _INKO.process_call_frame_line(trace, index) + if max <= 0 { return [] } - frames.push(StackFrame.new(path, name, line)) + let frames = Array.with_capacity(max) - index += 1 + while index < max { + let path = Path.new(_INKO.process_stack_frame_path(trace, index)) + let name = _INKO.process_stack_frame_name(trace, index) + let line = _INKO.process_stack_frame_line(trace, index) + + frames.push(StackFrame.new(path, name, line)) + index += 1 } _INKO.process_stacktrace_drop(trace) diff --git a/libstd/src/std/drop.inko b/std/src/std/drop.inko similarity index 100% rename from libstd/src/std/drop.inko rename to std/src/std/drop.inko diff --git a/libstd/src/std/endian/big.inko b/std/src/std/endian/big.inko similarity index 72% rename from libstd/src/std/endian/big.inko rename to std/src/std/endian/big.inko index f2677c46a..4b75e109e 100644 --- a/libstd/src/std/endian/big.inko +++ b/std/src/std/endian/big.inko @@ -15,10 +15,10 @@ # big.write_u32(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([7, 91, 205, 21]) fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { - into[at] = value >> 24 - into[at + 1] = value >> 16 - into[at + 2] = value >> 8 - into[at + 3] = value + into.set(at, value >> 24) + into.set(at + 1, value >> 16) + into.set(at + 2, value >> 8) + into.set(at + 3, value) } # Writes a value interpreted as a 64-bits signed integer into `into` as a series @@ -33,14 +33,14 @@ fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { # big.write_i64(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([7, 91, 205, 21]) fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { - into[at] = value >> 56 - into[at + 1] = value >> 48 - into[at + 2] = value >> 40 - into[at + 3] = value >> 32 - into[at + 4] = value >> 24 - into[at + 5] = value >> 16 - into[at + 6] = value >> 8 - into[at + 7] = value + into.set(at, value >> 56) + into.set(at + 1, value >> 48) + into.set(at + 2, value >> 40) + into.set(at + 3, value >> 32) + into.set(at + 4, value >> 24) + into.set(at + 5, value >> 16) + into.set(at + 6, value >> 8) + into.set(at + 7, value) } # Reads four bytes starting at `at` as a 32-bits signed integer. @@ -59,7 +59,10 @@ fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { # big.write_u32(123456789, into: bytes, at: 0) # big.read_u32(from: bytes, at: 0) # => 123456789 fn pub read_u32(from: ref ByteArray, at: Int) -> Int { - (from[at] << 24) | (from[at + 1] << 16) | (from[at + 2] << 8) | from[at + 3] + (from.get(at) << 24) + | (from.get(at + 1) << 16) + | (from.get(at + 2) << 8) + | from.get(at + 3) } # Reads eight bytes starting at `at` as a 64-bits signed integer. @@ -78,12 +81,12 @@ fn pub read_u32(from: ref ByteArray, at: Int) -> Int { # big.write_i64(123456789, into: bytes, at: 0) # big.read_i64(from: bytes, at: 0) # => 123456789 fn pub read_i64(from: ref ByteArray, at: Int) -> Int { - (from[at] << 56) - | (from[at + 1] << 48) - | (from[at + 2] << 40) - | (from[at + 3] << 32) - | (from[at + 4] << 24) - | (from[at + 5] << 16) - | (from[at + 6] << 8) - | from[at + 7] + (from.get(at) << 56) + | (from.get(at + 1) << 48) + | (from.get(at + 2) << 40) + | (from.get(at + 3) << 32) + | (from.get(at + 4) << 24) + | (from.get(at + 5) << 16) + | (from.get(at + 6) << 8) + | from.get(at + 7) } diff --git a/libstd/src/std/endian/little.inko b/std/src/std/endian/little.inko similarity index 73% rename from libstd/src/std/endian/little.inko rename to std/src/std/endian/little.inko index 804eab034..45773135c 100644 --- a/libstd/src/std/endian/little.inko +++ b/std/src/std/endian/little.inko @@ -15,10 +15,10 @@ # little.write_u32(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([21, 205, 91, 7]) fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { - into[at] = value - into[at + 1] = value >> 8 - into[at + 2] = value >> 16 - into[at + 3] = value >> 24 + into.set(at, value) + into.set(at + 1, value >> 8) + into.set(at + 2, value >> 16) + into.set(at + 3, value >> 24) } # Writes a value interpreted as a 64-bits signed integer into `into` as a series @@ -33,14 +33,14 @@ fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { # little.write_i64(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([21, 205, 91, 7]) fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { - into[at] = value - into[at + 1] = value >> 8 - into[at + 2] = value >> 16 - into[at + 3] = value >> 24 - into[at + 4] = value >> 32 - into[at + 5] = value >> 40 - into[at + 6] = value >> 48 - into[at + 7] = value >> 56 + into.set(at, value) + into.set(at + 1, value >> 8) + into.set(at + 2, value >> 16) + into.set(at + 3, value >> 24) + into.set(at + 4, value >> 32) + into.set(at + 5, value >> 40) + into.set(at + 6, value >> 48) + into.set(at + 7, value >> 56) } # Reads four bytes starting at `at` as a 32-bits signed integer. @@ -59,7 +59,10 @@ fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { # little.write_u32(123456789, into: bytes, at: 0) # little.read_u32(from: bytes, at: 0) # => 123456789 fn pub read_u32(from: ref ByteArray, at: Int) -> Int { - (from[at + 3] << 24) | (from[at + 2] << 16) | (from[at + 1] << 8) | from[at] + (from.get(at + 3) << 24) + | (from.get(at + 2) << 16) + | (from.get(at + 1) << 8) + | from.get(at) } # Reads eight bytes starting at `at` as a 64-bits signed integer. @@ -78,12 +81,12 @@ fn pub read_u32(from: ref ByteArray, at: Int) -> Int { # little.write_i64(123456789, into: bytes, at: 0) # little.read_i64(from: bytes, at: 0) # => 123456789 fn pub read_i64(from: ref ByteArray, at: Int) -> Int { - (from[at + 7] << 56) - | (from[at + 6] << 48) - | (from[at + 5] << 40) - | (from[at + 4] << 32) - | (from[at + 3] << 24) - | (from[at + 2] << 16) - | (from[at + 1] << 8) - | from[at] + (from.get(at + 7) << 56) + | (from.get(at + 6) << 48) + | (from.get(at + 5) << 40) + | (from.get(at + 4) << 32) + | (from.get(at + 3) << 24) + | (from.get(at + 2) << 16) + | (from.get(at + 1) << 8) + | from.get(at) } diff --git a/libstd/src/std/env.inko b/std/src/std/env.inko similarity index 56% rename from libstd/src/std/env.inko rename to std/src/std/env.inko index f65029b3f..9148f45b3 100644 --- a/libstd/src/std/env.inko +++ b/std/src/std/env.inko @@ -20,8 +20,18 @@ # - https://github.com/rustsec/advisory-db/issues/926 import std::fs::path::Path import std::io::Error +import std::string::ToString -# Returns the value of an environment variable. +# The architecture of the CPU the code is compiled for. +let pub ARCH = _INKO_ARCH + +# The operating system the code is compiled for. +let pub OS = _INKO_OS + +# The ABI of the operating system the code is compiled for. +let pub ABI = _INKO_ABI + +# Returns an optional value of an environment variable. # # # Examples # @@ -29,11 +39,12 @@ import std::io::Error # # import std::env # -# env.get('HOME') # => '/home/alice' -fn pub get(name: String) -> Option[String] { - let val = _INKO.env_get(name) - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } +# env.opt('HOME') # => Option.Some('/home/alice') +fn pub opt(name: String) -> Option[String] { + match _INKO.env_get(name) { + case { @tag = 0, @value = val } -> Option.Some(val as String) + case _ -> Option.None + } } # Returns all defined environment variables and their values. @@ -44,12 +55,14 @@ fn pub get(name: String) -> Option[String] { # # import std::env # -# env.variables # => Map { 'HOME': '/home/alice', ... } +# env.variables.opt('HOME') # => Option.Some('/home/alice') fn pub variables -> Map[String, String] { - _INKO.env_variables.into_iter.reduce(Map.new) fn (map, name) { - match get(name) { + let vars = _INKO.env_variables as Array[String] + + vars.into_iter.reduce(Map.new) fn (map, name) { + match opt(name) { case Some(v) -> { - map[name] = v + map.set(name, v) map } case None -> map @@ -65,11 +78,12 @@ fn pub variables -> Map[String, String] { # # import std::env # -# env.home_directory # => '/home/alice' +# env.home_directory # => Option.Some('/home/alice') fn pub home_directory -> Option[Path] { - let dir = _INKO.env_home_directory - - if _INKO.is_undefined(dir) { Option.None } else { Option.Some(dir.into_path) } + match _INKO.env_home_directory { + case { @tag = 0, @value = val } -> Option.Some(Path.new(val as String)) + case _ -> Option.None + } } # Returns the path to the temporary directory. @@ -87,7 +101,7 @@ fn pub temporary_directory -> Path { # Returns the current working directory. # -# This method will throw if the directory could not be obtained. Possible +# This method will return an `Error` if we failed to get the directory. Possible # causes for this could be: # # 1. The directory no longer exists. @@ -99,30 +113,27 @@ fn pub temporary_directory -> Path { # # import std::env # -# try! env.working_directory # => '/home/alice/example' -fn pub working_directory !! Error -> Path { - let path = - try _INKO.env_get_working_directory else (e) throw Error.from_int(e) - - path.into_path +# env.working_directory # => Result.Ok('/home/alice/example') +fn pub working_directory -> Result[Path, Error] { + match _INKO.env_get_working_directory { + case { @tag = 0, @value = val } -> Result.Ok(Path.new(val as String)) + case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + } } # Changes the current working directory to the given directory. # -# This method throws if the directory couldn't be changed. -# # # Examples # # Changing the current working directory: # # import std::env # -# try! env.working_directory = '..' -fn pub working_directory=(directory: String) !! Error { - try { - _INKO.env_set_working_directory(directory) - } else (e) { - throw Error.from_int(e) +# env.working_directory = '..' +fn pub working_directory=(directory: ref ToString) -> Result[Nil, Error] { + match _INKO.env_set_working_directory(directory.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) } } @@ -136,15 +147,16 @@ fn pub working_directory=(directory: String) !! Error { # # Assuming this program is executed using `inko foo.inko first second`: # env.arguments # => ['first', 'second'] fn pub arguments -> Array[String] { - _INKO.env_arguments + _INKO.env_arguments as Array[String] } # Returns the path to the current executable. # # If the program is executed through a symbolic link, the returned path may # point to the symbolic link instead of the executable the link points to. -fn pub executable !! Error -> Path { - let path = try _INKO.env_executable else (e) throw Error.from_int(e) - - path.into_path +fn pub executable -> Result[Path, Error] { + match _INKO.env_executable { + case { @tag = 0, @value = val } -> Result.Ok(Path.new(val as String)) + case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + } } diff --git a/libstd/src/std/float.inko b/std/src/std/float.inko similarity index 83% rename from libstd/src/std/float.inko rename to std/src/std/float.inko index 982dbec43..7dcd465bf 100644 --- a/libstd/src/std/float.inko +++ b/std/src/std/float.inko @@ -60,10 +60,11 @@ class builtin Float { # Parsing a `Float` with an exponent: # # Float.parse('1.2e1') # => Option.Some(12.0) - fn pub static parse(string: String) -> Option[Self] { - let res = _INKO.string_to_float(string, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static parse(string: String) -> Option[Float] { + match _INKO.string_to_float(string, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Float) + case _ -> Option.None + } } # Returns the absolute value of `self`. @@ -72,7 +73,7 @@ class builtin Float { # # 42.0.absolute # => 42 # -42.0.absolute # => 42 - fn pub absolute -> Self { + fn pub absolute -> Float { Float.from_bits(to_bits & MAX) } @@ -82,7 +83,7 @@ class builtin Float { # # 42.0.opposite # => -42 # -42.0.opposite # => 42 - fn pub opposite -> Self { + fn pub opposite -> Float { Float.from_bits(to_bits ^ MIN) } @@ -201,10 +202,6 @@ class builtin Float { } impl ToInt for Float { - # Converts a `Float` to an `Int`. - # - # The `Float` will be rounded towards zero. Converting a `NAN`, `INFINITY`, - # or `NEGATIVE_INFINITY` to an `Int` will result in a panic. fn pub to_int -> Int { _INKO.float_to_int(self) } @@ -216,44 +213,44 @@ impl ToFloat for Float { } } -impl Clone for Float { - fn pub clone -> Self { - _INKO.float_clone(self) +impl Clone[Float] for Float { + fn pub clone -> Float { + self } } -impl Add[Float] for Float { - fn pub +(other: ref Self) -> Self { +impl Add[Float, Float] for Float { + fn pub +(other: ref Float) -> Float { _INKO.float_add(self, other) } } -impl Subtract[Float] for Float { - fn pub -(other: ref Self) -> Self { +impl Subtract[Float, Float] for Float { + fn pub -(other: ref Float) -> Float { _INKO.float_sub(self, other) } } -impl Divide for Float { - fn pub /(other: ref Self) -> Self { +impl Divide[Float, Float] for Float { + fn pub /(other: ref Float) -> Float { _INKO.float_div(self, other) } } -impl Multiply for Float { - fn pub *(other: ref Self) -> Self { +impl Multiply[Float, Float] for Float { + fn pub *(other: ref Float) -> Float { _INKO.float_mul(self, other) } } -impl Modulo for Float { - fn pub %(other: ref Self) -> Self { +impl Modulo[Float, Float] for Float { + fn pub %(other: ref Float) -> Float { _INKO.float_mod(self, other) } } -impl Compare for Float { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Float] for Float { + fn pub cmp(other: ref Float) -> Ordering { if self > other { Ordering.Greater } else if self < other { @@ -263,25 +260,25 @@ impl Compare for Float { } } - fn pub <(other: ref Self) -> Bool { + fn pub <(other: ref Float) -> Bool { _INKO.float_lt(self, other) } - fn pub <=(other: ref Self) -> Bool { + fn pub <=(other: ref Float) -> Bool { _INKO.float_le(self, other) } - fn pub >(other: ref Self) -> Bool { + fn pub >(other: ref Float) -> Bool { _INKO.float_gt(self, other) } - fn pub >=(other: ref Self) -> Bool { + fn pub >=(other: ref Float) -> Bool { _INKO.float_ge(self, other) } } -impl Equal for Float { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Float] for Float { + fn pub ==(other: ref Float) -> Bool { _INKO.float_eq(self, other) } } diff --git a/libstd/src/std/fmt.inko b/std/src/std/fmt.inko similarity index 95% rename from libstd/src/std/fmt.inko rename to std/src/std/fmt.inko index 53d22eb36..29f10dce7 100644 --- a/libstd/src/std/fmt.inko +++ b/std/src/std/fmt.inko @@ -41,8 +41,8 @@ class pub DefaultFormatter { let @buffer: StringBuffer let @nesting: Int - fn pub static new -> Self { - Self { @buffer = StringBuffer.new, @nesting = 0 } + fn pub static new -> DefaultFormatter { + DefaultFormatter { @buffer = StringBuffer.new, @nesting = 0 } } } diff --git a/std/src/std/fs/dir.inko b/std/src/std/fs/dir.inko new file mode 100644 index 000000000..c35bedd7a --- /dev/null +++ b/std/src/std/fs/dir.inko @@ -0,0 +1,127 @@ +# Manipulating directories on a filesystem. +import std::fs::path::Path +import std::io::Error +import std::string::ToString + +# Creates a new empty directory at the given path. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to create the directory. +# 2. The directory already exists. +# +# # Examples +# +# Creating a directory: +# +# import std::fs::dir +# +# dir.create('/tmp/test').unwrap +fn pub create(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_create(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Creates a new empty directory at the given path, while also creating any +# intermediate directories. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to create the directory. +# +# # Examples +# +# Creating a directory: +# +# import std::fs::dir +# +# dir.create_all('/tmp/foo/bar/test').unwrap +fn pub create_all(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_create_recursive(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Removes the directory at the given path. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to remove the directory. +# 2. The directory is not empty. +# 3. The directory does not exist. +# +# # Examples +# +# Removing a directory: +# +# import std::fs::dir +# +# dir.create('/tmp/test').unwrap +# dir.remove('/tmp/test').unwrap +fn pub remove(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_remove(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Removes the directory and its contents at the given path. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to remove the directory. +# 2. The directory does not exist. +# +# # Examples +# +# Removing a directory: +# +# import std::fs::dir +# +# dir.create_all('/tmp/foo/bar').unwrap +# dir.remove_all('/tmp/foo').unwrap +fn pub remove_all(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_remove_recursive(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Returns an `Array` containing the paths to the contents of the directory. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to read the contents of the +# directory. +# 2. The path does not point to a valid directory. +# +# # Examples +# +# Listing the contents of a directory: +# +# import std::fs::dir +# +# let paths = dir.list('.').unwrap +# +# paths[0].to_string # => 'README.md' +fn pub list(path: ref ToString) -> Result[Array[Path], Error] { + match _INKO.directory_list(path.to_string) { + case { @tag = 0, @value = v } -> Result.Ok( + (v as Array[String]).into_iter.map fn (path) { Path.new(path) }.to_array + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} diff --git a/std/src/std/fs/file.inko b/std/src/std/fs/file.inko new file mode 100644 index 000000000..3e3a18cd3 --- /dev/null +++ b/std/src/std/fs/file.inko @@ -0,0 +1,308 @@ +# Types and methods for manipulating files on a filesystem. +# +# Rather than using a single "File" type for all different file modes, Inko uses +# three separate file types: +# +# - `ReadOnlyFile`: read-only file operations +# - `WriteOnlyFile`: write-only file operations +# - `ReadWriteFile`: read-write file operations +# +# Using different types per file mode allows for a type-safe file API. +# +# Files are automatically closed when they are dropped. Any errors that may +# occur when closing a file are ignored. +import std::drop::Drop +import std::fs::path::(IntoPath, Path) +import std::io::(Error, Read, Seek, Size, Write) +import std::string::ToString + +let FILE_READ_ONLY = 0 +let FILE_WRITE_ONLY = 1 +let FILE_APPEND_ONLY = 2 +let FILE_READ_WRITE = 3 +let FILE_READ_APPEND = 4 + +# Removes the file for the given file path. +# +# # Examples +# +# Removing a file: +# +# import std::fs::file::(self, WriteOnlyFile) +# +# let handle = WriteOnlyFile.new('/tmp/test.txt').unwrap +# +# handle.write('hello').unwrap +# file.remove('/tmp/test.txt').unwrap +fn pub remove(path: ref ToString) -> Result[Nil, Error] { + match _INKO.file_remove(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Copies a file from the source destination to the target destination, +# returning the number of copied bytes. +# +# # Examples +# +# Copying a file: +# +# import std::fs::file::(self, WriteOnlyFile) +# +# let handle = WriteOnlyFile.new('/tmp/test.txt').unwrap +# +# handle.write('hello').unwrap +# file.copy(from: '/tmp/test.txt', to: '/tmp/test2.txt').unwrap +fn pub copy(from: ref ToString, to: ref ToString) -> Result[Int, Error] { + match _INKO.file_copy(from.to_string, to.to_string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# A file that can only be used for reads. +class pub ReadOnlyFile { + # The path of the file. + let pub @path: Path + + # The internal file descriptor. + let @fd: Any + + # Returns a new `ReadOnlyFile`. + # + # # Examples + # + # Opening a file in read-only mode: + # + # import std::fs::file::ReadOnlyFile + # + # let handle = ReadOnlyFile.new('/dev/null').unwrap + fn pub static new(path: IntoPath) -> Result[ReadOnlyFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_READ_ONLY) { + case { @tag = 0, @value = v } -> Result.Ok( + ReadOnlyFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Drop for ReadOnlyFile { + fn mut drop { + _INKO.file_drop(@fd) + } +} + +impl Read for ReadOnlyFile { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.file_read(@fd, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Seek for ReadOnlyFile { + fn pub mut seek(position: Int) -> Result[Int, Error] { + match _INKO.file_seek(@fd, position) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Size for ReadOnlyFile { + fn pub size -> Result[Int, Error] { + match _INKO.file_size(@path.to_string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +# A file that can only be used for writes. +class pub WriteOnlyFile { + # The path of the file. + let pub @path: Path + + # The internal file descriptor. + let @fd: Any + + # Opens a file in write-only mode. + # + # # Examples + # + # import std::fs::file::WriteOnlyFile + # + # let file = WriteOnlyFile.new('/dev/null').unwrap + fn pub static new(path: IntoPath) -> Result[WriteOnlyFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_WRITE_ONLY) { + case { @tag = 0, @value = v } -> Result.Ok( + WriteOnlyFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + # Opens a file in append-only mode. + # + # # Examples + # + # import std::fs::file::WriteOnlyFile + # + # let file = WriteOnlyFile.append('/dev/null').unwrap + fn pub static append(path: IntoPath) -> Result[WriteOnlyFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_APPEND_ONLY) { + case { @tag = 0, @value = v } -> Result.Ok( + WriteOnlyFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Drop for WriteOnlyFile { + fn mut drop { + _INKO.file_drop(@fd) + } +} + +impl Write for WriteOnlyFile { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.file_write_bytes(@fd, bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.file_write_string(@fd, string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Error] { + match _INKO.file_flush(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Seek for WriteOnlyFile { + fn pub mut seek(position: Int) -> Result[Int, Error] { + match _INKO.file_seek(@fd, position) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +# A file that can be used for both reads and writes. +class pub ReadWriteFile { + # The path of the file. + let pub @path: Path + + # The internal file descriptor. + let @fd: Any + + # Opens a file for both reading and writing: + # + # # Examples + # + # import std::fs::file::ReadWriteFile + # + # let handle = ReadWriteFile.new('/dev/null').unwrap + fn pub static new(path: IntoPath) -> Result[ReadWriteFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_READ_WRITE) { + case { @tag = 0, @value = v } -> Result.Ok( + ReadWriteFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + # Opens a file for both reading and appending: + # + # # Examples + # + # import std::fs::file::ReadWriteFile + # + # let handle = ReadWriteFile.append('/dev/null').unwrap + fn pub static append(path: IntoPath) -> Result[ReadWriteFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_READ_APPEND) { + case { @tag = 0, @value = v } -> Result.Ok( + ReadWriteFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Drop for ReadWriteFile { + fn mut drop { + _INKO.file_drop(@fd) + } +} + +impl Read for ReadWriteFile { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.file_read(@fd, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Write for ReadWriteFile { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.file_write_bytes(@fd, bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.file_write_string(@fd, string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Error] { + match _INKO.file_flush(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Seek for ReadWriteFile { + fn pub mut seek(position: Int) -> Result[Int, Error] { + match _INKO.file_seek(@fd, position) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Size for ReadWriteFile { + fn pub size -> Result[Int, Error] { + match _INKO.file_size(@path.to_string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} diff --git a/libstd/src/std/fs/path.inko b/std/src/std/fs/path.inko similarity index 69% rename from libstd/src/std/fs/path.inko rename to std/src/std/fs/path.inko index ecdd361c5..cacab3a32 100644 --- a/libstd/src/std/fs/path.inko +++ b/std/src/std/fs/path.inko @@ -7,32 +7,12 @@ import std::string::(ToString, IntoString) import std::sys import std::time::DateTime -let FSLASH = 47 -let BSLASH = 92 -let COLON = 58 - -# Returns the main path separator for the current platform. -fn pub separator -> String { - if sys.windows? { '\\' } else { '/' } -} - -# Returns `True` if the given `String` starts with a Windows drive name, such as -# C:/. -fn starts_with_windows_drive_name?(path: String) -> Bool { - if path.size < 3 { return false } - - let start = path.byte(0) - - # Windows drive letters are limited to the range a-z and A-Z. - (start >= 97 and start <= 122) - or (start >= 65 and start <= 90) - and path.byte(1) == COLON - and path_separator?(path.byte(2)) -} +# The character used to separate components in a file path. +let pub SEPARATOR = '/' # Returns `True` if the byte is a valid path separator byte. fn path_separator?(byte: Int) -> Bool { - if sys.windows? { byte == FSLASH or byte == BSLASH } else { byte == FSLASH } + byte == 47 } # Returns the number of bytes leading up to the last path separator. @@ -41,17 +21,11 @@ fn path_separator?(byte: Int) -> Bool { fn bytes_before_last_separator(path: String) -> Int { if path.empty? { return -1 } - let windows_drive_path = - sys.windows? and starts_with_windows_drive_name?(path) - - # If the path starts with a Windows drive name (e.g. "C:\foo") we don't want - # to trim off the \ in C:\, as it's part of the drive name. - let trim_until = if windows_drive_path { 2 } else { 0 } let mut index = path.size - 1 # Trailing separators should be ignored, so we'll skip over them until the # first non-separator byte. - while index > trim_until and path_separator?(path.byte(index)) { + while index > 0 and path_separator?(path.byte(index)) { index -= 1 } @@ -63,10 +37,6 @@ fn bytes_before_last_separator(path: String) -> Int { if path_separator?(byte) { in_separator = true } else if in_separator { - # We have reached the ":" in a drive name such as "C:\". In this case we - # want to include the "\" since it's part of the drive name. - if windows_drive_path and index == 1 { return 3 } - return index + 1 } @@ -79,7 +49,6 @@ fn bytes_before_last_separator(path: String) -> Int { # Returns `True` if the given file path is an absolute path. fn absolute_path?(path: String) -> Bool { path_separator?(path.byte(0)) - or (sys.windows? and starts_with_windows_drive_name?(path)) } # A path to a file or directory. @@ -105,8 +74,8 @@ class pub Path { # The raw file path. let @path: String - fn pub static new(path: String) -> Self { - Self { @path = path } + fn pub static new(path: String) -> Path { + Path { @path = path } } # Returns `True` if the path points to a file. @@ -134,12 +103,14 @@ class pub Path { # # let path = Path.new('README.md') # - # try! path.created_at # => DateTime { ... } - fn pub created_at !! Error -> DateTime { - let time = - try _INKO.path_created_at(@path) else (err) throw Error.from_int(err) - - DateTime.from_timestamp(time, _INKO.time_system_offset) + # path.created_at.unwrap # => DateTime { ... } + fn pub created_at -> Result[DateTime, Error] { + match _INKO.path_created_at(@path) { + case { @tag = 0, @value = val } -> Result.Ok( + DateTime.from_timestamp(val as Float, _INKO.time_system_offset) + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns the modification time of `self`. @@ -152,12 +123,14 @@ class pub Path { # # let path = Path.new('README.md') # - # try! path.modified_at # => DateTime { ... } - fn pub modified_at !! Error -> DateTime { - let time = - try _INKO.path_modified_at(@path) else (err) throw Error.from_int(err) - - DateTime.from_timestamp(time, _INKO.time_system_offset) + # path.modified_at.unwrap # => DateTime { ... } + fn pub modified_at -> Result[DateTime, Error] { + match _INKO.path_modified_at(@path) { + case { @tag = 0, @value = val } -> Result.Ok( + DateTime.from_timestamp(val as Float, _INKO.time_system_offset) + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns the access time of `self`. @@ -170,12 +143,14 @@ class pub Path { # # let path = Path.new('README.md') # - # try! path.accessed_at # => DateTime { ... } - fn pub accessed_at !! Error -> DateTime { - let time = - try _INKO.path_accessed_at(@path) else (err) throw Error.from_int(err) - - DateTime.from_timestamp(time, _INKO.time_system_offset) + # path.accessed_at.unwrap # => DateTime { ... } + fn pub accessed_at -> Result[DateTime, Error] { + match _INKO.path_accessed_at(@path) { + case { @tag = 0, @value = val } -> Result.Ok( + DateTime.from_timestamp(val as Float, _INKO.time_system_offset) + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns `True` if this `Path` is an absolute path. @@ -229,7 +204,7 @@ class pub Path { } else if path_separator?(@path.byte(@path.size - 1)) { "{@path}{path_str}" } else { - "{@path}{separator}{path_str}" + "{@path}{SEPARATOR}{path_str}" } Path.new(new_path) @@ -260,6 +235,44 @@ class pub Path { Path.new(@path.slice_bytes(start: 0, length: length)) } + + # Returns the last component in `self`. + # + # If `self` is a file, then the tail will be the file name including its + # extension. If `self` is a directory, the directory name is returned. + # + # # Examples + # + # import std::fs::path::Path + # + # Path.new('foo/bar/baz.txt') # => 'baz.txt' + fn pub tail -> String { + let len = bytes_before_last_separator(@path) + + if len < 0 { return @path } + + @path.slice_bytes(start: len + 1, length: @path.size - len) + } + + # Returns the canonical, absolute version of `self`. + # + # # Errors + # + # This method may return an `Error` for cases such as when `self` doesn't + # exist, or when a component that isn't the last component is _not_ a + # directory. + # + # # Examples + # + # import std::fs::path::Path + # + # Path.new('/foo/../bar').expand.unwrap # => Path.new('/bar') + fn pub expand -> Result[Path, Error] { + match _INKO.path_expand(@path) { + case { @tag = 0, @value = v } -> Result.Ok(Path.new(v as String)) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } } # A type from which a new `Path` can be created. @@ -277,7 +290,7 @@ trait pub IntoPath { fn pub move into_path -> Path } -impl Equal for Path { +impl Equal[Path] for Path { # Returns `True` if `self` is equal to the given `Path`. # # # Examples @@ -290,7 +303,7 @@ impl Equal for Path { # let path2 = Path.new('foo') # # path1 == path2 # => True - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Path) -> Bool { @path == other.to_string } } @@ -329,14 +342,17 @@ impl Size for Path { # # let path = Path.new('/dev/null') # - # try! path.size # => 0 - fn pub size !! Error -> Int { - try _INKO.file_size(@path) else (err) throw Error.from_int(err) + # path.size.unwrap # => 0 + fn pub size -> Result[Int, Error] { + match _INKO.file_size(@path) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } -impl Clone for Path { - fn pub clone -> Self { +impl Clone[Path] for Path { + fn pub clone -> Path { Path.new(@path) } } diff --git a/libstd/src/std/hash.inko b/std/src/std/hash.inko similarity index 94% rename from libstd/src/std/hash.inko rename to std/src/std/hash.inko index 1717b26e1..8784977ab 100644 --- a/libstd/src/std/hash.inko +++ b/std/src/std/hash.inko @@ -9,7 +9,7 @@ trait pub Hasher { fn pub mut write(value: Int) # Returns the hash for the values written so far. - fn pub hash -> Int + fn pub move finish -> Int } # A value that can be hashed. diff --git a/std/src/std/hash/siphash.inko b/std/src/std/hash/siphash.inko new file mode 100644 index 000000000..4fbc79a25 --- /dev/null +++ b/std/src/std/hash/siphash.inko @@ -0,0 +1,99 @@ +# The SipHash hashing algorithm. +# +# SipHash isn't cryptographically secure, instead it's intended for e.g. hashing +# of objects as part of the `Map` type. +import std::crypto::math::(rotate_left_u64) +import std::endian::little::(read_i64) +import std::hash::Hasher + +# An implementation of the SipHash 1-3 algorithm. +# +# # Examples +# +# import std::hash::siphash::SipHasher13 +# import std::rand::Random +# +# let rng = Random.new +# let hasher = SipHasher13.new(key0: rng.int, key1: rng.int) +# +# hasher.write(42) +# hasher.finish +class pub SipHasher13 { + let @length: Int + let @m_index: Int + let @m: Int + let @v0: Int + let @v1: Int + let @v2: Int + let @v3: Int + + # Returns a new hasher using two default keys. + fn pub static default -> SipHasher13 { + new(_INKO.hash_key0, _INKO.hash_key1) + } + + # Returns a new hasher using the two keys. + # + # Both keys _should_ be randomly generated. The type `std::rand::Random` can + # be used to generate these keys. + fn pub static new(key0: Int, key1: Int) -> SipHasher13 { + SipHasher13 { + @length = 0, + @m_index = 0, + @m = 0, + @v0 = 0x736F6D6570736575 ^ key0, + @v1 = 0x646F72616E646F6D ^ key1, + @v2 = 0x6C7967656E657261 ^ key0, + @v3 = 0x7465646279746573 ^ key1, + } + } + + fn mut round { + @v0 = @v0.wrapping_add(@v1) + @v2 = @v2.wrapping_add(@v3) + @v1 = rotate_left_u64(@v1, 13) + @v3 = rotate_left_u64(@v3, 16) + + @v1 ^= @v0 + @v3 ^= @v2 + @v0 = rotate_left_u64(@v0, 32) + + @v2 = @v2.wrapping_add(@v1) + @v0 = @v0.wrapping_add(@v3) + @v1 = rotate_left_u64(@v1, 17) + @v3 = rotate_left_u64(@v3, 21) + + @v1 ^= @v2 + @v3 ^= @v0 + @v2 = rotate_left_u64(@v2, 32) + } +} + +impl Hasher for SipHasher13 { + fn pub mut write(value: Int) { + @length += 1 + @m |= (value & 0xFF) << ((@m_index := @m_index + 1) * 8) + + if @m_index < 8 { return } + + @v3 ^= @m + round + @v0 ^= @m + @m_index = 0 + @m = 0 + } + + fn pub move finish -> Int { + let len = @length + + while @m_index < 7 { write(0) } + + write(len) + @v2 ^= 0xFF + round + round + round + + @v0 ^ @v1 ^ @v2 ^ @v3 + } +} diff --git a/libstd/src/std/init.inko b/std/src/std/init.inko similarity index 88% rename from libstd/src/std/init.inko rename to std/src/std/init.inko index af2bdcbc5..800bf2e1a 100644 --- a/libstd/src/std/init.inko +++ b/std/src/std/init.inko @@ -13,3 +13,5 @@ import std::option import std::process import std::string import std::tuple +import std::channel +import std::result diff --git a/libstd/src/std/int.inko b/std/src/std/int.inko similarity index 76% rename from libstd/src/std/int.inko rename to std/src/std/int.inko index bee013918..b72fc699b 100644 --- a/libstd/src/std/int.inko +++ b/std/src/std/int.inko @@ -41,10 +41,11 @@ class builtin Int { # # Int.from_base2('11') # => Option.Some(3) # Int.from_base2('ff') # => Option.None - fn pub static from_base2(string: String) -> Option[Self] { - let res = _INKO.string_to_int(string, 2, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static from_base2(string: String) -> Option[Int] { + match _INKO.string_to_int(string, 2, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Int) + case _ -> Option.None + } } # Parses an `Int` from a `String`, using base 10. @@ -56,10 +57,11 @@ class builtin Int { # # Int.from_base10('12') # => Option.Some(12) # Int.from_base10('ff') # => Option.None - fn pub static from_base10(string: String) -> Option[Self] { - let res = _INKO.string_to_int(string, 10, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static from_base10(string: String) -> Option[Int] { + match _INKO.string_to_int(string, 10, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Int) + case _ -> Option.None + } } # Parses an `Int` from a `String`, using base 16. @@ -75,10 +77,11 @@ class builtin Int { # # Int.from_base16('ef') # => Option.Some(239) # Int.from_base16('zz') # => Option.None - fn pub static from_base16(string: String) -> Option[Self] { - let res = _INKO.string_to_int(string, 16, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static from_base16(string: String) -> Option[Int] { + match _INKO.string_to_int(string, 16, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Int) + case _ -> Option.None + } } # Formats `self` as a `String` using base 2. @@ -124,7 +127,7 @@ class builtin Int { # # -4.absolute # => 4 # 4.absolute # => 4 - fn pub absolute -> Self { + fn pub absolute -> Int { if self < 0 { 0 - self } else { clone } } @@ -134,7 +137,7 @@ class builtin Int { # # -42.opposite # => 42 # 42.opposite # => -42 - fn pub opposite -> Self { + fn pub opposite -> Int { 0 - self } @@ -300,8 +303,8 @@ impl ToFloat for Int { } } -impl Compare for Int { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Int] for Int { + fn pub cmp(other: ref Int) -> Ordering { if self > other { Ordering.Greater } else if self < other { @@ -311,32 +314,32 @@ impl Compare for Int { } } - fn pub <(other: ref Self) -> Bool { + fn pub <(other: ref Int) -> Bool { _INKO.int_lt(self, other) } - fn pub <=(other: ref Self) -> Bool { + fn pub <=(other: ref Int) -> Bool { _INKO.int_le(self, other) } - fn pub >(other: ref Self) -> Bool { + fn pub >(other: ref Int) -> Bool { _INKO.int_gt(self, other) } - fn pub >=(other: ref Self) -> Bool { + fn pub >=(other: ref Int) -> Bool { _INKO.int_ge(self, other) } } -impl Equal for Int { +impl Equal[Int] for Int { fn pub ==(other: ref Int) -> Bool { _INKO.int_eq(self, other) } } -impl Clone for Int { - fn pub clone -> Self { - _INKO.int_clone(self) +impl Clone[Int] for Int { + fn pub clone -> Int { + self } } @@ -346,74 +349,82 @@ impl ToString for Int { } } -impl Add[Int] for Int { - fn pub +(other: ref Self) -> Self { +impl Add[Int, Int] for Int { + fn pub +(other: ref Int) -> Int { _INKO.int_add(self, other) } } -impl Subtract[Int] for Int { - fn pub -(other: ref Self) -> Self { +impl Subtract[Int, Int] for Int { + fn pub -(other: ref Int) -> Int { _INKO.int_sub(self, other) } } -impl Divide for Int { - fn pub /(other: ref Self) -> Self { - _INKO.int_div(self, other) +impl Divide[Int, Int] for Int { + fn pub /(other: ref Int) -> Int { + # This implements floored division, rather than rounding towards zero. This + # makes division work more natural when using negative numbers. + # + # This code is based on the upcoming div_floor() implementation of the Rust + # standard library: https://github.com/rust-lang/rust/pull/88582. + let d = _INKO.int_div(self, other) + let r = _INKO.int_rem(self, other) + + if (r > 0 and other < 0) or (r < 0 and other > 0) { d - 1 } else { d } } } -impl Multiply for Int { - fn pub *(other: ref Self) -> Self { +impl Multiply[Int, Int] for Int { + fn pub *(other: ref Int) -> Int { _INKO.int_mul(self, other) } } -impl Modulo for Int { - fn pub %(other: ref Self) -> Self { - _INKO.int_mod(self, other) +impl Modulo[Int, Int] for Int { + fn pub %(other: ref Int) -> Int { + _INKO.int_rem(_INKO.int_add(_INKO.int_rem(self, other), other), other) } } -impl BitAnd for Int { - fn pub &(other: ref Self) -> Self { +impl BitAnd[Int, Int] for Int { + fn pub &(other: ref Int) -> Int { _INKO.int_bit_and(self, other) } } -impl BitOr for Int { - fn pub |(other: ref Self) -> Self { +impl BitOr[Int, Int] for Int { + fn pub |(other: ref Int) -> Int { _INKO.int_bit_or(self, other) } } -impl BitXor for Int { - fn pub ^(other: ref Self) -> Self { +impl BitXor[Int, Int] for Int { + fn pub ^(other: ref Int) -> Int { _INKO.int_bit_xor(self, other) } } -impl ShiftLeft for Int { - fn pub <<(other: ref Self) -> Self { +impl ShiftLeft[Int, Int] for Int { + fn pub <<(other: ref Int) -> Int { _INKO.int_shl(self, other) } } -impl ShiftRight for Int { - fn pub >>(other: ref Self) -> Self { +impl ShiftRight[Int, Int] for Int { + fn pub >>(other: ref Int) -> Int { _INKO.int_shr(self, other) } } -impl UnsignedShiftRight for Int { - fn pub >>>(other: ref Self) -> Self { +impl UnsignedShiftRight[Int, Int] for Int { + fn pub >>>(other: ref Int) -> Int { _INKO.int_unsigned_shr(self, other) } } -impl Power for Int { - fn pub **(other: ref Self) -> Self { +impl Power[Int, Int] for Int { + fn pub **(other: ref Int) -> Int { _INKO.int_pow(self, other) } } diff --git a/libstd/src/std/io.inko b/std/src/std/io.inko similarity index 91% rename from libstd/src/std/io.inko rename to std/src/std/io.inko index 122b969a3..748104730 100644 --- a/libstd/src/std/io.inko +++ b/std/src/std/io.inko @@ -36,7 +36,7 @@ class pub enum Error { case UnexpectedEof # Returns a new `Error` according to the given error code. - fn pub static from_int(code: Int) -> Self { + fn pub static from_int(code: Int) -> Error { match code { case 0 -> Other case 1 -> NotFound @@ -96,8 +96,8 @@ impl ToString for Error { } } -impl Equal for Error { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Error] for Error { + fn pub ==(other: ref Error) -> Bool { match self { case Other -> match other { case Other -> true @@ -199,7 +199,7 @@ impl Format for Error { # Trait for retrieving the size of an IO object. trait pub Size { - fn pub size !! Error -> Int + fn pub size -> Result[Int, Error] } # Trait for reading from a stream. @@ -210,7 +210,7 @@ trait pub Read { # # The `size` argument specifies how many bytes are to be read. The actual # number of bytes read may be less than this value. - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] # Reads all bytes from the stream into the `ByteArray`. # @@ -218,14 +218,14 @@ trait pub Read { # bytes and re-throws the error. # # The return value is the number of bytes read. - fn pub mut read_all(bytes: mut ByteArray) !! Error -> Int { + fn pub mut read_all(bytes: mut ByteArray) -> Result[Int, Error] { let mut total = 0 let mut read_size = INITIAL_READ_ALL_SIZE loop { let bytes_read = try read(into: bytes, size: read_size) - if bytes_read == 0 { return total } + if bytes_read == 0 { return Result.Ok(total) } total += bytes_read @@ -240,18 +240,18 @@ trait pub Read { trait pub Write { # Writes an `Array` of bytes to the stream, returning the number of bytes # written. - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] # Writes a `String` to the stream, returning the number of bytes written. - fn pub mut write_string(string: String) !! Error -> Int + fn pub mut write_string(string: String) -> Result[Int, Error] # Writes a `String` followed by a Unix newline to the stream. - fn pub mut print(string: String) !! Error -> Int { - try { write_string(string) } + try { write_string("\n") } + fn pub mut print(string: String) -> Result[Int, Error] { + Result.Ok((try write_string(string)) + (try write_string("\n"))) } # Flushes any pending writes. - fn pub mut flush !! Error + fn pub mut flush -> Result[Nil, Error] } # Trait for seeking to a given offset in a stream of bytes. @@ -260,5 +260,5 @@ trait pub Seek { # # If `position` is negative, seeking is performed in reverse order relative to # the end. - fn pub mut seek(position: Int) !! Error -> Int + fn pub mut seek(position: Int) -> Result[Int, Error] } diff --git a/libstd/src/std/iter.inko b/std/src/std/iter.inko similarity index 81% rename from libstd/src/std/iter.inko rename to std/src/std/iter.inko index d967e3d66..6b518ec5a 100644 --- a/libstd/src/std/iter.inko +++ b/std/src/std/iter.inko @@ -28,16 +28,12 @@ import std::string::(ToString, StringBuffer) let pub EOF = -1 # A generic iterator over a sequence of values of type `T`. -# -# The type parameter `T` is the type of values that is produced. The type -# parameter `E` is the error type that may be thrown. If an iterator doesn't -# throw, this parameter should be assigned to `Never`. -trait pub Iter[T, E] { +trait pub Iter[T] { # Returns the next value in the iterator. # # If a value is produced, it must be wrapped in a Some; otherwise a None is to # be returned. - fn pub mut next !! E -> Option[T] + fn pub mut next -> Option[T] # Calls the block for every value in `self`. # @@ -49,9 +45,9 @@ trait pub Iter[T, E] { # iter.each fn (num) { # num # => 10, 20, 30 # } - fn pub move each(block: fn (T)) !! E { + fn pub move each(block: fn (T)) { loop { - match try self.next { + match self.next { case Some(v) -> block.call(v) case _ -> return } @@ -70,8 +66,8 @@ trait pub Iter[T, E] { # index # => 0, 1, 2 # num # => 10, 20, 30 # } - fn pub move each_with_index(block: fn (Int, T)) !! E { - try with_index.each fn move (pair) { + fn pub move each_with_index(block: fn (Int, T)) { + with_index.each fn move (pair) { match pair { case (index, value) -> block.call(index, value) } @@ -86,7 +82,7 @@ trait pub Iter[T, E] { # # iter.next # => Option.Some((0, 10)) # iter.next # => Option.Some((1, 20)) - fn pub move with_index -> Iter[(Int, T), E] { + fn pub move with_index -> Iter[(Int, T)] { let mut index = 0 map fn move (val) { (index := index + 1, val) } @@ -102,9 +98,9 @@ trait pub Iter[T, E] { # let values = [1, 2, 3] # # values.iter.map fn (n) { n * 2 }.to_array # => [2, 4, 6] - fn pub move map[R](block: fn (T) -> R) -> Iter[R, E] { + fn pub move map[R](block: fn (T) -> R) -> Iter[R] { Enum.new fn move { - try { self.next }.map fn (v) { block.call(v) } + self.next.map fn (v) { block.call(v) } } } @@ -120,15 +116,40 @@ trait pub Iter[T, E] { # let numbers = [10, 20, 50, 80] # # numbers.iter.find fn (number) { number > 50 } # => 80 - fn pub mut find(block: fn (ref T) -> Bool) !! E -> Option[T] { + fn pub mut find(block: fn (ref T) -> Bool) -> Option[T] { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v) { return Option.Some(v) } case _ -> return Option.None } } } + # Returns an `Iter` that combines `find` with `map`. + # + # For each value in `self`, the supplied closure is called. If the closure + # returns a `Some`, the value is returned an iteration stops. + # + # # Examples + # + # let vals = [10, 20, 30] + # let val = vals.into_iter.find_map fn (v) { + # if v == 20 { Option.Some(v.to_string) } else { Option.None } + # } + # + # val # => Option.Some('20') + fn pub mut find_map[R](block: fn (T) -> Option[R]) -> Option[R] { + loop { + match self.next { + case Some(v) -> match block.call(v) { + case Some(r) -> return Option.Some(r) + case _ -> {} + } + case _ -> return Option.None + } + } + } + # Returns `True` if `self` contains any value for which the `block` argument # returned `True`. # @@ -139,9 +160,9 @@ trait pub Iter[T, E] { # Checking if an `Iter` contains a value: # # [10, 20, 30].iter.any? fn (value) { value >= 20 } # => True - fn pub mut any?(block: fn (T) -> Bool) !! E -> Bool { + fn pub mut any?(block: fn (T) -> Bool) -> Bool { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v) { return true } case _ -> return false } @@ -159,10 +180,10 @@ trait pub Iter[T, E] { # .iter # .select fn (value) { value > 10 } # .to_array # => [20, 30] - fn pub move select(block: fn (ref T) -> Bool) -> Iter[T, E] { + fn pub move select(block: fn (ref T) -> Bool) -> Iter[T] { Enum.new fn move { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v) { return Option.Some(v) } case _ -> return Option.None } @@ -183,10 +204,10 @@ trait pub Iter[T, E] { # iter.next # => Option.Some(10) # iter.next # => Option.Some(30) # iter.next # => Option.None - fn pub move select_map[R](block: fn (T) -> Option[R]) -> Iter[R, E] { + fn pub move select_map[R](block: fn (T) -> Option[R]) -> Iter[R] { Enum.new fn move { loop { - match try self.next { + match self.next { case Some(v) -> match block.call(v) { case Some(r) -> return Option.Some(r) case _ -> next @@ -212,8 +233,8 @@ trait pub Iter[T, E] { # # pair.0 # => [30, 40, 50] # pair.1 # => [10, 20] - fn pub move partition(block: fn (ref T) -> Bool) !! E -> (Array[T], Array[T]) { - try reduce(([], [])) fn move (acc, val) { + fn pub move partition(block: fn (ref T) -> Bool) -> (Array[T], Array[T]) { + reduce(([], [])) fn move (acc, val) { if block.call(ref val) { acc.0.push(val) } else { acc.1.push(val) } acc @@ -232,9 +253,9 @@ trait pub Iter[T, E] { # # [10, 20].iter.all? fn (value) { value.positive? } # => True # [-1, 20].iter.all? fn (value) { value.positive? } # => False - fn pub mut all?(block: fn (T) -> Bool) !! E -> Bool { + fn pub mut all?(block: fn (T) -> Bool) -> Bool { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v).false? { return false } case _ -> return true } @@ -264,10 +285,8 @@ trait pub Iter[T, E] { # let zip = a.iter.zip(b.iter) # # zip.next # => (10, 40) - fn pub move zip[U](other: Iter[U, E]) -> Iter[(T, U), E] { - Enum.new fn move { - try { self.next }.zip(try other.next) - } + fn pub move zip[U](other: Iter[U]) -> Iter[(T, U)] { + Enum.new fn move { self.next.zip(other.next) } } # Combines all values in the iterator into the specified accumulator. @@ -304,11 +323,11 @@ trait pub Iter[T, E] { # # For the last element the return value is `6`, so the return value of the # reduce method is also `6`. - fn pub move reduce[A](accumulator: A, block: fn (A, T) -> A) !! E -> A { + fn pub move reduce[A](accumulator: A, block: fn (A, T) -> A) -> A { let mut result = accumulator loop { - match try self.next { + match self.next { case Some(v) -> result = block.call(result, v) case _ -> return result } @@ -320,12 +339,12 @@ trait pub Iter[T, E] { # Each chunk is up to the amount specified by the `size` argument. If the # number of values can't be evenly divided, the last chunk may contain fewer # than `size` elements. - fn pub move chunks(size: Int) -> Iter[Array[T], E] { + fn pub move chunks(size: Int) -> Iter[Array[T]] { Enum.new fn move { let chunk = [] while chunk.length < size { - match try self.next { + match self.next { case Some(val) -> chunk.push(val) case _ -> break } @@ -344,8 +363,8 @@ trait pub Iter[T, E] { # Transforming an `Iter` back into an `Array`: # # [1, 2, 3].iter.to_array # => [1, 2, 3] - fn pub move to_array !! E -> Array[T] { - try reduce([]) fn (values, value) { + fn pub move to_array -> Array[T] { + reduce([]) fn (values, value) { values.push(value) values } @@ -358,15 +377,15 @@ trait pub Iter[T, E] { # # Examples # # [1, 2, 3].iter.count # => 3 - fn pub move count !! E -> Int { - try reduce(0) fn (count, _) { count + 1 } + fn pub move count -> Int { + reduce(0) fn (count, _) { count + 1 } } } # A type that can be moved into an iterator. -trait pub IntoIter[T, E] { +trait pub IntoIter[T] { # Moves `self` into an iterator. - fn pub move into_iter -> Iter[T, E] + fn pub move into_iter -> Iter[T] } # A type for easily creating iterators using blocks. @@ -379,7 +398,7 @@ trait pub IntoIter[T, E] { # # import std::iter::(Iter, Enum) # -# fn example(max: Int) -> Iter[Int, Never] { +# fn example(max: Int) -> Iter[Int] { # let mut index = 0 # # Enum.new fn move { @@ -397,12 +416,12 @@ trait pub IntoIter[T, E] { # nums.next # => Option.Some(0) # nums.next # => Option.Some(1) # } -class pub Enum[T, E] { - let @block: fn !! E -> Option[T] +class pub Enum[T] { + let @block: fn -> Option[T] # Returns a new iterator using the block. - fn pub static new(block: fn !! E -> Option[T]) -> Self { - Self { @block = block } + fn pub static new(block: fn -> Option[T]) -> Enum[T] { + Enum { @block = block } } # Returns an iterator that produces the given value once. @@ -415,10 +434,10 @@ class pub Enum[T, E] { # # vals.next # => Option.Some(42) # vals.next # => Option.None - fn pub static once(value: T) -> Iter[T, Never] { + fn pub static once(value: T) -> Enum[T] { let mut val = Option.Some(value) - Self { @block = fn move { val := Option.None } } + Enum { @block = fn move { val := Option.None } } } # Returns an iterator that iterates from zero up to (but not including) the @@ -441,23 +460,23 @@ class pub Enum[T, E] { # iter.next #=> Option.Some(10) # iter.next #=> Option.Some(20) # iter.next #=> Option.None - fn pub static indexed(limit: Int, block: fn (Int) !! E -> T) -> Self { + fn pub static indexed(limit: Int, block: fn (Int) -> T) -> Enum[T] { let mut index = 0 let wrapper = fn move { if index < limit { - Option.Some(try block.call(index := index + 1)) + Option.Some(block.call(index := index + 1)) } else { Option.None } } - Self { @block = wrapper } + Enum { @block = wrapper } } } -impl Iter[T, E] for Enum { - fn pub mut next !! E -> Option[T] { - try @block.call +impl Iter[T] for Enum { + fn pub mut next -> Option[T] { + @block.call } } @@ -473,7 +492,7 @@ impl Iter[T, E] for Enum { # easiest way of doing this is to have `Iter.next` reuse the implementation of # `Bytes.next_byte` like so: # -# impl Iter[Int, Never] for MyType { +# impl Iter[Int] for MyType { # fn pub mut next -> Option[Int] { # match next_byte { # case EOF -> Option.None @@ -482,19 +501,16 @@ impl Iter[T, E] for Enum { # } # } # -# impl Bytes[Never] for MyType { +# impl Bytes for MyType { # fn pub mut next_byte -> Int { # # ... # } # } -# -# The type parameter `E` specifies the error that `next` may throw. If a stream -# can't throw, this parameter should be assigned to `Never`. -trait pub Bytes[E]: Iter[Int, E] { +trait pub Bytes: Iter[Int] { # Returns the next byte in the iterator. # # If all input is consumed, this method must return `std::iter::EOF`. - fn pub mut next_byte !! E -> Int + fn pub mut next_byte -> Int } # Joins the values of an iterator together using a separator. @@ -506,8 +522,8 @@ trait pub Bytes[E]: Iter[Int, E] { # let vals = [10, 20, 30].into_iter # # iter.join(vals, ',') => '10,20,30' -fn pub join[T: ToString, E](iter: Iter[T, E], with: String) !! E -> String { - let result = try iter.reduce(StringBuffer.new) fn (buff, val) { +fn pub join[T: ToString](iter: Iter[T], with: String) -> String { + let result = iter.reduce(StringBuffer.new) fn (buff, val) { if buff.length > 0 { buff.push(with) } buff.push(val.to_string) diff --git a/libstd/src/std/json.inko b/std/src/std/json.inko similarity index 86% rename from libstd/src/std/json.inko rename to std/src/std/json.inko index 6daefc85b..52ac58263 100644 --- a/libstd/src/std/json.inko +++ b/std/src/std/json.inko @@ -12,7 +12,7 @@ # # import std::json # -# try! json.parse('[10]') # => Json.Array([Json.Int(10)]) +# json.parse('[10]').unwrap # => Json.Array([Json.Int(10)]) # # The parser enforces limits on the number of nested objects and the length of # strings. These limits can be adjusted by using the `Parser` type directly like @@ -24,8 +24,7 @@ # # parser.max_depth = 4 # parser.max_string_size = 128 -# -# try! parser.parse +# parser.parse # # # Generating JSON # @@ -162,6 +161,12 @@ class pub Error { let pub @offset: Int } +impl Equal[Error] for Error { + fn pub ==(other: ref Error) -> Bool { + @message == other.message and @line == other.line + } +} + impl Format for Error { fn pub fmt(formatter: mut Formatter) { formatter.write(to_string) @@ -251,7 +256,7 @@ impl Format for Json { } } -impl Equal for Json { +impl Equal[Json] for Json { fn pub ==(other: ref Json) -> Bool { match self { case Int(lhs) -> match other { @@ -322,8 +327,8 @@ class pub Parser { let pub @max_string_size: Int # Returns a new parser that will parse the given `String`. - fn pub static new(string: String) -> Self { - Self { + fn pub static new(string: String) -> Parser { + Parser { @string = string, @index = 0, @size = string.size, @@ -344,8 +349,8 @@ class pub Parser { # # let parser = Parser.new('[10, 20]') # - # try! parser.parse # => Json.Array([Json.Int(10), Json.Int(20)]) - fn pub move parse !! Error -> Json { + # parser.parse.unwrap # => Json.Array([Json.Int(10), Json.Int(20)]) + fn pub move parse -> Result[Json, Error] { let result = try value whitespace @@ -353,10 +358,10 @@ class pub Parser { # Only trailing whitespace is allowed. if current != EOF { throw unexpected(current) } - result + Result.Ok(result) } - fn mut value !! Error -> Json { + fn mut value -> Result[Json, Error] { if @depth >= @max_depth { throw error("Only up to {@max_depth} nested objects/arrays are allowed") } @@ -365,14 +370,14 @@ class pub Parser { loop { match current { - case MINUS -> return try number - case BRACKET_OPEN -> return try array - case CURLY_OPEN -> return try object - case LOWER_T -> return try self.true - case LOWER_F -> return try self.false - case LOWER_N -> return try null - case DQUOTE -> return try string - case byte if digit?(byte) -> return try number + case MINUS -> return number + case BRACKET_OPEN -> return array + case CURLY_OPEN -> return object + case LOWER_T -> return self.true + case LOWER_F -> return self.false + case LOWER_N -> return null + case DQUOTE -> return string + case byte if digit?(byte) -> return number # This is to take care of any random garbage that may be included in the # JSON document, including Unicode BOMs. This also saves us from having # to explicitly check for all the different BOMs. @@ -381,11 +386,11 @@ class pub Parser { } } - fn mut string !! Error -> Json { - Json.String(try string_value) + fn mut string -> Result[Json, Error] { + string_value.map fn (val) { Json.String(val) } } - fn mut string_value !! Error -> String { + fn mut string_value -> Result[String, Error] { advance let start = @index @@ -396,7 +401,7 @@ class pub Parser { # method of parsing that supports escape sequences. Using this as a # fallback instead of the default allows us to avoid unnecessary # allocations for many strings. - case BSLASH -> return try string_with_escape_sequence(start) + case BSLASH -> return string_with_escape_sequence(start) case DQUOTE -> break case EOF -> throw unexpected(current) case val if val >= 0x0 and val <= 0x001F -> throw invalid_control(val) @@ -416,16 +421,16 @@ class pub Parser { let string = slice_string(start) advance - string + Result.Ok(string) } - fn mut string_with_escape_sequence(started_at: Int) !! Error -> String { + fn mut string_with_escape_sequence(started_at: Int) -> Result[String, Error] { let buffer = @string.slice_bytes(started_at, @index - started_at).to_byte_array loop { match current { - case BSLASH -> match ESCAPE_TABLE[peek] { + case BSLASH -> match ESCAPE_TABLE.get(peek) { case -1 if peek == LOWER_U -> try escaped_unicode(buffer) case -1 -> { let start = @index @@ -457,17 +462,17 @@ class pub Parser { ) } - buffer.into_string + Result.Ok(buffer.into_string) } - fn mut escaped_unicode(buffer: mut ByteArray) !! Error { + fn mut escaped_unicode(buffer: mut ByteArray) -> Result[Nil, Error] { # Skip the "\u" @index += 2 let start = @index let high = try codepoint - if utf8.encode_scalar(high, buffer) > 0 { return } + if utf8.encode_scalar(high, buffer) > 0 { return Result.Ok(nil) } # At this point the codepoint is either straight up invalid (e.g. "\uZZZZ"), # or it's a UTF-16 surrogate. @@ -486,13 +491,13 @@ class pub Parser { let codepoint = utf8.codepoint_from_surrogates(high, low) # The encoding may fail for pairs such as "\uDFFF\uDFFF". - if utf8.encode_scalar(codepoint, buffer) > 0 { return } + if utf8.encode_scalar(codepoint, buffer) > 0 { return Result.Ok(nil) } } throw error("'{slice_string(start)}' is an invalid UTF-16 surrogate pair") } - fn mut codepoint !! Error -> Int { + fn mut codepoint -> Result[Int, Error] { let start = @index @index += 4 @@ -501,31 +506,30 @@ class pub Parser { throw error("Expected four hexadecimal digits, but we ran out of input") } - let codepoint = _INKO.string_to_int(@string, 16, start, @index) - - if _INKO.is_undefined(codepoint) { - throw error("'{slice_string(start)}' is an invalid Unicode codepoint") + match _INKO.string_to_int(@string, 16, start, @index) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case _ -> Result.Error( + error("'{slice_string(start)}' is an invalid Unicode codepoint") + ) } - - codepoint } - fn mut true !! Error -> Json { + fn mut true -> Result[Json, Error] { try identifier('true') - Json.Bool(true) + Result.Ok(Json.Bool(true)) } - fn mut false !! Error -> Json { + fn mut false -> Result[Json, Error] { try identifier('false') - Json.Bool(false) + Result.Ok(Json.Bool(false)) } - fn mut null !! Error -> Json { + fn mut null -> Result[Json, Error] { try identifier('null') - Json.Null + Result.Ok(Json.Null) } - fn mut array !! Error -> Json { + fn mut array -> Result[Json, Error] { advance let values = [] @@ -539,10 +543,10 @@ class pub Parser { @depth -= 1 advance - Json.Array(values) + Result.Ok(Json.Array(values)) } - fn mut object !! Error -> Json { + fn mut object -> Result[Json, Error] { advance whitespace @@ -561,15 +565,15 @@ class pub Parser { try separator(CURLY_CLOSE) - map[key] = val + map.set(key, val) } advance @depth -= 1 - Json.Object(map) + Result.Ok(Json.Object(map)) } - fn mut number !! Error -> Json { + fn mut number -> Result[Json, Error] { let start = @index if current == MINUS { @@ -603,27 +607,31 @@ class pub Parser { # number parser. As part of parsing the JSON number we already validate # it. This means we can bypass `Int.from_base10` (and `Float.parse` # below), and instead use the underlying runtime functions. - let res = _INKO.string_to_int(@string, 10, start, @index) - - # If the number is too big to fit in an integer, we'll promote the - # number to a float. - if _INKO.is_undefined(res).false? { return Json.Int(res) } + match _INKO.string_to_int(@string, 10, start, @index) { + # If the number is too big to fit in an integer, we'll promote the + # number to a float. + case { @tag = 0, @value = v } -> return Result.Ok(Json.Int(v as Int)) + case _ -> {} + } } } # At this point we've already validated the input format, and it's # compatible with the underlying float parser, so no extra checks are # needed. - Json.Float(_INKO.string_to_float(@string, start, @index)) + Result.Ok(Json.Float( + _INKO.string_to_float(@string, start, @index).value as Float + )) } - fn mut exponent !! Error { + fn mut exponent -> Result[Nil, Error] { advance if current == MINUS or current == PLUS { advance } if digit?(current).false? { throw unexpected(current) } while digit?(current) { advance } + Result.Ok(nil) } fn mut advance_line { @@ -651,7 +659,7 @@ class pub Parser { if index < @size { _INKO.string_byte(@string, index) } else { EOF } } - fn mut identifier(name: String) !! Error { + fn mut identifier(name: String) -> Result[Nil, Error] { let mut index = 0 let max = name.size @@ -661,9 +669,11 @@ class pub Parser { advance index += 1 } + + Result.Ok(nil) } - fn mut separator(closing: Int) !! Error { + fn mut separator(closing: Int) -> Result[Nil, Error] { whitespace if current != closing { @@ -674,6 +684,8 @@ class pub Parser { if current == closing { throw unexpected(current) } } + + Result.Ok(nil) } fn mut whitespace { @@ -725,8 +737,8 @@ class pub Generator { # The `indent` argument specifies the number of spaces to use per indentation # level. If this value is less than or equal to zero, no indentation is # applied. - fn pub static new(indent: Int) -> Self { - Self { + fn pub static new(indent: Int) -> Generator { + Generator { @pretty = indent > 0, @spaces = ' '.repeat(indent), @depth = 0, @@ -824,7 +836,7 @@ class pub Generator { # # import std::json # -# try! json.parse('[10]') # => Json.Array([Json.Int(10)]) -fn pub parse(string: String) !! Error -> Json { - try Parser.new(string).parse +# json.parse('[10]').unwrap # => Json.Array([Json.Int(10)]) +fn pub parse(string: String) -> Result[Json, Error] { + Parser.new(string).parse } diff --git a/libstd/src/std/map.inko b/std/src/std/map.inko similarity index 77% rename from libstd/src/std/map.inko rename to std/src/std/map.inko index c4276d406..b371717a7 100644 --- a/libstd/src/std/map.inko +++ b/std/src/std/map.inko @@ -1,9 +1,8 @@ # A hash map using linear probing and Robin Hood entry stealing. import std::cmp::(Contains, Equal) -import std::drop::Drop import std::fmt::(Format, Formatter) import std::hash::(Hash, Hasher) -import std::index::(Index, IndexMut, SetIndex) +import std::hash::siphash::SipHasher13 import std::iter::Iter import std::process::(panic) @@ -18,7 +17,7 @@ let EMPTY = -1 let DEFAULT_CAPACITY = 4 # An entry stored in a Map. -class pub Entry[K: Hash + Equal, V] { +class pub Entry[K: Hash + Equal[K], V] { # The key that was hashed. let @key: K @@ -48,38 +47,6 @@ class pub Entry[K: Hash + Equal, V] { } } -# The default hasher to use for `Map` types. -# -# Given two different instances of a `DefaultHasher`, both produce the same hash -# code for a given value V. Hash codes are not persistent between restarts of -# the OS process, and as such should never be relied upon directly, persisted, -# or in any way be relied upon directly. -class pub DefaultHasher { - # The internal/native hasher. - let @hasher: Any - - # Returns a new `Hasher`. - fn pub static new -> Self { - Self { @hasher = _INKO.hasher_new } - } -} - -impl Drop for DefaultHasher { - fn mut drop { - _INKO.hasher_drop(@hasher) - } -} - -impl Hasher for DefaultHasher { - fn pub mut write(value: Int) { - _INKO.hasher_write_int(@hasher, value) - } - - fn pub hash -> Int { - _INKO.hasher_to_hash(@hasher) - } -} - # A hash map using linear probing and Robin Hood hashing. # # A `Map` preserves the order in which values are inserted, even when entries @@ -107,7 +74,7 @@ impl Hasher for DefaultHasher { # * http://codecapsule.com/2013/11/11/robin-hood-hashing/ # * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ # * https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/ -class pub Map[K: Hash + Equal, V] { +class pub Map[K: Hash + Equal[K], V] { # The slots we can hash into. # # An index of `-1` indicates the slot isn't used. A value of `0` or more @@ -123,7 +90,7 @@ class pub Map[K: Hash + Equal, V] { let @resize_at: Int # Returns a new empty `Map`. - fn pub static new -> Self { + fn pub static new -> Map[K, V] { with_capacity(DEFAULT_CAPACITY) } @@ -135,14 +102,14 @@ class pub Map[K: Hash + Equal, V] { # # let map = Map.with_capacity(32) # - # map['name'] = 'Alice' - fn pub static with_capacity(amount: Int) -> Self { + # map.set('name', 'Alice') + fn pub static with_capacity(amount: Int) -> Map[K, V] { let size = if amount <= 0 { DEFAULT_CAPACITY } else { amount.nearest_power_of_two } let slots = Array.filled(with: EMPTY, times: size) let resize_at = resize_threshold(size) - Self { @slots = slots, @entries = [], @resize_at = resize_at } + Map { @slots = slots, @entries = [], @resize_at = resize_at } } # Removes the given key, returning its value if the key was present. @@ -159,13 +126,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.remove('name') # => Option.Some('Alice') fn pub mut remove(key: ref K) -> Option[V] { let mut slot = slot_index(hash_key(key)) let mut dist = 0 - let mut index = @slots[slot] + let mut index = @slots.get(slot) # For the removal we need both the slot and the entry index, so we have to # duplicate the logic of `entries_index()` here, as this is the only place @@ -173,24 +140,24 @@ class pub Map[K: Hash + Equal, V] { loop { if index == EMPTY { return Option.None } - let entry = @entries[index] + let entry = @entries.get(index) if dist > entry.distance { return Option.None } if entry.key == key { break } slot = slot_index(slot + 1) - index = @slots[slot] + index = @slots.get(slot) dist += 1 } let value = @entries.remove_at(index).into_value - @slots[slot] = EMPTY + @slots.set(slot, EMPTY) # Because we shifted the entries to the left, any slots pointing to entries # _after_ the removed value have to be updated accordingly. @slots.iter.each_with_index fn (slot, entry) { - if entry > index { @slots[slot] = entry - 1 } + if entry > index { @slots.set(slot, entry - 1) } } let mut prev_slot = slot @@ -198,15 +165,15 @@ class pub Map[K: Hash + Equal, V] { slot = slot_index(slot + 1) loop { - let mut index = @slots[slot] + let mut index = @slots.get(slot) if index == EMPTY { break } - let entry = @entries[index] + let entry = @entries.get_mut(index) if entry.distance > 0 { - @slots[slot] = EMPTY - @slots[prev_slot] = index + @slots.set(slot, EMPTY) + @slots.set(prev_slot, index) entry.distance -= 1 } else { @@ -228,13 +195,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.iter.each fn (entry) { # entry.key # => 'name' # entry.value # => 'Alice' # } - fn pub iter -> Iter[ref Entry[K, V], Never] { + fn pub iter -> Iter[ref Entry[K, V]] { @entries.iter } @@ -246,13 +213,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.iter_mut.each fn (entry) { # entry.key # => 'name' # entry.value # => 'Alice' # } - fn pub mut iter_mut -> Iter[mut Entry[K, V], Never] { + fn pub mut iter_mut -> Iter[mut Entry[K, V]] { @entries.iter_mut } @@ -263,13 +230,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.into_iter.each fn (e) { # e.key # => 'name' # e.value # => 'Alice' # } - fn pub move into_iter -> Iter[Entry[K, V], Never] { + fn pub move into_iter -> Iter[Entry[K, V]] { @entries.into_iter } @@ -281,12 +248,12 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.keys.each fn (key) { # key # => 'name' # } - fn pub keys -> Iter[ref K, Never] { + fn pub keys -> Iter[ref K] { iter.map fn (e) { e.key } } @@ -298,12 +265,12 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.values.each fn (value) { # value # => 'Alice' # } - fn pub values -> Iter[ref V, Never] { + fn pub values -> Iter[ref V] { iter.map fn (e) { e.value } } @@ -315,30 +282,62 @@ class pub Map[K: Hash + Equal, V] { # # let map = Map.new # - # map.get('name') # => Option.None + # map.opt('name') # => Option.None # # Getting the value of an existing key: # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # - # map.get('name') # => Option.Some('Alice') - fn pub get(key: ref K) -> Option[ref V] { + # map.opt('name') # => Option.Some('Alice') + fn pub opt(key: ref K) -> Option[ref V] { match entries_index(key) { case EMPTY -> Option.None - case index -> Option.Some(@entries[index].value) + case index -> Option.Some(@entries.get(index).value) } } - # Returns an optional mutable reference to the key's value. - fn pub mut get_mut(key: ref K) -> Option[mut V] { - match entries_index(key) { - case EMPTY -> Option.None - case index -> Option.Some(@entries[index].value) + # Returns an immutable reference to the value of the given key. + # + # # Panics + # + # This method panics if the key doesn't exist. + # + # # Examples + # + # Getting the value of an existing key: + # + # let mut map = Map.new + # + # map.set('name', 'Alice') + # map.get('name') # => 'Alice' + fn pub get(index: ref K) -> ref V { + match entries_index(index) { + case EMPTY -> panic("The key doesn't exist") + case index -> @entries.get(index).value } } + # Inserts the key and value in this `Map`, returning the inserted value. + # + # # Examples + # + # Inserting a new key-value pair: + # + # let mut map = Map.new + # + # map.set('name', 'Alice') # => 'Alice' + fn pub mut set(index: K, value: V) { + if length >= @resize_at { resize } + + let hash = hash_key(index) + let entry = + Entry { @key = index, @value = value, @hash = hash, @distance = 0 } + + insert_entry(entry) + } + # Merges two `Map` objects together. # # # Examples @@ -346,14 +345,14 @@ class pub Map[K: Hash + Equal, V] { # let map1 = Map.new # let map2 = Map.new # - # map1['name'] = 'Alice' - # map2['city'] = 'Amsterdam' + # map1.set('name', 'Alice') + # map2.set('city', 'Amsterdam') # # map1.merge(map2) # # map1['name'] # => 'Alice' # map2['city'] # => 'Amsterdam' - fn pub mut merge(other: Self) { + fn pub mut merge(other: Map[K, V]) { other.into_iter.each fn (entry) { entry.distance = 0 entry.hash = hash_key(entry.key) @@ -376,7 +375,7 @@ class pub Map[K: Hash + Equal, V] { # # let map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.length # => 1 fn pub length -> Int { @@ -405,18 +404,18 @@ class pub Map[K: Hash + Equal, V] { let mut slot = slot_index(rehash.hash) loop { - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { - @slots[slot] = rehash_index + @slots.set(slot, rehash_index) return } - let entry = @entries[index] + let entry = @entries.get(index) if entry.distance < rehash.distance { - @slots[slot] = rehash_index + @slots.set(slot, rehash_index) shift_stolen_slots(slot, index) return @@ -432,16 +431,16 @@ class pub Map[K: Hash + Equal, V] { let mut slot = slot_index(insert.hash) loop { - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { - @slots[slot] = @entries.length + @slots.set(slot, @entries.length) @entries.push(insert) return } - let entry = @entries[index] + let entry = @entries.get_mut(index) if entry.key == insert.key { entry.value = insert.into_value @@ -450,7 +449,7 @@ class pub Map[K: Hash + Equal, V] { } if entry.distance < insert.distance { - @slots[slot] = @entries.length + @slots.set(slot, @entries.length) @entries.push(insert) shift_stolen_slots(slot, index) @@ -479,23 +478,23 @@ class pub Map[K: Hash + Equal, V] { # at the next one. let mut slot = slot_index(start_slot + 1) let mut stolen_index = start_index - let mut stolen = @entries[stolen_index] + let mut stolen = @entries.get_mut(stolen_index) loop { stolen.distance += 1 - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { - @slots[slot] = stolen_index + @slots.set(slot, stolen_index) return } - let entry = @entries[index] + let entry = @entries.get_mut(index) if entry.distance < stolen.distance { - @slots[slot] = stolen_index + @slots.set(slot, stolen_index) stolen_index = index stolen = entry } @@ -509,11 +508,11 @@ class pub Map[K: Hash + Equal, V] { let mut dist = 0 loop { - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { return EMPTY } - let entry = @entries[index] + let entry = @entries.get(index) if dist > entry.distance { return EMPTY } if entry.key == key { return index } @@ -524,10 +523,10 @@ class pub Map[K: Hash + Equal, V] { } fn hash_key(key: ref K) -> Int { - let hasher: Hasher = DefaultHasher.new + let hasher: Hasher = SipHasher13.default key.hash(mut hasher) - hasher.hash + hasher.finish } fn slot_index(hash: Int) -> Int { @@ -537,89 +536,52 @@ class pub Map[K: Hash + Equal, V] { } } -impl Equal for Map if V: Equal { - # Returns `true` if `self` and the given `Map` are identical to each - # other. - # - # # Examples - # - # Comparing two `Map` instances: - # - # let map1 = Map.new - # let map2 = Map.new - # - # map1['name'] = 'Alice' - # map2['name'] = 'Alice' - # - # map1 == map2 # => true - fn pub ==(other: ref Self) -> Bool { - if length != other.length { return false } - - iter.all? fn (ours) { - match other.entries_index(ours.key) { - case EMPTY -> false - case index -> other.entries[index].value == ours.value - } - } - } -} - -impl Index[ref K, ref V] for Map { - # Returns a reference to the value of the given key. - # - # # Examples - # - # Getting the value of an existing key: - # - # let mut map = Map.new - # - # map['name'] = 'Alice' - # - # map['name'] # => 'Alice' - # - # # Panics - # - # This method panics if the key doesn't exist. - fn pub index(index: ref K) -> ref V { - match entries_index(index) { - case EMPTY -> panic("The key doesn't exist") - case index -> @entries[index].value +impl Map if V: mut { + # Returns an optional mutable reference to the key's value. + fn pub mut opt_mut(key: ref K) -> Option[mut V] { + match entries_index(key) { + case EMPTY -> Option.None + case index -> Option.Some(@entries.get_mut(index).value) } } -} -impl IndexMut[ref K, mut V] for Map { # Returns a mutable reference to the value of the given key. # # # Panics # # This method panics if the key doesn't exist. - fn pub mut index_mut(index: ref K) -> mut V { + fn pub mut get_mut(index: ref K) -> mut V { match entries_index(index) { case EMPTY -> panic("The key doesn't exist") - case index -> @entries[index].value + case index -> @entries.get_mut(index).value } } } -impl SetIndex[K, V] for Map { - # Inserts the key and value in this `Map`, returning the inserted value. +impl Equal[Map[K, V]] for Map if V: Equal[V] { + # Returns `true` if `self` and the given `Map` are identical to each + # other. # # # Examples # - # Inserting a new key-value pair: + # Comparing two `Map` instances: # - # let mut map = Map.new + # let map1 = Map.new + # let map2 = Map.new # - # map['name'] = 'Alice' # => 'Alice' - fn pub mut set_index(index: K, value: V) { - if length >= @resize_at { resize } - - let hash = hash_key(index) - let entry = - Entry { @key = index, @value = value, @hash = hash, @distance = 0 } + # map1.set('name', 'Alice') + # map2.set('name', 'Alice') + # + # map1 == map2 # => true + fn pub ==(other: ref Map[K, V]) -> Bool { + if length != other.length { return false } - insert_entry(entry) + iter.all? fn (ours) { + match other.entries_index(ours.key) { + case EMPTY -> false + case index -> other.entries.get(index).value == ours.value + } + } } } @@ -641,8 +603,7 @@ impl Contains[K] for Map { # # let map = Map.new # - # map['name'] = 'Alice' - # + # map.set('name', 'Alice') # map.contains?('name') # => true # map.contains?('city') # => false fn pub contains?(value: ref K) -> Bool { diff --git a/libstd/src/std/net/ip.inko b/std/src/std/net/ip.inko similarity index 91% rename from libstd/src/std/net/ip.inko rename to std/src/std/net/ip.inko index bb0512fe1..b4ef114d6 100644 --- a/libstd/src/std/net/ip.inko +++ b/std/src/std/net/ip.inko @@ -57,7 +57,7 @@ class pub enum IpAddress { # import std::net::ip::IpAddress # # IpAdress.v4(127, 0, 0, 1) - fn pub static v4(a: Int, b: Int, c: Int, d: Int) -> Self { + fn pub static v4(a: Int, b: Int, c: Int, d: Int) -> IpAddress { V4(Ipv4Address.new(a, b, c, d)) } @@ -79,7 +79,7 @@ class pub enum IpAddress { f: Int, g: Int, h: Int - ) -> Self { + ) -> IpAddress { V6(Ipv6Address.new(a, b, c, d, e, f, g, h)) } @@ -101,7 +101,7 @@ class pub enum IpAddress { # import std::net::ip::IpAddress # # IpAddress.parse('::1') # => Option.Some(IpAddress.V6(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1))) - fn pub static parse(address: String) -> Option[Self] { + fn pub static parse(address: String) -> Option[IpAddress] { if address.contains?(':') { Ipv6Address.parse(address).map fn (v) { V6(v) } } else { @@ -158,8 +158,8 @@ class pub enum IpAddress { } } -impl Equal for IpAddress { - fn pub ==(other: ref Self) -> Bool { +impl Equal[IpAddress] for IpAddress { + fn pub ==(other: ref IpAddress) -> Bool { match self { case V4(a) -> match other { case V4(b) -> a == b @@ -191,8 +191,8 @@ impl ToString for IpAddress { } } -impl Clone for IpAddress { - fn pub clone -> Self { +impl Clone[IpAddress] for IpAddress { + fn pub clone -> IpAddress { match self { case V4(ip) -> IpAddress.V4(ip.clone) case V6(ip) -> IpAddress.V6(ip.clone) @@ -229,7 +229,7 @@ class pub Ipv6Address { # import std::net::ip::Ipv6Address # # Ipv6Address.parse('::1').unwrap.v6? # => true - fn pub static parse(input: String) -> Option[Self] { + fn pub static parse(input: String) -> Option[Ipv6Address] { let bytes = input.to_byte_array let mut cursor = 0 let max = bytes.length @@ -248,17 +248,17 @@ class pub Ipv6Address { # IPv6 addresses can embed IPv4 addresses, so instead of reading until we # encounter a ":" we will also stop reading when running into a ".". while cursor < max - and bytes[cursor] != COLON_BYTE - and bytes[cursor] != DOT_BYTE + and bytes.get(cursor) != COLON_BYTE + and bytes.get(cursor) != DOT_BYTE { - segment_bytes.push(bytes[cursor]) + segment_bytes.push(bytes.get(cursor)) cursor += 1 } # The moment we encounter a dot we'll enter IPv4 mode, and remain in this # mode until we reach the end of the input, as embedded IPv4 addresses # must be at the end of an IPv6 address. - if ipv4_mode.false? and cursor < max and bytes[cursor] == DOT_BYTE { + if ipv4_mode.false? and cursor < max and bytes.get(cursor) == DOT_BYTE { ipv4_mode = true base16 = false max_hextet_value = IPV4_OCTET_MAXIMUM @@ -290,7 +290,7 @@ class pub Ipv6Address { # We have reached another ":", which is used to compress one or more empty # groups together. - if cursor < max and bytes[cursor] == COLON_BYTE { + if cursor < max and bytes.get(cursor) == COLON_BYTE { # Zero compression can only be applied once. if compressed { return Option.None } @@ -307,7 +307,7 @@ class pub Ipv6Address { # When the compression is at the end of the input (e.g. "1::") there is # no point in looking ahead, so we don't. while pad_cursor < max and look_ahead { - let byte = bytes[pad_cursor] + let byte = bytes.get(pad_cursor) if byte == COLON_BYTE { pad -= 1 } @@ -333,21 +333,21 @@ class pub Ipv6Address { } if ipv4_segments.length == IPV4_OCTETS { - segments.push(octets_to_hextet(ipv4_segments[0], ipv4_segments[1])) - segments.push(octets_to_hextet(ipv4_segments[2], ipv4_segments[3])) + segments.push(octets_to_hextet(ipv4_segments.get(0), ipv4_segments.get(1))) + segments.push(octets_to_hextet(ipv4_segments.get(2), ipv4_segments.get(3))) } if segments.length != IPV6_HEXTETS { return Option.None } Option.Some(Ipv6Address.new( - segments[0], - segments[1], - segments[2], - segments[3], - segments[4], - segments[5], - segments[6], - segments[7], + segments.get(0), + segments.get(1), + segments.get(2), + segments.get(3), + segments.get(4), + segments.get(5), + segments.get(6), + segments.get(7), )) } @@ -361,8 +361,10 @@ class pub Ipv6Address { f: Int, g: Int, h: Int - ) -> Self { - Self { @a = a, @b = b, @c = c, @d = d, @e = e, @f = f, @g = g, @h = h } + ) -> Ipv6Address { + Ipv6Address { + @a = a, @b = b, @c = c, @d = d, @e = e, @f = f, @g = g, @h = h + } } # Returns `true` if `self` is an IPv4-compatible IPv6 address. @@ -473,7 +475,7 @@ impl Format for Ipv6Address { } } -impl Equal for Ipv6Address { +impl Equal[Ipv6Address] for Ipv6Address { # Returns `true` if `self` and the given IP address are the same. # # # Examples @@ -488,7 +490,7 @@ impl Equal for Ipv6Address { # # addr1 == addr2 # => true # addr1 == addr3 # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Ipv6Address) -> Bool { @a == other.a and @b == other.b and @c == other.c @@ -544,7 +546,7 @@ impl ToString for Ipv6Address { # Find the longest sequence of empty hextets, which we will compress # together. while index < segments.length { - let hextet = segments[index] + let hextet = segments.get(index) if hextet == 0 { if current_len == 0 { current_at = index } @@ -591,9 +593,9 @@ impl IntoString for Ipv6Address { } } -impl Clone for Ipv6Address { - fn pub clone -> Self { - Self { +impl Clone[Ipv6Address] for Ipv6Address { + fn pub clone -> Ipv6Address { + Ipv6Address { @a = @a, @b = @b, @c = @c, @@ -624,7 +626,7 @@ class pub Ipv4Address { # let addr = Ipv4Address.parse('1.2.3.4').unwrap # # addr.v4? # => true - fn pub static parse(input: String) -> Option[Self] { + fn pub static parse(input: String) -> Option[Ipv4Address] { # No IPv4 address can be longer than 15 characters (255.255.255.255). if input.size > 15 { return Option.None } @@ -654,16 +656,16 @@ class pub Ipv4Address { if segments.length != IPV4_OCTETS { return Option.None } Option.Some(Ipv4Address { - @a = segments[0], - @b = segments[1], - @c = segments[2], - @d = segments[3] + @a = segments.get(0), + @b = segments.get(1), + @c = segments.get(2), + @d = segments.get(3), }) } # Returns a new IPv4 address using the given octets. - fn pub static new(a: Int, b: Int, c: Int, d: Int) -> Self { - Self { @a = a, @b = b, @c = c, @d = d } + fn pub static new(a: Int, b: Int, c: Int, d: Int) -> Ipv4Address { + Ipv4Address { @a = a, @b = b, @c = c, @d = d } } # Returns `true` if `self` is a broadcast address (255.255.255.255). @@ -844,7 +846,7 @@ impl Format for Ipv4Address { } } -impl Equal for Ipv4Address { +impl Equal[Ipv4Address] for Ipv4Address { # Returns `true` if `self` and the given IP address are the same. # # # Examples @@ -859,7 +861,7 @@ impl Equal for Ipv4Address { # # addr1 == addr2 # => true # addr1 == addr3 # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Ipv4Address) -> Bool { @a == other.a and @b == other.b and @c == other.c and @d == other.d } } @@ -886,8 +888,8 @@ impl IntoString for Ipv4Address { } } -impl Clone for Ipv4Address { - fn pub clone -> Self { - Self { @a = @a, @b = @b, @c = @c, @d = @d } +impl Clone[Ipv4Address] for Ipv4Address { + fn pub clone -> Ipv4Address { + Ipv4Address { @a = @a, @b = @b, @c = @c, @d = @d } } } diff --git a/libstd/src/std/net/socket.inko b/std/src/std/net/socket.inko similarity index 54% rename from libstd/src/std/net/socket.inko rename to std/src/std/net/socket.inko index e6c175250..2e6a80006 100644 --- a/libstd/src/std/net/socket.inko +++ b/std/src/std/net/socket.inko @@ -42,11 +42,11 @@ # import std::net::socket::TcpServer # import std::time::Duration # -# let server = try! TcpServer.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9000) +# let server = TcpServer.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9000).unwrap # let _guard = server.socket.timeout_after = Duration.from_secs(3) # # # This times out after roughly three seconds. -# try! server.accept +# server.accept.unwrap # # For more information about timeouts versus deadlines, consider reading [this # article](https://vorpus.org/blog/timeouts-and-cancellation-for-humans/). @@ -70,6 +70,10 @@ let MAXIMUM_LISTEN_BACKLOG = 65_535 # A value that signals the lack of a socket deadline. let NO_DEADLINE = -1 +let IPV4 = 0 +let IPV6 = 1 +let UNIX = 2 + # The type of a socket. class pub enum Type { # The type corresponding to `SOCK_STREAM`. @@ -106,8 +110,8 @@ class pub SocketAddress { # The port number of this socket address. let pub @port: Int - fn pub static new(address: String, port: Int) -> Self { - Self { @address = address, @port = port } + fn pub static new(address: String, port: Int) -> SocketAddress { + SocketAddress { @address = address, @port = port } } # Returns the IPv4/IPv6 address associated with `self`. @@ -116,9 +120,9 @@ class pub SocketAddress { } } -impl Equal for SocketAddress { +impl Equal[SocketAddress] for SocketAddress { # Returns `true` if `self` and `other` are the same. - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref SocketAddress) -> Bool { @address == other.address and @port == other.port } } @@ -155,12 +159,14 @@ class pub Socket { # # import std::net::socket::(Type, Socket) # - # try! Socket.ipv4(Type.DGRAM) - fn pub static ipv4(type: Type) !! Error -> Self { - let id = type.into_int - let fd = try _INKO.socket_allocate_ipv4(id) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + # Socket.ipv4(Type.DGRAM).unwrap + fn pub static ipv4(type: Type) -> Result[Socket, Error] { + match _INKO.socket_new(IPV4, type.into_int) { + case { @tag = 0, @value = v } -> Result.Ok( + Socket { @fd = v, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Creates a new IPv6 socket. @@ -169,12 +175,14 @@ class pub Socket { # # import std::net::socket::(Type, Socket) # - # try! Socket.ipv6(Type.DGRAM) - fn pub static ipv6(type: Type) !! Error -> Self { - let id = type.into_int - let fd = try _INKO.socket_allocate_ipv6(id) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + # Socket.ipv6(Type.DGRAM).unwrap + fn pub static ipv6(type: Type) -> Result[Socket, Error] { + match _INKO.socket_new(IPV6, type.into_int) { + case { @tag = 0, @value = v } -> Result.Ok( + Socket { @fd = v, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the point in time after which socket operations must time out, known as @@ -222,13 +230,14 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - fn pub mut bind(ip: ref ToString, port: Int) !! Error { - let addr = ip.to_string - - try _INKO.socket_bind(@fd, addr, port) else (e) throw Error.from_int(e) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + fn pub mut bind(ip: ref ToString, port: Int) -> Result[Nil, Error] { + match _INKO.socket_bind(@fd, ip.to_string, port) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Connects this socket to the specified address. @@ -240,19 +249,16 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let listener = try! Socket.ipv4(Type.STREAM) - # let client = try! Socket.ipv4(Type.STREAM) + # let listener = Socket.ipv4(Type.STREAM).unwrap + # let client = Socket.ipv4(Type.STREAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.listen - # try! client.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - fn pub mut connect(ip: ref ToString, port: Int) !! Error { - let addr = ip.to_string - - try { - _INKO.socket_connect(@fd, addr, port, @deadline) - } else (err) { - throw Error.from_int(err) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket.listen.unwrap + # client.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + fn pub mut connect(ip: ref ToString, port: Int) -> Result[Nil, Error] { + match _INKO.socket_connect(@fd, ip.to_string, port, @deadline) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -266,15 +272,14 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.STREAM) + # let socket = Socket.ipv4(Type.STREAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.listen - fn pub mut listen !! Error { - try { - _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) - } else (err) { - throw Error.from_int(err) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket.listen.unwrap + fn pub mut listen -> Result[Nil, Error] { + match _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -289,29 +294,28 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let listener = try! Socket.ipv4(Type.STREAM) - # let stream = try! Socket.ipv4(Type.STREAM) + # let listener = Socket.ipv4(Type.STREAM).unwrap + # let stream = Socket.ipv4(Type.STREAM).unwrap # - # try! listener.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! listener.listen + # listener.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # listener.listen.unwrap # - # try! stream.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! stream.write_string('ping') + # stream.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # stream.write_string('ping').unwrap # - # let client = try! listener.accept + # let client = listener.accept.unwrap # let buffer = ByteArray.new # - # try! client.read(into: buffer, size: 4) + # client.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> Socket { - let fd = try { - _INKO.socket_accept_ip(@fd, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub accept -> Result[Socket, Error] { + match _INKO.socket_accept(@fd, @deadline) { + case { @tag = 0, @value = fd } -> Result.Ok( + Socket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } - - Socket { @fd = fd, @deadline = NO_DEADLINE } } # Sends a `String` to the given address. @@ -323,23 +327,24 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.send_string_to( - # string: 'hello', - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket + # .send_string_to( + # string: 'hello', + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_string_to( string: String, ip: ref ToString, port: Int - ) !! Error -> Int { - try { - _INKO.socket_send_string_to(@fd, string, ip.to_string, port, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + match _INKO.socket_send_string_to(@fd, string, ip.to_string, port, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -352,24 +357,25 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.send_bytes_to( - # bytes: bytes, - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket + # .send_bytes_to( + # bytes: bytes, + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_bytes_to( bytes: ByteArray, ip: ref ToString, port: Int - ) !! Error -> Int { - try { - _INKO.socket_send_bytes_to(@fd, bytes, ip.to_string, port, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + match _INKO.socket_send_bytes_to(@fd, bytes, ip.to_string, port, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -386,16 +392,18 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # let bytes = ByteArray.new # - # try! socket.send_string_to( - # 'hello', - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket + # .send_string_to( + # 'hello', + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap # - # let received_from = try! socket.receive_from(bytes: bytes, size: 5) + # let received_from = socket.receive_from(bytes: bytes, size: 5).unwrap # # bytes.to_string # => 'hello' # received_from.address # => '0.0.0.0' @@ -403,180 +411,165 @@ class pub Socket { fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> SocketAddress { - let raw_addr = try { - _INKO.socket_receive_from(@fd, bytes, size, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[SocketAddress, Error] { + let raw_addr = match _INKO.socket_receive_from(@fd, bytes, size, @deadline) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) let port = _INKO.socket_address_pair_port(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - SocketAddress.new(addr, port) + Result.Ok(SocketAddress.new(addr, port)) } # Returns the local address of this socket. - fn pub local_address !! Error -> SocketAddress { - let raw_addr = - try _INKO.socket_local_address(@fd) else (e) throw Error.from_int(e) + fn pub local_address -> Result[SocketAddress, Error] { + let raw_addr = match _INKO.socket_local_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) let port = _INKO.socket_address_pair_port(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - SocketAddress.new(addr, port) + Result.Ok(SocketAddress.new(addr, port)) } # Returns the peer address of this socket. - fn pub peer_address !! Error -> SocketAddress { - let raw_addr = - try _INKO.socket_peer_address(@fd) else (e) throw Error.from_int(e) + fn pub peer_address -> Result[SocketAddress, Error] { + let raw_addr = match _INKO.socket_peer_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) let port = _INKO.socket_address_pair_port(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - SocketAddress.new(addr, port) - } - - # Returns the value of the `IP_TTL` option. - fn pub ttl !! Error -> Int { - try _INKO.socket_get_ttl(@fd) else (e) throw Error.from_int(e) + Result.Ok(SocketAddress.new(addr, port)) } # Sets the value of the `IP_TTL` option. - fn pub mut ttl=(value: Int) !! Error { - try _INKO.socket_set_ttl(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `IPV6_V6ONLY` option. - fn pub only_ipv6? !! Error -> Bool { - try _INKO.socket_get_only_v6(@fd) else (e) throw Error.from_int(e) + fn pub mut ttl=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_ttl(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `IPV6_V6ONLY` option. - fn pub mut only_ipv6=(value: Bool) !! Error { - try _INKO.socket_set_only_v6(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `TCP_NODELAY` option. - fn pub no_delay? !! Error -> Bool { - try _INKO.socket_get_nodelay(@fd) else (e) throw Error.from_int(e) + fn pub mut only_ipv6=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_only_v6(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `TCP_NODELAY` option. - fn pub mut no_delay=(value: Bool) !! Error { - try _INKO.socket_set_nodelay(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_BROADCAST` option. - fn pub broadcast? !! Error -> Bool { - try _INKO.socket_get_broadcast(@fd) else (e) throw Error.from_int(e) + fn pub mut no_delay=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_nodelay(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_BROADCAST` option. - fn pub mut broadcast=(value: Bool) !! Error { - try _INKO.socket_set_broadcast(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_LINGER` option. - fn pub linger !! Error -> Duration { - let nanos = - try _INKO.socket_get_linger(@fd) else (e) throw Error.from_int(e) - - Duration.from_nanos(nanos) + fn pub mut broadcast=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_broadcast(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_LINGER` option. - fn pub mut linger=(value: ref Duration) !! Error { - let nanos = value.to_nanos - - try _INKO.socket_set_linger(@fd, nanos) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_RCVBUF` option. - fn pub receive_buffer_size !! Error -> Int { - try _INKO.socket_get_recv_size(@fd) else (e) throw Error.from_int(e) + fn pub mut linger=(value: ref Duration) -> Result[Nil, Error] { + match _INKO.socket_set_linger(@fd, value.to_nanos) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_RCVBUF` option. - fn pub mut receive_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_recv_size(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_SNDBUF` option. - fn pub send_buffer_size !! Error -> Int { - try _INKO.socket_get_send_size(@fd) else (e) throw Error.from_int(e) + fn pub mut receive_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_recv_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_SNDBUF` option. - fn pub mut send_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_send_size(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_KEEPALIVE` option. - fn pub keepalive !! Error -> Bool { - try _INKO.socket_get_keepalive(@fd) else (e) throw Error.from_int(e) + fn pub mut send_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_send_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_KEEPALIVE` option. - fn pub mut keepalive=(value: Bool) !! Error { - try _INKO.socket_set_keepalive(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_REUSEADDR` option. - fn pub reuse_address !! Error -> Bool { - try _INKO.socket_get_reuse_address(@fd) else (e) throw Error.from_int(e) + fn pub mut keepalive=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_keepalive(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_REUSEADDR` option. - fn pub mut reuse_address=(value: Bool) !! Error { - try { - _INKO.socket_set_reuse_address(@fd, value) - } else (err) { - throw Error.from_int(err) + fn pub mut reuse_address=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_reuse_address(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - # Returns the value of the `SO_REUSEPORT` option. - # - # Not all platforms may support this option, in which case the returned value - # will be `false`. - fn pub reuse_port !! Error -> Bool { - try _INKO.socket_get_reuse_port(@fd) else (e) throw Error.from_int(e) - } - # Sets the value of the `SO_REUSEPORT` option. # # Not all platforms may support this option, in which case the supplied # argument will be ignored. - fn pub mut reuse_port=(value: Bool) !! Error { - try _INKO.socket_set_reuse_port(@fd, value) else (e) throw Error.from_int(e) + fn pub mut reuse_port=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_reuse_port(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try _INKO.socket_shutdown_read(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_read -> Result[Nil, Error] { + match _INKO.socket_shutdown_read(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try _INKO.socket_shutdown_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_write -> Result[Nil, Error] { + match _INKO.socket_shutdown_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try _INKO.socket_shutdown_read_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown -> Result[Nil, Error] { + match _INKO.socket_shutdown_read_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - let fd = try _INKO.socket_try_clone(@fd) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + fn pub try_clone -> Result[Socket, Error] { + match _INKO.socket_try_clone(@fd) { + case { @tag = 0, @value = fd } -> Result.Ok( + Socket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } @@ -587,34 +580,31 @@ impl Drop for Socket { } impl Read for Socket { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.socket_read(@fd, into, size, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.socket_read(@fd, into, size, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } impl Write for Socket { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try { - _INKO.socket_write_bytes(@fd, bytes, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.socket_write_bytes(@fd, bytes, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut write_string(string: String) !! Error -> Int { - try { - _INKO.socket_write_string(@fd, string, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.socket_write_string(@fd, string, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut flush { - # Sockets can't be flushed, so this method is just a noop. + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) } } @@ -635,10 +625,10 @@ class pub UdpSocket { # import std::net::socket::UdpSocket # import std::net::ip::IpAddress # - # let ip = try! IpAddress.parse('0.0.0.0') + # let ip = IpAddress.parse('0.0.0.0').unwrap # - # try! UdpSocket.new(ip, port: 0) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + # UdpSocket.new(ip, port: 0).unwrap + fn pub static new(ip: IpAddress, port: Int) -> Result[UdpSocket, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.DGRAM) } else { @@ -646,8 +636,7 @@ class pub UdpSocket { } try socket.bind(ip, port) - - Self { @socket = socket } + Result.Ok(UdpSocket { @socket = socket }) } # Connects `self` to the remote address. @@ -664,13 +653,13 @@ class pub UdpSocket { # import std::net::ip::IpAddress # # let socket1 = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 40_000) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 40_000).unwrap # let socket2 = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000).unwrap # - # try! socket1.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000) - fn pub mut connect(ip: ref ToString, port: Int) !! Error { - try @socket.connect(ip, port) + # socket1.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000).unwrap + fn pub mut connect(ip: ref ToString, port: Int) -> Result[Nil, Error] { + @socket.connect(ip, port) } # Sends a `String` to the given address. @@ -683,19 +672,21 @@ class pub UdpSocket { # import std::net::ip::IpAddress # # let socket = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # - # try! socket.send_string_to( - # string: 'hello', - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # + # socket + # .send_string_to( + # string: 'hello', + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_string_to( string: String, ip: ref ToString, port: Int - ) !! Error -> Int { - try @socket.send_string_to(string, ip, port) + ) -> Result[Int, Error] { + @socket.send_string_to(string, ip, port) } # Sends a `ByteArray` to the given address. @@ -708,20 +699,22 @@ class pub UdpSocket { # import std::net::ip::IpAddress # # let socket = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.send_bytes_to( - # bytes: bytes, - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket + # .send_bytes_to( + # bytes: bytes, + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_bytes_to( bytes: ByteArray, ip: ref ToString, port: Int - ) !! Error -> Int { - try @socket.send_bytes_to(bytes, ip, port) + ) -> Result[Int, Error] { + @socket.send_bytes_to(bytes, ip, port) } # Receives a single datagram message on the socket, returning the address the @@ -731,42 +724,44 @@ class pub UdpSocket { fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> SocketAddress { - try @socket.receive_from(bytes, size) + ) -> Result[SocketAddress, Error] { + @socket.receive_from(bytes, size) } # Returns the local address of this socket. # # See the documentation of `Socket.local_address` for more information. - fn pub local_address !! Error -> SocketAddress { - try @socket.local_address + fn pub local_address -> Result[SocketAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UdpSocket, Error] { + @socket.try_clone.map fn (sock) { UdpSocket { @socket = sock } } } } impl Read for UdpSocket { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for UdpSocket { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A TCP socket connected to another TCP socket. @@ -788,10 +783,10 @@ class pub TcpClient { # import std::net::socket::TcpClient # import std::net::ip::IpAddress # - # let ip = try! IpAddress.parse('127.0.0.1') + # let ip = IpAddress.parse('127.0.0.1').unwrap # - # try! TcpClient.new(ip, port: 40_000) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + # TcpClient.new(ip, port: 40_000).unwrap + fn pub static new(ip: IpAddress, port: Int) -> Result[TcpClient, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -799,8 +794,7 @@ class pub TcpClient { } try socket.connect(ip, port) - - Self { @socket = socket } + Result.Ok(TcpClient { @socket = socket }) } # Creates a new `TcpClient` but limits the amount of time spent waiting for @@ -817,16 +811,18 @@ class pub TcpClient { # import std::net::ip::IpAddress # import std::time::Duration # - # try! TcpClient.with_timeout( - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 40_000, - # timeout_after: Duration.from_secs(5) - # ) + # TcpClient + # .with_timeout( + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 40_000, + # timeout_after: Duration.from_secs(5) + # ) + # .unwrap fn pub static with_timeout( ip: IpAddress, port: Int, timeout_after: ToInstant - ) !! Error -> Self { + ) -> Result[TcpClient, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -836,64 +832,65 @@ class pub TcpClient { let _ = socket.timeout_after = timeout_after try socket.connect(ip, port) - - Self { @socket = socket } + Result.Ok(TcpClient { @socket = socket }) } # Returns the local address of this socket. # # See the documentation of `Socket.local_address` for more information. - fn pub local_address !! Error -> SocketAddress { - try @socket.local_address + fn pub local_address -> Result[SocketAddress, Error] { + @socket.local_address } # Returns the peer address of this socket. # # See the documentation of `Socket.peer_address` for more information. - fn pub peer_address !! Error -> SocketAddress { - try @socket.peer_address + fn pub peer_address -> Result[SocketAddress, Error] { + @socket.peer_address } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try @socket.shutdown_read + fn pub mut shutdown_read -> Result[Nil, Error] { + @socket.shutdown_read } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try @socket.shutdown_write + fn pub mut shutdown_write -> Result[Nil, Error] { + @socket.shutdown_write } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try @socket.shutdown + fn pub mut shutdown -> Result[Nil, Error] { + @socket.shutdown } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[TcpClient, Error] { + @socket.try_clone.map fn (sock) { TcpClient { @socket = sock } } } } impl Read for TcpClient { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for TcpClient { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A TCP socket server that can accept incoming connections. @@ -917,10 +914,10 @@ class pub TcpServer { # import std::net::socket::TcpServer # import std::net::ip::IpAddress # - # let ip = try! IpAddress.parse('0.0.0.0') + # let ip = IpAddress.parse('0.0.0.0').unwrap # - # try! TcpServer.new(ip, port: 40_000) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + # TcpServer.new(ip, port: 40_000).unwrap + fn pub static new(ip: IpAddress, port: Int) -> Result[TcpServer, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -929,11 +926,9 @@ class pub TcpServer { try socket.reuse_address = true try socket.reuse_port = true - try socket.bind(ip, port) try socket.listen - - Self { @socket = socket } + Result.Ok(TcpServer { @socket = socket }) } # Accepts a new incoming connection from `self`. @@ -948,35 +943,35 @@ class pub TcpServer { # import std::net::ip::IpAddress # # let listener = - # try! TcpServer.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000) + # TcpServer.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000).unwrap # let client = - # try! TcpClient.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000) + # TcpClient.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000).unwrap # # client.write_string('ping') # - # let connection = try! listener.accept + # let connection = listener.accept.unwrap # let buffer = ByteArray.new # - # try! connection.read(into: buffer, size: 4) + # connection.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> TcpClient { - TcpClient { @socket = try @socket.accept } + fn pub accept -> Result[TcpClient, Error] { + @socket.accept.map fn (sock) { TcpClient { @socket = sock } } } # Returns the local address of this socket. # # See the documentation of `Socket.local_address` for more information. - fn pub local_address !! Error -> SocketAddress { - try @socket.local_address + fn pub local_address -> Result[SocketAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[TcpServer, Error] { + @socket.try_clone.map fn (sock) { TcpServer { @socket = sock } } } } @@ -1009,8 +1004,8 @@ class pub UnixAddress { # import std::net::socket::UnixAddress # # UnixAddress.new("\0example") - fn pub static new(address: ref ToString) -> Self { - Self { @address = address.to_string } + fn pub static new(address: ref ToString) -> UnixAddress { + UnixAddress { @address = address.to_string } } # Returns the path of this address. @@ -1067,7 +1062,7 @@ impl Format for UnixAddress { } } -impl Equal for UnixAddress { +impl Equal[UnixAddress] for UnixAddress { # Returns `true` if `self` and `other` are the same socket addresses. # # # Examples @@ -1078,7 +1073,7 @@ impl Equal for UnixAddress { # # UnixAddress.new('a.sock') == UnixAddress.new('a.sock') # => true # UnixAddress.new('a.sock') == UnixAddress.new('b.sock') # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref UnixAddress) -> Bool { @address == other.address } } @@ -1127,12 +1122,14 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # try! UnixSocket.new(Type.DGRAM) - fn pub static new(type: Type) !! Error -> UnixSocket { - let id = type.into_int - let fd = try _INKO.socket_allocate_unix(id) else (e) throw Error.from_int(e) - - UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + # UnixSocket.new(Type.DGRAM).unwrap + fn pub static new(type: Type) -> Result[UnixSocket, Error] { + match _INKO.socket_new(UNIX, type.into_int) { + case { @tag = 0, @value = fd } -> Result.Ok( + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the point in time after which socket operations must time out, known as @@ -1179,14 +1176,13 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # - # try! socket.bind('/tmp/test.sock') - fn pub mut bind(path: ref ToString) !! Error { - try { - _INKO.socket_bind(@fd, path.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + # socket.bind('/tmp/test.sock').unwrap + fn pub mut bind(path: ref ToString) -> Result[Nil, Error] { + match _INKO.socket_bind(@fd, path.to_string, 0, @deadline) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1198,18 +1194,17 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let listener = try! UnixSocket.new(Type.STREAM) - # let stream = try! UnixSocket.new(Type.STREAM) + # let listener = UnixSocket.new(Type.STREAM).unwrap + # let stream = UnixSocket.new(Type.STREAM).unwrap # - # try! listener.bind('/tmp/test.sock') - # try! listener.listen + # listener.bind('/tmp/test.sock').unwrap + # listener.listen.unwrap # - # try! stream.connect('/tmp/test.sock') - fn pub mut connect(path: ref ToString) !! Error { - try { - _INKO.socket_connect(@fd, path.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + # stream.connect('/tmp/test.sock').unwrap + fn pub mut connect(path: ref ToString) -> Result[Nil, Error] { + match _INKO.socket_connect(@fd, path.to_string, 0, @deadline) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1222,15 +1217,14 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.STREAM) + # let socket = UnixSocket.new(Type.STREAM).unwrap # - # try! socket.bind('/tmp/test.sock') - # try! socket.listen - fn pub mut listen !! Error { - try { - _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) - } else (err) { - throw Error.from_int(err) + # socket.bind('/tmp/test.sock').unwrap + # socket.listen.unwrap + fn pub mut listen -> Result[Nil, Error] { + match _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1244,29 +1238,28 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let listener = try! UnixSocket.new(Type.STREAM) - # let stream = try! UnixSocket.new(Type.STREAM) + # let listener = UnixSocket.new(Type.STREAM).unwrap + # let stream = UnixSocket.new(Type.STREAM).unwrap # - # try! listener.bind('/tmp/test.sock') - # try! listener.listen + # listener.bind('/tmp/test.sock').unwrap + # listener.listen.unwrap # - # try! stream.connect('/tmp/test.sock') - # try! stream.write_string('ping') + # stream.connect('/tmp/test.sock').unwrap + # stream.write_string('ping').unwrap # - # let client = try! listener.accept + # let client = listener.accept.unwrap # let buffer = ByteArray.new # - # try! client.read(into: buffer, size: 4) + # client.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> UnixSocket { - let fd = try { - _INKO.socket_accept_unix(@fd, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub accept -> Result[UnixSocket, Error] { + match _INKO.socket_accept(@fd, @deadline) { + case { @tag = 0, @value = fd } -> Result.Ok( + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } - - UnixSocket { @fd = fd, @deadline = NO_DEADLINE } } # Sends a `String` to the given address. @@ -1277,18 +1270,19 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # - # try! socket.bind('/tmp/test.sock') - # try! socket.send_string_to(string: 'hello', address: '/tmp/test.sock') + # socket.bind('/tmp/test.sock').unwrap + # socket.send_string_to(string: 'hello', address: '/tmp/test.sock').unwrap fn pub mut send_string_to( string: String, address: ref ToString - ) !! Error -> Int { - try { - _INKO.socket_send_string_to(@fd, string, address.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + let addr = address.to_string + + match _INKO.socket_send_string_to(@fd, string, addr, 0, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1300,19 +1294,20 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.bind('/tmp/test.sock') - # try! socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock') + # socket.bind('/tmp/test.sock').unwrap + # socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock').unwrap fn pub mut send_bytes_to( bytes: ByteArray, address: ref ToString - ) !! Error -> Int { - try { - _INKO.socket_send_bytes_to(@fd, bytes, address.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + let addr = address.to_string + + match _INKO.socket_send_bytes_to(@fd, bytes, addr, 0, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1328,94 +1323,107 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # let bytes = ByteArray.new # - # try! socket.send_string_to('hello', address: '/tmp/test.sock') + # socket.send_string_to('hello', address: '/tmp/test.sock').unwrap # - # let received_from = try! socket.receive_from(bytes: bytes, size: 5) + # let received_from = socket.receive_from(bytes: bytes, size: 5).unwrap # # bytes.to_string # => 'hello' # received_from.to_string # => '/tmp/test.sock' fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> UnixAddress { - let raw_addr = try { - _INKO.socket_receive_from(@fd, bytes, size, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[UnixAddress, Error] { + let raw_addr = match _INKO.socket_receive_from(@fd, bytes, size, @deadline) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - UnixAddress.new(addr) + Result.Ok(UnixAddress.new(addr)) } # Returns the local address of this socket. - fn pub local_address !! Error -> UnixAddress { - let raw_addr = - try _INKO.socket_local_address(@fd) else (e) throw Error.from_int(e) + fn pub local_address -> Result[UnixAddress, Error] { + let raw_addr = match _INKO.socket_local_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - UnixAddress.new(addr) + Result.Ok(UnixAddress.new(addr)) } # Returns the peer address of this socket. - fn pub peer_address !! Error -> UnixAddress { - let raw_addr = - try _INKO.socket_peer_address(@fd) else (e) throw Error.from_int(e) + fn pub peer_address -> Result[UnixAddress, Error] { + let raw_addr = match _INKO.socket_peer_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - UnixAddress.new(addr) - } - - # Returns the value of the `SO_RCVBUF` option. - fn pub receive_buffer_size !! Error -> Int { - try _INKO.socket_get_recv_size(@fd) else (e) throw Error.from_int(e) + Result.Ok(UnixAddress.new(addr)) } # Sets the value of the `SO_RCVBUF` option. - fn pub mut receive_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_recv_size(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_SNDBUF` option. - fn pub send_buffer_size !! Error -> Int { - try _INKO.socket_get_send_size(@fd) else (e) throw Error.from_int(e) + fn pub mut receive_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_recv_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_SNDBUF` option. - fn pub mut send_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_send_size(@fd, value) else (e) throw Error.from_int(e) + fn pub mut send_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_send_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try _INKO.socket_shutdown_read(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_read -> Result[Nil, Error] { + match _INKO.socket_shutdown_read(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try _INKO.socket_shutdown_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_write -> Result[Nil, Error] { + match _INKO.socket_shutdown_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try _INKO.socket_shutdown_read_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown -> Result[Nil, Error] { + match _INKO.socket_shutdown_read_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - let fd = try _INKO.socket_try_clone(@fd) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + fn pub try_clone -> Result[UnixSocket, Error] { + match _INKO.socket_try_clone(@fd) { + case { @tag = 0, @value = fd } -> Result.Ok( + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } @@ -1426,33 +1434,32 @@ impl Drop for UnixSocket { } impl Read for UnixSocket { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.socket_read(@fd, into, size, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.socket_read(@fd, into, size, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } impl Write for UnixSocket { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try { - _INKO.socket_write_bytes(@fd, bytes, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.socket_write_bytes(@fd, bytes, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut write_string(string: String) !! Error -> Int { - try { - _INKO.socket_write_string(@fd, string, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.socket_write_string(@fd, string, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A Unix datagram socket. @@ -1468,13 +1475,12 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # try! UnixDatagram.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + # UnixDatagram.new('/tmp/test.sock').unwrap + fn pub static new(address: ref ToString) -> Result[UnixDatagram, Error] { let socket = try UnixSocket.new(Type.DGRAM) try socket.bind(address) - - Self { @socket = socket } + Result.Ok(UnixDatagram { @socket = socket }) } # Connects `self` to the remote addres.s @@ -1489,12 +1495,12 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # let socket1 = try! UnixDatagram.new('/tmp/test1.sock') - # let socket2 = try! UnixDatagram.new('/tmp/test2.sock') + # let socket1 = UnixDatagram.new('/tmp/test1.sock').unwrap + # let socket2 = UnixDatagram.new('/tmp/test2.sock').unwrap # - # try! socket1.connect('/tmp/test2.sock') - fn pub mut connect(address: ref ToString) !! Error { - try @socket.connect(address) + # socket1.connect('/tmp/test2.sock').unwrap + fn pub mut connect(address: ref ToString) -> Result[Nil, Error] { + @socket.connect(address) } # Sends a `String` to the given address. @@ -1505,14 +1511,14 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # let socket = try! UnixDatagram.new('/tmp/test.sock') + # let socket = UnixDatagram.new('/tmp/test.sock').unwrap # - # try! socket.send_string_to(string: 'hello', address: '/tmp/test.sock') + # socket.send_string_to(string: 'hello', address: '/tmp/test.sock').unwrap fn pub mut send_string_to( string: String, address: ref ToString - ) !! Error -> Int { - try @socket.send_string_to(string, address) + ) -> Result[Int, Error] { + @socket.send_string_to(string, address) } # Sends a `ByteArray` to the given address. @@ -1523,15 +1529,15 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # let socket = try! UnixDatagram.new('/tmp/test.sock') + # let socket = UnixDatagram.new('/tmp/test.sock').unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock') + # socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock').unwrap fn pub mut send_bytes_to( bytes: ByteArray, address: ref ToString - ) !! Error -> Int { - try @socket.send_bytes_to(bytes, address) + ) -> Result[Int, Error] { + @socket.send_bytes_to(bytes, address) } # Receives a single datagram message on the socket, returning the address the @@ -1541,42 +1547,44 @@ class pub UnixDatagram { fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> UnixAddress { - try @socket.receive_from(bytes, size) + ) -> Result[UnixAddress, Error] { + @socket.receive_from(bytes, size) } # Returns the local address of this socket. # # See the documentation of `UnixSocket.local_address` for more information. - fn pub local_address !! Error -> UnixAddress { - try @socket.local_address + fn pub local_address -> Result[UnixAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UnixDatagram, Error] { + @socket.try_clone.map fn (sock) { UnixDatagram { @socket = sock } } } } impl Read for UnixDatagram { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for UnixDatagram { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A Unix stream socket connected to another Unix socket. @@ -1596,15 +1604,14 @@ class pub UnixClient { # # import std::net::socket::(UnixServer, UnixClient) # - # let listener = try! UnixServer.new('/tmp/test.sock') + # let listener = UnixServer.new('/tmp/test.sock').unwrap # - # try! UnixClient.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + # UnixClient.new('/tmp/test.sock').unwrap + fn pub static new(address: ref ToString) -> Result[UnixClient, Error] { let socket = try UnixSocket.new(Type.STREAM) try socket.connect(address) - - Self { @socket = socket } + Result.Ok(UnixClient { @socket = socket }) } # Creates a new `UnixClient` but limits the amount of time spent waiting for @@ -1620,77 +1627,80 @@ class pub UnixClient { # import std::net::socket::UnixClient # import std::time::Duration # - # try! UnixClient.with_timeout( - # address: '/tmp/test.sock' - # timeout_after: Duration.from_secs(5) - # ) + # UnixClient + # .with_timeout( + # address: '/tmp/test.sock' + # timeout_after: Duration.from_secs(5) + # ) + # .unwrap fn pub static with_timeout( address: ref ToString, timeout_after: ToInstant - ) !! Error -> Self { + ) -> Result[UnixClient, Error] { let socket = try UnixSocket.new(Type.STREAM) let _ = socket.timeout_after = timeout_after try socket.connect(address) - - Self { @socket = socket } + Result.Ok(UnixClient { @socket = socket }) } # Returns the local address of this socket. # # See the documentation of `UnixSocket.local_address` for more information. - fn pub local_address !! Error -> UnixAddress { - try @socket.local_address + fn pub local_address -> Result[UnixAddress, Error] { + @socket.local_address } # Returns the peer address of this socket. # # See the documentation of `UnixSocket.peer_address` for more information. - fn pub peer_address !! Error -> UnixAddress { - try @socket.peer_address + fn pub peer_address -> Result[UnixAddress, Error] { + @socket.peer_address } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try @socket.shutdown_read + fn pub mut shutdown_read -> Result[Nil, Error] { + @socket.shutdown_read } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try @socket.shutdown_write + fn pub mut shutdown_write -> Result[Nil, Error] { + @socket.shutdown_write } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try @socket.shutdown + fn pub mut shutdown -> Result[Nil, Error] { + @socket.shutdown } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UnixClient, Error] { + @socket.try_clone.map fn (sock) { UnixClient { @socket = sock } } } } impl Read for UnixClient { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for UnixClient { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A Unix socket server that can accept incoming connections. @@ -1708,14 +1718,13 @@ class pub UnixServer { # # import std::net::socket::UnixServer # - # try! UnixServer.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + # UnixServer.new('/tmp/test.sock').unwrap + fn pub static new(address: ref ToString) -> Result[UnixServer, Error] { let socket = try UnixSocket.new(Type.STREAM) try socket.bind(address) try socket.listen - - Self { @socket = socket } + Result.Ok(UnixServer { @socket = socket }) } # Accepts a new incoming connection from `self`. @@ -1728,33 +1737,33 @@ class pub UnixServer { # # import std::net::socket::(UnixServer, UnixClient) # - # let listener = try! UnixServer.new('/tmp/test.sock') - # let client = try! UnixClient.new('/tmp/test.sock') + # let listener = UnixServer.new('/tmp/test.sock').unwrap + # let client = UnixClient.new('/tmp/test.sock').unwrap # # client.write_string('ping') # - # let connection = try! listener.accept + # let connection = listener.accept.unwrap # let buffer = ByteArray.new # - # try! connection.read(into: buffer, size: 4) + # connection.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> UnixClient { - UnixClient { @socket = try @socket.accept } + fn pub accept -> Result[UnixClient, Error] { + @socket.accept.map fn (sock) { UnixClient { @socket = sock } } } # Returns the local address of this socket. # # See the documentation of `UnixSocket.local_address` for more information. - fn pub local_address !! Error -> UnixAddress { - try @socket.local_address + fn pub local_address -> Result[UnixAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UnixServer, Error] { + @socket.try_clone.map fn (sock) { UnixServer { @socket = sock } } } } diff --git a/libstd/src/std/nil.inko b/std/src/std/nil.inko similarity index 84% rename from libstd/src/std/nil.inko rename to std/src/std/nil.inko index 8bd98e684..f417765e8 100644 --- a/libstd/src/std/nil.inko +++ b/std/src/std/nil.inko @@ -15,19 +15,19 @@ import std::fmt::(Format, Formatter) class builtin Nil { # Returns a new `Nil`. fn pub static new -> Nil { - _INKO.get_nil + nil } } -impl Equal for Nil { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Nil] for Nil { + fn pub ==(other: ref Nil) -> Bool { true } } -impl Clone for Nil { - fn pub clone -> Self { - _INKO.get_nil +impl Clone[Nil] for Nil { + fn pub clone -> Nil { + nil } } diff --git a/libstd/src/std/ops.inko b/std/src/std/ops.inko similarity index 54% rename from libstd/src/std/ops.inko rename to std/src/std/ops.inko index 5d3a26fac..3fc8bd5ce 100644 --- a/libstd/src/std/ops.inko +++ b/std/src/std/ops.inko @@ -4,83 +4,79 @@ # # This trait is generic as types may wish to customise the type on the # right-hand side of the operator. -trait pub Add[T] { - # Adds the given object to `self`, producing a new instance of the same type - # as `self`. - fn pub +(other: ref T) -> Self +trait pub Add[T, R] { + # Adds the given object to `self`. + fn pub +(other: ref T) -> R } # The binary `-` operator. # # This trait is generic as types may wish to customise the type on the # right-hand side of the operator. -trait pub Subtract[T] { - # Subtracts the given object from `self`, producing a new instance of the - # same type as `self`. - fn pub -(other: ref T) -> Self +trait pub Subtract[T, R] { + # Subtracts the given object from `self`. + fn pub -(other: ref T) -> R } # The binary `/` operator. -trait pub Divide { - # Divides `self` by the given object, producing a new one of the same type. - fn pub /(other: ref Self) -> Self +trait pub Divide[T, R] { + # Divides `self` by the given object. + fn pub /(other: ref T) -> R } # The binary `*` operator. -trait pub Multiply { - # Multiplies `self` with the given object, producing a new one of the same - # type. - fn pub *(other: ref Self) -> Self +trait pub Multiply[T, R] { + # Multiplies `self` with the given object. + fn pub *(other: ref T) -> R } # The binary `%` operator. -trait pub Modulo { - # Gets the remainder after dividing `self` by the given object, producing a - # new one of the same type. - fn pub %(other: ref Self) -> Self +trait pub Modulo[T, R] { + # Gets the remainder after dividing `self` by the given object. + fn pub %(other: ref T) -> R } # The binary `**` operator. -trait pub Power { +trait pub Power[T, R] { # Raises `self` to the power of the given exponent. - fn pub **(other: ref Self) -> Self + fn pub **(other: ref T) -> R } # The binary `&` (bitwise AND) operator. -trait pub BitAnd { +trait pub BitAnd[T, R] { # Returns the result of a bitwise AND with `self` and the given object. - fn pub &(other: ref Self) -> Self + fn pub &(other: ref T) -> R } # The binary `|` (bitwise OR) operator. -trait pub BitOr { +trait pub BitOr[T, R] { # Returns the result of a bitwise OR with `self` and the given object. - fn pub |(other: ref Self) -> Self + fn pub |(other: ref T) -> R } # The binary `^` operator. -trait pub BitXor { +trait pub BitXor[T, R] { # Returns the result of a bitwise XOR with `self` and the given object. - fn pub ^(other: ref Self) -> Self + fn pub ^(other: ref T) -> R } # The binary `<<` operator. -trait pub ShiftLeft { +trait pub ShiftLeft[T, R] { # Returns the result of a bitwise shift to the left with `self` and the given # object. - fn pub <<(other: ref Self) -> Self + fn pub <<(other: ref T) -> R } # The binary `>>` operator. -trait pub ShiftRight { +trait pub ShiftRight[T, R] { # Returns the result of a bitwise shift to the right with `self` and the # given object. - fn pub >>(other: ref Self) -> Self + fn pub >>(other: ref T) -> R } # The binary `>>>` operator. -trait pub UnsignedShiftRight { +trait pub UnsignedShiftRight[T, R] { # Casts `self` to an unsigned integer, shifts it to the right, then returns # the result as a signed integer. - fn pub >>>(other: ref Self) -> Self + fn pub >>>(other: ref T) -> R } diff --git a/libstd/src/std/option.inko b/std/src/std/option.inko similarity index 90% rename from libstd/src/std/option.inko rename to std/src/std/option.inko index b80c08105..1e25815f5 100644 --- a/libstd/src/std/option.inko +++ b/std/src/std/option.inko @@ -17,7 +17,10 @@ import std::process::(panic) # An `Option` is is either a `Some` containing a value, or a `None` that doesn't # contain a value. class pub enum Option[T] { + # A value of type `T`. case Some(T) + + # The lack of a value. case None # Returns an optional immutable reference to the wrapped value. @@ -33,29 +36,30 @@ class pub enum Option[T] { } } - # Returns an optional mutable reference to the wrapped value. + # Returns the wrapped value. + # + # This method panics when used with a `None`. # # # Examples # - # Option.Some([10]).as_mut # => Option.Some(mut [10]) - fn pub mut as_mut -> Option[mut T] { + # Option.Some(10).unwrap # => 10 + fn pub move unwrap -> T { match self { - case Some(v) -> Option.Some(v) - case None -> Option.None + case Some(v) -> v + case _ -> panic("Option.unwrap can't unwrap a None") } } - # Returns the wrapped value. - # - # This method panics when used with a `None`. + # Returns the wrapped value, triggering a panic with a custom message for a + # `None`. # # # Examples # - # Option.Some(10).unwrap # => 10 - fn pub move unwrap -> T { + # Option.Some(10).expect('a number must be present') # => 10 + fn pub move expect(message: String) -> T { match self { case Some(v) -> v - case None -> panic("A None can't be unwrapped") + case _ -> panic(message) } } @@ -174,7 +178,21 @@ class pub enum Option[T] { } } -impl Equal for Option if T: Equal { +impl Option if T: mut { + # Returns an optional mutable reference to the wrapped value. + # + # # Examples + # + # Option.Some([10]).as_mut # => Option.Some(mut [10]) + fn pub mut as_mut -> Option[mut T] { + match self { + case Some(v) -> Option.Some(v) + case None -> Option.None + } + } +} + +impl Equal[Option[T]] for Option if T: Equal[T] { # Returns `true` if `self` and the given `Option` are equal. # # Two options are considered equal to each other if: @@ -196,7 +214,7 @@ impl Equal for Option if T: Equal { # Comparing two None values: # # Option.None == Option.None # => true - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Option[T]) -> Bool { match self { case Some(ours) -> match other { case Some(theirs) -> ours == theirs @@ -210,8 +228,8 @@ impl Equal for Option if T: Equal { } } -impl Clone for Option if T: Clone { - fn pub clone -> Self { +impl Clone[Option[T]] for Option if T: Clone[T] { + fn pub clone -> Option[T] { match self { case Some(v) -> Option.Some(v.clone) case None -> Option.None diff --git a/std/src/std/process.inko b/std/src/std/process.inko new file mode 100644 index 000000000..9e0ddfe7e --- /dev/null +++ b/std/src/std/process.inko @@ -0,0 +1,21 @@ +# Lightweight Inko processes. +import std::time::Duration + +# Terminates the program with an error message. +# +# A panic is an unrecoverable error meant to guard against code bugs. For +# runtime errors, use `try` and `throw` instead. +fn pub panic(message: String) -> Never { + _INKO.panic(message) +} + +# Suspends the current process for at least the given duration. +# +# The actual time the process is suspended for may be larger than the given +# duration. +# +# If the specified duration is less than or equal to zero, the process is +# rescheduled immediately. +fn pub sleep(time: ref Duration) { + _INKO.process_suspend(time.to_nanos) +} diff --git a/libstd/src/std/rand.inko b/std/src/std/rand.inko similarity index 91% rename from libstd/src/std/rand.inko rename to std/src/std/rand.inko index 1fc2ebfef..ad95497e6 100644 --- a/libstd/src/std/rand.inko +++ b/std/src/std/rand.inko @@ -20,8 +20,8 @@ class pub Random { # import std::rand::Random # # Random.from_int(42) - fn pub static from_int(seed: Int) -> Self { - Self { @rng = _INKO.random_from_int(seed) } + fn pub static from_int(seed: Int) -> Random { + Random { @rng = _INKO.random_from_int(seed) } } # Returns a new `Random` seeded using a cryptographically secure seed. @@ -34,8 +34,8 @@ class pub Random { # import std::rand::Random # # Random.new - fn pub static new -> Self { - Self { @rng = _INKO.random_new } + fn pub static new -> Random { + Random { @rng = _INKO.random_new } } # Returns a randomly generated `Int`. @@ -99,13 +99,13 @@ class pub Shuffle { let @rng: Random # Returns a new `Shuffle` that sorts values in a random order. - fn pub static new -> Self { - Self { @rng = Random.new } + fn pub static new -> Shuffle { + Shuffle { @rng = Random.new } } # Returns a new `Shuffle` that uses the given seed for sorting values. - fn pub static from_int(seed: Int) -> Self { - Self { @rng = Random.from_int(seed) } + fn pub static from_int(seed: Int) -> Shuffle { + Shuffle { @rng = Random.from_int(seed) } } # Sorts the values of the given `Array` in place such that they are in a diff --git a/libstd/src/std/range.inko b/std/src/std/range.inko similarity index 87% rename from libstd/src/std/range.inko rename to std/src/std/range.inko index e26c6d5c0..32bd69e5f 100644 --- a/libstd/src/std/range.inko +++ b/std/src/std/range.inko @@ -24,7 +24,7 @@ import std::hash::(Hash, Hasher) import std::iter::(Enum, Iter) # A range of integers. -trait pub Range: Contains[Int] + Equal + Hash + Format { +trait pub Range: Contains[Int] + Hash + Format { # Returns the first value in the range. fn pub start -> Int @@ -38,13 +38,13 @@ trait pub Range: Contains[Int] + Equal + Hash + Format { # # If the number of values in `self` can't be evenly divided by `amount`, the # last range is shorter. - fn pub step_by(amount: Int) -> Enum[Range, Never] + fn pub step_by(amount: Int) -> Enum[Range] # Returns the number of values in this range. fn pub length -> Int # Returns an iterator over the values in `self`. - fn pub iter -> Iter[Int, Never] + fn pub iter -> Iter[Int] } # An inclusive range of integers. @@ -56,8 +56,8 @@ class pub InclusiveRange { let pub @end: Int # Returns a new `InclusiveRange` over the given values. - fn pub static new(start: Int, end: Int) -> Self { - Self { @start = start, @end = end } + fn pub static new(start: Int, end: Int) -> InclusiveRange { + InclusiveRange { @start = start, @end = end } } } @@ -74,7 +74,7 @@ impl Range for InclusiveRange { true } - fn pub step_by(amount: Int) -> Enum[InclusiveRange, Never] { + fn pub step_by(amount: Int) -> Enum[InclusiveRange] { let mut current = @start let max = @end @@ -93,7 +93,7 @@ impl Range for InclusiveRange { if @end >= @start { @end - @start + 1 } else { 0 } } - fn pub iter -> Enum[Int, Never] { + fn pub iter -> Enum[Int] { let mut current = @start let end = @end @@ -121,7 +121,7 @@ impl Contains[Int] for InclusiveRange { } } -impl Equal for InclusiveRange { +impl Equal[InclusiveRange] for InclusiveRange { # Returns `true` if `self` and `other` are identical. # # # Examples @@ -133,7 +133,7 @@ impl Equal for InclusiveRange { # Comparing two different ranges: # # 1.to(10) == 1.to(5) # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref InclusiveRange) -> Bool { @start == other.start and @end == other.end } } @@ -162,8 +162,8 @@ class pub ExclusiveRange { let pub @end: Int # Returns a new `ExclusiveRange` over the given values. - fn pub static new(start: Int, end: Int) -> Self { - Self { @start = start, @end = end } + fn pub static new(start: Int, end: Int) -> ExclusiveRange { + ExclusiveRange { @start = start, @end = end } } } @@ -180,7 +180,7 @@ impl Range for ExclusiveRange { false } - fn pub step_by(amount: Int) -> Enum[ExclusiveRange, Never] { + fn pub step_by(amount: Int) -> Enum[ExclusiveRange] { let mut current = @start let max = @end @@ -199,7 +199,7 @@ impl Range for ExclusiveRange { if @end >= @start { @end - @start } else { 0 } } - fn pub iter -> Enum[Int, Never] { + fn pub iter -> Enum[Int] { let mut current = @start let end = @end @@ -227,7 +227,7 @@ impl Contains[Int] for ExclusiveRange { } } -impl Equal for ExclusiveRange { +impl Equal[ExclusiveRange] for ExclusiveRange { # Returns `true` if `self` and `other` are identical. # # # Examples @@ -239,7 +239,7 @@ impl Equal for ExclusiveRange { # Comparing two different ranges: # # 1.until(10) == 1.until(5) # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref ExclusiveRange) -> Bool { @start == other.start and @end == other.end } } diff --git a/std/src/std/result.inko b/std/src/std/result.inko new file mode 100644 index 000000000..c005c7f94 --- /dev/null +++ b/std/src/std/result.inko @@ -0,0 +1,258 @@ +# Types for error handling. +# +# The `Result` type is used for error handling, and wraps either an OK value or +# an error value. +import std::clone::Clone +import std::cmp::Equal +import std::fmt::(Format, Formatter) + +# A type that represents either success (`Ok(T)`) or failure (`Error(E)`). +class pub enum Result[T, E] { + # The case and value for a successful result. + case Ok(T) + + # The case and value for an error. + case Error(E) + + # Returns `true` if `self` is an `Ok`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(42) + # + # res.ok? # => true + fn pub ok? -> Bool { + match self { + case Ok(_) -> true + case _ -> false + } + } + + # Returns `true` if `self` is an `Err`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Error('oops!') + # + # res.error? # => true + fn pub error? -> Bool { + match self { + case Error(_) -> true + case _ -> false + } + } + + # Converts `self` into an `Option[T]`. + # + # If `self` is an `Ok`, a `Some(T)` is returned, otherwise a `None` is + # returned. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(42) + # + # res.ok # => Option.Some(42) + fn pub move ok -> Option[T] { + match self { + case Ok(v) -> Option.Some(v) + case _ -> Option.None + } + } + + # Converts `self` into an `Option[E]`. + # + # If `self` is an `Error`, a `Some(E)` is returned, otherwise a `None` is + # returned. + # + # # Examples + # + # let res: Result[Int, String] = Result.Error('oops!') + # + # res.error # => Option.Some(42) + fn pub move error -> Option[E] { + match self { + case Error(v) -> Option.Some(v) + case _ -> Option.None + } + } + + # Unwraps `self` into its `Ok` value. + # + # # Panics + # + # This method panics if `self` is an `Error`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(42) + # + # res.unwrap # => 42 + fn pub move unwrap -> T { + match self { + case Ok(val) -> val + case _ -> panic("Result.unwrap can't unwrap an Error") + } + } + + # Returns the wrapped `Ok` value, triggering a panic with a custom message for + # a `Error`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(10) + # + # res.expect('a number must be present') # => 10 + fn pub move expect(message: String) -> T { + match self { + case Ok(v) -> v + case _ -> panic(message) + } + } + + # Unwraps `self` into its `Ok` value, returning a default value if `self` is + # an `Error`. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # let bar: Result[Int, String] = Result.Error('oops!') + # + # foo.unwrap_or(0) # => 42 + # bar.unwrap_or(0) # => 0 + fn pub move unwrap_or(default: T) -> T { + match self { + case Ok(val) -> val + case _ -> default + } + } + + # Unwraps `self` into its `Ok` value, returning a default value if `self` is + # an `Error`. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # let bar: Result[Int, String] = Result.Error('oops!') + # + # foo.unwrap_or_else fn { 0 } # => 42 + # bar.unwrap_or_else fn { 0 } # => 0 + fn pub move unwrap_or_else(block: fn -> T) -> T { + match self { + case Ok(val) -> val + case _ -> block.call + } + } + + # Maps a `Result[T, E]` into a `Result[R, E]`. + # + # If `self` is an `Ok`, the supplied closure is called and its value used to + # return a new `Ok`. If `self` is an `Error`, the `Error` is returned as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # + # res.map fn (val) { val.to_string } # => Result.Ok('42') + fn pub move map[R](block: fn (T) -> R) -> Result[R, E] { + match self { + case Ok(v) -> Result.Ok(block.call(v)) + case Error(e) -> Result.Error(e) + } + } + + # Maps a `Result[T, E]` into a `Result[T, R]`. + # + # If `self` is an `Error`, the supplied closure is called and its value used + # to return a new `Error`. If `self` is an `Ok`, the `Ok` is returned as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Error('oops!') + # + # res.map_error fn (val) { val.to_upper } # => Result.Ok('OOPS!') + fn pub move map_error[R](block: fn (E) -> R) -> Result[T, R] { + match self { + case Ok(v) -> Result.Ok(v) + case Error(e) -> Result.Error(block.call(e)) + } + } + + # Maps a `Result[T, E]` into a `Result[R, E]`. + # + # If `self` is an `Ok`, the supplied closure is called and its returned + # `Result` is returned. If `self` is an `Error`, the `Error` is returned + # as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # + # res.then fn (val) { Result.Ok(val.to_string) } # => Result.Ok('42') + fn pub move then[R](block: fn (T) -> Result[R, E]) -> Result[R, E] { + match self { + case Ok(v) -> block.call(v) + case Error(e) -> Result.Error(e) + } + } + + # Maps a `Result[T, E]` into a `Result[T, R]`. + # + # If `self` is an `Error`, the supplied closure is called and its returned + # `Result` is returned. If `self` is an `Ok`, the `Ok` is returned as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Error('oops!') + # + # res.else fn (val) { + # Result.Error(val.to_upper) + # } + # # => Result.Error('OOPS!') + fn pub move else[R](block: fn (E) -> Result[T, R]) -> Result[T, R] { + match self { + case Ok(v) -> Result.Ok(v) + case Error(e) -> block.call(e) + } + } +} + +impl Clone[Result[T, E]] for Result if T: Clone[T], E: Clone[E] { + fn pub clone -> Result[T, E] { + match self { + case Ok(v) -> Result.Ok(v.clone) + case Error(v) -> Result.Error(v.clone) + } + } +} + +impl Equal[Result[T, E]] for Result if T: Equal[T], E: Equal[E] { + fn pub ==(other: ref Result[T, E]) -> Bool { + match self { + case Ok(ours) -> match other { + case Ok(theirs) -> ours == theirs + case _ -> false + } + case Error(ours) -> match other { + case Error(theirs) -> ours == theirs + case _ -> false + } + } + } +} + +impl Format for Result if T: Format, E: Format { + fn pub fmt(formatter: mut Formatter) { + match self { + case Ok(v) -> { + formatter.write('Ok(') + v.fmt(formatter) + formatter.write(')') + } + case Error(v) -> { + formatter.write('Error(') + v.fmt(formatter) + formatter.write(')') + } + } + } +} diff --git a/libstd/src/std/set.inko b/std/src/std/set.inko similarity index 93% rename from libstd/src/std/set.inko rename to std/src/std/set.inko index 16c179312..f02da9fd2 100644 --- a/libstd/src/std/set.inko +++ b/std/src/std/set.inko @@ -8,15 +8,15 @@ import std::iter::Iter # # The order of values in this Set are not guaranteed. For values to be stored in # a `Set` they must implement the `Hash` and `Equal` traits. -class pub Set[V: Hash + Equal] { +class pub Set[V: Hash + Equal[V]] { # The Map used for storing values. # # The keys are the values inserted in this `Set`, the values are always set to # `True`. let @map: Map[V, Bool] - fn pub static new -> Self { - Self { @map = Map.new } + fn pub static new -> Set[V] { + Set { @map = Map.new } } # Inserts a new value into the `Set`. @@ -35,7 +35,7 @@ class pub Set[V: Hash + Equal] { fn pub mut insert(value: V) -> Bool { if @map.contains?(value) { return false } - @map[value] = true + @map.set(value, true) true } @@ -80,7 +80,7 @@ class pub Set[V: Hash + Equal] { # set.insert(20) # # set.iter.next # => 10 - fn pub iter -> Iter[ref V, Never] { + fn pub iter -> Iter[ref V] { @map.keys } @@ -106,7 +106,7 @@ class pub Set[V: Hash + Equal] { } } -impl Equal for Set { +impl Equal[Set[V]] for Set { # Returns `True` if `self` and the given `Set` are identical to each # other. # @@ -123,7 +123,7 @@ impl Equal for Set { # set2.insert(10) # # set1 == set2 # => True - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Set[V]) -> Bool { if length != other.length { return false } iter.all? fn (val) { other.contains?(val) } diff --git a/std/src/std/stdio.inko b/std/src/std/stdio.inko new file mode 100644 index 000000000..8e2874e71 --- /dev/null +++ b/std/src/std/stdio.inko @@ -0,0 +1,81 @@ +# STDIN, STDOUT, and STDERR streams. +import std::io::(Error, Read, Write) + +# The standard input stream of the current OS process. +class pub STDIN { + # Returns a new handle to the input stream. + fn pub static new -> STDIN { + STDIN {} + } +} + +impl Read for STDIN { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.stdin_read(into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +# The standard output stream of the current OS process. +# +# To make it easier to write to STDOUT, any errors produced while writing are +# ignored. +class pub STDOUT { + # Returns a new handle to the output stream. + fn pub static new -> STDOUT { + STDOUT {} + } +} + +impl Write for STDOUT { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.stdout_write_bytes(bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.stdout_write_string(string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(_INKO.stdout_flush) + } +} + +# The standard error stream of the current OS process. +# +# To make it easier to write to STDERR, any errors produced while writing are +# ignored. +class pub STDERR { + # Returns a new handle to the error stream. + fn pub static new -> STDERR { + STDERR {} + } +} + +impl Write for STDERR { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.stderr_write_bytes(bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.stderr_write_string(string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(_INKO.stderr_flush) + } +} diff --git a/libstd/src/std/string.inko b/std/src/std/string.inko similarity index 95% rename from libstd/src/std/string.inko rename to std/src/std/string.inko index df3dafff2..39cef48fb 100644 --- a/libstd/src/std/string.inko +++ b/std/src/std/string.inko @@ -5,6 +5,7 @@ # Various methods for `String` may operate on or mention "characters". Whenever # this is the case, we are referring to extended grapheme clusters, _not_ # Unicode scalar values or bytes. +import std::array::(bounds_check) import std::byte_array::(IntoByteArray, ToByteArray) import std::clone::Clone import std::cmp::(Contains, Equal) @@ -12,7 +13,6 @@ import std::drop::Drop import std::fmt::(Format, Formatter) import std::fs::path::(IntoPath, Path, ToPath) import std::hash::(Hash, Hasher) -import std::index::(bounds_check) import std::io::Read import std::iter::(Bytes as BytesTrait, EOF, Enum, Iter) import std::ops::Add @@ -275,7 +275,7 @@ class builtin String { # # iter.next # => Option.Some('foo') # iter.next # => Option.Some('bar') - fn pub split(separator: String) -> Enum[String, Never] { + fn pub split(separator: String) -> Enum[String] { let mut offset = 0 Enum.new fn move { @@ -458,7 +458,7 @@ class builtin String { return } - match ESCAPE_TABLE[byte] { + match ESCAPE_TABLE.get(byte) { case -1 -> buff.push(byte) case byte -> { buff.push(BSLASH) @@ -495,9 +495,9 @@ impl IntoByteArray for String { } } -impl Clone for String { - fn pub clone -> Self { - _INKO.string_clone(self) +impl Clone[String] for String { + fn pub clone -> String { + self } } @@ -513,7 +513,7 @@ impl IntoString for String { } } -impl Equal for String { +impl Equal[String] for String { # Returns `true` if the current `String` and `other` are equal to each other. # # # Examples @@ -525,7 +525,7 @@ impl Equal for String { # Returns `false` if two Strings are not equal: # # 'foo' == 'bar' # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref String) -> Bool { _INKO.string_eq(self, other) } } @@ -542,14 +542,14 @@ impl Hash for String { } } -impl Add[String] for String { +impl Add[String, String] for String { # Concatenates `self` and the given `String`, returning a new `String`. # # # Examples # # 'hello ' + 'world' # => 'hello world' - fn pub +(other: ref Self) -> Self { - _INKO.strings(self, other) + fn pub +(other: ref String) -> String { + _INKO.string_concat(self, other) } } @@ -590,14 +590,11 @@ class pub Characters { let @iter: Any } -impl Iter[String, Never] for Characters { +impl Iter[String] for Characters { fn pub mut next -> Option[String] { - let val = _INKO.string_characters_next(@iter) - - if _INKO.is_undefined(val) { - Option.None - } else { - Option.Some(val as String) + match _INKO.string_characters_next(@iter) { + case { @tag = 0, @value = v } -> Option.Some(v as String) + case _ -> Option.None } } } @@ -614,7 +611,7 @@ class pub Bytes { let @index: Int } -impl Iter[Int, Never] for Bytes { +impl Iter[Int] for Bytes { fn pub mut next -> Option[Int] { match next_byte { case EOF -> Option.None @@ -623,7 +620,7 @@ impl Iter[Int, Never] for Bytes { } } -impl BytesTrait[Never] for Bytes { +impl BytesTrait for Bytes { fn pub mut next_byte -> Int { if @index < @string.size { _INKO.string_byte(@string, @index := @index + 1) @@ -634,7 +631,7 @@ impl BytesTrait[Never] for Bytes { } impl Read for Bytes { - fn pub mut read(into: mut ByteArray, size: Int) -> Int { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Never] { let mut read = 0 while read < size { @@ -647,10 +644,10 @@ impl Read for Bytes { } } - read + Result.Ok(read) } - fn pub mut read_all(bytes: mut ByteArray) -> Int { + fn pub mut read_all(bytes: mut ByteArray) -> Result[Int, Never] { read(into: bytes, size: @string.size - @index) } } @@ -696,8 +693,8 @@ class pub StringBuffer { let @strings: Array[String] # Returns a new empty `StringBuffer`. - fn pub static new -> Self { - Self { @strings = [] } + fn pub static new -> StringBuffer { + StringBuffer { @strings = [] } } # Returns a new `StringBuffer` from an existing `Array`. @@ -711,8 +708,8 @@ class pub StringBuffer { # let strings = ['foo', 'bar'] # # StringBuffer.from_array(strings).to_string # => 'foobar' - fn pub static from_array(strings: Array[String]) -> Self { - Self { @strings = strings } + fn pub static from_array(strings: Array[String]) -> StringBuffer { + StringBuffer { @strings = strings } } # Adds the given `String` to the buffer. diff --git a/libstd/src/std/sys.inko b/std/src/std/sys.inko similarity index 72% rename from libstd/src/std/sys.inko rename to std/src/std/sys.inko index 58c4972f5..0590cc0bd 100644 --- a/libstd/src/std/sys.inko +++ b/std/src/std/sys.inko @@ -5,26 +5,6 @@ import std::int::ToInt import std::io::(Error, Read, Write) import std::string::IntoString -# Returns `True` if the program is running on Windows. -fn pub windows? -> Bool { - _INKO.env_platform == 0 -} - -# Returns `True` if the program is running on Linux. -fn pub linux? -> Bool { - _INKO.env_platform == 3 -} - -# Returns `True` if the program is running on a Unix system. -fn pub unix? -> Bool { - _INKO.env_platform != 0 -} - -# Returns `True` if the program is running on Mac OS. -fn pub mac? -> Bool { - _INKO.env_platform == 1 -} - # Returns the number of available CPU cores of the current system. # # This returns the number of _logical_ cores, with a minimum value of 1. @@ -93,13 +73,13 @@ impl ToInt for Stream { # # import std::sys::(Command, Stream) # -# try! Command.new('ls').stdout(Stream.Piped).spawn +# Command.new('ls').stdout(Stream.Piped).spawn.unwrap # # We can also ignore a stream: # # import std::sys::(Command, Stream) # -# try! Command.new('ls').stderr(Stream.Null).spawn +# Command.new('ls').stderr(Stream.Null).spawn.unwrap # # # Waiting for the child process # @@ -109,9 +89,9 @@ impl ToInt for Stream { # # import std::sys::Command # -# let child = try! Command.new('ls') +# let child = Command.new('ls').unwrap # -# try! child.wait +# child.wait.unwrap # # There's also `ChildProcess.try_wait`, which returns immediately if the process # is still running; instead of waiting for it to finish. @@ -122,11 +102,11 @@ impl ToInt for Stream { # # import std::sys::Command # -# let child = try! Command.new('ls') +# let child = Command.new('ls').unwrap # let bytes = ByteArray.new # -# try! child.wait -# try! child.stdout.read_all(bytes) +# child.wait.unwrap +# child.stdout.read_all(bytes).unwrap class pub Command { # The path to the program to spawn. let @program: String @@ -173,8 +153,8 @@ class pub Command { # import std::sys::Command # # Command.new('/usr/bin/ls') - fn pub static new(program: IntoString) -> Self { - Self { + fn pub static new(program: IntoString) -> Command { + Command { @program = program.into_string, @stdin = Stream.Inherit, @stdout = Stream.Inherit, @@ -241,7 +221,7 @@ class pub Command { # # Command.new('env').variable(name: 'FOO', value: 'bar') fn pub mut variable(name: String, value: String) { - @variables[name] = value + @variables.set(name, value) } # Adds or updates multiple environment variables to the command. @@ -285,10 +265,10 @@ class pub Command { # # # Examples # - # let child = try! Command.new('ls').spawn + # let child = Command.new('ls').spawn.unwrap # - # try! child.wait - fn pub spawn !! Error -> ChildProcess { + # child.wait.unwrap + fn pub spawn -> Result[ChildProcess, Error] { let vars = [] @variables.iter.each fn (entry) { @@ -296,23 +276,18 @@ class pub Command { vars.push(entry.value) } - let directory = @directory.as_ref.unwrap_or(ref '') - let raw_child = - try { - _INKO.child_process_spawn( - @program.to_string, - @arguments, - vars, - @stdin.to_int, - @stdout.to_int, - @stderr.to_int, - directory, - ) - } else (code) { - throw Error.from_int(code) - } - - ChildProcess { @raw = raw_child } + match _INKO.child_process_spawn( + @program.to_string, + @arguments, + vars, + @stdin.to_int, + @stdout.to_int, + @stderr.to_int, + @directory.as_ref.unwrap_or(ref ''), + ) { + case { @tag = 0, @value = v } -> Result.Ok(ChildProcess { @raw = v }) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } @@ -321,8 +296,8 @@ class pub ExitStatus { # The raw exit code. let @code: Int - fn pub static new(code: Int) -> Self { - Self { @code = code } + fn pub static new(code: Int) -> ExitStatus { + ExitStatus { @code = code } } # Returns `True` if the status signals success. @@ -349,8 +324,8 @@ class pub Stdin { # The child process the stream is connected to. let @process: ref ChildProcess - fn pub static new(process: ref ChildProcess) -> Self { - Self { @process = process } + fn pub static new(process: ref ChildProcess) -> Stdin { + Stdin { @process = process } } } @@ -361,27 +336,27 @@ impl Drop for Stdin { } impl Write for Stdin { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try { - _INKO.child_process_stdin_write_bytes(@process.raw, bytes) - } else (code) { - throw Error.from_int(code) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.child_process_stdin_write_bytes(@process.raw, bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut write_string(string: String) !! Error -> Int { - try { - _INKO.child_process_stdin_write_string(@process.raw, string.to_string) - } else (code) { - throw Error.from_int(code) + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.child_process_stdin_write_string( + @process.raw, + string.to_string + ) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut flush !! Error { - try { - _INKO.child_process_stdin_flush(@process.raw) - } else (code) { - throw Error.from_int(code) + fn pub mut flush -> Result[Nil, Error] { + match _INKO.child_process_stdin_flush(@process.raw) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } @@ -391,8 +366,8 @@ class pub Stdout { # The child process the stream is connected to. let @process: ref ChildProcess - fn pub static new(process: ref ChildProcess) -> Self { - Self { @process = process } + fn pub static new(process: ref ChildProcess) -> Stdout { + Stdout { @process = process } } } @@ -403,11 +378,10 @@ impl Drop for Stdout { } impl Read for Stdout { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.child_process_stdout_read(@process.raw, into, size) - } else (code) { - throw Error.from_int(code) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.child_process_stdout_read(@process.raw, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } @@ -417,8 +391,8 @@ class pub Stderr { # The child process the stream is connected to. let @process: ref ChildProcess - fn pub static new(process: ref ChildProcess) -> Self { - Self { @process = process } + fn pub static new(process: ref ChildProcess) -> Stderr { + Stderr { @process = process } } } @@ -429,11 +403,10 @@ impl Drop for Stderr { } impl Read for Stderr { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.child_process_stderr_read(@process.raw, into, size) - } else (code) { - throw Error.from_int(code) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.child_process_stderr_read(@process.raw, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } @@ -461,11 +434,11 @@ class pub ChildProcess { # Waits for the process to terminate. # # The STDIN stream is closed before waiting. - fn pub wait !! Error -> ExitStatus { - let id = - try _INKO.child_process_wait(@raw) else (code) throw Error.from_int(code) - - ExitStatus.new(id) + fn pub wait -> Result[ExitStatus, Error] { + match _INKO.child_process_wait(@raw) { + case { @tag = 0, @value = v } -> Result.Ok(ExitStatus.new(v as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns the exit status without blocking. @@ -473,13 +446,15 @@ class pub ChildProcess { # If the process is still running, a None is returned. # # This method doesn't close the STDIN stream before waiting. - fn pub try_wait !! Error -> Option[ExitStatus] { - let id = try { - _INKO.child_process_try_wait(@raw) - } else (code) { - throw Error.from_int(code) + fn pub try_wait -> Result[Option[ExitStatus], Error] { + match _INKO.child_process_try_wait(@raw) { + case { @tag = 0, @value = v } if (v as Int) == -1 -> { + Result.Ok(Option.None) + } + case { @tag = 0, @value = v } -> { + Result.Ok(Option.Some(ExitStatus.new(v as Int))) + } + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } - - if id == -1 { Option.None } else { Option.Some(ExitStatus.new(id)) } } } diff --git a/libstd/src/std/test.inko b/std/src/std/test.inko similarity index 53% rename from libstd/src/std/test.inko rename to std/src/std/test.inko index f99a83786..6dd67a2c0 100644 --- a/libstd/src/std/test.inko +++ b/std/src/std/test.inko @@ -59,17 +59,21 @@ # documentation of the `Test` type. import std::cmp::(Compare, Equal) import std::debug -import std::fmt +import std::env +import std::fmt::(DefaultFormatter, Format, Formatter) import std::fs::path::Path import std::io::Write -import std::process::(sleep, poll) +import std::process import std::rand::(Random, Shuffle) import std::stdio::STDOUT -import std::sys +import std::sys::(Command, ExitStatus, Stream, cpu_cores, exit) import std::time::(Duration, Instant) -fn format(value: ref fmt::Format) -> String { - let formatter: fmt::Formatter = fmt::DefaultFormatter.new +let CHILD_VAR = 'INKO_TEST_CHILD' +let TEST_PREFIX = 'test_' + +fn format(value: ref Format) -> String { + let formatter: Formatter = DefaultFormatter.new value.fmt(formatter) formatter.into_string @@ -106,8 +110,8 @@ class pub Test { path: Path, line: Int, code: uni fn (mut Test) - ) -> Self { - Self { + ) -> Test { + Test { @id = id, @name = name, @path = path, @@ -118,21 +122,21 @@ class pub Test { } # Asserts that the given arguments are equal to each other. - fn pub mut equal[T: Equal + fmt::Format](got: ref T, expected: ref T) { + fn pub mut equal[T: Equal[T] + Format](got: ref T, expected: ref T) { if got == expected { return } @failures.push(Failure.new(format(got), format(expected))) } # Asserts that the given arguments are not equal to each other. - fn pub mut not_equal[T: Equal + fmt::Format](got: ref T, expected: ref T) { + fn pub mut not_equal[T: Equal[T] + Format](got: ref T, expected: ref T) { if got != expected { return } @failures.push(Failure.new(format(got), format(expected))) } # Asserts that `got` is greater than `minimum`. - fn pub mut greater[T: Compare + fmt::Format](got: ref T, minimum: ref T) { + fn pub mut greater[T: Compare[T] + Format](got: ref T, minimum: ref T) { if got > minimum { return } @failures.push(Failure.new(format(got), "> {format(minimum)}")) @@ -152,34 +156,13 @@ class pub Test { @failures.push(Failure.new('true', 'false')) } - # Asserts that the given closure throws a value. - fn pub mut throw[E](block: fn !! E) { - try block.call else return - - @failures.push(Failure.new('no value is thrown', 'a value to be thrown')) - } - - # Asserts that the given closure doesn't throw a value. - fn pub mut no_throw[E: fmt::Format](block: fn !! E) { - try block.call else (err) { - @failures.push(Failure.new(format(err), 'no value is thrown')) + fn matches?(filter: ref Filter) -> Bool { + match filter { + case Pattern(pat) -> @name.contains?(pat) + case Location(path) -> @path == path + case None -> true } } - - # Asserts that an expression (which may throw) equals the given value. - fn pub mut try_equal[T: Equal + fmt::Format, E: fmt::Format]( - block: fn !! E -> T, - expected: ref T - ) { - let got = try block.call else (err) { - @failures.push(Failure.new(format(err), format(expected))) - return - } - - if got == expected { return } - - @failures.push(Failure.new(format(got), format(expected))) - } } # A type used for reporting test progress. @@ -200,7 +183,7 @@ trait pub Reporter { fn pub move finished(duration: Duration, seed: Int) -> Bool } -# A test reporter that writes to STDOUT. +# A test reporter that prints results in a simple text based format. class pub Plain { let @out: Write let @tests: Int @@ -208,8 +191,8 @@ class pub Plain { let @colors: Bool # Returns a new reporter that writes to the given output stream. - fn pub static new(out: Write, colors: Bool) -> Self { - Self { @out = out, @tests = 0, @failed = [], @colors = colors } + fn pub static new(out: Write, colors: Bool) -> Plain { + Plain { @out = out, @tests = 0, @failed = [], @colors = colors } } fn red(value: String) -> String { @@ -224,37 +207,36 @@ class pub Plain { impl Reporter for Plain { fn pub mut passed(test: Test) { @tests += 1 - - try! @out.write_string(green('.')) - try! @out.flush + @out.write_string(green('.')).unwrap + @out.flush.unwrap } fn pub mut failed(test: Test) { @tests += 1 - @failed.push(test) - - try! @out.write_string(red('F')) - try! @out.flush + @out.write_string(red('F')).unwrap + @out.flush.unwrap } fn pub move finished(duration: Duration, seed: Int) -> Bool { if @failed.length > 0 { - try! @out.print("\n\nFailures:") + @out.print("\n\nFailures:").unwrap @failed.iter.each_with_index fn (test_index, test) { test.failures.iter.each_with_index fn (failure_index, fail) { let num = "{test_index + failure_index + 1}." let indent = ' '.repeat(num.characters.count) - try! @out.print( - " + @out + .print( + " {num} Test: {test.name} {indent} Line: {fail.path}:{fail.line} {indent} {green("expected:")} {fail.expected} {indent} {red("got:")} {fail.got}" - ) + ) + .unwrap } } } @@ -270,21 +252,35 @@ impl Reporter for Plain { let failures = if failed > 0 { red("{failed} failures") } else { green('0 failures') } - try! @out.print( - "\nFinished running {@tests} tests in {dur}, {failures}, seed: {seed}" - ) + @out + .print( + "\nFinished running {@tests} tests in {dur}, {failures}, seed: {seed}" + ) + .unwrap @failed.empty? } } class async Runner { - fn async run(test: uni Test) -> uni Test { - recover { - let test = recover test + let @input: Channel[uni Test] + let @output: Channel[uni Test] + + fn async run { + loop { + let test = match @input.try_receive { + case Some(test) -> test + case None -> return + } + + let result = recover { + let test = recover test - test.code.call(test) - test + test.code.call(test) + test + } + + @output.send(result) } } } @@ -306,11 +302,16 @@ class pub Failure { # Returns a new failure for the given reason. # # The source location is determined automatically based on the call stack. - fn pub static new(got: String, expected: String) -> Self { + fn pub static new(got: String, expected: String) -> Failure { # This skips the call to stacktrace(), the call to this new(), and its # caller (which would be e.g. `assert_equal`). - match debug.stacktrace(skip: 3).pop.unwrap { - case { @path = path, @line = line } -> Self { + let frame = debug + .stacktrace(skip: 3) + .pop + .expect('at least one stack frame must be present') + + match frame { + case { @path = path, @line = line } -> Failure { @got = got, @expected = expected, @path = path, @@ -320,6 +321,138 @@ class pub Failure { } } +# A type describing how to filter out tests. +class pub enum Filter { + # Only run tests of which the description matches this pattern. + case Pattern(String) + + # Only run tests defined in the given file. + case Location(Path) + + # No filter is applied. + case None + + # Parses a `String` into a filter. + # + # If the `String` is a valid path, a `Location` is returned, if not a + # `Pattern` is returned. + fn pub static from_string(string: String) -> Filter { + if string.empty? { return Filter.None } + + match Path.new(string).expand { + case Ok(path) -> Filter.Location(path) + case _ -> Filter.Pattern(string) + } + } +} + +impl Equal[Filter] for Filter { + fn pub ==(other: ref Filter) -> Bool { + match self { + case Pattern(lhs) -> match other { + case Pattern(rhs) -> lhs == rhs + case _ -> false + } + case Location(lhs) -> match other { + case Location(rhs) -> lhs == rhs + case _ -> false + } + case None -> match other { + case None -> true + case _ -> false + } + } + } +} + +impl Format for Filter { + fn pub fmt(formatter: mut Formatter) { + match self { + case Pattern(val) -> { + formatter.write('Pattern(') + val.fmt(formatter) + formatter.write(')') + } + case Location(path) -> { + formatter.write('Location(') + path.fmt(formatter) + formatter.write(')') + } + case None -> formatter.write('None') + } + } +} + +# A child process to run as part of a unit test. +class pub Process { + let @cmd: Command + let @stdin: String + + fn static new(id: Int) -> Process { + let cmd = Command.new(env.executable.unwrap) + + cmd.stdin(Stream.Piped) + cmd.stdout(Stream.Piped) + cmd.stderr(Stream.Piped) + cmd.variable(CHILD_VAR, id.to_string) + Process { @cmd = cmd, @stdin = '' } + } + + # Adds an argument to the process. + fn pub mut argument(value: String) { + @cmd.argument(value) + } + + # Adds or updates an environment variable to the process. + fn pub mut variable(name: String, value: String) { + @cmd.variable(name, value) + } + + # Sets the data to write to STDIN. + fn pub mut stdin(bytes: String) { + @stdin = bytes + } + + # Spawns the process, waits for it to finish, and returns an `Output` + # containing the results. + fn pub move spawn -> Output { + let child = match @cmd.spawn { + case Ok(child) -> child + case Error(err) -> panic("Failed to spawn the child process: {err}") + } + + let _ = child.stdin.write_string(@stdin) + let stdout = ByteArray.new + let stderr = ByteArray.new + + child.stdout.read_all(stdout) + child.stderr.read_all(stderr) + + let status = match child.wait { + case Ok(val) -> val + case Error(err) -> panic("Failed to wait for the child process: {err}") + } + + Output { + @status = status, + @stdout = stdout.into_string, + @stderr = stderr.into_string + } + } +} + +# The output of a sub process. +class pub Output { + # The exit status of the process. + let pub @status: ExitStatus + + # The data written to STDOUT. + let pub @stdout: String + + # The data written to STDERR. + let pub @stderr: String +} + # A collection of tests to run. class pub Tests { # The number of tests to run concurrently. @@ -332,10 +465,8 @@ class pub Tests { # This defaults to the `Plain` reporter that writes to STDOUT. let pub @reporter: Reporter - # The pattern to use for filtering out tests. - # - # By default no tests are filtered out. - let pub @pattern: Option[String] + # The filter to apply to decide which tests to run. + let pub @filter: Filter # The seed to use for ordering the tests. # @@ -351,15 +482,20 @@ class pub Tests { # execution order you'll also need to set the `concurrency` field to `1`. let pub @seed: Option[Int] + # All the tests that have been registered. let @tests: Array[uni Test] + # Closures to call as part of a forking test/child process. + let @children: Array[fn] + # Returns a new test tests with its default settings. - fn pub static new -> Self { - Self { + fn pub static new -> Tests { + Tests { @tests = [], - @concurrency = sys.cpu_cores, - @reporter = Plain.new(out: STDOUT.new, colors: sys.unix?), - @pattern = Option.None, + @children = [], + @concurrency = cpu_cores, + @reporter = Plain.new(out: STDOUT.new, colors: true), + @filter = Filter.None, @seed = Option.None, } } @@ -368,33 +504,62 @@ class pub Tests { fn pub mut test(name: String, code: uni fn (mut Test)) { let id = @tests.length let test = recover { - match debug.stacktrace(skip: 2).pop.unwrap { - case { @path = path, @line = line } -> - Test.new(id, name, path, line, code) - } + let trace = debug.stacktrace(skip: 2) + let frame = trace + .reverse_iter + .find fn (frame) { frame.path.tail.starts_with?(TEST_PREFIX) } + .else fn { trace.opt(trace.length - 1) } + .expect('at least one stack frame must be present') + .clone + + Test.new(id, name, frame.path.clone, frame.line, code) } @tests.push(test) } + # Registers a new test using a fork/subprocess. + # + # This doesn't use the actual `fork()` system call. Instead, a new instance of + # the test executable is spawned such that it only runs the closure specified + # in the `child` argument. + fn pub mut fork(name: String, child: fn, test: uni fn (mut Test, Process)) { + let id = @children.length + + @children.push(child) + test(name, fn move (t) { test.call(t, Process.new(id)) }) + } + + # Registers a new test that asserts the given closure triggers a panic. + fn pub mut panic(name: String, code: uni fn) { + fork(name, code) fn (test, process) { + let output = process.spawn + let code = output.status.to_int + + if code == 101 { return } + + test.failures.push(Failure.new( + "the process exited with status {code}", + 'the process to panic with exit status 101' + )) + } + } + # Runs all the tests. fn pub move run { - let tests = match @pattern { - case Some(pat) -> { - @tests - .into_iter - .select_map fn (test) { - if test.name.contains?(pat) { - Option.Some(test) - } else { - Option.None - } - } - .to_array - } - case _ -> @tests + match env.opt(CHILD_VAR) { + case Some(id) -> return run_child(id) + case _ -> {} } + let filter = @filter + let tests = @tests + .into_iter + .select_map fn (test) { + if test.matches?(filter) { Option.Some(test) } else { Option.None } + } + .to_array + let seed = match @seed { case Some(seed) -> seed case _ -> Random.new.int @@ -405,30 +570,36 @@ class pub Tests { Shuffle.from_int(seed).sort(tests) let rep = @reporter - let futures = [] let start = Instant.new + let input = Channel.new(tests.length) + let output = Channel.new(tests.length) + let size = tests.length - while tests.length > 0 or futures.length > 0 { - # This schedules up to N concurrent tests, but only if we have any tests - # left to schedule. - while futures.length < @concurrency { - match tests.pop { - case Some(test) -> futures.push(async Runner {}.run(test)) - case _ -> break - } - } + # We send the tests first, as the test runners abort when the input channel + # is empty. + tests.into_iter.each fn (test) { input.send(test) } + + @concurrency.times fn (_) { + Runner { @input = input, @output = output }.run + } - poll(futures).into_iter.each fn (fut) { - let test: Test = fut.await + size.times fn (_) { + let test = recover output.receive - if test.failures.empty? { rep.passed(test) } else { rep.failed(test) } - } + if test.failures.empty? { rep.passed(test) } else { rep.failed(test) } } if rep.finished(start.elapsed, seed) { - sys.exit(status: 0) + exit(status: 0) } else { - sys.exit(status: 1) + exit(status: 1) + } + } + + fn pub move run_child(id: String) { + match Int.from_base10(id).then(fn (id) { @children.opt_mut(id) }) { + case Some(block) -> block.call + case _ -> process.panic("The child ID '{id}' is invalid") } } } diff --git a/libstd/src/std/time.inko b/std/src/std/time.inko similarity index 89% rename from libstd/src/std/time.inko rename to std/src/std/time.inko index f0fe3c820..311c6c397 100644 --- a/libstd/src/std/time.inko +++ b/std/src/std/time.inko @@ -48,7 +48,7 @@ class pub Duration { # # Duration.from_secs(10.5) fn pub static from_secs(secs: ToFloat) -> Duration { - Self { @nanos = (secs.to_float * NANOS_PER_SEC).to_int } + Duration { @nanos = (secs.to_float * NANOS_PER_SEC).to_int } } # Creates a new `Duration` from the given number of milliseconds. @@ -59,7 +59,7 @@ class pub Duration { # # Duration.from_millis(10) fn pub static from_millis(millis: ToInt) -> Duration { - Self { @nanos = millis.to_int * MICROS_PER_SEC } + Duration { @nanos = millis.to_int * MICROS_PER_SEC } } # Creates a new `Duration` from the given number of microseconds. @@ -70,7 +70,7 @@ class pub Duration { # # Duration.from_micros(10) fn pub static from_micros(micros: ToInt) -> Duration { - Self { @nanos = micros.to_int * MILLIS_PER_SEC } + Duration { @nanos = micros.to_int * MILLIS_PER_SEC } } # Creates a new `Duration` from the given number of nanoseconds. @@ -81,7 +81,7 @@ class pub Duration { # # Duration.from_nanos(10) fn pub static from_nanos(nanos: ToInt) -> Duration { - Self { @nanos = nanos.to_int } + Duration { @nanos = nanos.to_int } } # Returns the duration in seconds. @@ -150,32 +150,32 @@ impl ToInstant for Duration { } } -impl Clone for Duration { - fn pub clone -> Self { - Self { @nanos = @nanos } +impl Clone[Duration] for Duration { + fn pub clone -> Duration { + Duration { @nanos = @nanos } } } -impl Add[Duration] for Duration { - fn pub +(other: ref Duration) -> Self { +impl Add[Duration, Duration] for Duration { + fn pub +(other: ref Duration) -> Duration { Duration { @nanos = @nanos + other.nanos } } } -impl Subtract[Duration] for Duration { - fn pub -(other: ref Duration) -> Self { +impl Subtract[Duration, Duration] for Duration { + fn pub -(other: ref Duration) -> Duration { Duration { @nanos = @nanos - other.nanos } } } -impl Compare for Duration { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Duration] for Duration { + fn pub cmp(other: ref Duration) -> Ordering { @nanos.cmp(other.nanos) } } -impl Equal for Duration { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Duration] for Duration { + fn pub ==(other: ref Duration) -> Bool { @nanos == other.nanos } } @@ -243,7 +243,7 @@ class pub DateTime { # import std::time::DateTime # # DateTime.new - fn pub static new -> Self { + fn pub static new -> DateTime { from_timestamp(_INKO.time_system, _INKO.time_system_offset) } @@ -257,7 +257,7 @@ class pub DateTime { # import std::time::DateTime # # DateTime.utc - fn pub static utc -> Self { + fn pub static utc -> DateTime { from_timestamp(time: _INKO.time_system, utc_offset: 0) } @@ -270,7 +270,7 @@ class pub DateTime { # import std::time::DateTime # # DateTime.from_timestamp(time: 1661546853, utc_offset: 0).year # => 2022 - fn pub static from_timestamp(time: ToFloat, utc_offset: Int) -> Self { + fn pub static from_timestamp(time: ToFloat, utc_offset: Int) -> DateTime { # This implementation is based on the algorithms as described on # http://howardhinnant.github.io/date_algorithms.html, specifically the # `civil_from_days()` algorithm. @@ -303,7 +303,7 @@ class pub DateTime { let minute = (day_secs % SECS_PER_HOUR) / 60 let hour = day_secs / SECS_PER_HOUR - Self { + DateTime { @year = year, @month = month, @day = day, @@ -357,7 +357,7 @@ class pub DateTime { fn pub day_of_year -> Int { let days = if leap_year? { LEAP_DAYS } else { NORMAL_DAYS } - days[@month - 1] + @day + days.get(@month - 1) + @day } # Returns the number of days between `self` and the Unix epoch. @@ -390,14 +390,14 @@ class pub DateTime { # Converts the `DateTime` to another `DateTime` that uses UTC as the # timezone. - fn pub to_utc -> Self { + fn pub to_utc -> DateTime { DateTime.from_timestamp(time: to_float, utc_offset: 0) } } -impl Clone for DateTime { - fn pub clone -> Self { - Self { +impl Clone[DateTime] for DateTime { + fn pub clone -> DateTime { + DateTime { @year = @year, @month = @month, @day = @day, @@ -461,30 +461,30 @@ impl ToFloat for DateTime { } } -impl Add[Duration] for DateTime { - fn pub +(other: ref Duration) -> Self { +impl Add[Duration, DateTime] for DateTime { + fn pub +(other: ref Duration) -> DateTime { let timestamp = to_float + other.to_secs DateTime.from_timestamp(timestamp, utc_offset: @utc_offset) } } -impl Subtract[Duration] for DateTime { - fn pub -(other: ref Duration) -> Self { +impl Subtract[Duration, DateTime] for DateTime { + fn pub -(other: ref Duration) -> DateTime { let timestamp = to_float - other.to_secs DateTime.from_timestamp(timestamp, utc_offset: @utc_offset) } } -impl Compare for DateTime { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[DateTime] for DateTime { + fn pub cmp(other: ref DateTime) -> Ordering { to_float.cmp(other.to_float) } } -impl Equal for DateTime { - fn pub ==(other: ref Self) -> Bool { +impl Equal[DateTime] for DateTime { + fn pub ==(other: ref DateTime) -> Bool { @year == other.year and @month == other.month and @day == other.day @@ -519,8 +519,8 @@ class pub Instant { let @nanos: Int # Returns a new `Instant` representing the current time. - fn pub static new -> Self { - Self { @nanos = _INKO.time_monotonic } + fn pub static new -> Instant { + Instant { @nanos = _INKO.time_monotonic } } # Returns a `Duration` measuring the time elapsed since the point in time @@ -568,9 +568,9 @@ impl ToInstant for Instant { } } -impl Clone for Instant { - fn pub clone -> Self { - Self { @nanos = @nanos } +impl Clone[Instant] for Instant { + fn pub clone -> Instant { + Instant { @nanos = @nanos } } } @@ -586,8 +586,8 @@ impl ToFloat for Instant { } } -impl Add[Duration] for Instant { - fn pub +(other: ref Duration) -> Self { +impl Add[Duration, Instant] for Instant { + fn pub +(other: ref Duration) -> Instant { let nanos = @nanos + other.nanos if nanos < 0 { panic("Instant can't represent a negative time ({nanos})") } @@ -596,8 +596,8 @@ impl Add[Duration] for Instant { } } -impl Subtract[Duration] for Instant { - fn pub -(other: ref Duration) -> Self { +impl Subtract[Duration, Instant] for Instant { + fn pub -(other: ref Duration) -> Instant { let nanos = @nanos - other.nanos if nanos < 0 { panic("Instant can't represent a negative time ({nanos})") } @@ -606,14 +606,14 @@ impl Subtract[Duration] for Instant { } } -impl Compare for Instant { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Instant] for Instant { + fn pub cmp(other: ref Instant) -> Ordering { @nanos.cmp(other.nanos) } } -impl Equal for Instant { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Instant] for Instant { + fn pub ==(other: ref Instant) -> Bool { @nanos == other.nanos } } diff --git a/libstd/src/std/tuple.inko b/std/src/std/tuple.inko similarity index 76% rename from libstd/src/std/tuple.inko rename to std/src/std/tuple.inko index a2ac887de..01eaf2e30 100644 --- a/libstd/src/std/tuple.inko +++ b/std/src/std/tuple.inko @@ -44,8 +44,8 @@ class builtin Tuple1[A] { let pub @0: A } -impl Equal for Tuple1 if A: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple1[A]] for Tuple1 if A: Equal[A] { + fn pub ==(other: ref Tuple1[A]) -> Bool { @0 == other.0 } } @@ -56,8 +56,8 @@ impl Hash for Tuple1 if A: Hash { } } -impl Clone for Tuple1 if A: Clone { - fn pub clone -> Self { +impl Clone[Tuple1[A]] for Tuple1 if A: Clone[A] { + fn pub clone -> Tuple1[A] { (@0.clone,) } } @@ -76,8 +76,8 @@ class builtin Tuple2[A, B] { let pub @1: B } -impl Equal for Tuple2 if A: Equal, B: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple2[A, B]] for Tuple2 if A: Equal[A], B: Equal[B] { + fn pub ==(other: ref Tuple2[A, B]) -> Bool { @0 == other.0 and @1 == other.1 } } @@ -89,8 +89,8 @@ impl Hash for Tuple2 if A: Hash, B: Hash { } } -impl Clone for Tuple2 if A: Clone, B: Clone { - fn pub clone -> Self { +impl Clone[Tuple2[A, B]] for Tuple2 if A: Clone[A], B: Clone[B] { + fn pub clone -> Tuple2[A, B] { (@0.clone, @1.clone) } } @@ -112,8 +112,11 @@ class builtin Tuple3[A, B, C] { let pub @2: C } -impl Equal for Tuple3 if A: Equal, B: Equal, C: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple3[A, B, C]] for Tuple3 +if + A: Equal[A], B: Equal[B], C: Equal[C] +{ + fn pub ==(other: ref Tuple3[A, B, C]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 } } @@ -126,8 +129,8 @@ impl Hash for Tuple3 if A: Hash, B: Hash, C: Hash { } } -impl Clone for Tuple3 if A: Clone, B: Clone, C: Clone { - fn pub clone -> Self { +impl Clone[Tuple3[A, B, C]] for Tuple3 if A: Clone[A], B: Clone[B], C: Clone[C] { + fn pub clone -> Tuple3[A, B, C] { (@0.clone, @1.clone, @2.clone) } } @@ -152,8 +155,11 @@ class builtin Tuple4[A, B, C, D] { let pub @3: D } -impl Equal for Tuple4 if A: Equal, B: Equal, C: Equal, D: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple4[A, B, C, D]] for Tuple4 +if + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D] +{ + fn pub ==(other: ref Tuple4[A, B, C, D]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 and @3 == other.3 } } @@ -167,8 +173,11 @@ impl Hash for Tuple4 if A: Hash, B: Hash, C: Hash, D: Hash { } } -impl Clone for Tuple4 if A: Clone, B: Clone, C: Clone, D: Clone { - fn pub clone -> Self { +impl Clone[Tuple4[A, B, C, D]] for Tuple4 +if + A: Clone[A], B: Clone[B], C: Clone[C], D: Clone[D] +{ + fn pub clone -> Tuple4[A, B, C, D] { (@0.clone, @1.clone, @2.clone, @3.clone) } } @@ -196,8 +205,11 @@ class builtin Tuple5[A, B, C, D, E] { let pub @4: E } -impl Equal for Tuple5 if A: Equal, B: Equal, C: Equal, D: Equal, E: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple5[A, B, C, D, E]] for Tuple5 +if + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E] +{ + fn pub ==(other: ref Tuple5[A, B, C, D, E]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -216,8 +228,11 @@ impl Hash for Tuple5 if A: Hash, B: Hash, C: Hash, D: Hash, E: Hash { } } -impl Clone for Tuple5 if A: Clone, B: Clone, C: Clone, D: Clone, E: Clone { - fn pub clone -> Self { +impl Clone[Tuple5[A, B, C, D, E]] for Tuple5 +if + A: Clone[A], B: Clone[B], C: Clone[C], D: Clone[D], E: Clone[E] +{ + fn pub clone -> Tuple5[A, B, C, D, E] { (@0.clone, @1.clone, @2.clone, @3.clone, @4.clone) } } @@ -251,11 +266,11 @@ class builtin Tuple6[A, B, C, D, E, F] { let pub @5: F } -impl Equal for Tuple6 +impl Equal[Tuple6[A, B, C, D, E, F]] for Tuple6 if - A: Equal, B: Equal, C: Equal, D: Equal, E: Equal, F: Equal + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E], F: Equal[F] { - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Tuple6[A, B, C, D, E, F]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -277,11 +292,11 @@ impl Hash for Tuple6 if A: Hash, B: Hash, C: Hash, D: Hash, E: Hash, F: Hash } } -impl Clone for Tuple6 +impl Clone[Tuple6[A, B, C, D, E, F]] for Tuple6 if - A: Clone, B: Clone, C: Clone, D: Clone, E: Clone, F: Clone + A: Clone[A], B: Clone[B], C: Clone[C], D: Clone[D], E: Clone[E], F: Clone[F] { - fn pub clone -> Self { + fn pub clone -> Tuple6[A, B, C, D, E, F] { (@0.clone, @1.clone, @2.clone, @3.clone, @4.clone, @5.clone) } } @@ -318,11 +333,12 @@ class builtin Tuple7[A, B, C, D, E, F, G] { let pub @6: G } -impl Equal for Tuple7 +impl Equal[Tuple7[A, B, C, D, E, F, G]] for Tuple7 if - A: Equal, B: Equal, C: Equal, D: Equal, E: Equal, F: Equal, G: Equal + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E], F: Equal[F], + G: Equal[G] { - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Tuple7[A, B, C, D, E, F, G]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -348,11 +364,17 @@ if } } -impl Clone for Tuple7 +impl Clone[Tuple7[A, B, C, D, E, F, G]] for Tuple7 if - A: Clone, B: Clone, C: Clone, D: Clone, E: Clone, F: Clone, G: Clone + A: Clone[A], + B: Clone[B], + C: Clone[C], + D: Clone[D], + E: Clone[E], + F: Clone[F], + G: Clone[G] { - fn pub clone -> Self { + fn pub clone -> Tuple7[A, B, C, D, E, F, G] { (@0.clone, @1.clone, @2.clone, @3.clone, @4.clone, @5.clone, @6.clone) } } @@ -392,11 +414,12 @@ class builtin Tuple8[A, B, C, D, E, F, G, H] { let pub @7: H } -impl Equal for Tuple8 +impl Equal[Tuple8[A, B, C, D, E, F, G, H]] for Tuple8 if - A: Equal, B: Equal, C: Equal, D: Equal, E: Equal, F: Equal, G: Equal, H: Equal + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E], F: Equal[F], + G: Equal[G], H: Equal[H] { - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Tuple8[A, B, C, D, E, F, G, H]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -424,11 +447,18 @@ if } } -impl Clone for Tuple8 +impl Clone[Tuple8[A, B, C, D, E, F, G, H]] for Tuple8 if - A: Clone, B: Clone, C: Clone, D: Clone, E: Clone, F: Clone, G: Clone, H: Clone + A: Clone[A], + B: Clone[B], + C: Clone[C], + D: Clone[D], + E: Clone[E], + F: Clone[F], + G: Clone[G], + H: Clone[H] { - fn pub clone -> Self { + fn pub clone -> Tuple8[A, B, C, D, E, F, G, H] { ( @0.clone, @1.clone, diff --git a/libstd/src/std/utf8.inko b/std/src/std/utf8.inko similarity index 100% rename from libstd/src/std/utf8.inko rename to std/src/std/utf8.inko diff --git a/std/test/helpers.inko b/std/test/helpers.inko new file mode 100644 index 000000000..d55b99e35 --- /dev/null +++ b/std/test/helpers.inko @@ -0,0 +1,137 @@ +import std::drop::(drop) +import std::env +import std::fmt::(DefaultFormatter, Format, Formatter) +import std::fs::file::(WriteOnlyFile, remove) +import std::fs::path::Path +import std::hash::(Hash, Hasher) +import std::hash::siphash::SipHasher13 +import std::sys::(Command, Stream) + +fn pub hash(value: ref Hash) -> Int { + let hasher: Hasher = SipHasher13.default + + value.hash(hasher) + hasher.finish +} + +fn pub fmt(value: ref Format) -> String { + let fmt: Formatter = DefaultFormatter.new + + value.fmt(fmt) + fmt.into_string +} + +fn pub compiler_path -> Path { + let base = match env.working_directory { + case Ok(path) -> path + case Error(err) -> panic("Failed to determine the working directory: {err}") + } + + let target = if base.join('test').directory? { + base.join('..').join('target') + } else if base.join('std').directory? { + base.join('target') + } else { + panic('Tests must be run in either the project root, or the std/ directory') + } + + let debug = target.join('debug').join('inko') + let release = target.join('release').join('inko') + + match (debug.created_at, release.created_at) { + case (Ok(deb), Ok(rel)) -> if deb >= rel { debug } else { release } + case (Ok(_), Error(_)) -> debug + case (Error(_), Ok(_)) -> release + case _ -> panic("The path to the compiler couldn't be determined") + } +} + +class pub Script { + let @id: Int + let @path: Path + let @code: String + let @cmd: Command + let @stdin: Option[String] + let @imports: Array[String] + + fn pub static new(id: Int, code: String) -> Script { + let path = env.temporary_directory.join("inko_test_script_{id}.inko") + let exe = compiler_path + let cmd = Command.new(exe) + + cmd.argument('run') + cmd.argument('-f') + cmd.argument('plain') + cmd.argument(path.to_string) + cmd.stdin(Stream.Null) + cmd.stderr(Stream.Piped) + cmd.stdout(Stream.Piped) + + Script { + @id = id, + @path = path, + @code = code, + @cmd = cmd, + @stdin = Option.None, + @imports = ['std::stdio::(STDIN, STDERR, STDOUT)'] + } + } + + fn pub move import(module: String) -> Script { + @imports.push(module) + self + } + + fn pub move stdin(input: String) -> Script { + @stdin = Option.Some(input) + + @cmd.stdin(Stream.Piped) + self + } + + fn pub move argument(value: String) -> Script { + @cmd.argument(value) + self + } + + fn pub move variable(name: String, value: String) -> Script { + @cmd.variable(name, value) + self + } + + fn pub move run -> String { + let file = WriteOnlyFile.new(@path.clone).unwrap + + @imports.into_iter.each fn (mod) { + file.write_string("import {mod}\n").unwrap + } + + file + .write_string( +"class async Main \{ + fn async main \{ + {@code} + } +} +" + ) + .unwrap + + file.flush.unwrap + + let child = @cmd.spawn.unwrap + let bytes = ByteArray.new + + match @stdin { + case Some(input) -> child.stdin.write_string(input).unwrap + case _ -> 0 + } + + child.wait.unwrap + child.stdout.read_all(bytes).unwrap + child.stderr.read_all(bytes).unwrap + drop(file) + remove(@path).unwrap + bytes.into_string + } +} diff --git a/libstd/test/main.inko b/std/test/main.inko similarity index 89% rename from libstd/test/main.inko rename to std/test/main.inko index dd45d2f00..ddc700294 100644 --- a/libstd/test/main.inko +++ b/std/test/main.inko @@ -1,4 +1,6 @@ import std::env +import std::test::(Filter, Tests) +import std::fs::path::Path import std::crypto::test_chacha import std::crypto::test_hash @@ -12,16 +14,15 @@ import std::endian::test_little import std::fs::test_dir import std::fs::test_file import std::fs::test_path +import std::hash::test_siphash import std::net::test_ip import std::net::test_socket -import std::test::Tests import std::test_array import std::test_bool import std::test_byte_array import std::test_cmp import std::test_debug import std::test_env -import std::test_ffi import std::test_float import std::test_fmt import std::test_int @@ -34,6 +35,7 @@ import std::test_option import std::test_process import std::test_rand import std::test_range +import std::test_result import std::test_set import std::test_stdio import std::test_string @@ -56,7 +58,6 @@ class async Main { test_debug.tests(tests) test_dir.tests(tests) test_env.tests(tests) - test_ffi.tests(tests) test_file.tests(tests) test_float.tests(tests) test_fmt.tests(tests) @@ -77,9 +78,11 @@ class async Main { test_process.tests(tests) test_rand.tests(tests) test_range.tests(tests) + test_result.tests(tests) test_set.tests(tests) test_sha1.tests(tests) test_sha2.tests(tests) + test_siphash.tests(tests) test_socket.tests(tests) test_stdio.tests(tests) test_string.tests(tests) @@ -89,8 +92,7 @@ class async Main { test_tuple.tests(tests) test_utf8.tests(tests) - tests.pattern = env.arguments.into_iter.next - + tests.filter = Filter.from_string(env.arguments.opt(0).unwrap_or('')) tests.run } } diff --git a/libstd/test/std/crypto/test_chacha.inko b/std/test/std/crypto/test_chacha.inko similarity index 100% rename from libstd/test/std/crypto/test_chacha.inko rename to std/test/std/crypto/test_chacha.inko diff --git a/libstd/test/std/crypto/test_hash.inko b/std/test/std/crypto/test_hash.inko similarity index 95% rename from libstd/test/std/crypto/test_hash.inko rename to std/test/std/crypto/test_hash.inko index fcce5dbf4..7a1efdf37 100644 --- a/libstd/test/std/crypto/test_hash.inko +++ b/std/test/std/crypto/test_hash.inko @@ -44,15 +44,15 @@ fn pub tests(t: mut Tests) { block.add_padding(8) fn { calls.push(true) } t.equal(calls.length, 1) - t.equal(block[10], 0x80) + t.equal(block.get(10), 0x80) } t.test('Block.index') fn (t) { let block = Block.new(4) - block[0] = 42 + block.set(0, 42) - t.equal(block[0], 42) + t.equal(block.get(0), 42) } t.test('Hash.to_string') fn (t) { diff --git a/libstd/test/std/crypto/test_math.inko b/std/test/std/crypto/test_math.inko similarity index 76% rename from libstd/test/std/crypto/test_math.inko rename to std/test/std/crypto/test_math.inko index ad70254fa..99758eb65 100644 --- a/libstd/test/std/crypto/test_math.inko +++ b/std/test/std/crypto/test_math.inko @@ -9,6 +9,13 @@ fn pub tests(t: mut Tests) { t.equal(math.rotate_left_u32(4138929511350741, 1), 1050536875) } + t.test('math.rotate_left_u64') fn (t) { + t.equal(math.rotate_left_u64(0, 2), 0) + t.equal(math.rotate_left_u64(123, 4), 1968) + t.equal(math.rotate_left_u64(1, 63), -9223372036854775808) + t.equal(math.rotate_left_u64(4138929511350741, 1), 8277859022701482) + } + t.test('math.rotate_right_u32') fn (t) { t.equal(math.rotate_right_u32(0, 2), 0) t.equal(math.rotate_right_u32(123, 4), 2952790023) diff --git a/libstd/test/std/crypto/test_md5.inko b/std/test/std/crypto/test_md5.inko similarity index 92% rename from libstd/test/std/crypto/test_md5.inko rename to std/test/std/crypto/test_md5.inko index 8c420e92c..ce09d904a 100644 --- a/libstd/test/std/crypto/test_md5.inko +++ b/std/test/std/crypto/test_md5.inko @@ -19,10 +19,10 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) hasher.write('123456789012345678901234567890123456789'.to_byte_array) - t.equal(hasher.finalize.to_string, '9d86352a67b623f0dc685101cef98dd9') + t.equal(hasher.finish.to_string, '9d86352a67b623f0dc685101cef98dd9') } - t.test('Md5.finalize') fn (t) { + t.test('Md5.finish') fn (t) { let cases = [ ('', 'd41d8cd98f00b204e9800998ecf8427e'), ('a', '0cc175b9c0f1b6a831c399e269772661'), @@ -55,7 +55,7 @@ fn pub tests(t: mut Tests) { let hasher = Md5.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } } diff --git a/libstd/test/std/crypto/test_poly1305.inko b/std/test/std/crypto/test_poly1305.inko similarity index 97% rename from libstd/test/std/crypto/test_poly1305.inko rename to std/test/std/crypto/test_poly1305.inko index a01dd07e4..674c8bf61 100644 --- a/libstd/test/std/crypto/test_poly1305.inko +++ b/std/test/std/crypto/test_poly1305.inko @@ -112,7 +112,7 @@ fn test_data -> Array[(Array[Int], Array[Int], Array[Int])] { fn pub tests(t: mut Tests) { t.test('Poly1305.hash') fn (t) { let tests = test_data - let test = tests[0] + let test = tests.get(0) let key = ByteArray.from_array(test.0) let input = ByteArray.from_array(test.1) let output = ByteArray.from_array(test.2) @@ -120,7 +120,7 @@ fn pub tests(t: mut Tests) { t.equal(Poly1305.hash(key, input).bytes, output) } - t.test('Poly1305.finalize') fn (t) { + t.test('Poly1305.finish') fn (t) { test_data.into_iter.each fn (tuple) { let key = tuple.0 let input = ByteArray.from_array(tuple.1) @@ -128,7 +128,7 @@ fn pub tests(t: mut Tests) { let hasher = Poly1305.new(ByteArray.from_array(key)) hasher.write(input) - t.equal(hasher.finalize.bytes, output) + t.equal(hasher.finish.bytes, output) } } } diff --git a/libstd/test/std/crypto/test_sha1.inko b/std/test/std/crypto/test_sha1.inko similarity index 92% rename from libstd/test/std/crypto/test_sha1.inko rename to std/test/std/crypto/test_sha1.inko index fb1b4ff0e..1e2507934 100644 --- a/libstd/test/std/crypto/test_sha1.inko +++ b/std/test/std/crypto/test_sha1.inko @@ -18,10 +18,10 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) hasher.write('123456789012345678901234567890123456789'.to_byte_array) - t.equal(hasher.finalize.to_string, 'f3187648aff45ddbf1f2c9ebf1fd6705c10c2566') + t.equal(hasher.finish.to_string, 'f3187648aff45ddbf1f2c9ebf1fd6705c10c2566') } - t.test('Sha1.finalize') fn (t) { + t.test('Sha1.finish') fn (t) { let cases = [ ('', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'), ('a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8'), @@ -57,7 +57,7 @@ fn pub tests(t: mut Tests) { let hasher = Sha1.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } } diff --git a/libstd/test/std/crypto/test_sha2.inko b/std/test/std/crypto/test_sha2.inko similarity index 94% rename from libstd/test/std/crypto/test_sha2.inko rename to std/test/std/crypto/test_sha2.inko index 7f21dd068..ed6dd7b97 100644 --- a/libstd/test/std/crypto/test_sha2.inko +++ b/std/test/std/crypto/test_sha2.inko @@ -18,10 +18,10 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) hasher.write('123456789012345678901234567890123456789'.to_byte_array) - t.equal(hasher.finalize.to_string, '1002070a9f6d34be894acf21e62d01b1b25938a81c86546eb2642f8b9731caf7') + t.equal(hasher.finish.to_string, '1002070a9f6d34be894acf21e62d01b1b25938a81c86546eb2642f8b9731caf7') } - t.test('Sha256.finalize') fn (t) { + t.test('Sha256.finish') fn (t) { let cases = [ ('', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), ('a', 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb'), @@ -57,7 +57,7 @@ fn pub tests(t: mut Tests) { let hasher = Sha256.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } @@ -81,12 +81,12 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) t.equal( - hasher.finalize.to_string, + hasher.finish.to_string, '5e2534f21d213edc33c937e4cdcd996b7d7486c6689b4f054afd811b25b727497d89d01506186daefee7f4b0cff9a977c319f8b594fe1c6551da1826e10d94cc' ) } - t.test('Sha512.finalize') fn (t) { + t.test('Sha512.finish') fn (t) { let cases = [ ( '', @@ -137,7 +137,7 @@ fn pub tests(t: mut Tests) { let hasher = Sha512.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } } diff --git a/libstd/test/std/endian/test_big.inko b/std/test/std/endian/test_big.inko similarity index 80% rename from libstd/test/std/endian/test_big.inko rename to std/test/std/endian/test_big.inko index af26997e7..da05ef7f1 100644 --- a/libstd/test/std/endian/test_big.inko +++ b/std/test/std/endian/test_big.inko @@ -1,4 +1,3 @@ -import helpers::Script import std::endian::big import std::int::(MIN, MAX) import std::test::Tests @@ -21,13 +20,10 @@ fn pub tests(t: mut Tests) { big.write_u32(123456789, into: bytes, at: 0) t.equal(big.read_u32(from: bytes, at: 0), 123456789) - t.true( - Script - .new(id: t.id, code: 'big.read_u32(from: ByteArray.new, at: 0)') - .import('std::endian::big') - .run - .contains?('out of bounds') - ) + } + + t.panic('big.read_u32 with not enough bytes') fn { + big.read_u32(from: ByteArray.new, at: 0) } t.test('big.write_i64') fn (t) { @@ -56,12 +52,9 @@ fn pub tests(t: mut Tests) { t.equal(big.read_i64(from: b1, at: 0), 123456789) t.equal(big.read_i64(from: b2, at: 0), MAX) t.equal(big.read_i64(from: b3, at: 0), MIN) - t.true( - Script - .new(id: t.id, code: 'big.read_i64(from: ByteArray.new, at: 0)') - .import('std::endian::big') - .run - .contains?('out of bounds') - ) + } + + t.panic('big.read_i64 with not enough bytes') fn { + big.read_i64(from: ByteArray.new, at: 0) } } diff --git a/libstd/test/std/endian/test_little.inko b/std/test/std/endian/test_little.inko similarity index 80% rename from libstd/test/std/endian/test_little.inko rename to std/test/std/endian/test_little.inko index 1d1ff4579..b18118d31 100644 --- a/libstd/test/std/endian/test_little.inko +++ b/std/test/std/endian/test_little.inko @@ -1,4 +1,3 @@ -import helpers::Script import std::endian::little import std::int::(MIN, MAX) import std::test::Tests @@ -21,13 +20,10 @@ fn pub tests(t: mut Tests) { little.write_u32(123456789, into: bytes, at: 0) t.equal(little.read_u32(from: bytes, at: 0), 123456789) - t.true( - Script - .new(id: t.id, code: 'little.read_u32(from: ByteArray.new, at: 0)') - .import('std::endian::little') - .run - .contains?('out of bounds') - ) + } + + t.panic('little.read_u32 with not enough bytes') fn { + little.read_u32(from: ByteArray.new, at: 0) } t.test('little.write_i64') fn (t) { @@ -56,12 +52,9 @@ fn pub tests(t: mut Tests) { t.equal(little.read_i64(from: b1, at: 0), 123456789) t.equal(little.read_i64(from: b2, at: 0), MAX) t.equal(little.read_i64(from: b3, at: 0), MIN) - t.true( - Script - .new(id: t.id, code: 'little.read_i64(from: ByteArray.new, at: 0)') - .import('std::endian::little') - .run - .contains?('out of bounds') - ) + } + + t.panic('little.read_i64 with not enough bytes') fn { + little.read_i64(from: ByteArray.new, at: 0) } } diff --git a/libstd/test/std/fs/test_dir.inko b/std/test/std/fs/test_dir.inko similarity index 61% rename from libstd/test/std/fs/test_dir.inko rename to std/test/std/fs/test_dir.inko index efcb5b81b..684b5e75b 100644 --- a/libstd/test/std/fs/test_dir.inko +++ b/std/test/std/fs/test_dir.inko @@ -6,31 +6,31 @@ fn pub tests(t: mut Tests) { t.test('dir.create') fn (t) { let path = env.temporary_directory.join("inko-test-dir-{t.id}") - t.no_throw fn { try dir.create(path) } + t.true(dir.create(path).ok?) t.true(path.directory?) - t.throw fn { try dir.create(path) } + t.true(dir.create(path).error?) - try! dir.remove(path) + dir.remove(path).unwrap } t.test('dir.create_all') fn (t) { let root = env.temporary_directory.join("inko-test-dir-{t.id}") let path = root.join('foo').join('bar') - t.no_throw fn { try dir.create_all(path) } + t.true(dir.create_all(path).ok?) t.true(path.directory?) - t.no_throw fn { try dir.create_all(path) } + t.true(dir.create_all(path).ok?) - try! dir.remove_all(root) + dir.remove_all(root).unwrap } t.test('dir.remove') fn (t) { let path = env.temporary_directory.join("inko-test-dir-{t.id}") - try! dir.create(path) + dir.create(path).unwrap - t.no_throw fn { try dir.remove(path) } - t.throw fn { try dir.remove(path) } + t.true(dir.remove(path).ok?) + t.true(dir.remove(path).error?) t.false(path.directory?) } @@ -38,10 +38,10 @@ fn pub tests(t: mut Tests) { let root = env.temporary_directory.join("inko-test-dir-{t.id}") let path = root.join('foo').join('bar') - try! dir.create_all(path) + dir.create_all(path).unwrap - t.no_throw fn { try dir.remove_all(root) } - t.throw fn { try dir.remove_all(root) } + t.true(dir.remove_all(root).ok?) + t.true(dir.remove_all(root).error?) t.false(root.directory?) } @@ -50,15 +50,15 @@ fn pub tests(t: mut Tests) { let foo = root.join('foo') let bar = root.join('bar') - try! dir.create(root) - try! dir.create(foo) - try! dir.create(bar) + dir.create(root).unwrap + dir.create(foo).unwrap + dir.create(bar).unwrap - let paths = try! dir.list(root) + let paths = dir.list(root).unwrap t.true(paths.contains?(foo)) t.true(paths.contains?(bar)) - try! dir.remove_all(root) + dir.remove_all(root).unwrap } } diff --git a/libstd/test/std/fs/test_file.inko b/std/test/std/fs/test_file.inko similarity index 52% rename from libstd/test/std/fs/test_file.inko rename to std/test/std/fs/test_file.inko index c13f07212..664066b64 100644 --- a/libstd/test/std/fs/test_file.inko +++ b/std/test/std/fs/test_file.inko @@ -4,16 +4,16 @@ import std::fs::path::Path import std::test::Tests fn write(string: String, to: ref Path) { - let file = try! WriteOnlyFile.new(to.clone) + let file = WriteOnlyFile.new(to.clone).unwrap - try! file.write_string(string) + file.write_string(string).unwrap } fn read(from: ref Path) -> String { - let file = try! ReadOnlyFile.new(from.clone) + let file = ReadOnlyFile.new(from.clone).unwrap let bytes = ByteArray.new - try! file.read_all(bytes) + file.read_all(bytes).unwrap bytes.into_string } @@ -22,11 +22,11 @@ fn pub tests(t: mut Tests) { t.test('file.remove') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.throw fn { try file.remove(path) } + t.true(file.remove(path).error?) write('test', to: path) - t.no_throw fn { try file.remove(path) } + t.true(file.remove(path).ok?) t.false(path.exists?) } @@ -36,23 +36,23 @@ fn pub tests(t: mut Tests) { write('test', to: path1) - t.no_throw fn { try file.copy(from: path1, to: path2) } + t.true(file.copy(from: path1, to: path2).ok?) t.equal(read(path2), 'test') - try! file.remove(path1) - try! file.remove(path2) + file.remove(path1).unwrap + file.remove(path2).unwrap } t.test('ReadOnlyFile.new') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.throw fn { try ReadOnlyFile.new(path.clone) } + t.true(ReadOnlyFile.new(path.clone).error?) write('test', to: path) - t.no_throw fn { try ReadOnlyFile.new(path.clone) } + t.true(ReadOnlyFile.new(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadOnlyFile.read') fn (t) { @@ -60,14 +60,14 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadOnlyFile.new(path.clone) + let handle = ReadOnlyFile.new(path.clone).unwrap let bytes = ByteArray.new - try! handle.read(into: bytes, size: 4) + handle.read(into: bytes, size: 4).unwrap t.equal(bytes.into_string, 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadOnlyFile.seek') fn (t) { @@ -75,15 +75,15 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadOnlyFile.new(path.clone) + let handle = ReadOnlyFile.new(path.clone).unwrap let bytes = ByteArray.new - try! handle.seek(1) - try! handle.read(into: bytes, size: 4) + handle.seek(1).unwrap + handle.read(into: bytes, size: 4).unwrap t.equal(bytes.into_string, 'est') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadOnlyFile.size') fn (t) { @@ -91,112 +91,112 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadOnlyFile.new(path.clone) + let handle = ReadOnlyFile.new(path.clone).unwrap - t.true(try! { handle.size } >= 0) + t.true(handle.size.unwrap >= 0) - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.new') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try WriteOnlyFile.new(path.clone) } - t.no_throw fn { try WriteOnlyFile.new(path.clone) } + t.true(WriteOnlyFile.new(path.clone).ok?) + t.true(WriteOnlyFile.new(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.append') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try WriteOnlyFile.append(path.clone) } - t.no_throw fn { try WriteOnlyFile.append(path.clone) } + t.true(WriteOnlyFile.append(path.clone).ok?) + t.true(WriteOnlyFile.append(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.write_bytes') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_bytes('test'.to_byte_array) + handle.write_bytes('test'.to_byte_array).unwrap } { - let handle = try! WriteOnlyFile.append(path.clone) + let handle = WriteOnlyFile.append(path.clone).unwrap - try! handle.write_bytes('ing'.to_byte_array) + handle.write_bytes('ing'.to_byte_array).unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.write_string') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_string('test') + handle.write_string('test').unwrap } { - let handle = try! WriteOnlyFile.append(path.clone) + let handle = WriteOnlyFile.append(path.clone).unwrap - try! handle.write_string('ing') + handle.write_string('ing').unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.flush') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.flush + handle.write_string('test').unwrap + handle.flush.unwrap t.equal(read(path), 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.seek') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.seek(1) - try! handle.write_string('ing') + handle.write_string('test').unwrap + handle.seek(1).unwrap + handle.write_string('ing').unwrap t.equal(read(path), 'ting') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.new') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try ReadWriteFile.new(path.clone) } - t.no_throw fn { try ReadWriteFile.new(path.clone) } + t.true(ReadWriteFile.new(path.clone).ok?) + t.true(ReadWriteFile.new(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.append') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try ReadWriteFile.append(path.clone) } - t.no_throw fn { try ReadWriteFile.append(path.clone) } + t.true(ReadWriteFile.append(path.clone).ok?) + t.true(ReadWriteFile.append(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.read') fn (t) { @@ -204,79 +204,79 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap let bytes = ByteArray.new - try! handle.read(bytes, size: 4) + handle.read(bytes, size: 4).unwrap t.equal(bytes.to_string, 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.write_bytes') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_bytes('test'.to_byte_array) + handle.write_bytes('test'.to_byte_array).unwrap } { - let handle = try! ReadWriteFile.append(path.clone) + let handle = ReadWriteFile.append(path.clone).unwrap - try! handle.write_bytes('ing'.to_byte_array) + handle.write_bytes('ing'.to_byte_array).unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.write_string') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_string('test') + handle.write_string('test').unwrap } { - let handle = try! ReadWriteFile.append(path.clone) + let handle = ReadWriteFile.append(path.clone).unwrap - try! handle.write_string('ing') + handle.write_string('ing').unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.flush') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.flush + handle.write_string('test').unwrap + handle.flush.unwrap t.equal(read(path), 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.seek') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.seek(1) - try! handle.write_string('ing') + handle.write_string('test').unwrap + handle.seek(1).unwrap + handle.write_string('ing').unwrap t.equal(read(path), 'ting') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.size') fn (t) { @@ -284,10 +284,10 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - t.true(try! { handle.size } >= 0) + t.true(handle.size.unwrap >= 0) - try! file.remove(path) + file.remove(path).unwrap } } diff --git a/libstd/test/std/fs/test_path.inko b/std/test/std/fs/test_path.inko similarity index 63% rename from libstd/test/std/fs/test_path.inko rename to std/test/std/fs/test_path.inko index b90d298ac..cde175e72 100644 --- a/libstd/test/std/fs/test_path.inko +++ b/std/test/std/fs/test_path.inko @@ -1,34 +1,32 @@ import helpers::(fmt) import std::env +import std::fs::dir import std::fs::file::(self, WriteOnlyFile) import std::fs::path::(self, Path) import std::sys import std::test::Tests fn created_at? -> Bool { - try env.temporary_directory.created_at else return false - true + env.temporary_directory.created_at.ok? } fn modified_at? -> Bool { - try env.temporary_directory.modified_at else return false - true + env.temporary_directory.modified_at.ok? } fn accessed_at? -> Bool { - try env.temporary_directory.accessed_at else return false - true + env.temporary_directory.accessed_at.ok? } fn write(string: String, to: ref Path) { - let file = try! WriteOnlyFile.new(to.clone) + let file = WriteOnlyFile.new(to.clone).unwrap - try! file.write_string(string) + file.write_string(string).unwrap } fn pub tests(t: mut Tests) { - t.test('path.separator') fn (t) { - t.equal(path.separator, if sys.windows? { '\\' } else { '/' }) + t.test('path::SEPARATOR') fn (t) { + t.equal(path::SEPARATOR, '/') } t.test('Path.file?') fn (t) { @@ -38,7 +36,7 @@ fn pub tests(t: mut Tests) { write('test', to: path) t.true(path.file?) - try! file.remove(path) + file.remove(path).unwrap } t.test('Path.directory?') fn (t) { @@ -55,7 +53,7 @@ fn pub tests(t: mut Tests) { t.test('Path.created_at') fn (t) { let path = env.temporary_directory - t.no_throw fn { try path.created_at } + t.true(path.created_at.ok?) } } @@ -63,7 +61,7 @@ fn pub tests(t: mut Tests) { t.test('Path.modified_at') fn (t) { let path = env.temporary_directory - t.no_throw fn { try path.modified_at } + t.true(path.modified_at.ok?) } } @@ -71,32 +69,23 @@ fn pub tests(t: mut Tests) { t.test('Path.accessed_at') fn (t) { let path = env.temporary_directory - t.no_throw fn { try path.accessed_at } + t.true(path.accessed_at.ok?) } } t.test('Path.absolute?') fn (t) { - let abs = if sys.windows? { 'C:\\foo' } else { '/foo' } - - t.true(Path.new(abs).absolute?) + t.true(Path.new('/foo').absolute?) t.false(Path.new('foo').absolute?) } t.test('Path.relative?') fn (t) { - let abs = if sys.windows? { 'C:\\foo' } else { '/foo' } - t.true(Path.new('foo').relative?) - t.false(Path.new(abs).relative?) + t.false(Path.new('/foo').relative?) } t.test('Path.join') fn (t) { - if sys.windows? { - t.equal(Path.new('foo').join('bar'), Path.new('foo\\bar')) - t.equal(Path.new('foo').join('C:\\').join('bar'), Path.new('C:\\bar')) - } else { - t.equal(Path.new('foo').join('bar'), Path.new('foo/bar')) - t.equal(Path.new('foo').join('/').join('bar'), Path.new('/bar')) - } + t.equal(Path.new('foo').join('bar'), Path.new('foo/bar')) + t.equal(Path.new('foo').join('/').join('bar'), Path.new('/bar')) } t.test('Path.directory') fn (t) { @@ -117,7 +106,7 @@ fn pub tests(t: mut Tests) { } t.test('Path.size') fn (t) { - t.true(try! { env.temporary_directory.size } >= 0) + t.true(env.temporary_directory.size.unwrap >= 0) } t.test('Path.clone') fn (t) { @@ -135,4 +124,24 @@ fn pub tests(t: mut Tests) { t.test('Path.fmt') fn (t) { t.equal(fmt(Path.new('foo')), '"foo"') } + + t.test('Path.expand') fn (t) { + let temp = env.temporary_directory + let bar = temp.join('foo').join('bar') + + dir.create_all(bar).unwrap + + let expanded = bar.join('..').join('..').expand + + t.equal(expanded, Result.Ok(temp)) + dir.remove_all(bar) + } + + t.test('Path.tail') fn (t) { + t.equal(Path.new('foo').tail, 'foo') + t.equal(Path.new('foo').join('bar').tail, 'bar') + t.equal(Path.new('foo').join('bar.txt').tail, 'bar.txt') + t.equal(Path.new('').tail, '') + t.equal(Path.new('..').tail, '..') + } } diff --git a/std/test/std/hash/test_siphash.inko b/std/test/std/hash/test_siphash.inko new file mode 100644 index 000000000..eed1624f2 --- /dev/null +++ b/std/test/std/hash/test_siphash.inko @@ -0,0 +1,85 @@ +import std::test::Tests +import std::hash::siphash::SipHasher13 + +let KEY0 = 0x0706050403020100 +let KEY1 = 0x0F0E0D0C0B0A0908 +let EXPECTED = [ + -6076480319675972388, + -3894316307686372717, + -9021946994309475251, + -8360920918932981765, + -3497793463459282136, + -2379636529018225817, + -4247691247627591001, + -3201358290706427584, + 3931806377309739662, + 2712449776846519780, + 8781603583133944191, + 8124802424143463250, + 8692937602970737058, + 3490138030451851175, + 6943038873169124660, + -3233346569078990506, + -3724515260966597786, + -7137527490068883444, + -8071514186461223362, + -999906983011582692, + -4549709543058382784, + -5074804721957078972, + 9222944995288319790, + 5934071401310241059, + -836351549525156724, + 5029774389061290361, + 4196851155228758949, + -6260628767897666763, + -356931148583219187, + -8585764469560188428, + 6057079635794323043, + 2553784116283822524, + -9145267764139149811, + 5572283034219051455, + 8475513330862814526, + -3557382191000818799, + 3239505212207161862, + -5327727376832712617, + -5479626656086083935, + 7091858058578527122, + -4480459088322095823, + 7384990322848880964, + 7309430150919325733, + 2662331968129681830, + -4410681076612561045, + -4461294832492207722, + -6814337029234371037, + 5330879445531170045, + -6975719611647439802, + -4107934715622947149, + 9128596131873425807, + 2086032310832541594, + -4302572765519274222, + 3961735597980589776, + 3571270170472135910, + -6738863651867334272, + -5423247803822609035, + 2592059436507272467, + -8141558914245359096, + -2838163980098670264, + -651642346778770194, + -5853101887941232654, + -4345140119139347232, + -7126506181673372760, +] + +fn pub tests(t: mut Tests) { + t.test('SipHasher13.finish') fn (t) { + let buf = [] + + EXPECTED.iter.each_with_index fn (index, exp) { + let hasher = SipHasher13.new(KEY0, KEY1) + + buf.iter.each fn (v) { hasher.write(v) } + t.equal(hasher.finish, exp) + buf.push(index) + } + } +} diff --git a/libstd/test/std/net/test_ip.inko b/std/test/std/net/test_ip.inko similarity index 100% rename from libstd/test/std/net/test_ip.inko rename to std/test/std/net/test_ip.inko diff --git a/std/test/std/net/test_socket.inko b/std/test/std/net/test_socket.inko new file mode 100644 index 000000000..5c44de098 --- /dev/null +++ b/std/test/std/net/test_socket.inko @@ -0,0 +1,1415 @@ +import helpers::(fmt) +import std::drop::Drop +import std::env +import std::fs::file +import std::fs::path::Path +import std::net::ip::(IpAddress, Ipv4Address, Ipv6Address) +import std::net::socket::( + Socket, SocketAddress, TcpServer, TcpClient, Type, UdpSocket, UnixAddress, + UnixDatagram, UnixServer, UnixSocket, UnixClient +) +import std::string::ToString +import std::test::Tests +import std::time::(Duration, Instant) + +class SocketPath { + let @path: Path + + fn static pair(id: Int) -> (SocketPath, SocketPath) { + let p1 = env.temporary_directory.join("inko-test-{id}-1.sock") + let p2 = env.temporary_directory.join("inko-test-{id}-2.sock") + let _ = file.remove(p1) + let _ = file.remove(p2) + + (SocketPath { @path = p1 }, SocketPath { @path = p2 }) + } + + fn static new(id: Int) -> SocketPath { + let path = env.temporary_directory.join("inko-test-{id}.sock") + let _ = file.remove(path) + + SocketPath { @path = path } + } +} + +impl ToString for SocketPath { + fn pub to_string -> String { + @path.to_string + } +} + +impl Drop for SocketPath { + fn mut drop { + let _ = file.remove(@path) + } +} + +fn pub tests(t: mut Tests) { + t.test('SocketAddress.new') fn (t) { + let addr = SocketAddress.new(address: '127.0.0.1', port: 1234) + + t.equal(addr.ip, Option.Some(IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)))) + t.equal(addr.port, 1234) + } + + t.test('SocketAddress.==') fn (t) { + let addr1 = SocketAddress.new(address: '127.0.0.1', port: 1234) + let addr2 = SocketAddress.new(address: '127.0.0.1', port: 4567) + + t.equal(addr1, addr1) + t.not_equal(addr1, addr2) + } + + t.test('SocketAddress.fmt') fn (t) { + t.equal( + fmt(SocketAddress.new(address: '127.0.0.1', port: 1234)), + '127.0.0.1:1234' + ) + } + + t.test('Socket.ipv4') fn (t) { + t.true(Socket.ipv4(Type.STREAM).ok?) + t.true(Socket.ipv4(Type.DGRAM).ok?) + } + + t.test('Socket.ipv6') fn (t) { + t.true(Socket.ipv6(Type.STREAM).ok?) + t.true(Socket.ipv6(Type.DGRAM).ok?) + } + + t.test('Socket.bind') fn (t) { + { + let sock = Socket.ipv4(Type.STREAM).unwrap + + t.true(sock.bind(ip: 'foo', port: 0).error?) + } + + { + let sock = Socket.ipv4(Type.STREAM).unwrap + + t.true(sock.bind(ip: '0.0.0.0', port: 0).ok?) + } + } + + t.test('Socket.connect') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream1 = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + t.true(stream1.connect(ip: addr.ip.unwrap, port: addr.port.clone).ok?) + + let stream2 = Socket.ipv4(Type.STREAM).unwrap + + t.true( + # connect() may not immediately raise a "connection refused" error, due + # to connect() being non-blocking. In this case the "connection refused" + # error is raised on the next operation. + # + # Since a connect() _might_ still raise the error right away, we have to + # both connect and try to use the socket in some way. + stream2.connect(ip: '0.0.0.0', port: 40_000).error? + or stream2.write_string('ping').error? + ) + } + + t.test('Socket.listen') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + socket.bind(ip: '0.0.0.0', port: 0).unwrap + + t.true(socket.listen.ok?) + } + + t.test('Socket.accept') fn (t) { + let server = Socket.ipv4(Type.STREAM).unwrap + let client = Socket.ipv4(Type.STREAM).unwrap + + server.bind(ip: '127.0.0.1', port: 0).unwrap + server.listen.unwrap + + let addr = server.local_address.unwrap + + client.connect(addr.address.clone, addr.port.clone).unwrap + + let connection = server.accept.unwrap + + t.equal(connection.local_address.unwrap, server.local_address.unwrap) + } + + t.test('Socket.send_string_to') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let send_to = socket.local_address.unwrap + let buffer = ByteArray.new + + socket + .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) + .unwrap + + t.equal(socket.read(into: buffer, size: 4).unwrap, 4) + t.equal(buffer.into_string, 'ping') + } + + t.test('Socket.send_bytes_to') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let send_to = socket.local_address.unwrap + let buffer = ByteArray.new + + socket + .send_bytes_to( + bytes: 'ping'.to_byte_array, + ip: send_to.address, + port: send_to.port.clone + ) + .unwrap + + t.equal(socket.read(into: buffer, size: 4).unwrap, 4) + t.equal(buffer.into_string, 'ping') + } + + t.test('Socket.receive_from') fn (t) { + let listener = Socket.ipv4(Type.DGRAM).unwrap + let client = Socket.ipv4(Type.DGRAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + client.bind(ip: '127.0.0.1', port: 0).unwrap + + let send_to = listener.local_address.unwrap + + client + .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) + .unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.local_address with an unbound socket') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + let address = socket.local_address.unwrap + + t.equal(address.address, '0.0.0.0') + t.equal(address.port, 0) + } + + t.test('Socket.local_address with a bound socket') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let local_address = socket.local_address.unwrap + + t.equal(local_address.address, '127.0.0.1') + t.true(local_address.port > 0) + } + + t.test('Socket.peer_address with a disconnected socket') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + t.true(socket.peer_address.error?) + } + + t.test('Socket.peer_address with a connected socket') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let client = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + client.connect(ip: addr.address, port: addr.port.clone).unwrap + + t.equal(client.peer_address.unwrap, addr) + } + + t.test('Socket.ttl') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.ttl = 10).ok?) + } + + t.test('Socket.only_ipv6?') fn (t) { + let socket = Socket.ipv6(Type.STREAM).unwrap + + t.true((socket.only_ipv6 = true).ok?) + } + + t.test('Socket.no_delay?') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.no_delay = true).ok?) + } + + t.test('Socket.broadcast?') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + t.true((socket.broadcast = true).ok?) + } + + t.test('Socket.linger') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + let duration = Duration.from_secs(5) + + t.true((socket.linger = duration).ok?) + } + + t.test('Socket.receive_buffer_size') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.receive_buffer_size = 256).ok?) + } + + t.test('Socket.send_buffer_size') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.send_buffer_size = 256).ok?) + } + + t.test('Socket.keepalive') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.keepalive = true).ok?) + } + + t.test('Socket.reuse_adress') fn (t) { + let socket = Socket.ipv6(Type.DGRAM).unwrap + + t.true((socket.reuse_address = true).ok?) + } + + t.test('Socket.reuse_port') fn (t) { + let socket = Socket.ipv6(Type.DGRAM).unwrap + + t.true((socket.reuse_port = true).ok?) + } + + t.test('Socket.shutdown_read') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes.length, 0) + } + + t.test('Socket.shutdown_write') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('Socket.shutdown shuts down the writing half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('Socket.shutdown shuts down the reading half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes.length, 0) + } + + t.test('Socket.try_clone') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('Socket.read') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let addr = socket.local_address.unwrap + let bytes = ByteArray.new + + socket.send_string_to('ping', ip: addr.address, port: addr.port.clone).unwrap + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.write_bytes') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + + let written = stream.write_bytes('ping'.to_byte_array).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.write_string') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + + let written = stream.write_string('ping').unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.flush') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('UdpSocket.new') fn (t) { + t.true( + UdpSocket + .new(ip: IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)), port: 0) + .ok? + ) + + t.true( + UdpSocket + .new(ip: IpAddress.V6(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0)), port: 0) + .ok? + ) + } + + t.test('UdpSocket.connect') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket1 = UdpSocket.new(ip: ip.clone, port: 0).unwrap + let socket2 = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = socket2.local_address.unwrap + + t.true(socket1.connect(ip: addr.address, port: addr.port.clone).ok?) + } + + t.test('UdpSocket.send_string_to') fn (t) { + let socket = UdpSocket + .new(ip: IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)), port: 0) + .unwrap + + let addr = socket.local_address.unwrap + + socket.send_string_to('ping', ip: addr.address, port: addr.port.clone).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.send_bytes_to') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = socket.local_address.unwrap + + socket + .send_bytes_to( + 'ping'.to_byte_array, + ip: addr.address, + port: addr.port.clone + ) + .unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.receive_from') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let listener = UdpSocket.new(ip: ip.clone, port: 0).unwrap + let client = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + + client + .send_string_to('ping', ip: addr.address, port: addr.port.clone) + .unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.local_address') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + let local_address = socket.local_address.unwrap + + t.equal(local_address.address, '127.0.0.1') + t.true(local_address.port > 0) + } + + t.test('UdpSocket.try_clone') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('UdpSocket.read') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = socket.local_address.unwrap + + socket.send_string_to('ping', ip: addr.address, port: addr.port.clone).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.to_string, 'ping') + } + + t.test('UdpSocket.write_bytes') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let server_socket = UdpSocket.new(ip: ip.clone, port: 0).unwrap + let client_socket = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = server_socket.local_address.unwrap + + client_socket.connect(ip: addr.address, port: addr.port.clone).unwrap + client_socket.write_bytes('ping'.to_byte_array).unwrap + + let bytes = ByteArray.new + + t.equal(server_socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.flush') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('TcpClient.new') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + t.true(TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).ok?) + } + + t.test('TcpClient.with_timeout') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + t.true( + TcpClient + .with_timeout( + ip: addr.ip.unwrap, + port: addr.port.clone, + timeout_after: Duration.from_secs(2) + ) + .ok? + ) + + t.true( + # This address is unroutable and so the connect times out. + TcpClient + .with_timeout( + ip: IpAddress.v4(192, 168, 0, 0), + port: addr.port.clone, + timeout_after: Duration.from_micros(500) + ) + .error? + ) + } + + t.test('TcpClient.local_address') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let local_addr = stream.local_address.unwrap + + t.equal(local_addr.address, '127.0.0.1') + t.true(local_addr.port > 0) + } + + t.test('TcpClient.peer_address') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let peer_addr = stream.peer_address.unwrap + + t.equal(peer_addr.address, addr.address) + t.equal(peer_addr.port, addr.port) + } + + t.test('TcpClient.read') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let bytes = ByteArray.new + let client = listener.accept.unwrap + + client.write_string('ping').unwrap + stream.read(into: bytes, size: 4).unwrap + + t.equal(bytes.into_string, 'ping') + } + + t.test('TcpClient.write_bytes') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_bytes('ping'.to_byte_array).unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('TcpClient.write_string') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_string('ping').unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('TcpClient.flush') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + t.true(stream.flush.ok?) + } + + t.test('TcpClient.shutdown_read') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + stream.read(into: bytes, size: 4).unwrap + + t.equal(bytes, ByteArray.new) + } + + t.test('TcpClient.shutdown_write') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('TcpClient.shutdown shuts down the writing half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('TcpClient.shutdown shuts down the reading half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown.unwrap + + let bytes = ByteArray.new + + let message = stream.read(into: bytes, size: 4).unwrap + + t.equal(bytes, ByteArray.new) + } + + t.test('TcpClient.try_clone') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let client = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + t.true(client.try_clone.ok?) + } + + t.test('TcpServer.new') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)) + + t.true(TcpServer.new(ip: ip.clone, port: 0).ok?) + + let listener = TcpServer.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + + t.true(TcpServer.new(ip: addr.ip.unwrap, port: addr.port.clone).ok?) + } + + t.test('TcpServer.accept') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let listener = TcpServer.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let connection = listener.accept.unwrap + + t.equal(connection.local_address.unwrap, stream.peer_address.unwrap) + } + + t.test('TcpServer.local_address') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let listener = TcpServer.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + + t.equal(addr.address, '127.0.0.1') + t.true(addr.port > 0) + } + + t.test('TcpServer.try_clone') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let server = TcpServer.new(ip: ip, port: 0).unwrap + + t.true(server.try_clone.ok?) + } + + t.test('UnixAddress.to_path') fn (t) { + t.equal(UnixAddress.new('foo.sock').to_path, Option.Some('foo.sock'.to_path)) + t.true(UnixAddress.new("\0foo").to_path.none?) + t.true(UnixAddress.new('').to_path.none?) + } + + t.test('UnixAddress.to_string') fn (t) { + t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') + t.equal(UnixAddress.new("\0foo").to_string, "\0foo") + t.equal(UnixAddress.new('').to_string, '') + } + + t.test('UnixAddress.abstract?') fn (t) { + t.false(UnixAddress.new('').abstract?) + t.false(UnixAddress.new('foo.sock').abstract?) + t.true(UnixAddress.new("\0foo").abstract?) + } + + t.test('UnixAddress.unnamed?') fn (t) { + t.false(UnixAddress.new('foo.sock').unnamed?) + t.false(UnixAddress.new("\0foo").unnamed?) + t.true(UnixAddress.new('').unnamed?) + } + + t.test('UnixAddress.fmt') fn (t) { + t.equal(fmt(UnixAddress.new('foo.sock')), 'foo.sock') + t.equal(fmt(UnixAddress.new("\0foo")), '@foo') + t.equal(fmt(UnixAddress.new('')), 'unnamed') + } + + t.test('UnixAddress.==') fn (t) { + t.equal(UnixAddress.new('a.sock'), UnixAddress.new('a.sock')) + t.not_equal(UnixAddress.new('a.sock'), UnixAddress.new('b.sock')) + } + + t.test('UnixAddress.to_string') fn (t) { + t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') + t.equal(UnixAddress.new("\0foo").to_string, "\0foo") + t.equal(UnixAddress.new('').to_string, '') + } + + t.test('UnixSocket.new') fn (t) { + t.true(UnixSocket.new(Type.DGRAM).ok?) + t.true(UnixSocket.new(Type.STREAM).ok?) + } + + t.test('UnixSocket.bind') fn (t) { + let socket1 = UnixSocket.new(Type.STREAM).unwrap + let socket2 = UnixSocket.new(Type.STREAM).unwrap + let path = SocketPath.new(t.id) + + t.true(socket1.bind(path).ok?) + t.true(socket2.bind(path).error?) + + if env::OS == 'linux' { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true(socket.bind("\0inko-test-{t.id}").ok?) + } + } + + t.test('UnixSocket.connect') fn (t) { + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + let path = SocketPath.new(t.id) + + t.true(stream.connect(path).error?) + + listener.bind(path).unwrap + listener.listen.unwrap + + t.true(stream.connect(path).ok?) + + if env::OS == 'linux' { + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + let addr = "\0inko-test-{t.id}" + + listener.bind(addr).unwrap + listener.listen.unwrap + + t.true(stream.connect(addr).ok?) + } + } + + t.test('UnixSocket.listen') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.STREAM).unwrap + + socket.bind(path).unwrap + + t.true(socket.listen.ok?) + } + + t.test('UnixSocket.accept') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + + let client = listener.accept.unwrap + + t.equal(client.peer_address.unwrap, stream.local_address.unwrap) + } + + t.test('UnixSocket.send_string_to') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + socket.send_string_to('ping', path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.send_bytes_to') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + socket.send_bytes_to('ping'.to_byte_array, path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.receive_from') fn (t) { + let pair = SocketPath.pair(t.id) + let listener = UnixSocket.new(Type.DGRAM).unwrap + let client = UnixSocket.new(Type.DGRAM).unwrap + + listener.bind(pair.0).unwrap + client.bind(pair.1).unwrap + client.send_string_to('ping', pair.0).unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.local_address with an unbound socket') fn (t) { + let socket = UnixSocket.new(Type.DGRAM).unwrap + let address = socket.local_address.unwrap + + t.equal(address, UnixAddress.new('')) + } + + t.test('UnixSocket.local_address with a bound socket') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + + t.equal(socket.local_address.unwrap, UnixAddress.new(path.to_string)) + } + + t.test('UnixSocket.peer_address with a disconnected socket') fn (t) { + let socket = UnixSocket.new(Type.DGRAM).unwrap + + t.true(socket.peer_address.error?) + } + + t.test('UnixSocket.peer_address with a connected socket') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let client = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + client.connect(path).unwrap + + t.equal(client.peer_address.unwrap, listener.local_address.unwrap) + } + + t.test('UnixSocket.read') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + socket.send_string_to('ping', path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.write_bytes') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + + let written = stream.write_bytes('ping'.to_byte_array).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.write_string') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + + let written = stream.write_string('ping').unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.flush') fn (t) { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('UnixSocket.receive_buffer_size') fn (t) { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true((socket.receive_buffer_size = 256).ok?) + } + + t.test('UnixSocket.send_buffer_size') fn (t) { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true((socket.send_buffer_size = 256).ok?) + } + + t.test('UnixSocket.shutdown_read') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixSocket.shutdown_write') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixSocket.shutdown shuts down the writing half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixSocket.shutdown shuts down the reading half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixSocket.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('UnixDatagram.new') fn (t) { + let path = SocketPath.new(t.id) + + t.true(UnixDatagram.new(path).ok?) + t.true(UnixDatagram.new(path).error?) + } + + t.test('UnixDatagram.connect') fn (t) { + let pair = SocketPath.pair(t.id) + let socket1 = UnixDatagram.new(pair.0).unwrap + let socket2 = UnixDatagram.new(pair.1).unwrap + + t.true(socket2.connect(pair.0).ok?) + } + + t.test('UnixDatagram.send_to') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + socket.send_string_to('ping', address: path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.receive_from') fn (t) { + let pair = SocketPath.pair(t.id) + let listener = UnixDatagram.new(pair.0).unwrap + let client = UnixDatagram.new(pair.1).unwrap + + client.send_string_to('ping', pair.0).unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.local_address') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + t.equal(socket.local_address.unwrap, UnixAddress.new(path.to_string)) + } + + t.test('UnixDatagram.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('UnixDatagram.read') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + socket.send_string_to('ping', address: path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.write_bytes') fn (t) { + let pair = SocketPath.pair(t.id) + let server = UnixDatagram.new(pair.0).unwrap + let client = UnixDatagram.new(pair.1).unwrap + + client.connect(pair.0).unwrap + + let bytes = ByteArray.new + + t.equal(client.write_bytes('ping'.to_byte_array).unwrap, 4) + t.equal(server.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.flush') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('UnixClient.new') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + t.true(UnixClient.new(path).ok?) + } + + t.test('UnixClient.with_timeout') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + # Unlike IP sockets there's no unroutable address we can use, so at best we + # can assert the following doesn't time out. This means this test is mostly + # a smoke test, unlikely to actually fail unless our runtime is bugged. + t.true( + UnixClient + .with_timeout(address: path, timeout_after: Duration.from_secs(5)) + .ok? + ) + } + + t.test('UnixClient.local_address') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + t.equal(stream.local_address.unwrap, UnixAddress.new('')) + } + + t.test('UnixClient.peer_address') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + t.equal(stream.peer_address.unwrap, UnixAddress.new(path.to_string)) + } + + t.test('UnixClient.read') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + + connection.write_string('ping').unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixClient.write_bytes') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_bytes('ping'.to_byte_array).unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixClient.write_string') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_string('ping').unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixClient.flush') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + t.true(stream.flush.ok?) + } + + t.test('UnixClient.shutdown_read') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixClient.shutdown_write') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixClient.shutdown shuts down the writing half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixClient.shutdown shuts down the reading half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixClient.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let client = UnixClient.new(path).unwrap + + t.true(client.try_clone.ok?) + } + + t.test('UnixServer.new') fn (t) { + let path = SocketPath.new(t.id) + + t.true(UnixServer.new(path).ok?) + } + + t.test('UnixServer.accept') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixServer.new(path).unwrap + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + + t.equal(connection.local_address.unwrap, stream.peer_address.unwrap) + } + + t.test('UnixServer.local_address') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixServer.new(path).unwrap + let addr = listener.local_address.unwrap + + t.equal(addr, UnixAddress.new(path.to_string)) + } + + t.test('UnixServer.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let server = UnixServer.new(path).unwrap + + t.true(server.try_clone.ok?) + } +} diff --git a/libstd/test/std/test_array.inko b/std/test/std/test_array.inko similarity index 77% rename from libstd/test/std/test_array.inko rename to std/test/std/test_array.inko index 3376a06a3..8893eef3c 100644 --- a/libstd/test/std/test_array.inko +++ b/std/test/std/test_array.inko @@ -1,4 +1,4 @@ -import helpers::(fmt, hash, Script) +import helpers::(fmt, hash) import std::drop::(drop, Drop) import std::rand::Random import std::test::Tests @@ -6,16 +6,16 @@ import std::test::Tests class Counter { let @value: Int - fn static new -> Self { - Self { @value = 0 } + fn static new -> Counter { + Counter { @value = 0 } } } class TrackDrop { let @counter: mut Counter - fn static new(counter: mut Counter) -> Self { - Self { @counter = counter } + fn static new(counter: mut Counter) -> TrackDrop { + TrackDrop { @counter = counter } } } @@ -31,7 +31,7 @@ fn pub tests(t: mut Tests) { let ary2: Array[Int] = Array.with_capacity(2) t.equal(ary1.capacity, 0) - t.equal(ary2.capacity, 4) + t.equal(ary2.capacity, 2) } t.test('Array.filled') fn (t) { @@ -72,28 +72,26 @@ fn pub tests(t: mut Tests) { t.equal(vals.remove_at(1), 20) t.equal(vals, [10, 30]) - t.true( - Script - .new(id: t.id, code: '[10].remove_at(5)') - .run - .contains?('out of bounds') - ) } - t.test('Array.get') fn (t) { + t.panic('Array.remove_at with an invalid index') fn { + [10].remove_at(5) + } + + t.test('Array.opt') fn (t) { let vals = [10, 20, 30] - t.equal(vals.get(1), Option.Some(ref 20)) - t.equal(vals.get(5), Option.None) - t.equal(vals.get(-5), Option.None) + t.equal(vals.opt(1), Option.Some(ref 20)) + t.equal(vals.opt(5), Option.None) + t.equal(vals.opt(-5), Option.None) } - t.test('Array.get_mut') fn (t) { + t.test('Array.opt_mut') fn (t) { let vals = [10, 20, 30] - t.equal(vals.get(1), Option.Some(mut 20)) - t.equal(vals.get(5), Option.None) - t.equal(vals.get(-5), Option.None) + t.equal(vals.opt_mut(1), Option.Some(mut 20)) + t.equal(vals.opt_mut(5), Option.None) + t.equal(vals.opt_mut(-5), Option.None) } t.test('Array.swap') fn (t) { @@ -101,12 +99,10 @@ fn pub tests(t: mut Tests) { t.equal(vals.swap(index: 1, with: 40), 20) t.equal(vals, [10, 40, 30]) - t.true( - Script - .new(id: t.id, code: '[10].swap(index: 5, with: 42)') - .run - .contains?('out of bounds') - ) + } + + t.panic('Array.swap with an invalid index') fn { + [10].swap(index: 5, with: 42) } t.test('Array.iter') fn (t) { @@ -194,37 +190,38 @@ fn pub tests(t: mut Tests) { t.false([10, 20].contains?(30)) } - t.test('Array.index') fn (t) { - let vals = [10, 20, 30] + t.test('Array.get') fn (t) { + t.equal([10].get(0), 10) + } - t.equal((ref vals)[1], ref 20) - t.true( - Script.new(id: t.id, code: '(ref [10])[2]').run.contains?('out of bounds') - ) + t.panic('Array.get with an invalid index') fn { + [10].get(1) } - t.test('Array.index_mut') fn (t) { + t.test('Array.get_mut') fn (t) { let vals = [10, 20, 30] - t.equal((mut vals)[1], mut 20) - t.true( - Script.new(id: t.id, code: '(mut [10])[2]').run.contains?('out of bounds') - ) + t.equal([10].get_mut(0), 10) } - t.test('Array.set_index') fn (t) { + t.panic('Array.get_mut with an invalid index') fn { + [10].get_mut(1) + } + + t.test('Array.set') fn (t) { let count = Counter.new let drops = [TrackDrop.new(count)] let vals = [10, 20, 30] - vals[1] = 40 - drops[0] = TrackDrop.new(count) + vals.set(1, 40) + drops.set(0, TrackDrop.new(count)) t.equal(vals, [10, 40, 30]) t.equal(count.value, 1) - t.true( - Script.new(id: t.id, code: '[10][2] = 4').run.contains?('out of bounds') - ) + } + + t.panic('Array.set with an invalid index') fn { + [10].set(1, 20) } t.test('Array.clone') fn (t) { diff --git a/libstd/test/std/test_bool.inko b/std/test/std/test_bool.inko similarity index 100% rename from libstd/test/std/test_bool.inko rename to std/test/std/test_bool.inko diff --git a/libstd/test/std/test_byte_array.inko b/std/test/std/test_byte_array.inko similarity index 85% rename from libstd/test/std/test_byte_array.inko rename to std/test/std/test_byte_array.inko index b49d7f1f0..96f8d6ef7 100644 --- a/libstd/test/std/test_byte_array.inko +++ b/std/test/std/test_byte_array.inko @@ -1,4 +1,4 @@ -import helpers::(fmt, hash, Script) +import helpers::(fmt, hash) import std::iter::EOF import std::test::Tests @@ -10,8 +10,8 @@ fn pub tests(t: mut Tests) { t.test('ByteArray.from_array') fn (t) { let bytes = ByteArray.from_array([10, 20]) - t.equal(bytes[0], 10) - t.equal(bytes[1], 20) + t.equal(bytes.get(0), 10) + t.equal(bytes.get(1), 20) } t.test('ByteArray.filled') fn (t) { @@ -75,12 +75,12 @@ fn pub tests(t: mut Tests) { t.equal(bytes.slice(start: 0, length: 10), bytes) } - t.test('ByteArray.get') fn (t) { + t.test('ByteArray.opt') fn (t) { let bytes = ByteArray.from_array([105, 110]) - t.equal(bytes.get(0), Option.Some(105)) - t.equal(bytes.get(1), Option.Some(110)) - t.equal(bytes.get(2), Option.None) + t.equal(bytes.opt(0), Option.Some(105)) + t.equal(bytes.opt(1), Option.Some(110)) + t.equal(bytes.opt(2), Option.None) } t.test('ByteArray.length') fn (t) { @@ -104,19 +104,23 @@ fn pub tests(t: mut Tests) { t.equal(bytes.iter.to_array, [10, 20]) } - t.test('ByteArray.index') fn (t) { + t.test('ByteArray.get') fn (t) { let bytes = ByteArray.from_array([10, 20]) - t.equal(bytes[0], 10) - t.equal(bytes[1], 20) + t.equal(bytes.get(0), 10) + t.equal(bytes.get(1), 20) + } + + t.panic('ByteArray.get with an invalid index') fn { + ByteArray.new.get(0) } - t.test('ByteArray.set_index') fn (t) { + t.test('ByteArray.set') fn (t) { let bytes = ByteArray.from_array([10, 20]) - bytes[0] = 50 + bytes.set(0, 50) - t.equal(bytes[0], 50) + t.equal(bytes.get(0), 50) } t.test('ByteArray.to_byte_array') fn (t) { @@ -192,11 +196,11 @@ fn pub tests(t: mut Tests) { let iter = input.iter let buff = ByteArray.new - t.equal(iter.read(into: buff, size: 2), 2) + t.equal(iter.read(into: buff, size: 2), Result.Ok(2)) t.equal(buff.to_string, 'fo') - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'foo') - t.equal(iter.read(into: buff, size: 2), 0) + t.equal(iter.read(into: buff, size: 2), Result.Ok(0)) t.equal(buff.to_string, 'foo') } @@ -205,15 +209,15 @@ fn pub tests(t: mut Tests) { let iter = input.iter let buff = ByteArray.new - t.equal(iter.read(into: buff, size: 1), 1) + t.equal(iter.read(into: buff, size: 1), Result.Ok(1)) t.equal(buff.to_string, 'f') input.pop - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'fo') input.push(111) - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'foo') } @@ -222,8 +226,8 @@ fn pub tests(t: mut Tests) { let iter = input.iter let buff = ByteArray.new - t.equal(iter.read_all(buff), 3) - t.equal(iter.read_all(buff), 0) + t.equal(iter.read_all(buff), Result.Ok(3)) + t.equal(iter.read_all(buff), Result.Ok(0)) t.equal(buff.to_string, 'foo') } @@ -255,12 +259,9 @@ fn pub tests(t: mut Tests) { bytes.resize(length: 0, value: 0) t.equal(bytes, ByteArray.new) + } - t.true( - Script - .new(id: t.id, code: 'ByteArray.new.resize(length: -5, value: 0)') - .run - .contains?('greater than zero') - ) + t.panic('ByteArray.resize with an invalid length') fn { + ByteArray.new.resize(length: -5, value: 0) } } diff --git a/libstd/test/std/test_cmp.inko b/std/test/std/test_cmp.inko similarity index 93% rename from libstd/test/std/test_cmp.inko rename to std/test/std/test_cmp.inko index 0380e02d8..b445207b9 100644 --- a/libstd/test/std/test_cmp.inko +++ b/std/test/std/test_cmp.inko @@ -7,8 +7,8 @@ class enum Letter { case B } -impl Compare for Letter { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Letter] for Letter { + fn pub cmp(other: ref Letter) -> Ordering { match self { case A -> match other { case A -> Ordering.Equal @@ -22,8 +22,8 @@ impl Compare for Letter { } } -impl Equal for Letter { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Letter] for Letter { + fn pub ==(other: ref Letter) -> Bool { match self { case A -> match other { case A -> true diff --git a/libstd/test/std/test_debug.inko b/std/test/std/test_debug.inko similarity index 85% rename from libstd/test/std/test_debug.inko rename to std/test/std/test_debug.inko index b8e2a9980..f258e79f4 100644 --- a/libstd/test/std/test_debug.inko +++ b/std/test/std/test_debug.inko @@ -14,9 +14,9 @@ fn pub tests(t: mut Tests) { t.test('debug.stacktrace') fn (t) { let trace = stacktrace(skip: 0) - let last = trace[trace.length - 1] + let last = trace.get(trace.length - 1) - t.equal(last.name, 'stacktrace') + t.equal(last.name, 'std::debug.stacktrace') t.true(last.path.to_string.ends_with?('debug.inko')) t.true(last.line >= 1) } diff --git a/std/test/std/test_env.inko b/std/test/std/test_env.inko new file mode 100644 index 000000000..17ab4059b --- /dev/null +++ b/std/test/std/test_env.inko @@ -0,0 +1,93 @@ +import std::env +import std::stdio::STDOUT +import std::test::Tests + +fn pub tests(t: mut Tests) { + t.test('env::ARCH') fn (t) { + t.true(env::ARCH.size > 0) + } + + t.test('env::OS') fn (t) { + t.true(env::OS.size > 0) + } + + t.test('env::ABI') fn (t) { + t.true(env::ABI.size > 0) + } + + t.fork( + 'env.opt', + child: fn { STDOUT.new.write_string(env.opt('INKO_TEST').unwrap_or('?')) }, + test: fn (test, process) { + process.variable('INKO_TEST', 'foo') + test.equal(process.spawn.stdout, 'foo') + } + ) + + t.fork( + 'env.variables', + child: fn { + let out = STDOUT.new + let vars = env.variables + + out.print(vars.get('INKO_FOO')) + out.print(vars.get('INKO_BAR')) + }, + test: fn (test, process) { + process.variable('INKO_FOO', 'foo') + process.variable('INKO_BAR', 'bar') + test.equal(process.spawn.stdout, "foo\nbar\n") + } + ) + + t.test('env.home_directory') fn (t) { + # Home directories are optional, and even if they're set the actual path may + # not exist. As such there's not really anything we can test for, other than + # asserting the path isn't empty. + match env.home_directory { + case Some(path) -> t.true(path.to_string.size > 0) + case _ -> {} + } + } + + t.test('env.working_directory') fn (t) { + let path = env.working_directory.unwrap + + t.true(path.directory?) + } + + t.fork( + 'env.working_directory=', + child: fn { + let out = STDOUT.new + + env.working_directory = env.temporary_directory + out.write_string(env.working_directory.unwrap.to_string) + }, + test: fn (test, process) { + test.equal(process.spawn.stdout, env.temporary_directory.to_string) + } + ) + + t.fork( + 'env.arguments', + child: fn { + let out = STDOUT.new + let args = env.arguments + + out.print(args.get(0)) + out.print(args.get(1)) + }, + test: fn (test, process) { + process.argument('foo') + process.argument('bar') + test.equal(process.spawn.stdout, "foo\nbar\n") + } + ) + + t.test('env.executable') fn (t) { + let path = env.executable.unwrap + + t.true(path.file?) + } +} diff --git a/libstd/test/std/test_float.inko b/std/test/std/test_float.inko similarity index 97% rename from libstd/test/std/test_float.inko rename to std/test/std/test_float.inko index 8537c7d25..bbe8f2826 100644 --- a/libstd/test/std/test_float.inko +++ b/std/test/std/test_float.inko @@ -1,7 +1,6 @@ import helpers::(fmt, hash) import std::cmp::Ordering import std::hash::Hasher -import std::map::DefaultHasher import std::test::Tests fn pub tests(t: mut Tests) { @@ -110,6 +109,9 @@ fn pub tests(t: mut Tests) { t.equal(-0.0.to_int, 0) t.equal(10.5.to_int, 10) t.equal(-10.5.to_int, -10) + t.equal(Float.not_a_number.to_int, 0) + t.equal(Float.infinity.to_int, 9_223_372_036_854_775_807) + t.equal(Float.negative_infinity.to_int, -9_223_372_036_854_775_808) } t.test('Float.to_float') fn (t) { diff --git a/libstd/test/std/test_fmt.inko b/std/test/std/test_fmt.inko similarity index 100% rename from libstd/test/std/test_fmt.inko rename to std/test/std/test_fmt.inko diff --git a/libstd/test/std/test_int.inko b/std/test/std/test_int.inko similarity index 100% rename from libstd/test/std/test_int.inko rename to std/test/std/test_int.inko diff --git a/libstd/test/std/test_io.inko b/std/test/std/test_io.inko similarity index 81% rename from libstd/test/std/test_io.inko rename to std/test/std/test_io.inko index 1853b9b63..8fcedae0d 100644 --- a/libstd/test/std/test_io.inko +++ b/std/test/std/test_io.inko @@ -6,48 +6,50 @@ class Reader { let @index: Int let @bytes: ByteArray - fn static new -> Self { - Self { @index = 0, @bytes = ByteArray.from_array([1, 2, 3]) } + fn static new -> Reader { + Reader { @index = 0, @bytes = ByteArray.from_array([1, 2, 3]) } } } impl Read for Reader { - fn pub mut read(into: mut ByteArray, size: Int) -> Int { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Never] { let start = @index let mut max = @index + size if max > @bytes.length { max = @bytes.length } while @index < max { - into.push(@bytes[@index]) + into.push(@bytes.get(@index)) @index += 1 } - @index - start + Result.Ok(@index - start) } } class Writer { let @buffer: ByteArray - fn static new -> Self { - Self { @buffer = ByteArray.new } + fn static new -> Writer { + Writer { @buffer = ByteArray.new } } } impl Write for Writer { - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Never] { bytes.iter.each fn (byte) { @buffer.push(byte) } - bytes.length + Result.Ok(bytes.length) } - fn pub mut write_string(string: String) -> Int { + fn pub mut write_string(string: String) -> Result[Int, Never] { string.to_byte_array.iter.each fn (byte) { @buffer.push(byte) } - string.size + Result.Ok(string.size) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } fn pub tests(t: mut Tests) { @@ -95,7 +97,7 @@ fn pub tests(t: mut Tests) { t.test('Read.read_all') fn (t) { let reader = Reader.new let bytes = ByteArray.new - let size = try! reader.read_all(bytes) + let size = reader.read_all(bytes).unwrap t.equal(size, 3) t.equal(bytes, ByteArray.from_array([1, 2, 3])) @@ -104,7 +106,7 @@ fn pub tests(t: mut Tests) { t.test('Write.print') fn (t) { let writer = Writer.new - try! writer.print('foo') + writer.print('foo').unwrap t.equal(writer.buffer, "foo\n".to_byte_array) } diff --git a/libstd/test/std/test_iter.inko b/std/test/std/test_iter.inko similarity index 84% rename from libstd/test/std/test_iter.inko rename to std/test/std/test_iter.inko index 65553499d..517c4460c 100644 --- a/libstd/test/std/test_iter.inko +++ b/std/test/std/test_iter.inko @@ -46,6 +46,30 @@ fn pub tests(t: mut Tests) { t.equal(iter.next, Option.Some(ref 20)) } + t.test('Iter.find_map') fn (t) { + let vals = [10, 20] + + t.equal( + vals.iter.find_map fn (val) { + if val == 20 { Option.Some('yes') } else { Option.None } + }, + Option.Some('yes') + ) + + t.equal( + vals.iter.find_map fn (val) { Option.None as Option[Int] }, + Option.None + ) + + let iter = vals.iter + + iter.find_map fn (val) { + if val == 10 { Option.Some(val) } else { Option.None } + } + + t.equal(iter.next, Option.Some(ref 20)) + } + t.test('Iter.any?') fn (t) { let vals = [10, 20] @@ -127,7 +151,7 @@ fn pub tests(t: mut Tests) { t.test('Enum.indexed') fn (t) { let vals = [10, 20, 30] - let iter = Enum.indexed(vals.length) fn (index) { vals[index] } + let iter = Enum.indexed(vals.length) fn (index) { vals.get(index) } t.equal(iter.to_array, [mut 10, mut 20, mut 30]) } diff --git a/std/test/std/test_json.inko b/std/test/std/test_json.inko new file mode 100644 index 000000000..ab2d45ec4 --- /dev/null +++ b/std/test/std/test_json.inko @@ -0,0 +1,404 @@ +import helpers::(fmt) +import std::json::(self, Error, Json, Parser) +import std::test::Tests + +fn parse(input: String) -> Result[Json, Error] { + Parser.new(input).parse +} + +fn parse_invalid(input: String) -> Option[String] { + Parser.new(input).parse.error.map fn (v) { v.to_string } +} + +fn pub tests(t: mut Tests) { + t.test('Error.fmt') fn (t) { + let err = Error { @message = 'foo', @line = 1, @offset = 5 } + + t.equal(fmt(err), 'foo, on line 1 at byte offset 5') + } + + t.test('Error.to_string') fn (t) { + let err = Error { @message = 'foo', @line = 1, @offset = 5 } + + t.equal(err.to_string, 'foo, on line 1 at byte offset 5') + } + + t.test('Json.fmt') fn (t) { + let map = Map.new + + map.set('a', Json.Int(10)) + + t.equal(fmt(Json.Int(42)), 'Int(42)') + t.equal(fmt(Json.Float(42.0)), 'Float(42.0)') + t.equal(fmt(Json.String('test')), 'String("test")') + t.equal(fmt(Json.Array([Json.Int(10)])), 'Array([Int(10)])') + t.equal(fmt(Json.Object(map)), 'Object({"a": Int(10)})') + t.equal(fmt(Json.Bool(true)), 'Bool(true)') + t.equal(fmt(Json.Bool(false)), 'Bool(false)') + t.equal(fmt(Json.Null), 'Null') + } + + t.test('Json.==') fn (t) { + let map1 = Map.new + let map2 = Map.new + let map3 = Map.new + + map1.set('a', Json.Int(10)) + map2.set('a', Json.Int(10)) + map3.set('a', Json.Int(10)) + + t.equal(Json.Int(10), Json.Int(10)) + t.not_equal(Json.Int(10), Json.Int(20)) + t.not_equal(Json.Int(10), Json.Float(20.0)) + + t.equal(Json.Float(10.0), Json.Float(10.0)) + t.not_equal(Json.Float(10.0), Json.Float(20.0)) + t.not_equal(Json.Float(10.0), Json.Int(10)) + + t.equal(Json.String('foo'), Json.String('foo')) + t.not_equal(Json.String('foo'), Json.String('bar')) + t.not_equal(Json.String('foo'), Json.Int(10)) + + t.equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(10)])) + t.not_equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(20)])) + t.not_equal(Json.Array([Json.Int(10)]), Json.Int(10)) + + t.equal(Json.Object(map1), Json.Object(map2)) + t.not_equal(Json.Object(map3), Json.Object(Map.new)) + t.not_equal(Json.Object(Map.new), Json.Int(10)) + + t.equal(Json.Bool(true), Json.Bool(true)) + t.not_equal(Json.Bool(true), Json.Bool(false)) + t.not_equal(Json.Bool(true), Json.Int(10)) + + t.equal(Json.Null, Json.Null) + t.not_equal(Json.Null, Json.Int(10)) + } + + t.test('Json.to_string') fn (t) { + let map = Map.new + + map.set('a', Json.Int(1)) + map.set('b', Json.Int(2)) + + t.equal(Json.Int(42).to_string, '42') + t.equal(Json.Float(1.2).to_string, '1.2') + t.equal(Json.String('foo').to_string, '"foo"') + t.equal(Json.String("a\nb").to_string, '"a\nb"') + t.equal(Json.String("a\rb").to_string, '"a\rb"') + t.equal(Json.String("a\tb").to_string, '"a\tb"') + t.equal(Json.String("a\u{C}b").to_string, '"a\fb"') + t.equal(Json.String("a\u{8}b").to_string, '"a\bb"') + t.equal(Json.String('a\\b').to_string, '"a\\\\b"') + t.equal(Json.Array([]).to_string, '[]') + t.equal(Json.Array([Json.Int(1), Json.Int(2)]).to_string, '[1, 2]') + t.equal(Json.Object(map).to_string, '{"a": 1, "b": 2}') + t.equal(Json.Object(Map.new).to_string, '{}') + t.equal(Json.Bool(true).to_string, 'true') + t.equal(Json.Bool(false).to_string, 'false') + t.equal(Json.Null.to_string, 'null') + } + + t.test('Json.to_pretty_string') fn (t) { + t.equal(Json.Int(42).to_pretty_string, '42') + t.equal(Json.Float(1.2).to_pretty_string, '1.2') + t.equal(Json.String('foo').to_pretty_string, '"foo"') + t.equal(Json.String("a\nb").to_pretty_string, '"a\nb"') + t.equal(Json.String("a\rb").to_pretty_string, '"a\rb"') + t.equal(Json.String("a\tb").to_pretty_string, '"a\tb"') + t.equal(Json.String("a\u{C}b").to_pretty_string, '"a\fb"') + t.equal(Json.String("a\u{8}b").to_pretty_string, '"a\bb"') + t.equal(Json.String('a\\b').to_pretty_string, '"a\\\\b"') + t.equal(Json.Bool(true).to_pretty_string, 'true') + t.equal(Json.Bool(false).to_pretty_string, 'false') + t.equal(Json.Null.to_pretty_string, 'null') + + t.equal(Json.Array([]).to_pretty_string, '[]') + t.equal( + Json.Array([Json.Int(1), Json.Int(2)]).to_pretty_string, + '[ + 1, + 2 +]' + ) + + t.equal( + Json.Array([Json.Array([Json.Int(1), Json.Int(2)])]).to_pretty_string, + '[ + [ + 1, + 2 + ] +]' + ) + + let map1 = Map.new + let map2 = Map.new + let map3 = Map.new + + map1.set('a', Json.Int(1)) + map1.set('b', Json.Int(2)) + map2.set('a', Json.Array([Json.Int(1), Json.Int(2)])) + map3.set('a', Json.Int(1)) + map3.set('b', Json.Object(map2)) + + t.equal(Json.Object(Map.new).to_pretty_string, '{}') + t.equal( + Json.Object(map1).to_pretty_string, + '{ + "a": 1, + "b": 2 +}' + ) + + t.equal( + Json.Object(map3).to_pretty_string, + '{ + "a": 1, + "b": { + "a": [ + 1, + 2 + ] + } +}' + ) + } + + t.test('Parsing integers') fn (t) { + t.equal(parse('0'), Result.Ok(Json.Int(0))) + t.equal(parse('42'), Result.Ok(Json.Int(42))) + t.equal(parse(' 42'), Result.Ok(Json.Int(42))) + t.equal(parse('42 '), Result.Ok(Json.Int(42))) + t.equal(parse("\t42"), Result.Ok(Json.Int(42))) + t.equal(parse("\r42"), Result.Ok(Json.Int(42))) + t.equal(parse('-42'), Result.Ok(Json.Int(-42))) + + t.true(parse('00').error?) + t.true(parse('10,').error?) + t.true(parse('-').error?) + t.true(parse('-01').error?) + t.true(parse('01').error?) + t.true(parse('1a').error?) + t.true(parse('-a').error?) + } + + t.test('Parsing floats') fn (t) { + t.equal(parse(' 1.2'), Result.Ok(Json.Float(1.2))) + t.equal(parse('1.2 '), Result.Ok(Json.Float(1.2))) + t.equal(parse('1.2'), Result.Ok(Json.Float(1.2))) + t.equal(parse('-1.2'), Result.Ok(Json.Float(-1.2))) + t.equal(parse('1.2e+123'), Result.Ok(Json.Float(1.2e+123))) + t.equal(parse('1.2e-123'), Result.Ok(Json.Float(1.2e-123))) + t.equal(parse('1.2E+123'), Result.Ok(Json.Float(1.2e+123))) + t.equal(parse('1.2E-123'), Result.Ok(Json.Float(1.2e-123))) + t.equal(parse('-1.2E-123'), Result.Ok(Json.Float(-1.2e-123))) + t.equal(parse('0.0'), Result.Ok(Json.Float(0.0))) + t.equal(parse('0E0'), Result.Ok(Json.Float(0.0))) + t.equal(parse('0e+1'), Result.Ok(Json.Float(0.0))) + t.equal(parse('1.2E1'), Result.Ok(Json.Float(1.2e1))) + t.equal(parse('1.2e1'), Result.Ok(Json.Float(1.2e1))) + t.equal( + parse('1.7976931348623157e+310'), + Result.Ok(Json.Float(Float.infinity)) + ) + t.equal( + parse('4.940656458412465441765687928682213723651e-330'), + Result.Ok(Json.Float(0.0)) + ) + t.equal( + parse('-0.000000000000000000000000000000000000000000000000000000000000000000000000000001'), + Result.Ok(Json.Float(-1.0E-78)) + ) + + # These numbers are too big for regular integers, so we promote them to + # floats. + t.equal( + parse('11111111111111111111111111111111111111111'), + Result.Ok(Json.Float(11111111111111111111111111111111111111111.0)) + ) + t.equal( + parse('10000000000000000999'), + Result.Ok(Json.Float(10000000000000000999.0)) + ) + + t.true(parse('00.0').error?) + t.true(parse('1.2e').error?) + t.true(parse('1.2e+').error?) + t.true(parse('1.2e-').error?) + t.true(parse('1.2E').error?) + t.true(parse('1.2E+').error?) + t.true(parse('1.2E-').error?) + t.true(parse('1.2E+a').error?) + t.true(parse('1.2E-a').error?) + t.true(parse('0E').error?) + t.true(parse('10.2,').error?) + + t.equal( + parse_invalid("\n1.2e"), + Option.Some( + 'One or more tokens are required, but we ran out of input, \ + on line 2 at byte offset 5' + ) + ) + } + + t.test('Parsing arrays') fn (t) { + t.equal(parse('[]'), Result.Ok(Json.Array([]))) + t.equal(parse('[10]'), Result.Ok(Json.Array([Json.Int(10)]))) + t.equal( + parse('[10, 20]'), + Result.Ok(Json.Array([Json.Int(10), Json.Int(20)])) + ) + + t.true(parse('[').error?) + t.true(parse(']').error?) + t.true(parse('[,10]').error?) + t.true(parse('[10,]').error?) + t.true(parse('[10').error?) + t.true(parse('[10,').error?) + t.true(parse('[10true]').error?) + t.true(parse('[],').error?) + + { + let parser = Parser.new('[[[[10]]]]') + + parser.max_depth = 2 + t.true(parser.parse.error?) + } + } + + t.test('Parsing booleans') fn (t) { + t.equal(parse('true'), Result.Ok(Json.Bool(true))) + t.equal(parse('false'), Result.Ok(Json.Bool(false))) + + t.true(parse('t').error?) + t.true(parse('tr').error?) + t.true(parse('tru').error?) + t.true(parse('f').error?) + t.true(parse('fa').error?) + t.true(parse('fal').error?) + t.true(parse('fals').error?) + } + + t.test('Parsing null') fn (t) { + t.equal(parse('null'), Result.Ok(Json.Null)) + + t.true(parse('n').error?) + t.true(parse('nu').error?) + t.true(parse('nul').error?) + } + + t.test('Parsing strings') fn (t) { + t.equal(parse('"foo"'), Result.Ok(Json.String('foo'))) + t.equal(parse('"foo bar"'), Result.Ok(Json.String('foo bar'))) + t.equal(parse('"foo\nbar"'), Result.Ok(Json.String("foo\nbar"))) + t.equal(parse('"foo\tbar"'), Result.Ok(Json.String("foo\tbar"))) + t.equal(parse('"foo\rbar"'), Result.Ok(Json.String("foo\rbar"))) + t.equal(parse('"foo\bbar"'), Result.Ok(Json.String("foo\u{0008}bar"))) + t.equal(parse('"foo\fbar"'), Result.Ok(Json.String("foo\u{000C}bar"))) + t.equal(parse('"foo\\"bar"'), Result.Ok(Json.String('foo"bar'))) + t.equal(parse('"foo\\/bar"'), Result.Ok(Json.String('foo/bar'))) + t.equal(parse('"foo\\\\bar"'), Result.Ok(Json.String("foo\\bar"))) + t.equal(parse('"foo\u005Cbar"'), Result.Ok(Json.String('foo\\bar'))) + t.equal( + parse('"foo\\u001Fbar"'), + Result.Ok(Json.String("foo\u{001F}bar")) + ) + t.equal(parse('"\uD834\uDD1E"'), Result.Ok(Json.String("\u{1D11E}"))) + t.equal( + parse('"\uE000\uE000"'), + Result.Ok(Json.String("\u{E000}\u{E000}")) + ) + + t.true(parse("\"\0\"").error?) + t.true(parse("\"\n\"").error?) + t.true(parse("\"\t\"").error?) + t.true(parse("\"\r\"").error?) + t.true(parse("\"\u{8}\"").error?) # \b + t.true(parse("\"\u{c}\"").error?) # \f + + t.true(parse('"\x42"').error?) + t.true(parse('"\u1"').error?) + t.true(parse('"\u12"').error?) + t.true(parse('"\u123"').error?) + t.true(parse('"\u{XXXX}"').error?) + t.true(parse('"\uD834\uE000"').error?) + t.true(parse('"\uD834\uZZZZ"').error?) + t.true(parse('"\uDFFF\uDFFF"').error?) + + { + let parser = Parser.new('"foo"') + + parser.max_string_size = 2 + t.true(parser.parse.error?) + } + + t.equal( + parse_invalid('"a'), + Option.Some( + 'One or more tokens are required, but we ran out of input, \ + on line 1 at byte offset 2' + ) + ) + } + + t.test('Parsing objects') fn (t) { + let map1 = Map.new + let map2 = Map.new + let map3 = Map.new + let map4 = Map.new + let map5 = Map.new + + map2.set('a', Json.Int(10)) + map3.set('a', Json.Int(20)) + map4.set('a', Json.Int(10)) + map4.set('b', Json.Int(20)) + map5.set('a', Json.Int(10)) + map5.set('b', Json.Int(20)) + + t.equal(parse('{}'), Result.Ok(Json.Object(map1))) + t.equal(parse('{ "a": 10 }'), Result.Ok(Json.Object(map2))) + t.equal(parse('{"a": 10, "a": 20}'), Result.Ok(Json.Object(map3))) + t.equal(parse('{"a": 10, "b": 20}'), Result.Ok(Json.Object(map4))) + t.equal( + parse('{ + "a": 10, + "b": 20 + }' + ), + Result.Ok(Json.Object(map5)) + ) + + t.true(parse('{').error?) + t.true(parse('}').error?) + t.true(parse('{{}}').error?) + t.true(parse('{"a"}').error?) + t.true(parse('{"a":}').error?) + t.true(parse('{"a":10,}').error?) + t.true(parse('{},').error?) + t.true(parse('{"a": true} "x"').error?) + + { + let parser = Parser.new('{"a": {"b": {"c": 10}}}') + + parser.max_depth = 2 + t.true(parser.parse.error?) + } + + t.equal( + parse_invalid('{"a"}'), + Option.Some("The character '}' is unexpected, on line 1 at byte offset 4") + ) + } + + t.test('Parsing Unicode BOMs') fn (t) { + t.true(parse("\u{FEFF}10").error?) + t.true(parse("\u{FFFE}10").error?) + t.true(parse("\u{EF}\u{BB}\u{BF}10").error?) + } + + t.test('json.parse') fn (t) { + t.equal(json.parse('[10]'), Result.Ok(Json.Array([Json.Int(10)]))) + } +} diff --git a/libstd/test/std/test_map.inko b/std/test/std/test_map.inko similarity index 59% rename from libstd/test/std/test_map.inko rename to std/test/std/test_map.inko index 5f9494425..20115e0fb 100644 --- a/libstd/test/std/test_map.inko +++ b/std/test/std/test_map.inko @@ -1,12 +1,12 @@ import helpers::(fmt, hash) -import std::map::(DefaultHasher, Hasher) +import std::hash::Hasher import std::test::Tests fn pub tests(t: mut Tests) { t.test('Entry.key') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') let entry = map.iter.next.unwrap @@ -16,23 +16,13 @@ fn pub tests(t: mut Tests) { t.test('Entry.value') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') let entry = map.iter.next.unwrap t.equal(entry.value, 'Alice') } - t.test('DefaultHasher.hash') fn (t) { - let hasher1: Hasher = DefaultHasher.new - let hasher2: Hasher = DefaultHasher.new - - 'foo'.hash(hasher1) - 'foo'.hash(hasher2) - - t.equal(hasher1.hash, hasher2.hash) - } - t.test('Map.with_capacity') fn (t) { let map1: Map[Int, Int] = Map.new let map2: Map[Int, Int] = Map.with_capacity(0) @@ -50,10 +40,10 @@ fn pub tests(t: mut Tests) { t.test('Map.remove') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' - map['foo'] = 'bar' - map['bar'] = 'baz' + map.set('name', 'Alice') + map.set('city', 'Bla') + map.set('foo', 'bar') + map.set('bar', 'baz') t.equal(map.remove('city'), Option.Some('Bla')) t.equal(map.remove('city'), Option.None) @@ -64,101 +54,101 @@ fn pub tests(t: mut Tests) { t.test('Map.iter') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let entries = map.iter.to_array t.equal(entries.length, 2) - t.equal(entries[0].key, 'name') - t.equal(entries[0].value, 'Alice') - t.equal(entries[1].key, 'city') - t.equal(entries[1].value, 'Bla') + t.equal(entries.get(0).key, 'name') + t.equal(entries.get(0).value, 'Alice') + t.equal(entries.get(1).key, 'city') + t.equal(entries.get(1).value, 'Bla') } t.test('Map.iter_mut') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let entries = map.iter_mut.to_array t.equal(entries.length, 2) - t.equal(entries[0].key, 'name') - t.equal(entries[0].value, 'Alice') - t.equal(entries[1].key, 'city') - t.equal(entries[1].value, 'Bla') + t.equal(entries.get(0).key, 'name') + t.equal(entries.get(0).value, 'Alice') + t.equal(entries.get(1).key, 'city') + t.equal(entries.get(1).value, 'Bla') } t.test('Map.into_iter') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let entries = map.into_iter.to_array t.equal(entries.length, 2) - t.equal(entries[0].key, 'name') - t.equal(entries[0].value, 'Alice') - t.equal(entries[1].key, 'city') - t.equal(entries[1].value, 'Bla') + t.equal(entries.get(0).key, 'name') + t.equal(entries.get(0).value, 'Alice') + t.equal(entries.get(1).key, 'city') + t.equal(entries.get(1).value, 'Bla') } t.test('Map.keys') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let keys = map.keys.to_array t.equal(keys.length, 2) - t.true(keys[0] == 'name' or keys[0] == 'city') + t.true(keys.get(0) == 'name' or keys.get(0) == 'city') } t.test('Map.values') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let values = map.values.to_array t.equal(values.length, 2) - t.true(values[0] == 'Alice' or values[0] == 'Bla') + t.true(values.get(0) == 'Alice' or values.get(0) == 'Bla') } - t.test('Map.get') fn (t) { + t.test('Map.opt') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(map.get('name'), Option.Some(ref 'Alice')) - t.equal(map.get('city'), Option.None) + t.equal(map.opt('name'), Option.Some(ref 'Alice')) + t.equal(map.opt('city'), Option.None) } - t.test('Map.get_mut') fn (t) { + t.test('Map.opt_mut') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(map.get_mut('name'), Option.Some(mut 'Alice')) - t.equal(map.get_mut('city'), Option.None) + t.equal(map.opt_mut('name'), Option.Some(mut 'Alice')) + t.equal(map.opt_mut('city'), Option.None) } t.test('Map.merge') fn (t) { let map1 = Map.new let map2 = Map.new - map1['name'] = 'Alice' - map2['city'] = 'Bla' + map1.set('name', 'Alice') + map2.set('city', 'Bla') map1.merge(map2) - t.equal(map1['name'], 'Alice') - t.equal(map1['city'], 'Bla') + t.equal(map1.get('name'), 'Alice') + t.equal(map1.get('city'), 'Bla') } t.test('Map.length') fn (t) { @@ -166,7 +156,7 @@ fn pub tests(t: mut Tests) { t.equal(map.length, 0) - map['name'] = 'Alice' + map.set('name', 'Alice') t.equal(map.length, 1) } @@ -179,15 +169,15 @@ fn pub tests(t: mut Tests) { let map5 = Map.new let map6 = Map.new - map1['name'] = 'Alice' - map2['name'] = 'Alice' - map4['foo'] = 'bar' + map1.set('name', 'Alice') + map2.set('name', 'Alice') + map4.set('foo', 'bar') - map5['foo'] = 'bar' - map5['bar'] = 'baz' + map5.set('foo', 'bar') + map5.set('bar', 'baz') - map6['bar'] = 'baz' - map6['foo'] = 'bar' + map6.set('bar', 'baz') + map6.set('foo', 'bar') t.equal(map1, map2) t.equal(map5, map6) @@ -199,17 +189,17 @@ fn pub tests(t: mut Tests) { let map = Map.new let alias = ref map - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(alias['name'], 'Alice') + t.equal(alias.get('name'), 'Alice') } t.test('Map.index_mut') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(map['name'], 'Alice') + t.equal(map.get('name'), 'Alice') } t.test('Map.hash') fn (t) { @@ -217,8 +207,8 @@ fn pub tests(t: mut Tests) { let map2 = Map.new let map3: Map[String, String] = Map.new - map1['name'] = 'Alice' - map2['name'] = 'Alice' + map1.set('name', 'Alice') + map2.set('name', 'Alice') t.equal(hash(map1), hash(map2)) t.not_equal(hash(map1), hash(map3)) @@ -228,7 +218,7 @@ fn pub tests(t: mut Tests) { let map1 = Map.new let map2: Map[String, String] = Map.new - map1['name'] = 'Alice' + map1.set('name', 'Alice') t.true(map1.contains?('name')) t.false(map2.contains?('name')) @@ -239,9 +229,9 @@ fn pub tests(t: mut Tests) { let map2 = Map.new let map3 = Map.new - map2['name'] = 'Alice' - map3['name'] = 'Alice' - map3['city'] = 'Bla' + map2.set('name', 'Alice') + map3.set('name', 'Alice') + map3.set('city', 'Bla') t.equal(fmt(map1), '{}') t.equal(fmt(map2), '{"name": "Alice"}') diff --git a/libstd/test/std/test_nil.inko b/std/test/std/test_nil.inko similarity index 100% rename from libstd/test/std/test_nil.inko rename to std/test/std/test_nil.inko diff --git a/libstd/test/std/test_option.inko b/std/test/std/test_option.inko similarity index 85% rename from libstd/test/std/test_option.inko rename to std/test/std/test_option.inko index f4649df0b..0db7792e1 100644 --- a/libstd/test/std/test_option.inko +++ b/std/test/std/test_option.inko @@ -1,4 +1,4 @@ -import helpers::(fmt, Script) +import helpers::(fmt) import std::test::Tests fn pub tests(t: mut Tests) { @@ -18,14 +18,24 @@ fn pub tests(t: mut Tests) { t.equal(b.as_mut, Option.None) } - t.test('Option.unwrap') fn (t) { + t.test('Option.unwrap with a Some') fn (t) { t.equal(Option.Some(42).unwrap, 42) - t.true( - Script - .new(id: t.id, code: '(Option.None as Option[Int]).unwrap') - .run - .contains?("A None can't be unwrapped") - ) + } + + t.test('Option.expect with a Some') fn (t) { + t.equal(Option.Some(42).expect('foo'), 42) + } + + t.panic('Option.unwrap with a None') fn { + let opt: Option[Int] = Option.None + + opt.unwrap + } + + t.panic('Option.expect with a None') fn { + let opt: Option[Int] = Option.None + + opt.expect('foo') } t.test('Option.unwrap_or') fn (t) { diff --git a/std/test/std/test_process.inko b/std/test/std/test_process.inko new file mode 100644 index 000000000..df3157a06 --- /dev/null +++ b/std/test/std/test_process.inko @@ -0,0 +1,12 @@ +import std::process +import std::test::Tests +import std::time::(Duration, Instant) + +fn pub tests(t: mut Tests) { + t.test('process.sleep') fn (t) { + let start = Instant.new + + process.sleep(Duration.from_millis(10)) + t.true(start.elapsed.to_millis >= 10) + } +} diff --git a/libstd/test/std/test_rand.inko b/std/test/std/test_rand.inko similarity index 100% rename from libstd/test/std/test_rand.inko rename to std/test/std/test_rand.inko diff --git a/libstd/test/std/test_range.inko b/std/test/std/test_range.inko similarity index 100% rename from libstd/test/std/test_range.inko rename to std/test/std/test_range.inko diff --git a/std/test/std/test_result.inko b/std/test/std/test_result.inko new file mode 100644 index 000000000..711558d51 --- /dev/null +++ b/std/test/std/test_result.inko @@ -0,0 +1,132 @@ +import helpers::(fmt) +import std::test::Tests + +fn pub tests(t: mut Tests) { + t.test('Result.ok?') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.true(foo.ok?) + t.false(bar.ok?) + } + + t.test('Result.error?') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.false(foo.error?) + t.true(bar.error?) + } + + t.test('Result.ok') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.ok, Option.Some(42)) + t.equal(bar.ok, Option.None) + } + + t.test('Result.error') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.error, Option.None) + t.equal(bar.error, Option.Some('oops!')) + } + + t.test('Result.unwrap with an Ok') fn (t) { + let res: Result[Int, String] = Result.Ok(42) + + t.equal(res.unwrap, 42) + } + + t.panic('Result.unwrap with an Error') fn { + let res: Result[Int, Int] = Result.Error(0) + + res.unwrap + } + + t.test('Result.expect with an Ok') fn (t) { + let res: Result[Int, String] = Result.Ok(42) + + t.equal(res.expect('foo'), 42) + } + + t.panic('Result.expect with an Error') fn { + let res: Result[Int, Int] = Result.Error(0) + + res.expect('foo') + } + + t.test('Result.unwrap_or') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.unwrap_or(0), 42) + t.equal(bar.unwrap_or(0), 0) + } + + t.test('Result.unwrap_or_else') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.unwrap_or_else fn { 0 }, 42) + t.equal(bar.unwrap_or_else fn { 0 }, 0) + } + + t.test('Result.map') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.map fn (v) { v.to_string }, Result.Ok('42')) + t.equal(bar.map fn (v) { v.to_string }, Result.Error('oops!')) + } + + t.test('Result.map_error') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.map_error fn (v) { v.to_upper }, Result.Ok(42)) + t.equal(bar.map_error fn (v) { v.to_upper }, Result.Error('OOPS!')) + } + + t.test('Result.then') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.then fn (v) { Result.Ok(v.to_string) }, Result.Ok('42')) + t.equal(bar.then fn (v) { Result.Ok(v.to_string) }, Result.Error('oops!')) + } + + t.test('Result.else') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.else fn (v) { Result.Error(v.to_upper) }, Result.Ok(42)) + t.equal(bar.else fn (v) { Result.Error(v.to_upper) }, Result.Error('OOPS!')) + } + + t.test('Result.clone') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.clone, Result.Ok(42)) + t.equal(bar.clone, Result.Error('oops!')) + } + + t.test('Result.==') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo, Result.Ok(42)) + t.equal(bar, Result.Error('oops!')) + } + + t.test('Result.fmt') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(fmt(foo), 'Ok(42)') + t.equal(fmt(bar), 'Error("oops!")') + } +} diff --git a/libstd/test/std/test_set.inko b/std/test/std/test_set.inko similarity index 100% rename from libstd/test/std/test_set.inko rename to std/test/std/test_set.inko diff --git a/std/test/std/test_stdio.inko b/std/test/std/test_stdio.inko new file mode 100644 index 000000000..fa2ec9474 --- /dev/null +++ b/std/test/std/test_stdio.inko @@ -0,0 +1,87 @@ +import std::test::Tests +import std::stdio::(STDERR, STDIN, STDOUT) + +fn pub tests(t: mut Tests) { + t.fork( + 'STDIN.read', + child: fn { + let out = STDOUT.new + let in = STDIN.new + let bytes = ByteArray.new + let _ = in.read_all(bytes) + let _ = out.write_bytes(bytes) + }, + test: fn (test, process) { + process.stdin('hello') + test.equal(process.spawn.stdout, 'hello') + } + ) + + t.fork( + 'STDOUT.write_bytes', + child: fn { + let _ = STDOUT.new.write_bytes('hello'.to_byte_array) + }, + test: fn (test, process) { test.equal(process.spawn.stdout, 'hello') } + ) + + t.fork( + 'STDOUT.write_string', + child: fn { + let _ = STDOUT.new.write_string('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stdout, 'hello') } + ) + + t.fork( + 'STDOUT.print', + child: fn { + let _ = STDOUT.new.print('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stdout, "hello\n") } + ) + + t.fork( + 'STDOUT.flush', + child: fn { + let out = STDOUT.new + let _ = out.write_string('hello') + let _ = out.flush + }, + test: fn (test, process) { test.equal(process.spawn.stdout, 'hello') } + ) + + t.fork( + 'STDERR.write_bytes', + child: fn { + let _ = STDERR.new.write_bytes('hello'.to_byte_array) + }, + test: fn (test, process) { test.equal(process.spawn.stderr, 'hello') } + ) + + t.fork( + 'STDERR.write_string', + child: fn { + let _ = STDERR.new.write_string('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stderr, 'hello') } + ) + + t.fork( + 'STDERR.print', + child: fn { + let _ = STDERR.new.print('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stderr, "hello\n") } + ) + + t.fork( + 'STDERR.flush', + child: fn { + let out = STDERR.new + let _ = out.write_string('hello') + let _ = out.flush + }, + test: fn (test, process) { test.equal(process.spawn.stderr, 'hello') } + ) +} diff --git a/libstd/test/std/test_string.inko b/std/test/std/test_string.inko similarity index 97% rename from libstd/test/std/test_string.inko rename to std/test/std/test_string.inko index e0f192b6c..23464d40c 100644 --- a/libstd/test/std/test_string.inko +++ b/std/test/std/test_string.inko @@ -326,11 +326,11 @@ fn pub tests(t: mut Tests) { let iter = string.bytes let buff = ByteArray.new - t.equal(iter.read(into: buff, size: 2), 2) + t.equal(iter.read(into: buff, size: 2), Result.Ok(2)) t.equal(buff.to_string, 'fo') - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'foo') - t.equal(iter.read(into: buff, size: 2), 0) + t.equal(iter.read(into: buff, size: 2), Result.Ok(0)) t.equal(buff.to_string, 'foo') } @@ -339,8 +339,8 @@ fn pub tests(t: mut Tests) { let iter = string.bytes let buff = ByteArray.new - t.equal(iter.read_all(buff), 3) - t.equal(iter.read_all(buff), 0) + t.equal(iter.read_all(buff), Result.Ok(3)) + t.equal(iter.read_all(buff), Result.Ok(0)) t.equal(buff.to_string, 'foo') } } diff --git a/libstd/test/std/test_sys.inko b/std/test/std/test_sys.inko similarity index 71% rename from libstd/test/std/test_sys.inko rename to std/test/std/test_sys.inko index fd391c3af..0c5991ffb 100644 --- a/libstd/test/std/test_sys.inko +++ b/std/test/std/test_sys.inko @@ -1,28 +1,18 @@ -import helpers::Script +import helpers::(compiler_path) import std::env import std::sys::(self, Command, ExitStatus, Stream) import std::test::Tests fn pub tests(t: mut Tests) { - t.test('sys.unix?') fn (t) { - t.equal(sys.unix?, sys.linux? or sys.mac?) - t.not_equal(sys.unix?, sys.windows?) - } - t.test('sys.cpu_cores') fn (t) { t.true(sys.cpu_cores > 0) } - t.test('sys.exit') fn (t) { - let code = ' - sys.exit(4) - STDOUT.new.print("hello") - ' - - let output = Script.new(t.id, code).import('std::sys').run - - t.false(output.contains?('hello')) - } + t.fork( + 'sys.exit', + child: fn { sys.exit(4) }, + test: fn (test, process) { test.equal(process.spawn.status.to_int, 4) } + ) t.test('Stream.to_int') fn (t) { t.equal(Stream.Null.to_int, 0) @@ -63,42 +53,38 @@ fn pub tests(t: mut Tests) { t.equal(cmd.current_variables, Map.new) cmd.variable('TEST', 'foo') - t.equal(cmd.current_variables['TEST'], 'foo') + t.equal(cmd.current_variables.get('TEST'), 'foo') } t.test('Command.variables') fn (t) { let cmd = Command.new('ls') let vars = Map.new - vars['TEST'] = 'foo' + vars.set('TEST', 'foo') t.equal(cmd.current_variables, Map.new) cmd.variables(vars) - t.equal(cmd.current_variables['TEST'], 'foo') + t.equal(cmd.current_variables.get('TEST'), 'foo') } t.test('Command.spawn with a valid command') fn (t) { - let exe = try! env.executable - let cmd = Command.new(exe) + let cmd = Command.new(compiler_path) cmd.stdin(Stream.Null) cmd.stderr(Stream.Null) cmd.stdout(Stream.Piped) cmd.argument('--help') - let child = try! cmd.spawn - let status = try! child.wait + let child = cmd.spawn.unwrap + let status = child.wait.unwrap let bytes = ByteArray.new - try! child.stdout.read_all(bytes) - + child.stdout.read_all(bytes).unwrap t.true(bytes.into_string.contains?('Usage: inko')) } t.test('Command.spawn with an invalid command') fn (t) { - let cmd = Command.new('inko-test-invalid') - - t.throw fn { try cmd.spawn } + t.true(Command.new('inko-test-invalid').spawn.error?) } t.test('ExitStatus.to_int') fn (t) { diff --git a/libstd/test/std/test_test.inko b/std/test/std/test_test.inko similarity index 70% rename from libstd/test/std/test_test.inko rename to std/test/std/test_test.inko index ad3589bba..08db786ae 100644 --- a/libstd/test/std/test_test.inko +++ b/std/test/std/test_test.inko @@ -1,32 +1,36 @@ +import helpers::(fmt) +import std::env import std::fs::path::Path import std::io::Write import std::sys -import std::test::(Plain, Test, Tests) +import std::test::(Filter, Plain, Test, Tests) import std::time::Duration class Buffer { let @bytes: mut ByteArray - fn static new(bytes: mut ByteArray) -> Self { - Self { @bytes = bytes} + fn static new(bytes: mut ByteArray) -> Buffer { + Buffer { @bytes = bytes} } } impl Write for Buffer { - fn pub mut write_string(string: String) -> Int { + fn pub mut write_string(string: String) -> Result[Int, Never] { let bytes = string.to_byte_array let size = bytes.length @bytes.append(bytes) - size + Result.Ok(size) } - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Never] { @bytes.append(bytes.clone) - bytes.length + Result.Ok(bytes.length) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } fn pub tests(t: mut Tests) { @@ -132,6 +136,32 @@ fn pub tests(t: mut Tests) { let tests = Tests.new t.equal(tests.concurrency, sys.cpu_cores) - t.true(tests.pattern.none?) + } + + t.test('Filter.from_string') fn (t) { + let exe = env.executable.unwrap + + t.equal(Filter.from_string(''), Filter.None) + t.equal(Filter.from_string('foo'), Filter.Pattern('foo')) + t.equal(Filter.from_string(exe.to_string), Filter.Location(exe.clone)) + } + + t.test('Filter.==') fn (t) { + t.equal(Filter.None, Filter.None) + t.equal(Filter.Pattern('foo'), Filter.Pattern('foo')) + t.equal(Filter.Location(Path.new('foo')), Filter.Location(Path.new('foo'))) + + t.not_equal(Filter.None, Filter.Pattern('foo')) + t.not_equal(Filter.Pattern('foo'), Filter.Pattern('bar')) + t.not_equal( + Filter.Location(Path.new('foo')), + Filter.Location(Path.new('bar')) + ) + } + + t.test('Filter.fmt') fn (t) { + t.equal(fmt(Filter.None), 'None') + t.equal(fmt(Filter.Pattern('foo')), 'Pattern("foo")') + t.equal(fmt(Filter.Location(Path.new('foo'))), 'Location("foo")') } } diff --git a/libstd/test/std/test_time.inko b/std/test/std/test_time.inko similarity index 91% rename from libstd/test/std/test_time.inko rename to std/test/std/test_time.inko index af6c32d93..5a79bb921 100644 --- a/libstd/test/std/test_time.inko +++ b/std/test/std/test_time.inko @@ -1,4 +1,4 @@ -import helpers::(Script, fmt) +import helpers::(fmt) import std::cmp::Ordering import std::process::(sleep) import std::test::Tests @@ -39,17 +39,10 @@ fn pub tests(t: mut Tests) { Duration.from_secs(-1) + Duration.from_secs(2), Duration.from_secs(1) ) + } - let code = 'Duration.from_nanos(9_223_372_036_854_775_807) + - Duration.from_nanos(1)' - - t.true( - Script - .new(id: t.id, code: code) - .import('std::time::Duration') - .run - .contains?('overflow') - ) + t.panic('Duration.+ with an argument that overflows') fn { + Duration.from_nanos(9_223_372_036_854_775_807) + Duration.from_nanos(1) } t.test('Duration.-') fn (t) { @@ -62,17 +55,10 @@ fn pub tests(t: mut Tests) { Duration.from_secs(-1) - Duration.from_secs(2), Duration.from_secs(-3) ) + } - let code = 'Duration.from_nanos(-9_223_372_036_854_775_808) - - Duration.from_nanos(1)' - - t.true( - Script - .new(id: t.id, code: code) - .import('std::time::Duration') - .run - .contains?('overflow') - ) + t.panic('Duration.- with an argument that overflows') fn { + Duration.from_nanos(-9_223_372_036_854_775_808) - Duration.from_nanos(1) } t.test('Duration.cmp') fn (t) { @@ -354,16 +340,10 @@ fn pub tests(t: mut Tests) { let t2 = t1 + Duration.from_secs(2) t.equal(t2.to_int, t1.to_int + 2_000_000_000) - t.true( - Script - .new( - id: t.id, - code: 'Instant.new + Duration.from_nanos(0 - (Instant.new.to_int * 2))' - ) - .import('std::time::(Duration, Instant)') - .run - .contains?('represent a negative time') - ) + } + + t.panic('Instant.+ with an argument that overflows') fn { + Instant.new + Duration.from_nanos(0 - (Instant.new.to_int * 2)) } t.test('Instant.-') fn (t) { @@ -372,16 +352,10 @@ fn pub tests(t: mut Tests) { let t2 = t1 - Duration.from_secs(1) t.equal(t2.to_int, base.to_int + 1_000_000_000) - t.true( - Script - .new( - id: t.id, - code: 'Instant.new - Duration.from_nanos(Instant.new.to_int * 2)' - ) - .import('std::time::(Duration, Instant)') - .run - .contains?('represent a negative time') - ) + } + + t.panic('Instant.- with an argument that overflows') fn { + Instant.new - Duration.from_nanos(Instant.new.to_int * 2) } t.test('Instant.cmp') fn (t) { diff --git a/libstd/test/std/test_tuple.inko b/std/test/std/test_tuple.inko similarity index 100% rename from libstd/test/std/test_tuple.inko rename to std/test/std/test_tuple.inko diff --git a/libstd/test/std/test_utf8.inko b/std/test/std/test_utf8.inko similarity index 100% rename from libstd/test/std/test_utf8.inko rename to std/test/std/test_utf8.inko diff --git a/types/Cargo.toml b/types/Cargo.toml index c79db9f81..c5c5cab58 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -7,6 +7,3 @@ license = "MPL-2.0" [lib] doctest = false - -[dependencies] -bytecode = { path = "../bytecode" } diff --git a/types/src/check.rs b/types/src/check.rs new file mode 100644 index 000000000..83a1b22c3 --- /dev/null +++ b/types/src/check.rs @@ -0,0 +1,1948 @@ +//! Type checking of types. +use crate::{ + Arguments, ClassInstance, Database, MethodId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameterId, TypePlaceholderId, TypeRef, +}; +use std::collections::HashSet; + +#[derive(Copy, Clone)] +struct Rules { + /// When set to `true`, subtyping of types through traits is allowed. + subtyping: bool, + + /// When set to `true`, owned types can be type checked against reference + /// types. + relaxed_ownership: bool, + + /// When encountering an Infer() type, turn it into a rigid type. + infer_as_rigid: bool, +} + +impl Rules { + fn new() -> Rules { + Rules { + subtyping: true, + relaxed_ownership: false, + infer_as_rigid: false, + } + } + + fn no_subtyping(mut self) -> Rules { + self.subtyping = false; + self + } + + fn infer_as_rigid(mut self) -> Rules { + self.infer_as_rigid = true; + self + } + + fn dont_infer_as_rigid(mut self) -> Rules { + self.infer_as_rigid = false; + self + } + + fn relaxed(mut self) -> Rules { + self.relaxed_ownership = true; + self + } + + fn strict(mut self) -> Rules { + self.relaxed_ownership = false; + self + } +} + +/// The type-checking environment. +/// +/// This structure contains the type arguments to expose to types that are +/// checked. +#[derive(Clone)] +pub struct Environment { + /// The type arguments to expose to types on the left-hand side of the + /// check. + pub left: TypeArguments, + + /// The type arguments to expose to types on the right-hand side of the + /// check. + pub right: TypeArguments, +} + +impl Environment { + pub fn for_types( + db: &Database, + left: TypeRef, + right: TypeRef, + ) -> Environment { + Environment::new(left.type_arguments(db), right.type_arguments(db)) + } + + pub fn new( + left_arguments: TypeArguments, + right_arguments: TypeArguments, + ) -> Environment { + Environment { left: left_arguments, right: right_arguments } + } + + fn with_left_as_right(&self) -> Environment { + Environment { left: self.left.clone(), right: self.left.clone() } + } +} + +/// A type for checking if two types are compatible with each other. +pub struct TypeChecker<'a> { + db: &'a Database, + checked: HashSet<(TypeRef, TypeRef)>, +} + +impl<'a> TypeChecker<'a> { + pub fn check(db: &'a Database, left: TypeRef, right: TypeRef) -> bool { + let mut env = + Environment::new(left.type_arguments(db), right.type_arguments(db)); + + TypeChecker::new(db).run(left, right, &mut env) + } + + pub fn new(db: &'a Database) -> TypeChecker<'a> { + TypeChecker { db, checked: HashSet::new() } + } + + pub fn run( + mut self, + left: TypeRef, + right: TypeRef, + env: &mut Environment, + ) -> bool { + self.check_type_ref(left, right, env, Rules::new()) + } + + pub fn check_method( + mut self, + left: MethodId, + right: MethodId, + env: &mut Environment, + ) -> bool { + let rules = Rules::new(); + let lhs = left.get(self.db); + let rhs = right.get(self.db); + + if lhs.kind != rhs.kind { + return false; + } + + if lhs.visibility != rhs.visibility { + return false; + } + + if lhs.name != rhs.name { + return false; + } + + if lhs.type_parameters.len() != rhs.type_parameters.len() { + return false; + } + + let param_rules = rules.no_subtyping(); + + lhs.type_parameters + .values() + .iter() + .zip(rhs.type_parameters.values().iter()) + .all(|(&lhs, &rhs)| { + self.check_parameters(lhs, rhs, env, param_rules) + }) + && self.check_arguments( + &lhs.arguments, + &rhs.arguments, + env, + rules, + true, + ) + && self.check_type_ref(lhs.return_type, rhs.return_type, env, rules) + } + + pub fn check_bounds( + &mut self, + bounds: &TypeBounds, + env: &mut Environment, + ) -> bool { + let rules = Rules::new(); + + // When verifying bounds, the type on the right-hand side is the bound. + // This bound may indirectly refer to type parameters from the type on + // the left (e.g. through a required trait). As such we must expose + // whatever values are assigned to such type parameters to the + // right-hand side arguments. + // + // We do this by storing the assignment in the left-hand side arguments, + // then expose those arguments as the right-hand side arguments. This + // ensures that we de don't overwrite any assignments in the right-hand + // side, as that could mess up type-checking (as these arguments are + // provided by the user, instead of always being derived from the type + // on the left). + bounds.iter().all(|(¶m, &bound)| { + let val = env.left.get(param).unwrap(); + + env.left.assign(bound, val); + + let mut env = env.with_left_as_right(); + let rules = rules.relaxed(); + + if bound.is_mutable(self.db) && !val.is_mutable(self.db) { + return false; + } + + bound.requirements(self.db).into_iter().all(|r| { + self.check_type_ref_with_trait(val, r, &mut env, rules) + }) + }) + } + + fn check_type_ref( + &mut self, + left: TypeRef, + right: TypeRef, + env: &mut Environment, + rules: Rules, + ) -> bool { + if !self.checked.insert((left, right)) { + return true; + } + + // Relaxed ownership only applies to the check we perform below, not any + // sub checks. The approach we take here means we only need to "reset" + // this once for the sub checks. + let relaxed = rules.relaxed_ownership; + let rules = rules.strict(); + + // Resolve any assigned type parameters/placeholders to the types + // they're assigned to. + let left = self.resolve(left, &env.left, rules); + + // We only apply the "infer as rigid" rule to the type on the left, + // otherwise we may end up comparing e.g. a class instance to the rigid + // type parameter on the right, which would always fail. + // + // This is OK because in practise, Infer() only shows up on the left in + // a select few cases. + let rules = rules.dont_infer_as_rigid(); + let right = self.resolve(right, &env.right, rules); + + // If at this point we encounter a type placeholder, it means the + // placeholder is yet to be assigned a value. + match left { + TypeRef::Any => match right { + TypeRef::Any | TypeRef::Error => true, + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + id.required(self.db) + .map_or(true, |p| p.requirements(self.db).is_empty()) + } + _ => false, + }, + // A `Never` can't be passed around because it, well, would never + // happen. We allow the comparison so code such as `try else panic` + // (where `panic` returns `Never`) is valid. + TypeRef::Never => match right { + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + true + } + _ => true, + }, + // Type errors are compatible with all other types to prevent a + // cascade of type errors. + TypeRef::Error => match right { + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + true + } + _ => true, + }, + // Rigid values are more restrictive when it comes to ownership, as + // at compile-time we can't always know the exact ownership (i.e. + // the parameter may be a ref at runtime). + TypeRef::Owned(left_id @ TypeId::RigidTypeParameter(lhs)) => { + match right { + TypeRef::Owned(TypeId::RigidTypeParameter(rhs)) => { + lhs == rhs + } + TypeRef::Infer(right_id) => { + self.check_rigid_with_type_id(lhs, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => true, + _ => false, + } + } + TypeRef::Owned(left_id) => match right { + TypeRef::Owned(right_id) | TypeRef::Infer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Ref(right_id) + | TypeRef::Mut(right_id) + | TypeRef::Uni(right_id) + if left.is_value_type(self.db) || relaxed => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any => { + if let TypeId::ClassInstance(ins) = left_id { + if ins.instance_of().kind(self.db).is_extern() { + return false; + } + } + + true + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::Uni(left_id) => match right { + TypeRef::Owned(right_id) + | TypeRef::Infer(right_id) + | TypeRef::Uni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Ref(right_id) | TypeRef::Mut(right_id) + if left.is_value_type(self.db) => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any => { + if let TypeId::ClassInstance(ins) = left_id { + if ins.instance_of().kind(self.db).is_extern() { + return false; + } + } + + true + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::Infer(left_id) => match right { + // Mut and Owned are not allowed because we don't know the + // runtime ownership of our value. Ref is fine, because we can + // always turn an Owned/Ref/Mut/etc into a Ref. + TypeRef::Infer(right_id) | TypeRef::Ref(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => true, + _ => false, + }, + TypeRef::Ref(left_id) => match right { + TypeRef::Infer(TypeId::TypeParameter(pid)) + if pid.is_mutable(self.db) + && !left.is_value_type(self.db) => + { + false + } + TypeRef::Ref(right_id) | TypeRef::Infer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Owned(right_id) + | TypeRef::Uni(right_id) + | TypeRef::Mut(right_id) + if left.is_value_type(self.db) => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => { + if let Some(req) = id.required(self.db) { + if req.is_mutable(self.db) + && !left.is_value_type(self.db) + { + return false; + } + } + + self.check_type_id_with_placeholder( + left, left_id, id, env, rules, + ) + } + TypeRef::Any | TypeRef::Error => true, + _ => false, + }, + TypeRef::Mut(left_id) => match right { + TypeRef::Ref(right_id) | TypeRef::Infer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Mut(right_id) => self.check_type_id( + left_id, + right_id, + env, + rules.no_subtyping(), + ), + TypeRef::Owned(right_id) | TypeRef::Uni(right_id) + if left.is_value_type(self.db) => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => true, + _ => false, + }, + TypeRef::RefUni(left_id) => match right { + TypeRef::RefUni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::MutUni(left_id) => match right { + TypeRef::MutUni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::Placeholder(left_id) => { + // If we reach this point it means the placeholder isn't + // assigned a value. + left_id.assign(self.db, right); + true + } + _ => false, + } + } + + fn check_type_id( + &mut self, + left_id: TypeId, + right_id: TypeId, + env: &mut Environment, + rules: Rules, + ) -> bool { + match left_id { + TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) => { + // Classes, traits and modules themselves aren't treated as + // types and thus can't be passed around, mostly because this + // just isn't useful. To further reinforce this, these types + // aren't compatible with anything. + false + } + TypeId::ClassInstance(lhs) => match right_id { + TypeId::ClassInstance(rhs) => { + if lhs.instance_of != rhs.instance_of { + return false; + } + + if !lhs.instance_of.is_generic(self.db) { + return true; + } + + let lhs_args = lhs.type_arguments(self.db); + let rhs_args = rhs.type_arguments(self.db); + + lhs.instance_of.type_parameters(self.db).into_iter().all( + |param| { + let lhs = lhs_args.get(param).unwrap(); + let rhs = rhs_args.get(param).unwrap(); + + self.check_type_ref(lhs, rhs, env, rules) + }, + ) + } + TypeId::TraitInstance(rhs) + if !lhs.instance_of().kind(self.db).is_extern() => + { + self.check_class_with_trait(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) + if !lhs.instance_of().kind(self.db).is_extern() => + { + rhs.requirements(self.db).into_iter().all(|req| { + self.check_class_with_trait(lhs, req, env, rules) + }) + } + _ => false, + }, + TypeId::TraitInstance(lhs) => match right_id { + TypeId::TraitInstance(rhs) => { + self.check_traits(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => rhs + .requirements(self.db) + .into_iter() + .all(|req| self.check_traits(lhs, req, env, rules)), + _ => false, + }, + TypeId::TypeParameter(lhs) => match right_id { + TypeId::TraitInstance(rhs) => { + self.check_parameter_with_trait(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => { + self.check_parameters(lhs, rhs, env, rules) + } + _ => false, + }, + TypeId::RigidTypeParameter(lhs) => { + self.check_rigid_with_type_id(lhs, right_id, env, rules) + } + TypeId::Closure(lhs) => match right_id { + TypeId::Closure(rhs) => { + let lhs_obj = lhs.get(self.db); + let rhs_obj = rhs.get(self.db); + + self.check_arguments( + &lhs_obj.arguments, + &rhs_obj.arguments, + env, + rules, + false, + ) && self.check_type_ref( + lhs_obj.return_type, + rhs_obj.return_type, + env, + rules, + ) + } + TypeId::TypeParameter(rhs) + if rhs.requirements(self.db).is_empty() => + { + // Closures can't implement traits, so they're only + // compatible with type parameters that don't have any + // requirements. + true + } + _ => false, + }, + } + } + + fn check_rigid_with_type_id( + &mut self, + left: TypeParameterId, + right: TypeId, + env: &mut Environment, + rules: Rules, + ) -> bool { + match right { + TypeId::RigidTypeParameter(rhs) => left == rhs, + TypeId::TraitInstance(rhs) => { + self.check_parameter_with_trait(left, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => { + if left == rhs { + return true; + } + + rhs.requirements(self.db).into_iter().all(|req| { + self.check_parameter_with_trait(left, req, env, rules) + }) + } + _ => false, + } + } + + fn check_type_id_with_placeholder( + &mut self, + left: TypeRef, + left_id: TypeId, + placeholder: TypePlaceholderId, + env: &mut Environment, + rules: Rules, + ) -> bool { + // By assigning the placeholder first, recursive checks against the same + // placeholder don't keep recursing into this method, instead checking + // against the value on the left. + placeholder.assign(self.db, left); + + let req = if let Some(req) = placeholder.required(self.db) { + req + } else { + return true; + }; + + let reqs = req.requirements(self.db); + + if reqs.is_empty() { + return true; + } + + let res = match left_id { + TypeId::ClassInstance(lhs) => reqs + .into_iter() + .all(|req| self.check_class_with_trait(lhs, req, env, rules)), + TypeId::TraitInstance(lhs) => reqs + .into_iter() + .all(|req| self.check_traits(lhs, req, env, rules)), + TypeId::TypeParameter(lhs) | TypeId::RigidTypeParameter(lhs) => { + reqs.into_iter().all(|req| { + self.check_parameter_with_trait(lhs, req, env, rules) + }) + } + _ => false, + }; + + // If we keep the assignment in case of a type error, formatted type + // errors may be confusing as they would report the left-hand side as + // the expected value, rather than the underlying type parameter. + if !res { + placeholder.assign(self.db, TypeRef::Unknown); + } + + res + } + + pub fn class_implements_trait( + &mut self, + left: ClassInstance, + right: TraitInstance, + ) -> bool { + let mut env = Environment::new( + TypeArguments::for_class(self.db, left), + TypeArguments::for_trait(self.db, right), + ); + + self.check_class_with_trait(left, right, &mut env, Rules::new()) + } + + fn check_class_with_trait( + &mut self, + left: ClassInstance, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + // `Array[Cat]` isn't compatible with `mut Array[Animal]`, as that could + // result in a `Dog` being added to the Array. + if !rules.subtyping { + return false; + } + + let imp = if let Some(found) = + left.instance_of.trait_implementation(self.db, right.instance_of) + { + found + } else { + return false; + }; + + // Trait implementations may be over owned values (e.g. + // `impl Equal[Foo] for Foo`). We allow such implementations to be + // compatible with those over references (e.g. `Equal[ref Foo]`), as + // otherwise the type system becomes to painful to work with. + // + // The reason this is sound is as follows: + // + // In the trait's methods, we can only refer to conrete types (in which + // case the type arguments of the implementation are irrelevant), or to + // the type parameters (if any) of the trait. These type parameters + // either have their ownership inferred, or use whatever ownership is + // explicitly provided. + // + // If any arguments need e.g. a reference then this is handled at the + // call site. If a reference needs to be produced (e.g. as the return + // value), it's up to the implementation of the method to do so. + // References in turn can be created from both owned values and other + // references. + let rules = rules.relaxed(); + + if left.instance_of.is_generic(self.db) { + // The implemented trait may refer to type parameters of the + // implementing class, so we need to expose those using a new scope. + let mut sub_scope = env.clone(); + + left.type_arguments(self.db).copy_into(&mut sub_scope.left); + + self.check_bounds(&imp.bounds, &mut sub_scope) + && self.check_traits(imp.instance, right, &mut sub_scope, rules) + } else { + self.check_bounds(&imp.bounds, env) + && self.check_traits(imp.instance, right, env, rules) + } + } + + fn check_type_ref_with_trait( + &mut self, + left: TypeRef, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + match left { + TypeRef::Owned(id) + | TypeRef::Uni(id) + | TypeRef::Ref(id) + | TypeRef::Mut(id) + | TypeRef::RefUni(id) + | TypeRef::MutUni(id) + | TypeRef::Infer(id) => match id { + TypeId::ClassInstance(lhs) => { + self.check_class_with_trait(lhs, right, env, rules) + } + TypeId::TraitInstance(lhs) => { + self.check_traits(lhs, right, env, rules) + } + TypeId::TypeParameter(lhs) + | TypeId::RigidTypeParameter(lhs) => { + self.check_parameter_with_trait(lhs, right, env, rules) + } + _ => false, + }, + TypeRef::Placeholder(id) => match id.value(self.db) { + Some(typ) => { + self.check_type_ref_with_trait(typ, right, env, rules) + } + // When the placeholder isn't assigned a value, the comparison + // is treated as valid but we don't assign a type. This is + // because in this scenario we can't reliably guess what the + // type is, and what its ownership should be. + _ => true, + }, + TypeRef::Never => true, + _ => false, + } + } + + fn check_parameter_with_trait( + &mut self, + left: TypeParameterId, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + left.requirements(self.db) + .into_iter() + .any(|left| self.check_traits(left, right, env, rules)) + } + + fn check_parameters( + &mut self, + left: TypeParameterId, + right: TypeParameterId, + env: &mut Environment, + rules: Rules, + ) -> bool { + if left == right { + return true; + } + + right + .requirements(self.db) + .into_iter() + .all(|req| self.check_parameter_with_trait(left, req, env, rules)) + } + + fn check_traits( + &mut self, + left: TraitInstance, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + if left == right { + return true; + } + + if left.instance_of != right.instance_of { + return if rules.subtyping { + left.instance_of + .required_traits(self.db) + .into_iter() + .any(|lhs| self.check_traits(lhs, right, env, rules)) + } else { + false + }; + } + + if !left.instance_of.is_generic(self.db) { + return true; + } + + let lhs_args = left.type_arguments(self.db); + let rhs_args = right.type_arguments(self.db); + + left.instance_of.type_parameters(self.db).into_iter().all(|param| { + let rules = rules.infer_as_rigid(); + let lhs = lhs_args.get(param).unwrap(); + let rhs = rhs_args.get(param).unwrap(); + + self.check_type_ref(lhs, rhs, env, rules) + }) + } + + fn check_arguments( + &mut self, + left: &Arguments, + right: &Arguments, + env: &mut Environment, + rules: Rules, + same_name: bool, + ) -> bool { + if left.len() != right.len() { + return false; + } + + left.mapping.values().iter().zip(right.mapping.values().iter()).all( + |(ours, theirs)| { + if same_name && ours.name != theirs.name { + return false; + } + + self.check_type_ref( + ours.value_type, + theirs.value_type, + env, + rules, + ) + }, + ) + } + + fn resolve( + &self, + typ: TypeRef, + arguments: &TypeArguments, + rules: Rules, + ) -> TypeRef { + let result = match typ { + TypeRef::Owned(TypeId::TypeParameter(id)) + | TypeRef::Uni(TypeId::TypeParameter(id)) + | TypeRef::Infer(TypeId::TypeParameter(id)) => { + self.resolve_type_parameter(typ, id, arguments, rules) + } + TypeRef::Ref(TypeId::TypeParameter(id)) => self + .resolve_type_parameter(typ, id, arguments, rules) + .as_ref(self.db), + TypeRef::Mut(TypeId::TypeParameter(id)) => self + .resolve_type_parameter(typ, id, arguments, rules) + .as_mut(self.db), + TypeRef::Placeholder(id) => id + .value(self.db) + .map_or(typ, |v| self.resolve(v, arguments, rules)), + _ => typ, + }; + + match result { + TypeRef::Infer(TypeId::TypeParameter(id)) + if rules.infer_as_rigid => + { + TypeRef::Owned(TypeId::RigidTypeParameter(id)) + } + _ => result, + } + } + + fn resolve_type_parameter( + &self, + typ: TypeRef, + id: TypeParameterId, + arguments: &TypeArguments, + rules: Rules, + ) -> TypeRef { + match arguments.get(id) { + Some(arg @ TypeRef::Placeholder(id)) => id + .value(self.db) + .map(|v| self.resolve(v, arguments, rules)) + .unwrap_or(arg), + Some(arg) => arg, + _ => typ, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::format::format_type; + use crate::test::{ + closure, generic_instance_id, generic_trait_instance, + generic_trait_instance_id, immutable, implement, infer, instance, + mutable, new_class, new_extern_class, new_parameter, new_trait, owned, + parameter, placeholder, rigid, trait_instance, trait_instance_id, + type_arguments, type_bounds, uni, + }; + use crate::{ + Block, ClassId, Closure, TraitImplementation, TypePlaceholder, + }; + + #[track_caller] + fn check_ok(db: &Database, left: TypeRef, right: TypeRef) { + if !TypeChecker::check(db, left, right) { + panic!( + "Expected {} to be compatible with {}", + format_type(db, left), + format_type(db, right) + ); + } + } + + #[track_caller] + fn check_err(db: &Database, left: TypeRef, right: TypeRef) { + assert!( + !TypeChecker::check(db, left, right), + "Expected {} to not be compatible with {}", + format_type(db, left), + format_type(db, right) + ); + } + + #[test] + fn test_any() { + let mut db = Database::new(); + let to_string = new_trait(&mut db, "ToString"); + let param1 = new_parameter(&mut db, "T"); + let param2 = new_parameter(&mut db, "T"); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, Some(param1)); + let var3 = TypePlaceholder::alloc(&mut db, Some(param2)); + + param2.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok(&db, TypeRef::Any, TypeRef::Any); + check_ok(&db, TypeRef::Any, TypeRef::Error); + check_ok(&db, TypeRef::Any, placeholder(var1)); + check_ok(&db, TypeRef::Any, placeholder(var2)); + + check_err(&db, TypeRef::Any, placeholder(var3)); + check_err(&db, TypeRef::Any, TypeRef::Never); + } + + #[test] + fn test_never() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "T"); + let to_string = new_trait(&mut db, "ToString"); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, Some(param)); + + param.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok(&db, TypeRef::Never, placeholder(var1)); + check_ok(&db, TypeRef::Never, placeholder(var2)); + check_ok(&db, TypeRef::Never, TypeRef::Any); + check_ok(&db, TypeRef::Never, TypeRef::Never); + } + + #[test] + fn test_owned_class_instance() { + let mut db = Database::new(); + let foo = new_class(&mut db, "Foo"); + let bar = new_class(&mut db, "Bar"); + let int = ClassId::int(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let to_string = new_trait(&mut db, "ToString"); + let param = new_parameter(&mut db, "T"); + let var2 = TypePlaceholder::alloc(&mut db, Some(param)); + let var3 = TypePlaceholder::alloc(&mut db, Some(param)); + + param.add_requirements(&mut db, vec![trait_instance(to_string)]); + implement(&mut db, trait_instance(to_string), bar); + + check_ok(&db, owned(instance(foo)), owned(instance(foo))); + check_ok(&db, owned(instance(foo)), infer(instance(foo))); + + // This placeholder doesn't have any requirements + check_ok(&db, owned(instance(foo)), placeholder(var1)); + assert_eq!(var1.value(&db), Some(owned(instance(foo)))); + + // The placeholder is now assigned to Foo, so Bar shouldn't be + // compatible with it. + check_err(&db, owned(instance(bar)), placeholder(var1)); + + // Foo doesn't implement ToString, so the check fails. + check_err(&db, owned(instance(foo)), placeholder(var2)); + assert!(var2.value(&db).is_none()); + + // Bar implements ToString, so this _does_ check and assigns the + // placeholder. + check_ok(&db, owned(instance(bar)), placeholder(var3)); + assert_eq!(var3.value(&db), Some(owned(instance(bar)))); + + // Value types can be passed to a reference/unique values. + check_ok(&db, owned(instance(int)), immutable(instance(int))); + check_ok(&db, owned(instance(int)), mutable(instance(int))); + check_ok(&db, owned(instance(int)), uni(instance(int))); + + check_ok(&db, owned(instance(foo)), TypeRef::Any); + check_ok(&db, owned(instance(foo)), TypeRef::Error); + + check_err(&db, owned(instance(foo)), immutable(instance(foo))); + check_err(&db, owned(instance(foo)), mutable(instance(foo))); + check_err(&db, owned(instance(foo)), owned(instance(bar))); + check_err(&db, owned(instance(foo)), TypeRef::Never); + } + + #[test] + fn test_extern_class_instance() { + let mut db = Database::new(); + let foo = new_extern_class(&mut db, "Foo"); + let bar = new_extern_class(&mut db, "Bar"); + let param = new_parameter(&mut db, "T"); + + check_ok(&db, owned(instance(foo)), owned(instance(foo))); + + check_err(&db, owned(instance(foo)), owned(instance(bar))); + check_err(&db, owned(instance(foo)), TypeRef::Any); + check_err(&db, owned(instance(foo)), owned(parameter(param))); + check_err(&db, uni(instance(foo)), owned(parameter(param))); + } + + #[test] + fn test_owned_generic_class_instance() { + let mut db = Database::new(); + let array = new_class(&mut db, "Array"); + let thing = new_class(&mut db, "Thing"); + let to_string = new_trait(&mut db, "ToString"); + let length = new_trait(&mut db, "Length"); + let equal = new_trait(&mut db, "Equal"); + + equal.new_type_parameter(&mut db, "X".to_string()); + + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let var = TypePlaceholder::alloc(&mut db, None); + + // V: Equal[V] + let v_param = new_parameter(&mut db, "V"); + + { + let req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(v_param))], + ); + + v_param.add_requirements(&mut db, vec![req]); + } + + { + let bound = new_parameter(&mut db, "Tbound"); + + bound.add_requirements(&mut db, vec![trait_instance(to_string)]); + + let trait_impl = TraitImplementation { + instance: trait_instance(to_string), + bounds: type_bounds(vec![(array_param, bound)]), + }; + + // impl ToString for Array if T: ToString + array.add_trait_implementation(&mut db, trait_impl); + } + + // impl Length for Array + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(length), + bounds: TypeBounds::new(), + }, + ); + + // impl ToString for Thing + thing.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(to_string), + bounds: TypeBounds::new(), + }, + ); + + // impl Equal[Thing] for Thing + { + let eq = generic_trait_instance( + &mut db, + equal, + vec![owned(instance(thing))], + ); + + thing.add_trait_implementation( + &mut db, + TraitImplementation { instance: eq, bounds: TypeBounds::new() }, + ); + } + + // impl Equal[Array[T]] for Array if T: Equal[T] + { + let bound = new_parameter(&mut db, "Tbound"); + let bound_eq = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(bound))], + ); + + bound.add_requirements(&mut db, vec![bound_eq]); + + let array_t = owned(generic_instance_id( + &mut db, + array, + vec![infer(parameter(bound))], + )); + + let impl_ins = + generic_trait_instance(&mut db, equal, vec![array_t]); + let trait_impl = TraitImplementation { + instance: impl_ins, + bounds: type_bounds(vec![(array_param, bound)]), + }; + + array.add_trait_implementation(&mut db, trait_impl); + } + + let things1 = + generic_instance_id(&mut db, array, vec![owned(instance(thing))]); + let things2 = + generic_instance_id(&mut db, array, vec![owned(instance(thing))]); + let thing_refs = generic_instance_id( + &mut db, + array, + vec![immutable(instance(thing))], + ); + let floats = + generic_instance_id(&mut db, array, vec![TypeRef::float()]); + let vars = generic_instance_id(&mut db, array, vec![placeholder(var)]); + let eq_things = + generic_trait_instance_id(&mut db, equal, vec![owned(things1)]); + + check_ok(&db, owned(things1), owned(things1)); + check_ok(&db, owned(things1), owned(things2)); + check_ok(&db, owned(things1), owned(trait_instance_id(length))); + check_ok(&db, owned(floats), owned(trait_instance_id(length))); + check_ok(&db, owned(things1), owned(trait_instance_id(to_string))); + + check_ok(&db, owned(vars), owned(trait_instance_id(to_string))); + assert!(var.value(&db).is_none()); + + check_ok(&db, owned(things1), owned(eq_things)); + check_ok(&db, owned(things1), infer(parameter(v_param))); + check_ok(&db, owned(thing_refs), owned(parameter(v_param))); + + check_err(&db, owned(things1), owned(floats)); + check_err(&db, owned(floats), owned(trait_instance_id(to_string))); + check_err(&db, owned(floats), infer(parameter(v_param))); + } + + #[test] + fn test_uni_class_instance() { + let mut db = Database::new(); + let foo = new_class(&mut db, "Foo"); + let bar = new_class(&mut db, "Bar"); + let int = ClassId::int(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let to_string = new_trait(&mut db, "ToString"); + let param = new_parameter(&mut db, "T"); + let var2 = TypePlaceholder::alloc(&mut db, Some(param)); + let var3 = TypePlaceholder::alloc(&mut db, Some(param)); + + param.add_requirements(&mut db, vec![trait_instance(to_string)]); + implement(&mut db, trait_instance(to_string), bar); + + check_ok(&db, uni(instance(foo)), uni(instance(foo))); + check_ok(&db, uni(instance(foo)), owned(instance(foo))); + check_ok(&db, uni(instance(foo)), infer(instance(foo))); + + // This placeholder doesn't have any requirements + check_ok(&db, uni(instance(foo)), placeholder(var1)); + assert_eq!(var1.value(&db), Some(uni(instance(foo)))); + + // The placeholder is now assigned to Foo, so Bar shouldn't be + // compatible with it. + check_err(&db, uni(instance(bar)), placeholder(var1)); + + // Foo doesn't implement ToString, so the check fails. + check_err(&db, uni(instance(foo)), placeholder(var2)); + assert!(var2.value(&db).is_none()); + + // Bar implements ToString, so this _does_ check and assigns the + // placeholder. + check_ok(&db, uni(instance(bar)), placeholder(var3)); + assert_eq!(var3.value(&db), Some(uni(instance(bar)))); + + // Value types can be passed to a reference. + check_ok(&db, uni(instance(int)), immutable(instance(int))); + check_ok(&db, uni(instance(int)), mutable(instance(int))); + + check_ok(&db, uni(instance(foo)), TypeRef::Any); + check_ok(&db, uni(instance(foo)), TypeRef::Error); + + check_err(&db, uni(instance(foo)), immutable(instance(foo))); + check_err(&db, uni(instance(foo)), mutable(instance(foo))); + check_err(&db, uni(instance(foo)), uni(instance(bar))); + check_err(&db, uni(instance(foo)), TypeRef::Never); + } + + #[test] + fn test_uni_generic_class_instance() { + let mut db = Database::new(); + let array = new_class(&mut db, "Array"); + let thing = new_class(&mut db, "Thing"); + let to_string = new_trait(&mut db, "ToString"); + let length = new_trait(&mut db, "Length"); + let equal = new_trait(&mut db, "Equal"); + + equal.new_type_parameter(&mut db, "X".to_string()); + + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let var = TypePlaceholder::alloc(&mut db, None); + + // V: Equal[V] + let v_param = new_parameter(&mut db, "V"); + + { + let req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(v_param))], + ); + + v_param.add_requirements(&mut db, vec![req]); + } + + { + let bound = new_parameter(&mut db, "Tbound"); + + bound.add_requirements(&mut db, vec![trait_instance(to_string)]); + + let trait_impl = TraitImplementation { + instance: trait_instance(to_string), + bounds: type_bounds(vec![(array_param, bound)]), + }; + + // impl ToString for Array if T: ToString + array.add_trait_implementation(&mut db, trait_impl); + } + + // impl Length for Array + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(length), + bounds: TypeBounds::new(), + }, + ); + + // impl ToString for Thing + thing.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(to_string), + bounds: TypeBounds::new(), + }, + ); + + // impl Equal[uni Thing] for Thing + { + let eq = generic_trait_instance( + &mut db, + equal, + vec![uni(instance(thing))], + ); + + thing.add_trait_implementation( + &mut db, + TraitImplementation { instance: eq, bounds: TypeBounds::new() }, + ); + } + + // impl Equal[uni Array[T]] for Array if T: Equal[T] + { + let bound = new_parameter(&mut db, "Tbound"); + let bound_eq = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(bound))], + ); + + bound.add_requirements(&mut db, vec![bound_eq]); + + let array_t = uni(generic_instance_id( + &mut db, + array, + vec![infer(parameter(bound))], + )); + + let impl_ins = + generic_trait_instance(&mut db, equal, vec![array_t]); + let trait_impl = TraitImplementation { + instance: impl_ins, + bounds: type_bounds(vec![(array_param, bound)]), + }; + + array.add_trait_implementation(&mut db, trait_impl); + } + + let things1 = + generic_instance_id(&mut db, array, vec![uni(instance(thing))]); + let things2 = + generic_instance_id(&mut db, array, vec![uni(instance(thing))]); + let thing_refs = generic_instance_id( + &mut db, + array, + vec![immutable(instance(thing))], + ); + let floats = + generic_instance_id(&mut db, array, vec![TypeRef::float()]); + let vars = generic_instance_id(&mut db, array, vec![placeholder(var)]); + let eq_things = + generic_trait_instance_id(&mut db, equal, vec![uni(things1)]); + + check_ok(&db, uni(things1), uni(things1)); + check_ok(&db, uni(things1), uni(things2)); + check_ok(&db, uni(things1), uni(trait_instance_id(length))); + check_ok(&db, uni(floats), uni(trait_instance_id(length))); + check_ok(&db, uni(things1), uni(trait_instance_id(to_string))); + + check_ok(&db, uni(vars), uni(trait_instance_id(to_string))); + assert!(var.value(&db).is_none()); + + check_ok(&db, uni(things1), uni(eq_things)); + check_ok(&db, uni(things1), infer(parameter(v_param))); + check_ok(&db, uni(things1), uni(parameter(v_param))); + + check_err(&db, uni(thing_refs), uni(parameter(v_param))); + check_err(&db, uni(things1), uni(floats)); + check_err(&db, uni(floats), uni(trait_instance_id(to_string))); + check_err(&db, uni(floats), infer(parameter(v_param))); + } + + #[test] + fn test_infer() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, infer(parameter(param1)), infer(parameter(param2))); + check_ok(&db, infer(parameter(param1)), immutable(parameter(param2))); + check_ok(&db, infer(parameter(param1)), TypeRef::Any); + check_ok(&db, infer(parameter(param1)), TypeRef::Error); + + check_ok(&db, infer(parameter(param1)), placeholder(var)); + assert_eq!(var.value(&db), Some(infer(parameter(param1)))); + + check_err(&db, infer(parameter(param1)), owned(parameter(param2))); + check_err(&db, infer(parameter(param1)), uni(parameter(param2))); + check_err(&db, infer(parameter(param1)), mutable(parameter(param2))); + } + + #[test] + fn test_ref() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + let param = new_parameter(&mut db, "T"); + let mutable_var = TypePlaceholder::alloc(&mut db, Some(param)); + + param.set_mutable(&mut db); + + check_ok(&db, immutable(instance(thing)), immutable(instance(thing))); + check_ok(&db, immutable(instance(thing)), infer(instance(thing))); + + // Value types can be passed around this way. + check_ok(&db, immutable(instance(int)), mutable(instance(int))); + check_ok(&db, immutable(instance(int)), owned(instance(int))); + check_ok(&db, immutable(instance(int)), uni(instance(int))); + + check_ok(&db, immutable(instance(thing)), placeholder(var)); + assert_eq!(var.value(&db), Some(immutable(instance(thing)))); + + check_ok(&db, immutable(instance(thing)), TypeRef::Any); + check_ok(&db, immutable(instance(thing)), TypeRef::Error); + check_ok(&db, immutable(instance(int)), infer(parameter(param))); + check_ok(&db, immutable(instance(int)), placeholder(mutable_var)); + + check_err(&db, immutable(instance(thing)), mutable(instance(thing))); + check_err(&db, immutable(instance(thing)), owned(instance(thing))); + check_err(&db, immutable(instance(thing)), infer(parameter(param))); + check_err(&db, immutable(instance(thing)), placeholder(mutable_var)); + } + + #[test] + fn test_mut() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, mutable(instance(thing)), immutable(instance(thing))); + check_ok(&db, mutable(instance(thing)), mutable(instance(thing))); + check_ok(&db, mutable(instance(thing)), infer(instance(thing))); + + // Value types can be passed around this way. + check_ok(&db, mutable(instance(int)), owned(instance(int))); + check_ok(&db, mutable(instance(int)), uni(instance(int))); + + check_ok(&db, mutable(instance(thing)), placeholder(var)); + assert_eq!(var.value(&db), Some(mutable(instance(thing)))); + + check_ok(&db, mutable(instance(thing)), TypeRef::Any); + check_ok(&db, mutable(instance(thing)), TypeRef::Error); + + check_err(&db, mutable(instance(thing)), owned(instance(thing))); + check_err(&db, mutable(instance(thing)), uni(instance(thing))); + } + + #[test] + fn test_ref_uni() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok( + &db, + TypeRef::RefUni(instance(thing)), + TypeRef::RefUni(instance(thing)), + ); + check_ok(&db, TypeRef::RefUni(instance(thing)), TypeRef::Error); + + check_err( + &db, + TypeRef::RefUni(instance(thing)), + TypeRef::MutUni(instance(thing)), + ); + check_err(&db, TypeRef::RefUni(instance(thing)), placeholder(var)); + check_err(&db, TypeRef::RefUni(instance(thing)), TypeRef::Any); + } + + #[test] + fn test_mut_uni() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok( + &db, + TypeRef::MutUni(instance(thing)), + TypeRef::MutUni(instance(thing)), + ); + check_ok(&db, TypeRef::MutUni(instance(thing)), TypeRef::Error); + + check_err( + &db, + TypeRef::MutUni(instance(thing)), + TypeRef::RefUni(instance(thing)), + ); + check_err(&db, TypeRef::MutUni(instance(thing)), placeholder(var)); + check_err(&db, TypeRef::MutUni(instance(thing)), TypeRef::Any); + } + + #[test] + fn test_placeholder() { + let mut db = Database::new(); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, placeholder(var), TypeRef::int()); + assert_eq!(var.value(&db), Some(TypeRef::int())); + } + + #[test] + fn test_class_with_trait() { + let mut db = Database::new(); + let animal = new_trait(&mut db, "Animal"); + let cat = new_class(&mut db, "Cat"); + let array = ClassId::array(); + + array.new_type_parameter(&mut db, "T".to_string()); + implement(&mut db, trait_instance(animal), cat); + + // Array[Cat] + let cats = owned(generic_instance_id( + &mut db, + array, + vec![owned(instance(cat))], + )); + + // ref Array[Cat] + let ref_cats = immutable(generic_instance_id( + &mut db, + array, + vec![owned(instance(cat))], + )); + + // mut Array[Cat] + let mut_cats = mutable(generic_instance_id( + &mut db, + array, + vec![owned(instance(cat))], + )); + + // Array[Animal] + let animals = owned(generic_instance_id( + &mut db, + array, + vec![owned(trait_instance_id(animal))], + )); + + // ref Array[Animal] + let ref_animals = immutable(generic_instance_id( + &mut db, + array, + vec![owned(trait_instance_id(animal))], + )); + + // mut Array[Animal] + let mut_animals = mutable(generic_instance_id( + &mut db, + array, + vec![owned(trait_instance_id(animal))], + )); + + check_ok(&db, cats, animals); + check_ok(&db, ref_cats, ref_animals); + check_ok(&db, mut_cats, ref_animals); + + // This isn't OK as this could result in a Dog being added to the Array. + check_err(&db, mut_cats, mut_animals); + } + + #[test] + fn test_traits() { + let mut db = Database::new(); + let to_string = new_trait(&mut db, "ToString"); + let display = new_trait(&mut db, "Display"); + let debug = new_trait(&mut db, "Debug"); + + display.add_required_trait(&mut db, trait_instance(to_string)); + debug.add_required_trait(&mut db, trait_instance(display)); + + check_ok( + &db, + owned(trait_instance_id(to_string)), + owned(trait_instance_id(to_string)), + ); + check_ok( + &db, + owned(trait_instance_id(display)), + owned(trait_instance_id(to_string)), + ); + check_ok( + &db, + owned(trait_instance_id(debug)), + owned(trait_instance_id(to_string)), + ); + check_err( + &db, + owned(trait_instance_id(to_string)), + owned(trait_instance_id(display)), + ); + } + + #[test] + fn test_generic_traits() { + let mut db = Database::new(); + let equal = new_trait(&mut db, "Equal"); + let thing = new_class(&mut db, "Thing"); + + equal.new_type_parameter(&mut db, "T".to_string()); + + // Equal[Thing] + let eq_thing = owned(generic_trait_instance_id( + &mut db, + equal, + vec![owned(instance(thing))], + )); + let eq_ref_thing = owned(generic_trait_instance_id( + &mut db, + equal, + vec![immutable(instance(thing))], + )); + let eq_mut_thing = owned(generic_trait_instance_id( + &mut db, + equal, + vec![mutable(instance(thing))], + )); + + check_ok(&db, eq_thing, eq_thing); + check_ok(&db, eq_ref_thing, eq_ref_thing); + check_ok(&db, eq_mut_thing, eq_mut_thing); + check_err(&db, eq_thing, eq_ref_thing); + check_err(&db, eq_thing, eq_mut_thing); + } + + #[test] + fn test_type_parameter_with_trait() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let param3 = new_parameter(&mut db, "C"); + let equal = new_trait(&mut db, "Equal"); + let to_string = new_trait(&mut db, "ToString"); + + param1.add_requirements(&mut db, vec![trait_instance(equal)]); + param3.add_requirements(&mut db, vec![trait_instance(equal)]); + param3.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok( + &db, + owned(parameter(param1)), + owned(trait_instance_id(equal)), + ); + check_ok( + &db, + owned(parameter(param3)), + owned(trait_instance_id(equal)), + ); + check_ok( + &db, + owned(parameter(param3)), + owned(trait_instance_id(to_string)), + ); + check_err( + &db, + owned(parameter(param2)), + owned(trait_instance_id(equal)), + ); + } + + #[test] + fn test_trait_with_parameter() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let equal = new_trait(&mut db, "Equal"); + let foo = new_trait(&mut db, "Foo"); + let to_string = new_trait(&mut db, "ToString"); + + foo.add_required_trait(&mut db, trait_instance(equal)); + foo.add_required_trait(&mut db, trait_instance(to_string)); + + param1.add_requirements(&mut db, vec![trait_instance(equal)]); + param2.add_requirements(&mut db, vec![trait_instance(equal)]); + param2.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok( + &db, + owned(trait_instance_id(equal)), + owned(parameter(param1)), + ); + check_ok(&db, owned(trait_instance_id(foo)), owned(parameter(param2))); + check_err( + &db, + owned(trait_instance_id(to_string)), + owned(parameter(param1)), + ); + } + + #[test] + fn test_parameters() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let param3 = new_parameter(&mut db, "C"); + let param4 = new_parameter(&mut db, "D"); + let equal = new_trait(&mut db, "Equal"); + let test = new_trait(&mut db, "Test"); + + test.add_required_trait(&mut db, trait_instance(equal)); + param3.add_requirements(&mut db, vec![trait_instance(equal)]); + param4.add_requirements(&mut db, vec![trait_instance(test)]); + + check_ok(&db, owned(parameter(param1)), owned(parameter(param2))); + check_ok(&db, owned(parameter(param4)), owned(parameter(param3))); + check_err(&db, owned(parameter(param3)), owned(parameter(param4))); + } + + #[test] + fn test_type_parameter_ref_assigned_to_owned() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let thing = new_class(&mut db, "Thing"); + let args = type_arguments(vec![(param, owned(instance(thing)))]); + let mut env = Environment::new(args.clone(), args); + let res = TypeChecker::new(&db).run( + immutable(instance(thing)), + immutable(parameter(param)), + &mut env, + ); + + assert!(res); + } + + #[test] + fn test_rigid() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, owned(rigid(param1)), TypeRef::Any); + check_ok(&db, owned(rigid(param1)), TypeRef::Error); + check_ok(&db, immutable(rigid(param1)), immutable(rigid(param1))); + check_ok(&db, owned(rigid(param1)), owned(rigid(param1))); + check_ok(&db, owned(rigid(param1)), infer(rigid(param1))); + check_ok(&db, owned(rigid(param1)), infer(parameter(param1))); + check_ok(&db, immutable(rigid(param1)), immutable(parameter(param1))); + + check_ok(&db, owned(rigid(param1)), placeholder(var)); + assert_eq!(var.value(&db), Some(owned(rigid(param1)))); + + check_err(&db, owned(rigid(param1)), owned(rigid(param2))); + check_err(&db, immutable(rigid(param1)), immutable(rigid(param2))); + check_err(&db, owned(rigid(param1)), owned(parameter(param1))); + } + + #[test] + fn test_rigid_with_trait() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let to_string = new_trait(&mut db, "ToString"); + let equal = new_trait(&mut db, "Equal"); + + param1.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok( + &db, + immutable(rigid(param1)), + immutable(trait_instance_id(to_string)), + ); + check_ok(&db, owned(rigid(param1)), infer(parameter(param2))); + + // A doesn't implement Equal + check_err( + &db, + immutable(rigid(param1)), + immutable(trait_instance_id(equal)), + ); + } + + #[test] + fn test_simple_closures() { + let mut db = Database::new(); + let fun1 = Closure::alloc(&mut db, false); + let fun2 = Closure::alloc(&mut db, false); + + fun1.set_return_type(&mut db, TypeRef::Any); + fun2.set_return_type(&mut db, TypeRef::Any); + + check_ok(&db, owned(closure(fun1)), owned(closure(fun2))); + } + + #[test] + fn test_closures_with_arguments() { + let mut db = Database::new(); + let fun1 = Closure::alloc(&mut db, false); + let fun2 = Closure::alloc(&mut db, false); + let fun3 = Closure::alloc(&mut db, false); + let fun4 = Closure::alloc(&mut db, false); + let int = TypeRef::int(); + let float = TypeRef::float(); + + fun1.new_argument(&mut db, "a".to_string(), int, int); + fun2.new_argument(&mut db, "b".to_string(), int, int); + fun4.new_argument(&mut db, "a".to_string(), float, float); + fun1.set_return_type(&mut db, TypeRef::Any); + fun2.set_return_type(&mut db, TypeRef::Any); + fun3.set_return_type(&mut db, TypeRef::Any); + fun4.set_return_type(&mut db, TypeRef::Any); + + check_ok(&db, owned(closure(fun1)), owned(closure(fun2))); + check_err(&db, owned(closure(fun3)), owned(closure(fun2))); + check_err(&db, owned(closure(fun1)), owned(closure(fun4))); + } + + #[test] + fn test_closures_with_return_types() { + let mut db = Database::new(); + let fun1 = Closure::alloc(&mut db, false); + let fun2 = Closure::alloc(&mut db, false); + let fun3 = Closure::alloc(&mut db, false); + let int = TypeRef::int(); + let float = TypeRef::float(); + + fun1.set_return_type(&mut db, int); + fun2.set_return_type(&mut db, int); + fun3.set_return_type(&mut db, float); + + check_ok(&db, owned(closure(fun1)), owned(closure(fun2))); + check_err(&db, owned(closure(fun1)), owned(closure(fun3))); + } + + #[test] + fn test_closure_with_parameter() { + let mut db = Database::new(); + let fun = Closure::alloc(&mut db, false); + let equal = new_trait(&mut db, "Equal"); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + + param2.add_requirements(&mut db, vec![trait_instance(equal)]); + + check_ok(&db, owned(closure(fun)), owned(parameter(param1))); + check_err(&db, owned(closure(fun)), owned(parameter(param2))); + } + + #[test] + fn test_closure_with_placeholder() { + let mut db = Database::new(); + let fun = Closure::alloc(&mut db, false); + let param = new_parameter(&mut db, "A"); + let var = TypePlaceholder::alloc(&mut db, Some(param)); + + check_ok(&db, owned(closure(fun)), placeholder(var)); + } + + #[test] + fn test_recursive_type() { + let mut db = Database::new(); + let array = ClassId::array(); + let var = TypePlaceholder::alloc(&mut db, None); + + array.new_type_parameter(&mut db, "T".to_string()); + + let given = + owned(generic_instance_id(&mut db, array, vec![placeholder(var)])); + let ints = + owned(generic_instance_id(&mut db, array, vec![TypeRef::int()])); + let exp = owned(generic_instance_id(&mut db, array, vec![ints])); + + var.assign(&db, given); + check_err(&db, given, exp); + } + + #[test] + fn test_mutable_bounds() { + let mut db = Database::new(); + let array = ClassId::array(); + let thing = new_class(&mut db, "Thing"); + let update = new_trait(&mut db, "Update"); + let param = array.new_type_parameter(&mut db, "T".to_string()); + let bound = new_parameter(&mut db, "T"); + + bound.set_mutable(&mut db); + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(update), + bounds: type_bounds(vec![(param, bound)]), + }, + ); + + // Array[Thing] + let owned_things = owned(generic_instance_id( + &mut db, + array, + vec![owned(instance(thing))], + )); + + // Array[ref Thing] + let ref_things = owned(generic_instance_id( + &mut db, + array, + vec![immutable(instance(thing))], + )); + + check_ok(&db, owned_things, owned(trait_instance_id(update))); + + // `ref Thing` isn't mutable, so this check should fail. + check_err(&db, ref_things, owned(trait_instance_id(update))); + } + + #[test] + fn test_array_of_generic_classes_with_traits() { + let mut db = Database::new(); + let iter = new_trait(&mut db, "Iter"); + let array = ClassId::array(); + + array.new_type_parameter(&mut db, "ArrayT".to_string()); + iter.new_type_parameter(&mut db, "IterT".to_string()); + + let iterator = new_class(&mut db, "Iterator"); + let iterator_param = + iterator.new_type_parameter(&mut db, "IteratorT".to_string()); + + // impl Iter[T] for Iterator + let iter_impl = TraitImplementation { + instance: generic_trait_instance( + &mut db, + iter, + vec![infer(parameter(iterator_param))], + ), + bounds: TypeBounds::new(), + }; + + iterator.add_trait_implementation(&mut db, iter_impl); + + let int_iterator = + owned(generic_instance_id(&mut db, iterator, vec![TypeRef::int()])); + + let int_iter = owned(generic_trait_instance_id( + &mut db, + iter, + vec![TypeRef::int()], + )); + + // Array[Iterator[Int]] + let lhs = + owned(generic_instance_id(&mut db, array, vec![int_iterator])); + + // Array[Iter[Int]] + let rhs = owned(generic_instance_id(&mut db, array, vec![int_iter])); + + check_ok(&db, lhs, rhs); + } + + #[test] + fn test_rigid_type_parameter() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let param = new_parameter(&mut db, "T"); + let args = type_arguments(vec![(param, owned(instance(thing)))]); + let mut env = Environment::new(args.clone(), args); + let res = TypeChecker::new(&db).run( + owned(instance(thing)), + owned(rigid(param)), + &mut env, + ); + + assert!(!res); + check_ok(&db, owned(rigid(param)), infer(parameter(param))); + } + + #[test] + fn test_rigid_type_parameter_with_requirements_with_placeholder() { + let mut db = Database::new(); + let equal = new_trait(&mut db, "Equal"); + let param1 = new_parameter(&mut db, "T"); + let param2 = new_parameter(&mut db, "T"); + let var = TypePlaceholder::alloc(&mut db, Some(param2)); + + equal.new_type_parameter(&mut db, "T".to_string()); + + let param1_req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(param1))], + ); + + let param2_req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(param2))], + ); + + param1.add_requirements(&mut db, vec![param1_req]); + param2.add_requirements(&mut db, vec![param2_req]); + + let args = type_arguments(vec![(param2, placeholder(var))]); + let mut env = Environment::new(TypeArguments::new(), args); + let res = TypeChecker::new(&db).run( + owned(rigid(param1)), + infer(parameter(param2)), + &mut env, + ); + + assert!(res); + } +} diff --git a/types/src/either.rs b/types/src/either.rs new file mode 100644 index 000000000..8cde6ae4c --- /dev/null +++ b/types/src/either.rs @@ -0,0 +1,5 @@ +/// A type that is either something of type A, or something of type B. +pub(crate) enum Either { + Left(L), + Right(R), +} diff --git a/types/src/format.rs b/types/src/format.rs new file mode 100644 index 000000000..f423f34cc --- /dev/null +++ b/types/src/format.rs @@ -0,0 +1,815 @@ +//! Formatting of types. +use crate::{ + Arguments, ClassId, ClassInstance, ClassKind, ClosureId, Database, + MethodId, MethodKind, ModuleId, TraitId, TraitInstance, TypeArguments, + TypeId, TypeParameterId, TypePlaceholderId, TypeRef, Visibility, +}; + +const MAX_FORMATTING_DEPTH: usize = 8; + +pub fn format_type(db: &Database, typ: T) -> String { + TypeFormatter::new(db, None).format(typ) +} + +pub fn format_type_with_arguments( + db: &Database, + arguments: &TypeArguments, + typ: T, +) -> String { + TypeFormatter::new(db, Some(arguments)).format(typ) +} + +/// A buffer for formatting type names. +/// +/// We use a simple wrapper around a String so we can more easily change the +/// implementation in the future if necessary. +pub struct TypeFormatter<'a> { + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + buffer: String, + depth: usize, +} + +impl<'a> TypeFormatter<'a> { + pub fn new( + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + ) -> Self { + Self { db, type_arguments, buffer: String::new(), depth: 0 } + } + + pub fn verbose( + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + ) -> Self { + Self { db, type_arguments, buffer: String::new(), depth: 0 } + } + + pub fn format(mut self, typ: T) -> String { + typ.format_type(&mut self); + self.buffer + } + + pub(crate) fn descend(&mut self, block: F) { + if self.depth == MAX_FORMATTING_DEPTH { + self.write("..."); + } else { + self.depth += 1; + + block(self); + + self.depth -= 1; + } + } + + pub(crate) fn write(&mut self, thing: &str) { + self.buffer.push_str(thing); + } + + /// If a uni/ref/mut value wraps a type parameter, and that parameter is + /// assigned another value with ownership, you can end up with e.g. + /// `ref mut T` or `uni uni T`. This method provides a simple way of + /// preventing this from happening, without complicating the type formatting + /// process. + pub(crate) fn write_ownership(&mut self, thing: &str) { + if !self.buffer.ends_with(thing) { + self.write(thing); + } + } + + pub(crate) fn type_arguments( + &mut self, + parameters: &[TypeParameterId], + arguments: &TypeArguments, + ) { + for (index, ¶m) in parameters.iter().enumerate() { + if index > 0 { + self.write(", "); + } + + match arguments.get(param) { + Some(TypeRef::Placeholder(id)) + if id.value(self.db).is_none() => + { + // Placeholders without values aren't useful to show to the + // developer, so we show the type parameter instead. + // + // The parameter itself may be assigned a value through the + // type context (e.g. when a type is nested such as + // `Array[Array[T]]`), and we don't want to display that + // assignment as it's only to be used for the outer most + // type. As such, we don't use format_type() here. + param.format_type_without_argument(self); + } + Some(typ) => typ.format_type(self), + _ => param.format_type(self), + } + } + } + + pub(crate) fn arguments( + &mut self, + arguments: &Arguments, + include_name: bool, + ) { + if arguments.len() == 0 { + return; + } + + self.write(" ("); + + for (index, arg) in arguments.iter().enumerate() { + if index > 0 { + self.write(", "); + } + + if include_name { + self.write(&arg.name); + self.write(": "); + } + + arg.value_type.format_type(self); + } + + self.write(")"); + } + + pub(crate) fn return_type(&mut self, typ: TypeRef) { + match typ { + TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} + _ if typ == TypeRef::nil() => {} + _ => { + self.write(" -> "); + typ.format_type(self); + } + } + } +} + +/// A type of which the name can be formatted into something human-readable. +pub trait FormatType { + fn format_type(&self, buffer: &mut TypeFormatter); +} + +impl FormatType for TypePlaceholderId { + fn format_type(&self, buffer: &mut TypeFormatter) { + if let Some(value) = self.value(buffer.db) { + value.format_type(buffer); + } else { + buffer.write("?"); + } + } +} + +impl TypeParameterId { + fn format_type_without_argument(&self, buffer: &mut TypeFormatter) { + let param = self.get(buffer.db); + + buffer.write(¶m.name); + + if param.mutable { + buffer.write(": mut"); + } + } +} + +impl FormatType for TypeParameterId { + fn format_type(&self, buffer: &mut TypeFormatter) { + // Formatting type parameters is a bit tricky, as they may be assigned + // to themselves directly or through a placeholder. The below code isn't + // going to win any awards, but it should ensure we don't blow the stack + // when trying to format recursive type parameters, such as + // `T -> placeholder -> T`. + + if let Some(arg) = buffer.type_arguments.and_then(|a| a.get(*self)) { + if let TypeRef::Placeholder(p) = arg { + match p.value(buffer.db) { + Some(t) if t.as_type_parameter() == Some(*self) => { + self.format_type_without_argument(buffer) + } + Some(t) => t.format_type(buffer), + None => self.format_type_without_argument(buffer), + } + + return; + } + + if arg.as_type_parameter() == Some(*self) { + self.format_type_without_argument(buffer); + return; + } + + arg.format_type(buffer); + } else { + self.format_type_without_argument(buffer); + }; + } +} + +impl FormatType for TraitId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.write(&self.get(buffer.db).name); + } +} + +impl FormatType for TraitInstance { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.descend(|buffer| { + let ins_of = self.instance_of.get(buffer.db); + + buffer.write(&ins_of.name); + + if ins_of.type_parameters.len() > 0 { + let params = ins_of.type_parameters.values(); + let args = self.type_arguments(buffer.db); + + buffer.write("["); + buffer.type_arguments(params, args); + buffer.write("]"); + } + }); + } +} + +impl FormatType for ClassId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.write(&self.get(buffer.db).name); + } +} + +impl FormatType for ClassInstance { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.descend(|buffer| { + let ins_of = self.instance_of.get(buffer.db); + + if ins_of.kind != ClassKind::Tuple { + buffer.write(&ins_of.name); + } + + if ins_of.type_parameters.len() > 0 { + let (open, close) = if ins_of.kind == ClassKind::Tuple { + ("(", ")") + } else { + ("[", "]") + }; + + let params = ins_of.type_parameters.values(); + let args = self.type_arguments(buffer.db); + + buffer.write(open); + buffer.type_arguments(params, args); + buffer.write(close); + } + }); + } +} + +impl FormatType for MethodId { + fn format_type(&self, buffer: &mut TypeFormatter) { + let block = self.get(buffer.db); + + buffer.write("fn "); + + if block.visibility == Visibility::Public { + buffer.write("pub "); + } + + match block.kind { + MethodKind::Async => buffer.write("async "), + MethodKind::AsyncMutable => buffer.write("async mut "), + MethodKind::Static => buffer.write("static "), + MethodKind::Moving => buffer.write("move "), + MethodKind::Mutable | MethodKind::Destructor => { + buffer.write("mut ") + } + _ => {} + } + + buffer.write(&block.name); + + if block.type_parameters.len() > 0 { + buffer.write(" ["); + + for (index, param) in + block.type_parameters.values().iter().enumerate() + { + if index > 0 { + buffer.write(", "); + } + + param.format_type(buffer); + } + + buffer.write("]"); + } + + buffer.arguments(&block.arguments, true); + buffer.return_type(block.return_type); + } +} + +impl FormatType for ModuleId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.write(&self.get(buffer.db).name.to_string()); + } +} + +impl FormatType for ClosureId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.descend(|buffer| { + let fun = self.get(buffer.db); + + if fun.moving { + buffer.write("fn move"); + } else { + buffer.write("fn"); + } + + buffer.arguments(&fun.arguments, false); + buffer.return_type(fun.return_type); + }); + } +} + +impl FormatType for TypeRef { + fn format_type(&self, buffer: &mut TypeFormatter) { + match self { + TypeRef::Owned(id) => id.format_type(buffer), + TypeRef::Infer(id) => id.format_type(buffer), + TypeRef::Uni(id) => { + buffer.write_ownership("uni "); + id.format_type(buffer); + } + TypeRef::RefUni(id) => { + buffer.write_ownership("ref uni "); + id.format_type(buffer); + } + TypeRef::MutUni(id) => { + buffer.write_ownership("mut uni "); + id.format_type(buffer); + } + TypeRef::Ref(id) => { + buffer.write_ownership("ref "); + id.format_type(buffer); + } + TypeRef::Mut(id) => { + buffer.write_ownership("mut "); + id.format_type(buffer); + } + TypeRef::Never => buffer.write("Never"), + TypeRef::Any => buffer.write("Any"), + TypeRef::RefAny => buffer.write("ref Any"), + TypeRef::Error => buffer.write(""), + TypeRef::Unknown => buffer.write(""), + TypeRef::Placeholder(id) => id.format_type(buffer), + }; + } +} + +impl FormatType for TypeId { + fn format_type(&self, buffer: &mut TypeFormatter) { + match self { + TypeId::Class(id) => id.format_type(buffer), + TypeId::Trait(id) => id.format_type(buffer), + TypeId::Module(id) => id.format_type(buffer), + TypeId::ClassInstance(ins) => ins.format_type(buffer), + TypeId::TraitInstance(id) => id.format_type(buffer), + TypeId::TypeParameter(id) => id.format_type(buffer), + TypeId::RigidTypeParameter(id) => { + id.format_type_without_argument(buffer); + } + TypeId::Closure(id) => id.format_type(buffer), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + Block, Class, ClassInstance, ClassKind, Closure, Database, Method, + MethodKind, Module, ModuleId, ModuleName, Trait, TraitInstance, + TypeArguments, TypeId, TypeParameter, TypeRef, Visibility, + }; + + #[test] + fn test_trait_instance_format_type_with_regular_trait() { + let mut db = Database::new(); + let trait_id = Trait::alloc( + &mut db, + "A".to_string(), + ModuleId(0), + Visibility::Private, + ); + let trait_ins = TraitInstance::new(trait_id); + + assert_eq!(format_type(&db, trait_ins), "A".to_string()); + } + + #[test] + fn test_trait_instance_format_type_with_generic_trait() { + let mut db = Database::new(); + let trait_id = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param1 = trait_id.new_type_parameter(&mut db, "A".to_string()); + + trait_id.new_type_parameter(&mut db, "B".to_string()); + + let mut targs = TypeArguments::new(); + + targs.assign(param1, TypeRef::Any); + + let trait_ins = TraitInstance::generic(&mut db, trait_id, targs); + + assert_eq!(format_type(&db, trait_ins), "ToString[Any, B]"); + } + + #[test] + fn test_method_id_format_type_with_instance_method() { + let mut db = Database::new(); + let class_a = Class::alloc( + &mut db, + "A".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_b = Class::alloc( + &mut db, + "B".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_d = Class::alloc( + &mut db, + "D".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Instance, + ); + + let ins_a = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); + + let ins_b = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); + + let ins_d = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); + + block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); + block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); + block.set_return_type(&mut db, ins_d); + + assert_eq!(format_type(&db, block), "fn foo (a: A, b: B) -> D"); + } + + #[test] + fn test_method_id_format_type_with_moving_method() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Moving, + ); + + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn move foo -> Any"); + } + + #[test] + fn test_method_id_format_type_with_type_parameters() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Static, + ); + + block.new_type_parameter(&mut db, "A".to_string()); + block.new_type_parameter(&mut db, "B".to_string()); + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn static foo [A, B] -> Any"); + } + + #[test] + fn test_method_id_format_type_with_static_method() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Static, + ); + + block.new_argument( + &mut db, + "a".to_string(), + TypeRef::Any, + TypeRef::Any, + ); + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn static foo (a: Any) -> Any"); + } + + #[test] + fn test_method_id_format_type_with_async_method() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Async, + ); + + block.new_argument( + &mut db, + "a".to_string(), + TypeRef::Any, + TypeRef::Any, + ); + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn async foo (a: Any) -> Any"); + } + + #[test] + fn test_closure_id_format_type_never_returns() { + let mut db = Database::new(); + let block = Closure::alloc(&mut db, false); + + block.set_return_type(&mut db, TypeRef::Never); + + assert_eq!(format_type(&db, block), "fn -> Never"); + } + + #[test] + fn test_type_id_format_type_with_class() { + let mut db = Database::new(); + let id = TypeId::Class(Class::alloc( + &mut db, + "String".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + )); + + assert_eq!(format_type(&db, id), "String"); + } + + #[test] + fn test_type_id_format_type_with_trait() { + let mut db = Database::new(); + let id = TypeId::Trait(Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + )); + + assert_eq!(format_type(&db, id), "ToString"); + } + + #[test] + fn test_type_id_format_type_with_module() { + let mut db = Database::new(); + let id = TypeId::Module(Module::alloc( + &mut db, + ModuleName::new("foo::bar"), + "foo/bar.inko".into(), + )); + + assert_eq!(format_type(&db, id), "foo::bar"); + } + + #[test] + fn test_type_id_format_type_with_class_instance() { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "String".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let ins = TypeId::ClassInstance(ClassInstance::new(id)); + + assert_eq!(format_type(&db, ins), "String"); + } + + #[test] + fn test_type_id_format_type_with_tuple_instance() { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "MyTuple".to_string(), + ClassKind::Tuple, + Visibility::Private, + ModuleId(0), + ); + let param1 = id.new_type_parameter(&mut db, "A".to_string()); + let param2 = id.new_type_parameter(&mut db, "B".to_string()); + let mut args = TypeArguments::new(); + + args.assign(param1, TypeRef::Any); + args.assign(param2, TypeRef::Never); + + let ins = + TypeId::ClassInstance(ClassInstance::generic(&mut db, id, args)); + + assert_eq!(format_type(&db, ins), "(Any, Never)"); + } + + #[test] + fn test_type_id_format_type_with_trait_instance() { + let mut db = Database::new(); + let id = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let ins = TypeId::TraitInstance(TraitInstance::new(id)); + + assert_eq!(format_type(&db, ins), "ToString"); + } + + #[test] + fn test_type_id_format_type_with_generic_class_instance() { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "Thing".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let param1 = id.new_type_parameter(&mut db, "T".to_string()); + + id.new_type_parameter(&mut db, "E".to_string()); + + let mut targs = TypeArguments::new(); + + targs.assign(param1, TypeRef::Any); + + let ins = + TypeId::ClassInstance(ClassInstance::generic(&mut db, id, targs)); + + assert_eq!(format_type(&db, ins), "Thing[Any, E]"); + } + + #[test] + fn test_type_id_format_type_with_generic_trait_instance() { + let mut db = Database::new(); + let id = Trait::alloc( + &mut db, + "ToFoo".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param1 = id.new_type_parameter(&mut db, "T".to_string()); + + id.new_type_parameter(&mut db, "E".to_string()); + + let mut targs = TypeArguments::new(); + + targs.assign(param1, TypeRef::Any); + + let ins = + TypeId::TraitInstance(TraitInstance::generic(&mut db, id, targs)); + + assert_eq!(format_type(&db, ins), "ToFoo[Any, E]"); + } + + #[test] + fn test_type_id_format_type_with_type_parameter() { + let mut db = Database::new(); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let to_string = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param_ins = TypeId::TypeParameter(param); + let to_string_ins = TraitInstance::new(to_string); + + param.add_requirements(&mut db, vec![to_string_ins]); + + assert_eq!(format_type(&db, param_ins), "T"); + } + + #[test] + fn test_type_id_format_type_with_rigid_type_parameter() { + let mut db = Database::new(); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let to_string = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param_ins = TypeId::RigidTypeParameter(param); + let to_string_ins = TraitInstance::new(to_string); + + param.add_requirements(&mut db, vec![to_string_ins]); + + assert_eq!(format_type(&db, param_ins), "T"); + } + + #[test] + fn test_type_id_format_type_with_closure() { + let mut db = Database::new(); + let class_a = Class::alloc( + &mut db, + "A".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_b = Class::alloc( + &mut db, + "B".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_d = Class::alloc( + &mut db, + "D".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let block = Closure::alloc(&mut db, true); + + let ins_a = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); + + let ins_b = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); + + let ins_d = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); + + block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); + block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); + block.set_return_type(&mut db, ins_d); + + let block_ins = TypeId::Closure(block); + + assert_eq!(format_type(&db, block_ins), "fn move (A, B) -> D"); + } + + #[test] + fn test_type_ref_type_name() { + let mut db = Database::new(); + let string = Class::alloc( + &mut db, + "String".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); + let param = TypeId::TypeParameter(TypeParameter::alloc( + &mut db, + "T".to_string(), + )); + + assert_eq!( + format_type(&db, TypeRef::Owned(string_ins)), + "String".to_string() + ); + assert_eq!(format_type(&db, TypeRef::Infer(param)), "T".to_string()); + assert_eq!( + format_type(&db, TypeRef::Ref(string_ins)), + "ref String".to_string() + ); + assert_eq!(format_type(&db, TypeRef::Never), "Never".to_string()); + assert_eq!(format_type(&db, TypeRef::Any), "Any".to_string()); + assert_eq!(format_type(&db, TypeRef::Error), "".to_string()); + assert_eq!(format_type(&db, TypeRef::Unknown), "".to_string()); + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs index 02ab679c2..9674731d7 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -2,24 +2,33 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::len_without_is_empty))] +#[cfg(test)] +pub mod test; + +pub mod check; pub mod collections; +pub mod either; +pub mod format; pub mod module_name; +pub mod resolve; use crate::collections::IndexMap; use crate::module_name::ModuleName; -use bytecode::{BuiltinFunction as BIF, Opcode}; +use crate::resolve::TypeResolver; use std::cell::Cell; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; +// The IDs of these built-in types must match the order of the fields in the +// State type. pub const INT_ID: u32 = 0; pub const FLOAT_ID: u32 = 1; pub const STRING_ID: u32 = 2; -const ARRAY_ID: u32 = 3; +pub const ARRAY_ID: u32 = 3; pub const BOOLEAN_ID: u32 = 4; -const NIL_ID: u32 = 5; -const BYTE_ARRAY_ID: u32 = 6; -const FUTURE_ID: u32 = 7; +pub const NIL_ID: u32 = 5; +pub const BYTE_ARRAY_ID: u32 = 6; +pub const CHANNEL_ID: u32 = 7; const TUPLE1_ID: u32 = 8; const TUPLE2_ID: u32 = 9; @@ -29,8 +38,9 @@ const TUPLE5_ID: u32 = 12; const TUPLE6_ID: u32 = 13; const TUPLE7_ID: u32 = 14; const TUPLE8_ID: u32 = 15; +const RESULT_ID: u32 = 16; -pub const FIRST_USER_CLASS_ID: u32 = TUPLE8_ID + 1; +pub const FIRST_USER_CLASS_ID: u32 = RESULT_ID + 1; /// The default module ID to assign to builtin types. /// @@ -44,8 +54,7 @@ const ARRAY_NAME: &str = "Array"; const BOOLEAN_NAME: &str = "Bool"; const NIL_NAME: &str = "Nil"; const BYTE_ARRAY_NAME: &str = "ByteArray"; -const FUTURE_NAME: &str = "Future"; - +const CHANNEL_NAME: &str = "Channel"; const TUPLE1_NAME: &str = "Tuple1"; const TUPLE2_NAME: &str = "Tuple2"; const TUPLE3_NAME: &str = "Tuple3"; @@ -54,25 +63,27 @@ const TUPLE5_NAME: &str = "Tuple5"; const TUPLE6_NAME: &str = "Tuple6"; const TUPLE7_NAME: &str = "Tuple7"; const TUPLE8_NAME: &str = "Tuple8"; +const RESULT_NAME: &str = "Result"; pub const STRING_MODULE: &str = "std::string"; pub const TO_STRING_TRAIT: &str = "ToString"; pub const TO_STRING_METHOD: &str = "to_string"; - pub const CALL_METHOD: &str = "call"; pub const MAIN_CLASS: &str = "Main"; pub const MAIN_METHOD: &str = "main"; - pub const DROP_MODULE: &str = "std::drop"; pub const DROP_TRAIT: &str = "Drop"; - pub const DROP_METHOD: &str = "drop"; pub const DROPPER_METHOD: &str = "$dropper"; pub const ASYNC_DROPPER_METHOD: &str = "$async_dropper"; - -pub const CLONE_MODULE: &str = "std::clone"; -pub const CLONE_TRAIT: &str = "Clone"; -pub const CLONE_METHOD: &str = "clone"; +pub const OPTION_MODULE: &str = "std::option"; +pub const OPTION_CLASS: &str = "Option"; +pub const RESULT_MODULE: &str = "std::result"; +pub const RESULT_CLASS: &str = "Result"; +pub const OPTION_SOME: &str = "Some"; +pub const OPTION_NONE: &str = "None"; +pub const RESULT_OK: &str = "Ok"; +pub const RESULT_ERROR: &str = "Error"; pub const ENUM_TAG_FIELD: &str = "tag"; pub const ENUM_TAG_INDEX: usize = 0; @@ -83,205 +94,27 @@ pub const VARIANTS_LIMIT: usize = u16::MAX as usize; /// The maximum number of fields a class can define. pub const FIELDS_LIMIT: usize = u8::MAX as usize; -const MAX_FORMATTING_DEPTH: usize = 8; - -/// The maximum recursion/depth to restrict ourselves to when inferring types or -/// checking if they are inferred. +/// A type inference placeholder. /// -/// In certain cases we may end up with cyclic types, where the cycles are -/// non-trivial (e.g. `A -> B -> C -> D -> A`). To prevent runaway recursion we -/// limit such operations to a certain depth. +/// A type placeholder reprents a value of which the exact type isn't +/// immediately known, and is to be inferred based on how the value is used. +/// Take this code for example: /// -/// The depth here is sufficiently large that no sane program should run into -/// it, but we also won't blow the stack. -const MAX_TYPE_DEPTH: usize = 64; - -pub fn format_type(db: &Database, typ: T) -> String { - TypeFormatter::new(db, None, None).format(typ) -} - -pub fn format_type_with_self( - db: &Database, - self_type: TypeId, - typ: T, -) -> String { - TypeFormatter::new(db, Some(self_type), None).format(typ) -} - -pub fn format_type_with_context( - db: &Database, - context: &TypeContext, - typ: T, -) -> String { - TypeFormatter::new( - db, - Some(context.self_type), - Some(&context.type_arguments), - ) - .format(typ) -} - -#[derive(Copy, Clone)] -pub enum CompilerMacro { - FutureGet, - FutureGetFor, - StringClone, - Moved, - PanicThrown, - Strings, -} - -impl CompilerMacro { - pub fn name(self) -> &'static str { - match self { - CompilerMacro::FutureGet => "future_get", - CompilerMacro::FutureGetFor => "future_get_for", - CompilerMacro::StringClone => "string_clone", - CompilerMacro::Moved => "moved", - CompilerMacro::PanicThrown => "panic_thrown", - CompilerMacro::Strings => "strings", - } - } -} - -/// A buffer for formatting type names. +/// let vals = [] /// -/// We use a simple wrapper around a String so we can more easily change the -/// implementation in the future if necessary. -pub struct TypeFormatter<'a> { - db: &'a Database, - self_type: Option, - type_arguments: Option<&'a TypeArguments>, - buffer: String, - depth: usize, -} - -impl<'a> TypeFormatter<'a> { - pub fn new( - db: &'a Database, - self_type: Option, - type_arguments: Option<&'a TypeArguments>, - ) -> Self { - Self { db, self_type, type_arguments, buffer: String::new(), depth: 0 } - } - - pub fn format(mut self, typ: T) -> String { - typ.format_type(&mut self); - self.buffer - } - - fn descend(&mut self, block: F) { - if self.depth == MAX_FORMATTING_DEPTH { - self.write("..."); - } else { - self.depth += 1; - - block(self); - - self.depth -= 1; - } - } - - fn write(&mut self, thing: &str) { - self.buffer.push_str(thing); - } - - /// If a uni/ref/mut value wraps a type parameter, and that parameter is - /// assigned another value with ownership, you can end up with e.g. - /// `ref mut T` or `uni uni T`. This method provides a simple way of - /// preventing this from happening, without complicating the type formatting - /// process. - fn write_ownership(&mut self, thing: &str) { - if !self.buffer.ends_with(thing) { - self.write(thing); - } - } - - fn type_arguments( - &mut self, - parameters: &[TypeParameterId], - arguments: &TypeArguments, - ) { - for (index, ¶m) in parameters.iter().enumerate() { - if index > 0 { - self.write(", "); - } - - match arguments.get(param) { - Some(TypeRef::Placeholder(id)) - if id.value(self.db).is_none() => - { - // Placeholders without values aren't useful to show to the - // developer, so we show the type parameter instead. - // - // The parameter itself may be assigned a value through the - // type context (e.g. when a type is nested such as - // `Array[Array[T]]`), and we don't want to display that - // assignment as it's only to be used for the outer most - // type. As such, we don't use format_type() here. - param.format_type_without_argument(self); - } - Some(typ) => typ.format_type(self), - _ => param.format_type(self), - } - } - } - - fn arguments(&mut self, arguments: &Arguments, include_name: bool) { - if arguments.len() == 0 { - return; - } - - self.write(" ("); - - for (index, arg) in arguments.iter().enumerate() { - if index > 0 { - self.write(", "); - } - - if include_name { - self.write(&arg.name); - self.write(": "); - } - - arg.value_type.format_type(self); - } - - self.write(")"); - } - - fn throw_type(&mut self, typ: TypeRef) { - if typ.is_never(self.db) { - return; - } - - match typ { - TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} - _ => { - self.write(" !! "); - typ.format_type(self); - } - } - } - - fn return_type(&mut self, typ: TypeRef) { - match typ { - TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} - _ if typ == TypeRef::nil() => {} - _ => { - self.write(" -> "); - typ.format_type(self); - } - } - } -} - -/// A type of which the name can be formatted into something human-readable. -pub trait FormatType { - fn format_type(&self, buffer: &mut TypeFormatter); -} - -/// A placeholder for a type that has yet to be inferred. +/// While we know that `vals` is an array, we don't know the type of the values +/// in the array. In this case we use a type placeholder, meaning that `vals` is +/// of type `Array[V₁]` where V₁ is a type placeholder. +/// +/// At some point we may push a value into the array, for example: +/// +/// vals.push(42) +/// +/// In this case V₁ is assigned to `Int`, and we end up with `vals` inferred as +/// `Array[Int]`. +/// +/// The concept of type placeholder is taken from the Hindley-Milner type +/// system. pub struct TypePlaceholder { /// The value assigned to this placeholder. /// @@ -292,24 +125,19 @@ pub struct TypePlaceholder { /// fields). value: Cell, - /// When `self` is assigned a value, these placeholders are assigned the - /// same value. - /// - /// One place where this is needed is array literals with multiple values: - /// only the first placeholder/value is stored in the Array type, so further - /// inferring of that type doesn't affect values at index 1, 2, etc. By - /// recording `ours` here, updating `id` also updates `ours`, without - /// introducing a placeholder cycle. - depending: Vec, + /// The type parameter a type must be compatible with before it can be + /// assigned to this type variable. + required: Option, } impl TypePlaceholder { - fn alloc(db: &mut Database) -> TypePlaceholderId { + fn alloc( + db: &mut Database, + required: Option, + ) -> TypePlaceholderId { let id = db.type_placeholders.len(); - let typ = TypePlaceholder { - value: Cell::new(TypeRef::Unknown), - depending: Vec::new(), - }; + let typ = + TypePlaceholder { value: Cell::new(TypeRef::Unknown), required }; db.type_placeholders.push(typ); TypePlaceholderId(id) @@ -317,23 +145,30 @@ impl TypePlaceholder { } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct TypePlaceholderId(usize); +pub struct TypePlaceholderId(pub(crate) usize); impl TypePlaceholderId { pub fn value(self, db: &Database) -> Option { - match self.get(db).value.get() { + // Chains of type variables are very rare in practise, but they _can_ + // occur and thus must be handled. Because they are so rare and unlikely + // to be more than 2-3 levels deep, we just use recursion here instead + // of a loop. + let typ = self.get(db).value.get(); + + match typ { + TypeRef::Placeholder(id) => id.value(db), TypeRef::Unknown => None, - value => Some(value), + _ => Some(typ), } } - fn add_depending(self, db: &mut Database, placeholder: TypePlaceholderId) { - self.get_mut(db).depending.push(placeholder); + fn required(self, db: &Database) -> Option { + self.get(db).required } fn assign(self, db: &Database, value: TypeRef) { - // Assigning placeholders to themselves creates cycles that aren't - // useful, so we ignore those. + // Assigning placeholders to themselves isn't useful and results in + // resolve() getting stuck. if let TypeRef::Placeholder(id) = value { if id.0 == self.0 { return; @@ -341,92 +176,15 @@ impl TypePlaceholderId { } self.get(db).value.set(value); - - for &id in &self.get(db).depending { - id.get(db).value.set(value); - } } fn get(self, db: &Database) -> &TypePlaceholder { &db.type_placeholders[self.0] } - - fn get_mut(self, db: &mut Database) -> &mut TypePlaceholder { - &mut db.type_placeholders[self.0] - } -} - -impl FormatType for TypePlaceholderId { - fn format_type(&self, buffer: &mut TypeFormatter) { - if let Some(value) = self.value(buffer.db) { - value.format_type(buffer); - } else { - buffer.write("?"); - } - } -} - -/// A collection of values needed when checking and substituting types. -#[derive(Clone)] -pub struct TypeContext { - /// The type of `Self`. - /// - /// This isn't the same type as `self`: `Self` is a new instance of a type, - /// whereas `self` is the receiver. Consider this example: - /// - /// class A { - /// fn foo -> Self {} - /// } - /// - /// Within `foo`, the type of `self` is `ref A`, but the type of `Self` is - /// `A`. - pub self_type: TypeId, - - /// The type arguments available to this context. - /// - /// When type-checking a method call, this table contains the type - /// parameters and values of both the receiver and the method itself. - pub type_arguments: TypeArguments, - - /// The nesting/recursion depth when e.g. inferring a type. - /// - /// This value is used to prevent runaway recursion that can occur when - /// dealing with (complex) cyclic types. - depth: usize, -} - -impl TypeContext { - pub fn new(self_type_id: TypeId) -> Self { - Self { - self_type: self_type_id, - type_arguments: TypeArguments::new(), - depth: 0, - } - } - - pub fn for_class_instance( - db: &Database, - self_type: TypeId, - instance: ClassInstance, - ) -> Self { - let type_arguments = if instance.instance_of().is_generic(db) { - instance.type_arguments(db).clone() - } else { - TypeArguments::new() - }; - - Self { self_type, type_arguments, depth: 0 } - } - - pub fn with_arguments( - self_type_id: TypeId, - type_arguments: TypeArguments, - ) -> Self { - Self { self_type: self_type_id, type_arguments, depth: 0 } - } } /// A type parameter for a method or class. +#[derive(Clone)] pub struct TypeParameter { /// The name of the type parameter. name: String, @@ -434,24 +192,34 @@ pub struct TypeParameter { /// The traits that must be implemented before a type can be assigned to /// this type parameter. requirements: Vec, + + /// If mutable references to this type parameter are allowed. + mutable: bool, + + /// The ID of the original type parameter in case the current one is a + /// parameter introduced through additional type bounds. + original: Option, } impl TypeParameter { pub fn alloc(db: &mut Database, name: String) -> TypeParameterId { + TypeParameter::add(db, TypeParameter::new(name)) + } + + fn add(db: &mut Database, parameter: TypeParameter) -> TypeParameterId { let id = db.type_parameters.len(); - let typ = TypeParameter::new(name); - db.type_parameters.push(typ); + db.type_parameters.push(parameter); TypeParameterId(id) } fn new(name: String) -> Self { - Self { name, requirements: Vec::new() } + Self { name, requirements: Vec::new(), mutable: false, original: None } } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub struct TypeParameterId(usize); +pub struct TypeParameterId(pub usize); impl TypeParameterId { pub fn name(self, db: &Database) -> &String { @@ -482,126 +250,44 @@ impl TypeParameterId { None } - fn all_requirements_met( - self, - db: &mut Database, - mut func: impl FnMut(&mut Database, TraitInstance) -> bool, - ) -> bool { - self.get(db).requirements.clone().into_iter().all(|r| func(db, r)) + pub fn set_original(self, db: &mut Database, parameter: TypeParameterId) { + self.get_mut(db).original = Some(parameter); } - fn get(self, db: &Database) -> &TypeParameter { - &db.type_parameters[self.0] + pub fn original(self, db: &Database) -> Option { + self.get(db).original } - fn get_mut(self, db: &mut Database) -> &mut TypeParameter { - &mut db.type_parameters[self.0] + pub fn set_mutable(self, db: &mut Database) { + self.get_mut(db).mutable = true; } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::TraitInstance(theirs) => self - .type_check_with_trait_instance(db, theirs, context, subtyping), - TypeId::TypeParameter(theirs) => self - .type_check_with_type_parameter(db, theirs, context, subtyping), - _ => false, - } + pub fn is_mutable(self, db: &Database) -> bool { + self.get(db).mutable } - fn type_check_with_type_parameter( - self, - db: &mut Database, - with: TypeParameterId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - with.all_requirements_met(db, |db, req| { - self.type_check_with_trait_instance(db, req, context, subtyping) - }) + pub fn as_immutable(self, db: &mut Database) -> TypeParameterId { + let mut copy = self.get(db).clone(); + + copy.mutable = false; + TypeParameter::add(db, copy) } - fn type_check_with_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - self.get(db).requirements.clone().into_iter().any(|req| { - req.type_check_with_trait_instance( - db, instance, None, context, subtyping, - ) - }) + fn get(self, db: &Database) -> &TypeParameter { + &db.type_parameters[self.0] } - fn as_rigid_type(self, bounds: &TypeBounds) -> TypeId { - TypeId::RigidTypeParameter(bounds.get(self).unwrap_or(self)) + fn get_mut(self, db: &mut Database) -> &mut TypeParameter { + &mut db.type_parameters[self.0] } fn as_owned_rigid(self) -> TypeRef { TypeRef::Owned(TypeId::RigidTypeParameter(self)) } - - fn format_type_without_argument(&self, buffer: &mut TypeFormatter) { - let param = self.get(buffer.db); - - buffer.write(¶m.name); - - if !param.requirements.is_empty() { - buffer.write(": "); - - for (index, req) in param.requirements.iter().enumerate() { - if index > 0 { - buffer.write(" + "); - } - - req.format_type(buffer); - } - } - } -} - -impl FormatType for TypeParameterId { - fn format_type(&self, buffer: &mut TypeFormatter) { - // Formatting type parameters is a bit tricky, as they may be assigned - // to themselves directly or through a placeholder. The below code isn't - // going to win any awards, but it should ensure we don't blow the stack - // when trying to format recursive type parameters, such as - // `T -> placeholder -> T`. - - if let Some(arg) = buffer.type_arguments.and_then(|a| a.get(*self)) { - if let TypeRef::Placeholder(p) = arg { - match p.value(buffer.db) { - Some(t) if t.as_type_parameter() == Some(*self) => { - self.format_type_without_argument(buffer) - } - Some(t) => t.format_type(buffer), - None => self.format_type_without_argument(buffer), - } - - return; - } - - if arg.as_type_parameter() == Some(*self) { - self.format_type_without_argument(buffer); - return; - } - - arg.format_type(buffer); - } else { - self.format_type_without_argument(buffer); - }; - } } /// Type parameters and the types assigned to them. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TypeArguments { /// We use a HashMap as parameters can be assigned in any order, and some /// may not be assigned at all. @@ -609,14 +295,20 @@ pub struct TypeArguments { } impl TypeArguments { - fn rigid(db: &mut Database, index: u32, bounds: &TypeBounds) -> Self { - let mut new_args = Self::new(); - - for (param, value) in db.type_arguments[index as usize].pairs() { - new_args.assign(param, value.as_rigid_type(db, bounds)); + pub fn for_class(db: &Database, instance: ClassInstance) -> TypeArguments { + if instance.instance_of().is_generic(db) { + instance.type_arguments(db).clone() + } else { + TypeArguments::new() } + } - new_args + pub fn for_trait(db: &Database, instance: TraitInstance) -> TypeArguments { + if instance.instance_of().is_generic(db) { + instance.type_arguments(db).clone() + } else { + TypeArguments::new() + } } pub fn new() -> Self { @@ -658,24 +350,6 @@ impl TypeArguments { } } } - - fn assigned_or_placeholders( - &self, - db: &mut Database, - parameters: Vec, - ) -> Self { - let mut new_args = Self::new(); - - for param in parameters { - if let Some(val) = self.get(param) { - new_args.assign(param, val); - } else { - new_args.assign(param, TypeRef::placeholder(db)); - } - } - - new_args - } } /// An Inko trait. @@ -800,6 +474,10 @@ impl TraitId { self_typ.required_traits.push(requirement); } + pub fn implemented_by(self, db: &Database) -> &Vec { + &self.get(db).implemented_by + } + pub fn method_exists(self, db: &Database, name: &str) -> bool { self.get(db).default_methods.contains_key(name) || self.get(db).required_methods.contains_key(name) @@ -894,12 +572,6 @@ impl TraitId { } } -impl FormatType for TraitId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.write(&self.get(buffer.db).name); - } -} - /// An instance of a trait, along with its type arguments in case the trait is /// generic. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -916,9 +588,11 @@ pub struct TraitInstance { } impl TraitInstance { - /// Returns an instance to use as the type of `Self` in required and default - /// methods. - pub fn for_self_type( + pub fn new(instance_of: TraitId) -> Self { + Self { instance_of, type_arguments: 0 } + } + + pub fn rigid( db: &mut Database, instance_of: TraitId, bounds: &TypeBounds, @@ -939,10 +613,6 @@ impl TraitInstance { } } - pub fn new(instance_of: TraitId) -> Self { - Self { instance_of, type_arguments: 0 } - } - pub fn generic( db: &mut Database, instance_of: TraitId, @@ -995,180 +665,19 @@ impl TraitInstance { self.instance_of.method(db, name) } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::TraitInstance(ins) => self.type_check_with_trait_instance( - db, ins, None, context, subtyping, - ), - TypeId::TypeParameter(id) => { - id.all_requirements_met(db, |db, req| { - self.type_check_with_trait_instance( - db, req, None, context, subtyping, - ) - }) - } - _ => false, - } - } - - fn type_check_with_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - arguments: Option<&TypeArguments>, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if self.instance_of != instance.instance_of { - return if subtyping { - self.instance_of - .get(db) - .required_traits - .clone() - .into_iter() - .any(|req| { - req.type_check_with_trait_instance( - db, instance, None, context, subtyping, - ) - }) - } else { - false - }; - } - - let our_trait = self.instance_of.get(db); - - if !our_trait.is_generic() { - return true; - } - - let our_args = self.type_arguments(db).clone(); - let their_args = instance.type_arguments(db).clone(); - - // If additional type arguments are given (e.g. when comparing a generic - // class instance to a trait), we need to remap the implementation - // arguments accordingly. This way if `Box[T]` implements `Iter[T]`, for - // a `Box[Int]` we produce a `Iter[Int]` rather than an `Iter[T]`. - if let Some(args) = arguments { - our_trait.type_parameters.values().clone().into_iter().all( - |param| { - our_args - .get(param) - .zip(their_args.get(param)) - .map(|(ours, theirs)| { - ours.as_type_parameter() - .and_then(|id| args.get(id)) - .unwrap_or(ours) - .type_check(db, theirs, context, subtyping) - }) - .unwrap_or(false) - }, - ) - } else { - our_trait.type_parameters.values().clone().into_iter().all( - |param| { - our_args - .get(param) - .zip(their_args.get(param)) - .map(|(ours, theirs)| { - ours.type_check(db, theirs, context, subtyping) - }) - .unwrap_or(false) - }, - ) - } - } - fn named_type(self, db: &Database, name: &str) -> Option { self.instance_of.named_type(db, name) } +} - fn implements_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - context: &mut TypeContext, - ) -> bool { - self.instance_of.get(db).required_traits.clone().into_iter().any( - |req| { - req.type_check_with_trait_instance( - db, instance, None, context, true, - ) - }, - ) - } - - fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { - self.instance_of - .get(db) - .required_traits - .iter() - .any(|req| req.implements_trait_id(db, trait_id)) - } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - if !self.instance_of.get(db).is_generic() { - return self; - } - - let new_args = TypeArguments::rigid(db, self.type_arguments, bounds); - - TraitInstance::generic(db, self.instance_of, new_args) - } - - fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> Self { - if !self.instance_of.is_generic(db) { - return self; - } - - let mut new_args = TypeArguments::new(); - - for (arg, val) in self.type_arguments(db).pairs() { - new_args.assign(arg, val.inferred(db, context, immutable)); - } - - Self::generic(db, self.instance_of, new_args) - } -} - -impl FormatType for TraitInstance { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.descend(|buffer| { - let ins_of = self.instance_of.get(buffer.db); - - buffer.write(&ins_of.name); - - if ins_of.type_parameters.len() > 0 { - let params = ins_of.type_parameters.values(); - let args = self.type_arguments(buffer.db); - - buffer.write("["); - buffer.type_arguments(params, args); - buffer.write("]"); - } - }); - } -} - -/// A field for a class. -pub struct Field { - index: usize, - name: String, - value_type: TypeRef, - visibility: Visibility, - module: ModuleId, -} +/// A field for a class. +pub struct Field { + index: usize, + name: String, + value_type: TypeRef, + visibility: Visibility, + module: ModuleId, +} impl Field { pub fn alloc( @@ -1228,17 +737,13 @@ impl FieldId { /// Additional requirements for type parameters inside a trait implementation of /// method. /// -/// Additional bounds are set using the `when` keyword like so: -/// -/// impl Debug[T] for Array when T: X { ... } -/// /// This structure maps the original type parameters (`T` in this case) to type /// parameters created for the bounds. These new type parameters have their /// requirements set to the union of the original type parameter's requirements, /// and the requirements specified in the bounds. In other words, if the /// original parameter is defined as `T: A` and the bounds specify `T: B`, this /// structure maps `T: A` to `T: A + B`. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TypeBounds { mapping: HashMap, } @@ -1255,6 +760,38 @@ impl TypeBounds { pub fn get(&self, parameter: TypeParameterId) -> Option { self.mapping.get(¶meter).cloned() } + + pub fn iter( + &self, + ) -> impl Iterator { + self.mapping.iter() + } + + pub fn values_mut(&mut self) -> impl Iterator { + self.mapping.values_mut() + } + + pub fn is_empty(&self) -> bool { + self.mapping.is_empty() + } + + pub fn union(&self, with: &TypeBounds) -> TypeBounds { + let mut union = self.clone(); + + for (&key, &val) in &with.mapping { + union.set(key, val); + } + + union + } + + pub fn make_immutable(&mut self, db: &mut Database) { + for bound in self.mapping.values_mut() { + if bound.is_mutable(db) { + *bound = bound.as_immutable(db); + } + } + } } /// An implementation of a trait, with (optionally) additional bounds for the @@ -1324,6 +861,9 @@ pub enum ClassKind { Enum, Regular, Tuple, + Closure, + Module, + Extern, } impl ClassKind { @@ -1342,12 +882,30 @@ impl ClassKind { pub fn is_tuple(self) -> bool { matches!(self, ClassKind::Tuple) } + + pub fn is_closure(self) -> bool { + matches!(self, ClassKind::Closure) + } + + pub fn is_module(self) -> bool { + matches!(self, ClassKind::Module) + } + + pub fn is_extern(self) -> bool { + matches!(self, ClassKind::Extern) + } + + pub fn allow_pattern_matching(self) -> bool { + matches!(self, ClassKind::Regular | ClassKind::Extern) + } } /// An Inko class as declared using the `class` keyword. pub struct Class { kind: ClassKind, name: String, + atomic: bool, + value_type: bool, // A flag indicating the presence of a custom destructor. // // We store a flag for this so we can check for the presence of a destructor @@ -1390,6 +948,8 @@ impl Class { kind, visibility, destructor: false, + atomic: kind.is_async(), + value_type: matches!(kind, ClassKind::Async | ClassKind::Extern), fields: IndexMap::new(), type_parameters: IndexMap::new(), methods: HashMap::new(), @@ -1408,6 +968,40 @@ impl Class { ) } + fn external(name: String) -> Self { + Self::new( + name, + ClassKind::Extern, + Visibility::Public, + ModuleId(DEFAULT_BUILTIN_MODULE_ID), + ) + } + + fn value_type(name: String) -> Self { + let mut class = Self::new( + name, + ClassKind::Regular, + Visibility::Public, + ModuleId(DEFAULT_BUILTIN_MODULE_ID), + ); + + class.value_type = true; + class + } + + fn atomic(name: String) -> Self { + let mut class = Self::new( + name, + ClassKind::Regular, + Visibility::Public, + ModuleId(DEFAULT_BUILTIN_MODULE_ID), + ); + + class.atomic = true; + class.value_type = true; + class + } + fn tuple(name: String) -> Self { Self::new( name, @@ -1446,8 +1040,8 @@ impl ClassId { ClassId(NIL_ID) } - pub fn future() -> ClassId { - ClassId(FUTURE_ID) + pub fn channel() -> ClassId { + ClassId(CHANNEL_ID) } pub fn array() -> ClassId { @@ -1504,6 +1098,10 @@ impl ClassId { } } + pub fn result() -> ClassId { + ClassId(RESULT_ID) + } + pub fn name(self, db: &Database) -> &String { &self.get(db).name } @@ -1657,6 +1255,10 @@ impl ClassId { !self.is_public(db) } + pub fn is_atomic(self, db: &Database) -> bool { + self.get(db).atomic + } + pub fn set_module(self, db: &mut Database, module: ModuleId) { self.get_mut(db).module = module; } @@ -1692,6 +1294,10 @@ impl ClassId { self.get(db).destructor } + pub fn is_builtin(self) -> bool { + self.0 <= CHANNEL_ID + } + fn get(self, db: &Database) -> &Class { &db.classes[self.0 as usize] } @@ -1701,12 +1307,6 @@ impl ClassId { } } -impl FormatType for ClassId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.write(&self.get(buffer.db).name); - } -} - /// An instance of a class, along with its type arguments in case the class is /// generic. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -1723,9 +1323,11 @@ pub struct ClassInstance { } impl ClassInstance { - /// Returns a class instance to use as the type of `Self` in instance - /// methods. - pub fn for_instance_self_type( + pub fn new(instance_of: ClassId) -> Self { + Self { instance_of, type_arguments: 0 } + } + + pub fn rigid( db: &mut Database, instance_of: ClassId, bounds: &TypeBounds, @@ -1746,28 +1348,6 @@ impl ClassInstance { } } - /// Returns a class instance to use as the type of `Self` in static methods. - pub fn for_static_self_type( - db: &mut Database, - instance_of: ClassId, - ) -> Self { - if instance_of.is_generic(db) { - let mut arguments = TypeArguments::new(); - - for param in instance_of.type_parameters(db) { - arguments.assign(param, param.as_owned_rigid()); - } - - Self::generic(db, instance_of, arguments) - } else { - Self::new(instance_of) - } - } - - pub fn new(instance_of: ClassId) -> Self { - Self { instance_of, type_arguments: 0 } - } - pub fn generic( db: &mut Database, instance_of: ClassId, @@ -1781,39 +1361,38 @@ impl ClassInstance { ClassInstance { instance_of, type_arguments: args_id } } - pub fn generic_with_types( + pub fn with_types( db: &mut Database, - instance_of: ClassId, - types: Vec, + class: ClassId, + arguments: Vec, ) -> Self { let mut args = TypeArguments::new(); - for (index, param) in - instance_of.type_parameters(db).into_iter().enumerate() + for (index, param) in class.type_parameters(db).into_iter().enumerate() { - args.assign( - param, - types - .get(index) - .cloned() - .unwrap_or_else(|| TypeRef::placeholder(db)), - ); + let val = arguments + .get(index) + .cloned() + .unwrap_or_else(|| TypeRef::placeholder(db, Some(param))); + + args.assign(param, val); } - Self::generic(db, instance_of, args) + Self::generic(db, class, args) } - pub fn generic_with_placeholders( - db: &mut Database, - instance_of: ClassId, - ) -> Self { + pub fn empty(db: &mut Database, class: ClassId) -> Self { + if !class.is_generic(db) { + return Self::new(class); + } + let mut args = TypeArguments::new(); - for param in instance_of.type_parameters(db) { - args.assign(param, TypeRef::placeholder(db)); + for param in class.type_parameters(db) { + args.assign(param, TypeRef::placeholder(db, Some(param))); } - Self::generic(db, instance_of, args) + Self::generic(db, class, args) } pub fn instance_of(self) -> ClassId { @@ -1859,71 +1438,6 @@ impl ClassInstance { self.type_arguments(db).copy_into(target); } - pub fn type_check_with_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if !subtyping { - return false; - } - - let trait_impl = if let Some(found) = self - .instance_of - .trait_implementation(db, instance.instance_of) - .cloned() - { - found - } else { - return false; - }; - - let mut trait_instance = trait_impl.instance; - - if self.instance_of.is_generic(db) - && trait_instance.instance_of.is_generic(db) - { - // The generic trait implementation may refer to (or contain a type - // that refers) to a type parameter defined in our class. If we end - // up comparing such a type parameter, we must compare its assigned - // value instead if there is any. - // - // To achieve this we must first expose the type parameter - // assignments in the context, then infer the trait instance into a - // type that uses those assignments (if needed). - self.type_arguments(db).copy_into(&mut context.type_arguments); - trait_instance = trait_instance.inferred(db, context, false); - } - - let args = if self.instance_of.is_generic(db) { - let args = self.type_arguments(db).clone(); - let available = - trait_impl.bounds.mapping.into_iter().all(|(orig, bound)| { - args.get(orig).map_or(false, |t| { - t.is_compatible_with_type_parameter(db, bound, context) - }) - }); - - if !available { - return false; - } - - Some(args) - } else { - None - }; - - trait_instance.type_check_with_trait_instance( - db, - instance, - args.as_ref(), - context, - subtyping, - ) - } - pub fn method(self, db: &Database, name: &str) -> Option { self.instance_of.method(db, name) } @@ -1938,126 +1452,9 @@ impl ClassInstance { .collect() } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::ClassInstance(ins) => { - if self.instance_of != ins.instance_of { - return false; - } - - let our_class = self.instance_of.get(db); - - if !our_class.is_generic() { - return true; - } - - let our_args = self.type_arguments(db).clone(); - let their_args = ins.type_arguments(db).clone(); - - if our_args.mapping.is_empty() { - // Empty types are compatible with those that do have - // assigned parameters. For example, an empty Array that has - // not yet been mutated (and thus its parameter is - // unassigned) can be safely passed to something that - // expects e.g. `Array[Int]`. - return true; - } - - our_class.type_parameters.values().clone().into_iter().all( - |param| { - our_args - .get(param) - .zip(their_args.get(param)) - .map(|(ours, theirs)| { - ours.type_check(db, theirs, context, subtyping) - }) - .unwrap_or(false) - }, - ) - } - TypeId::TraitInstance(ins) => { - self.type_check_with_trait_instance(db, ins, context, subtyping) - } - TypeId::TypeParameter(id) => { - id.all_requirements_met(db, |db, req| { - self.type_check_with_trait_instance( - db, req, context, subtyping, - ) - }) - } - _ => false, - } - } - - fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { - self.instance_of.trait_implementation(db, trait_id).is_some() - } - fn named_type(self, db: &Database, name: &str) -> Option { self.instance_of.named_type(db, name) } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - if !self.instance_of.get(db).is_generic() { - return self; - } - - let new_args = TypeArguments::rigid(db, self.type_arguments, bounds); - - ClassInstance::generic(db, self.instance_of, new_args) - } - - fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> Self { - if !self.instance_of.is_generic(db) { - return self; - } - - let mut new_args = TypeArguments::new(); - - for (param, val) in self.type_arguments(db).pairs() { - new_args.assign(param, val.inferred(db, context, immutable)); - } - - Self::generic(db, self.instance_of, new_args) - } -} - -impl FormatType for ClassInstance { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.descend(|buffer| { - let ins_of = self.instance_of.get(buffer.db); - - if ins_of.kind != ClassKind::Tuple { - buffer.write(&ins_of.name); - } - - if ins_of.type_parameters.len() > 0 { - let (open, close) = if ins_of.kind == ClassKind::Tuple { - ("(", ")") - } else { - ("[", "]") - }; - - let params = ins_of.type_parameters.values(); - let args = self.type_arguments(buffer.db); - - buffer.write(open); - buffer.type_arguments(params, args); - buffer.write(close); - } - }); - } } /// A collection of arguments. @@ -2094,37 +1491,6 @@ impl Arguments { fn len(&self) -> usize { self.mapping.len() } - - fn type_check( - &self, - db: &mut Database, - with: &Arguments, - context: &mut TypeContext, - same_name: bool, - ) -> bool { - if self.len() != with.len() { - return false; - } - - for (ours, theirs) in - self.mapping.values().iter().zip(with.mapping.values().iter()) - { - if same_name && ours.name != theirs.name { - return false; - } - - if !ours.value_type.type_check( - db, - theirs.value_type, - context, - false, - ) { - return false; - } - } - - true - } } /// An argument defined in a method or closure. @@ -2145,8 +1511,6 @@ pub trait Block { variable_type: TypeRef, argument_type: TypeRef, ) -> VariableId; - fn throw_type(&self, db: &Database) -> TypeRef; - fn set_throw_type(&self, db: &mut Database, typ: TypeRef); fn return_type(&self, db: &Database) -> TypeRef; fn set_return_type(&self, db: &mut Database, typ: TypeRef); } @@ -2177,59 +1541,903 @@ impl Visibility { } } -#[derive(Copy, Clone)] -pub enum BuiltinFunctionKind { - Function(BIF), - Instruction(Opcode), - Macro(CompilerMacro), -} - -/// A function built into the compiler or VM. -pub struct BuiltinFunction { - kind: BuiltinFunctionKind, - return_type: TypeRef, - throw_type: TypeRef, +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum BuiltinFunction { + ArrayCapacity, + ArrayClear, + ArrayDrop, + ArrayGet, + ArrayLength, + ArrayPop, + ArrayPush, + ArrayRemove, + ArrayReserve, + ArraySet, + ByteArrayAppend, + ByteArrayClear, + ByteArrayClone, + ByteArrayCopyFrom, + ByteArrayDrainToString, + ByteArrayDrop, + ByteArrayEq, + ByteArrayGet, + ByteArrayLength, + ByteArrayNew, + ByteArrayPop, + ByteArrayPush, + ByteArrayRemove, + ByteArrayResize, + ByteArraySet, + ByteArraySlice, + ByteArrayToString, + ChannelDrop, + ChannelNew, + ChannelReceive, + ChannelReceiveUntil, + ChannelSend, + ChannelTryReceive, + ChannelWait, + ChildProcessDrop, + ChildProcessSpawn, + ChildProcessStderrClose, + ChildProcessStderrRead, + ChildProcessStdinClose, + ChildProcessStdinFlush, + ChildProcessStdinWriteBytes, + ChildProcessStdinWriteString, + ChildProcessStdoutClose, + ChildProcessStdoutRead, + ChildProcessTryWait, + ChildProcessWait, + CpuCores, + DirectoryCreate, + DirectoryCreateRecursive, + DirectoryList, + DirectoryRemove, + DirectoryRemoveRecursive, + EnvArguments, + EnvExecutable, + EnvGet, + EnvGetWorkingDirectory, + EnvHomeDirectory, + EnvSetWorkingDirectory, + EnvTempDirectory, + EnvVariables, + Exit, + FileCopy, + FileDrop, + FileFlush, + FileOpen, + FileRead, + FileRemove, + FileSeek, + FileSize, + FileWriteBytes, + FileWriteString, + FloatAdd, + FloatCeil, + FloatDiv, + FloatEq, + FloatFloor, + FloatFromBits, + FloatGe, + FloatGt, + FloatIsInf, + FloatIsNan, + FloatLe, + FloatLt, + FloatMod, + FloatMul, + FloatRound, + FloatSub, + FloatToBits, + FloatToInt, + FloatToString, + IntAdd, + IntBitAnd, + IntBitNot, + IntBitOr, + IntBitXor, + IntDiv, + IntEq, + IntGe, + IntGt, + IntLe, + IntLt, + IntMul, + IntPow, + IntRem, + IntRotateLeft, + IntRotateRight, + IntShl, + IntShr, + IntSub, + IntToFloat, + IntToString, + IntUnsignedShr, + IntWrappingAdd, + IntWrappingMul, + IntWrappingSub, + Moved, + ObjectEq, + Panic, + PathAccessedAt, + PathCreatedAt, + PathExists, + PathIsDirectory, + PathIsFile, + PathModifiedAt, + PathExpand, + ProcessStackFrameLine, + ProcessStackFrameName, + ProcessStackFramePath, + ProcessStacktrace, + ProcessStacktraceDrop, + ProcessStacktraceLength, + ProcessSuspend, + RandomBytes, + RandomDrop, + RandomFloat, + RandomFloatRange, + RandomFromInt, + RandomInt, + RandomIntRange, + RandomNew, + SocketAccept, + SocketAddressPairAddress, + SocketAddressPairDrop, + SocketAddressPairPort, + SocketBind, + SocketConnect, + SocketDrop, + SocketListen, + SocketLocalAddress, + SocketNew, + SocketPeerAddress, + SocketRead, + SocketReceiveFrom, + SocketSendBytesTo, + SocketSendStringTo, + SocketSetBroadcast, + SocketSetKeepalive, + SocketSetLinger, + SocketSetNodelay, + SocketSetOnlyV6, + SocketSetRecvSize, + SocketSetReuseAddress, + SocketSetReusePort, + SocketSetSendSize, + SocketSetTtl, + SocketShutdownRead, + SocketShutdownReadWrite, + SocketShutdownWrite, + SocketTryClone, + SocketWriteBytes, + SocketWriteString, + StderrFlush, + StderrWriteBytes, + StderrWriteString, + StdinRead, + StdoutFlush, + StdoutWriteBytes, + StdoutWriteString, + StringByte, + StringCharacters, + StringCharactersDrop, + StringCharactersNext, + StringConcat, + StringConcatArray, + StringDrop, + StringEq, + StringSize, + StringSliceBytes, + StringToByteArray, + StringToFloat, + StringToInt, + StringToLower, + StringToUpper, + TimeMonotonic, + TimeSystem, + TimeSystemOffset, + HashKey0, + HashKey1, } impl BuiltinFunction { - pub fn alloc( - db: &mut Database, - kind: BuiltinFunctionKind, - name: &str, - return_type: TypeRef, - throw_type: TypeRef, - ) -> BuiltinFunctionId { - let func = Self { kind, return_type, throw_type }; - - Self::add(db, name.to_string(), func) + pub fn mapping() -> HashMap { + vec![ + BuiltinFunction::ArrayCapacity, + BuiltinFunction::ArrayClear, + BuiltinFunction::ArrayDrop, + BuiltinFunction::ArrayGet, + BuiltinFunction::ArrayLength, + BuiltinFunction::ArrayPop, + BuiltinFunction::ArrayPush, + BuiltinFunction::ArrayRemove, + BuiltinFunction::ArrayReserve, + BuiltinFunction::ArraySet, + BuiltinFunction::ByteArrayNew, + BuiltinFunction::ByteArrayAppend, + BuiltinFunction::ByteArrayClear, + BuiltinFunction::ByteArrayClone, + BuiltinFunction::ByteArrayCopyFrom, + BuiltinFunction::ByteArrayDrainToString, + BuiltinFunction::ByteArrayDrop, + BuiltinFunction::ByteArrayEq, + BuiltinFunction::ByteArrayGet, + BuiltinFunction::ByteArrayLength, + BuiltinFunction::ByteArrayPop, + BuiltinFunction::ByteArrayPush, + BuiltinFunction::ByteArrayRemove, + BuiltinFunction::ByteArrayResize, + BuiltinFunction::ByteArraySet, + BuiltinFunction::ByteArraySlice, + BuiltinFunction::ByteArrayToString, + BuiltinFunction::ChildProcessDrop, + BuiltinFunction::ChildProcessSpawn, + BuiltinFunction::ChildProcessStderrClose, + BuiltinFunction::ChildProcessStderrRead, + BuiltinFunction::ChildProcessStdinClose, + BuiltinFunction::ChildProcessStdinFlush, + BuiltinFunction::ChildProcessStdinWriteBytes, + BuiltinFunction::ChildProcessStdinWriteString, + BuiltinFunction::ChildProcessStdoutClose, + BuiltinFunction::ChildProcessStdoutRead, + BuiltinFunction::ChildProcessTryWait, + BuiltinFunction::ChildProcessWait, + BuiltinFunction::CpuCores, + BuiltinFunction::DirectoryCreate, + BuiltinFunction::DirectoryCreateRecursive, + BuiltinFunction::DirectoryList, + BuiltinFunction::DirectoryRemove, + BuiltinFunction::DirectoryRemoveRecursive, + BuiltinFunction::EnvArguments, + BuiltinFunction::EnvExecutable, + BuiltinFunction::EnvGet, + BuiltinFunction::EnvGetWorkingDirectory, + BuiltinFunction::EnvHomeDirectory, + BuiltinFunction::EnvSetWorkingDirectory, + BuiltinFunction::EnvTempDirectory, + BuiltinFunction::EnvVariables, + BuiltinFunction::Exit, + BuiltinFunction::FileCopy, + BuiltinFunction::FileDrop, + BuiltinFunction::FileFlush, + BuiltinFunction::FileOpen, + BuiltinFunction::FileRead, + BuiltinFunction::FileRemove, + BuiltinFunction::FileSeek, + BuiltinFunction::FileSize, + BuiltinFunction::FileWriteBytes, + BuiltinFunction::FileWriteString, + BuiltinFunction::FloatAdd, + BuiltinFunction::FloatCeil, + BuiltinFunction::FloatDiv, + BuiltinFunction::FloatEq, + BuiltinFunction::FloatFloor, + BuiltinFunction::FloatFromBits, + BuiltinFunction::FloatGe, + BuiltinFunction::FloatGt, + BuiltinFunction::FloatIsInf, + BuiltinFunction::FloatIsNan, + BuiltinFunction::FloatLe, + BuiltinFunction::FloatLt, + BuiltinFunction::FloatMod, + BuiltinFunction::FloatMul, + BuiltinFunction::FloatRound, + BuiltinFunction::FloatSub, + BuiltinFunction::FloatToBits, + BuiltinFunction::FloatToInt, + BuiltinFunction::FloatToString, + BuiltinFunction::ChannelDrop, + BuiltinFunction::ChannelNew, + BuiltinFunction::ChannelReceive, + BuiltinFunction::ChannelReceiveUntil, + BuiltinFunction::ChannelSend, + BuiltinFunction::ChannelTryReceive, + BuiltinFunction::ChannelWait, + BuiltinFunction::IntAdd, + BuiltinFunction::IntBitAnd, + BuiltinFunction::IntBitNot, + BuiltinFunction::IntBitOr, + BuiltinFunction::IntBitXor, + BuiltinFunction::IntDiv, + BuiltinFunction::IntEq, + BuiltinFunction::IntGe, + BuiltinFunction::IntGt, + BuiltinFunction::IntLe, + BuiltinFunction::IntLt, + BuiltinFunction::IntRem, + BuiltinFunction::IntMul, + BuiltinFunction::IntPow, + BuiltinFunction::IntRotateLeft, + BuiltinFunction::IntRotateRight, + BuiltinFunction::IntShl, + BuiltinFunction::IntShr, + BuiltinFunction::IntSub, + BuiltinFunction::IntToFloat, + BuiltinFunction::IntToString, + BuiltinFunction::IntUnsignedShr, + BuiltinFunction::IntWrappingAdd, + BuiltinFunction::IntWrappingMul, + BuiltinFunction::IntWrappingSub, + BuiltinFunction::Moved, + BuiltinFunction::ObjectEq, + BuiltinFunction::Panic, + BuiltinFunction::PathAccessedAt, + BuiltinFunction::PathCreatedAt, + BuiltinFunction::PathExists, + BuiltinFunction::PathIsDirectory, + BuiltinFunction::PathIsFile, + BuiltinFunction::PathModifiedAt, + BuiltinFunction::PathExpand, + BuiltinFunction::ProcessStackFrameLine, + BuiltinFunction::ProcessStackFrameName, + BuiltinFunction::ProcessStackFramePath, + BuiltinFunction::ProcessStacktrace, + BuiltinFunction::ProcessStacktraceDrop, + BuiltinFunction::ProcessStacktraceLength, + BuiltinFunction::ProcessSuspend, + BuiltinFunction::RandomBytes, + BuiltinFunction::RandomDrop, + BuiltinFunction::RandomFloat, + BuiltinFunction::RandomFloatRange, + BuiltinFunction::RandomFromInt, + BuiltinFunction::RandomInt, + BuiltinFunction::RandomIntRange, + BuiltinFunction::RandomNew, + BuiltinFunction::SocketAccept, + BuiltinFunction::SocketAddressPairAddress, + BuiltinFunction::SocketAddressPairDrop, + BuiltinFunction::SocketAddressPairPort, + BuiltinFunction::SocketNew, + BuiltinFunction::SocketBind, + BuiltinFunction::SocketConnect, + BuiltinFunction::SocketDrop, + BuiltinFunction::SocketListen, + BuiltinFunction::SocketLocalAddress, + BuiltinFunction::SocketPeerAddress, + BuiltinFunction::SocketRead, + BuiltinFunction::SocketReceiveFrom, + BuiltinFunction::SocketSendBytesTo, + BuiltinFunction::SocketSendStringTo, + BuiltinFunction::SocketSetBroadcast, + BuiltinFunction::SocketSetKeepalive, + BuiltinFunction::SocketSetLinger, + BuiltinFunction::SocketSetNodelay, + BuiltinFunction::SocketSetOnlyV6, + BuiltinFunction::SocketSetRecvSize, + BuiltinFunction::SocketSetReuseAddress, + BuiltinFunction::SocketSetReusePort, + BuiltinFunction::SocketSetSendSize, + BuiltinFunction::SocketSetTtl, + BuiltinFunction::SocketShutdownRead, + BuiltinFunction::SocketShutdownReadWrite, + BuiltinFunction::SocketShutdownWrite, + BuiltinFunction::SocketTryClone, + BuiltinFunction::SocketWriteBytes, + BuiltinFunction::SocketWriteString, + BuiltinFunction::StderrFlush, + BuiltinFunction::StderrWriteBytes, + BuiltinFunction::StderrWriteString, + BuiltinFunction::StdinRead, + BuiltinFunction::StdoutFlush, + BuiltinFunction::StdoutWriteBytes, + BuiltinFunction::StdoutWriteString, + BuiltinFunction::StringByte, + BuiltinFunction::StringCharacters, + BuiltinFunction::StringCharactersDrop, + BuiltinFunction::StringCharactersNext, + BuiltinFunction::StringConcat, + BuiltinFunction::StringConcatArray, + BuiltinFunction::StringDrop, + BuiltinFunction::StringEq, + BuiltinFunction::StringSize, + BuiltinFunction::StringSliceBytes, + BuiltinFunction::StringToByteArray, + BuiltinFunction::StringToFloat, + BuiltinFunction::StringToInt, + BuiltinFunction::StringToLower, + BuiltinFunction::StringToUpper, + BuiltinFunction::TimeMonotonic, + BuiltinFunction::TimeSystem, + BuiltinFunction::TimeSystemOffset, + BuiltinFunction::HashKey0, + BuiltinFunction::HashKey1, + ] + .into_iter() + .fold(HashMap::new(), |mut map, func| { + map.insert(func.name().to_string(), func); + map + }) } - fn add(db: &mut Database, name: String, func: Self) -> BuiltinFunctionId { - let id = db.builtin_functions.len(); + pub fn name(self) -> &'static str { + match self { + BuiltinFunction::ArrayCapacity => "array_capacity", + BuiltinFunction::ArrayClear => "array_clear", + BuiltinFunction::ArrayDrop => "array_drop", + BuiltinFunction::ArrayGet => "array_get", + BuiltinFunction::ArrayLength => "array_length", + BuiltinFunction::ArrayPop => "array_pop", + BuiltinFunction::ArrayPush => "array_push", + BuiltinFunction::ArrayRemove => "array_remove", + BuiltinFunction::ArrayReserve => "array_reserve", + BuiltinFunction::ArraySet => "array_set", + BuiltinFunction::ByteArrayNew => "byte_array_new", + BuiltinFunction::ByteArrayAppend => "byte_array_append", + BuiltinFunction::ByteArrayClear => "byte_array_clear", + BuiltinFunction::ByteArrayClone => "byte_array_clone", + BuiltinFunction::ByteArrayCopyFrom => "byte_array_copy_from", + BuiltinFunction::ByteArrayDrainToString => { + "byte_array_drain_to_string" + } + BuiltinFunction::ByteArrayDrop => "byte_array_drop", + BuiltinFunction::ByteArrayEq => "byte_array_eq", + BuiltinFunction::ByteArrayGet => "byte_array_get", + BuiltinFunction::ByteArrayLength => "byte_array_length", + BuiltinFunction::ByteArrayPop => "byte_array_pop", + BuiltinFunction::ByteArrayPush => "byte_array_push", + BuiltinFunction::ByteArrayRemove => "byte_array_remove", + BuiltinFunction::ByteArrayResize => "byte_array_resize", + BuiltinFunction::ByteArraySet => "byte_array_set", + BuiltinFunction::ByteArraySlice => "byte_array_slice", + BuiltinFunction::ByteArrayToString => "byte_array_to_string", + BuiltinFunction::ChildProcessDrop => "child_process_drop", + BuiltinFunction::ChildProcessSpawn => "child_process_spawn", + BuiltinFunction::ChildProcessStderrClose => { + "child_process_stderr_close" + } + BuiltinFunction::ChildProcessStderrRead => { + "child_process_stderr_read" + } + BuiltinFunction::ChildProcessStdinClose => { + "child_process_stdin_close" + } + BuiltinFunction::ChildProcessStdinFlush => { + "child_process_stdin_flush" + } + BuiltinFunction::ChildProcessStdinWriteBytes => { + "child_process_stdin_write_bytes" + } + BuiltinFunction::ChildProcessStdinWriteString => { + "child_process_stdin_write_string" + } + BuiltinFunction::ChildProcessStdoutClose => { + "child_process_stdout_close" + } + BuiltinFunction::ChildProcessStdoutRead => { + "child_process_stdout_read" + } + BuiltinFunction::ChildProcessTryWait => "child_process_try_wait", + BuiltinFunction::ChildProcessWait => "child_process_wait", + BuiltinFunction::CpuCores => "cpu_cores", + BuiltinFunction::DirectoryCreate => "directory_create", + BuiltinFunction::DirectoryCreateRecursive => { + "directory_create_recursive" + } + BuiltinFunction::DirectoryList => "directory_list", + BuiltinFunction::DirectoryRemove => "directory_remove", + BuiltinFunction::DirectoryRemoveRecursive => { + "directory_remove_recursive" + } + BuiltinFunction::EnvArguments => "env_arguments", + BuiltinFunction::EnvExecutable => "env_executable", + BuiltinFunction::EnvGet => "env_get", + BuiltinFunction::EnvGetWorkingDirectory => { + "env_get_working_directory" + } + BuiltinFunction::EnvHomeDirectory => "env_home_directory", + BuiltinFunction::EnvSetWorkingDirectory => { + "env_set_working_directory" + } + BuiltinFunction::EnvTempDirectory => "env_temp_directory", + BuiltinFunction::EnvVariables => "env_variables", + BuiltinFunction::Exit => "exit", + BuiltinFunction::FileCopy => "file_copy", + BuiltinFunction::FileDrop => "file_drop", + BuiltinFunction::FileFlush => "file_flush", + BuiltinFunction::FileOpen => "file_open", + BuiltinFunction::FileRead => "file_read", + BuiltinFunction::FileRemove => "file_remove", + BuiltinFunction::FileSeek => "file_seek", + BuiltinFunction::FileSize => "file_size", + BuiltinFunction::FileWriteBytes => "file_write_bytes", + BuiltinFunction::FileWriteString => "file_write_string", + BuiltinFunction::FloatAdd => "float_add", + BuiltinFunction::FloatCeil => "float_ceil", + BuiltinFunction::FloatDiv => "float_div", + BuiltinFunction::FloatEq => "float_eq", + BuiltinFunction::FloatFloor => "float_floor", + BuiltinFunction::FloatFromBits => "float_from_bits", + BuiltinFunction::FloatGe => "float_ge", + BuiltinFunction::FloatGt => "float_gt", + BuiltinFunction::FloatIsInf => "float_is_inf", + BuiltinFunction::FloatIsNan => "float_is_nan", + BuiltinFunction::FloatLe => "float_le", + BuiltinFunction::FloatLt => "float_lt", + BuiltinFunction::FloatMod => "float_mod", + BuiltinFunction::FloatMul => "float_mul", + BuiltinFunction::FloatRound => "float_round", + BuiltinFunction::FloatSub => "float_sub", + BuiltinFunction::FloatToBits => "float_to_bits", + BuiltinFunction::FloatToInt => "float_to_int", + BuiltinFunction::FloatToString => "float_to_string", + BuiltinFunction::ChannelReceive => "channel_receive", + BuiltinFunction::ChannelReceiveUntil => "channel_receive_until", + BuiltinFunction::ChannelDrop => "channel_drop", + BuiltinFunction::ChannelWait => "channel_wait", + BuiltinFunction::ChannelNew => "channel_new", + BuiltinFunction::ChannelSend => "channel_send", + BuiltinFunction::ChannelTryReceive => "channel_try_receive", + BuiltinFunction::IntAdd => "int_add", + BuiltinFunction::IntBitAnd => "int_bit_and", + BuiltinFunction::IntBitNot => "int_bit_not", + BuiltinFunction::IntBitOr => "int_bit_or", + BuiltinFunction::IntBitXor => "int_bit_xor", + BuiltinFunction::IntDiv => "int_div", + BuiltinFunction::IntEq => "int_eq", + BuiltinFunction::IntGe => "int_ge", + BuiltinFunction::IntGt => "int_gt", + BuiltinFunction::IntLe => "int_le", + BuiltinFunction::IntLt => "int_lt", + BuiltinFunction::IntRem => "int_rem", + BuiltinFunction::IntMul => "int_mul", + BuiltinFunction::IntPow => "int_pow", + BuiltinFunction::IntRotateLeft => "int_rotate_left", + BuiltinFunction::IntRotateRight => "int_rotate_right", + BuiltinFunction::IntShl => "int_shl", + BuiltinFunction::IntShr => "int_shr", + BuiltinFunction::IntSub => "int_sub", + BuiltinFunction::IntToFloat => "int_to_float", + BuiltinFunction::IntToString => "int_to_string", + BuiltinFunction::IntUnsignedShr => "int_unsigned_shr", + BuiltinFunction::IntWrappingAdd => "int_wrapping_add", + BuiltinFunction::IntWrappingMul => "int_wrapping_mul", + BuiltinFunction::IntWrappingSub => "int_wrapping_sub", + BuiltinFunction::Moved => "moved", + BuiltinFunction::ObjectEq => "object_eq", + BuiltinFunction::Panic => "panic", + BuiltinFunction::PathAccessedAt => "path_accessed_at", + BuiltinFunction::PathCreatedAt => "path_created_at", + BuiltinFunction::PathExists => "path_exists", + BuiltinFunction::PathIsDirectory => "path_is_directory", + BuiltinFunction::PathIsFile => "path_is_file", + BuiltinFunction::PathModifiedAt => "path_modified_at", + BuiltinFunction::PathExpand => "path_expand", + BuiltinFunction::ProcessStackFrameLine => { + "process_stack_frame_line" + } + BuiltinFunction::ProcessStackFrameName => { + "process_stack_frame_name" + } + BuiltinFunction::ProcessStackFramePath => { + "process_stack_frame_path" + } + BuiltinFunction::ProcessStacktrace => "process_stacktrace", + BuiltinFunction::ProcessStacktraceDrop => "process_stacktrace_drop", + BuiltinFunction::ProcessStacktraceLength => { + "process_stacktrace_length" + } + BuiltinFunction::ProcessSuspend => "process_suspend", + BuiltinFunction::RandomBytes => "random_bytes", + BuiltinFunction::RandomDrop => "random_drop", + BuiltinFunction::RandomFloat => "random_float", + BuiltinFunction::RandomFloatRange => "random_float_range", + BuiltinFunction::RandomFromInt => "random_from_int", + BuiltinFunction::RandomInt => "random_int", + BuiltinFunction::RandomIntRange => "random_int_range", + BuiltinFunction::RandomNew => "random_new", + BuiltinFunction::SocketAccept => "socket_accept", + BuiltinFunction::SocketAddressPairAddress => { + "socket_address_pair_address" + } + BuiltinFunction::SocketAddressPairDrop => { + "socket_address_pair_drop" + } + BuiltinFunction::SocketAddressPairPort => { + "socket_address_pair_port" + } + BuiltinFunction::SocketNew => "socket_new", + BuiltinFunction::SocketBind => "socket_bind", + BuiltinFunction::SocketConnect => "socket_connect", + BuiltinFunction::SocketDrop => "socket_drop", + BuiltinFunction::SocketListen => "socket_listen", + BuiltinFunction::SocketLocalAddress => "socket_local_address", + BuiltinFunction::SocketPeerAddress => "socket_peer_address", + BuiltinFunction::SocketRead => "socket_read", + BuiltinFunction::SocketReceiveFrom => "socket_receive_from", + BuiltinFunction::SocketSendBytesTo => "socket_send_bytes_to", + BuiltinFunction::SocketSendStringTo => "socket_send_string_to", + BuiltinFunction::SocketSetBroadcast => "socket_set_broadcast", + BuiltinFunction::SocketSetKeepalive => "socket_set_keepalive", + BuiltinFunction::SocketSetLinger => "socket_set_linger", + BuiltinFunction::SocketSetNodelay => "socket_set_nodelay", + BuiltinFunction::SocketSetOnlyV6 => "socket_set_only_v6", + BuiltinFunction::SocketSetRecvSize => "socket_set_recv_size", + BuiltinFunction::SocketSetReuseAddress => { + "socket_set_reuse_address" + } + BuiltinFunction::SocketSetReusePort => "socket_set_reuse_port", + BuiltinFunction::SocketSetSendSize => "socket_set_send_size", + BuiltinFunction::SocketSetTtl => "socket_set_ttl", + BuiltinFunction::SocketShutdownRead => "socket_shutdown_read", + BuiltinFunction::SocketShutdownReadWrite => { + "socket_shutdown_read_write" + } + BuiltinFunction::SocketShutdownWrite => "socket_shutdown_write", + BuiltinFunction::SocketTryClone => "socket_try_clone", + BuiltinFunction::SocketWriteBytes => "socket_write_bytes", + BuiltinFunction::SocketWriteString => "socket_write_string", + BuiltinFunction::StderrFlush => "stderr_flush", + BuiltinFunction::StderrWriteBytes => "stderr_write_bytes", + BuiltinFunction::StderrWriteString => "stderr_write_string", + BuiltinFunction::StdinRead => "stdin_read", + BuiltinFunction::StdoutFlush => "stdout_flush", + BuiltinFunction::StdoutWriteBytes => "stdout_write_bytes", + BuiltinFunction::StdoutWriteString => "stdout_write_string", + BuiltinFunction::StringByte => "string_byte", + BuiltinFunction::StringCharacters => "string_characters", + BuiltinFunction::StringCharactersDrop => "string_characters_drop", + BuiltinFunction::StringCharactersNext => "string_characters_next", + BuiltinFunction::StringConcat => "string_concat", + BuiltinFunction::StringConcatArray => "string_concat_array", + BuiltinFunction::StringDrop => "string_drop", + BuiltinFunction::StringEq => "string_eq", + BuiltinFunction::StringSize => "string_size", + BuiltinFunction::StringSliceBytes => "string_slice_bytes", + BuiltinFunction::StringToByteArray => "string_to_byte_array", + BuiltinFunction::StringToFloat => "string_to_float", + BuiltinFunction::StringToInt => "string_to_int", + BuiltinFunction::StringToLower => "string_to_lower", + BuiltinFunction::StringToUpper => "string_to_upper", + BuiltinFunction::TimeMonotonic => "time_monotonic", + BuiltinFunction::TimeSystem => "time_system", + BuiltinFunction::TimeSystemOffset => "time_system_offset", + BuiltinFunction::HashKey0 => "hash_key0", + BuiltinFunction::HashKey1 => "hash_key1", + } + } + + pub fn return_type(self) -> TypeRef { + let result = TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new( + ClassId::result(), + ))); - db.builtin_functions.insert(name, func); - BuiltinFunctionId(id) + match self { + BuiltinFunction::ArrayCapacity => TypeRef::int(), + BuiltinFunction::ArrayClear => TypeRef::nil(), + BuiltinFunction::ArrayDrop => TypeRef::nil(), + BuiltinFunction::ArrayGet => TypeRef::Any, + BuiltinFunction::ArrayLength => TypeRef::int(), + BuiltinFunction::ArrayPop => result, + BuiltinFunction::ArrayPush => TypeRef::nil(), + BuiltinFunction::ArrayRemove => TypeRef::Any, + BuiltinFunction::ArrayReserve => TypeRef::nil(), + BuiltinFunction::ArraySet => TypeRef::Any, + BuiltinFunction::ByteArrayNew => TypeRef::byte_array(), + BuiltinFunction::ByteArrayAppend => TypeRef::nil(), + BuiltinFunction::ByteArrayClear => TypeRef::nil(), + BuiltinFunction::ByteArrayClone => TypeRef::byte_array(), + BuiltinFunction::ByteArrayCopyFrom => TypeRef::int(), + BuiltinFunction::ByteArrayDrainToString => TypeRef::string(), + BuiltinFunction::ByteArrayDrop => TypeRef::nil(), + BuiltinFunction::ByteArrayEq => TypeRef::boolean(), + BuiltinFunction::ByteArrayGet => TypeRef::int(), + BuiltinFunction::ByteArrayLength => TypeRef::int(), + BuiltinFunction::ByteArrayPop => TypeRef::int(), + BuiltinFunction::ByteArrayPush => TypeRef::nil(), + BuiltinFunction::ByteArrayRemove => TypeRef::int(), + BuiltinFunction::ByteArrayResize => TypeRef::nil(), + BuiltinFunction::ByteArraySet => TypeRef::int(), + BuiltinFunction::ByteArraySlice => TypeRef::byte_array(), + BuiltinFunction::ByteArrayToString => TypeRef::string(), + BuiltinFunction::ChildProcessDrop => TypeRef::nil(), + BuiltinFunction::ChildProcessSpawn => result, + BuiltinFunction::ChildProcessStderrClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStderrRead => result, + BuiltinFunction::ChildProcessStdinClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStdinFlush => result, + BuiltinFunction::ChildProcessStdinWriteBytes => result, + BuiltinFunction::ChildProcessStdinWriteString => result, + BuiltinFunction::ChildProcessStdoutClose => result, + BuiltinFunction::ChildProcessStdoutRead => result, + BuiltinFunction::ChildProcessTryWait => result, + BuiltinFunction::ChildProcessWait => result, + BuiltinFunction::CpuCores => TypeRef::int(), + BuiltinFunction::DirectoryCreate => result, + BuiltinFunction::DirectoryCreateRecursive => result, + BuiltinFunction::DirectoryList => result, + BuiltinFunction::DirectoryRemove => result, + BuiltinFunction::DirectoryRemoveRecursive => result, + BuiltinFunction::EnvArguments => TypeRef::Any, + BuiltinFunction::EnvExecutable => result, + BuiltinFunction::EnvGet => result, + BuiltinFunction::EnvGetWorkingDirectory => result, + BuiltinFunction::EnvHomeDirectory => result, + BuiltinFunction::EnvSetWorkingDirectory => result, + BuiltinFunction::EnvTempDirectory => TypeRef::string(), + BuiltinFunction::EnvVariables => TypeRef::Any, + BuiltinFunction::Exit => TypeRef::Never, + BuiltinFunction::FileCopy => result, + BuiltinFunction::FileDrop => TypeRef::nil(), + BuiltinFunction::FileFlush => result, + BuiltinFunction::FileOpen => result, + BuiltinFunction::FileRead => result, + BuiltinFunction::FileRemove => result, + BuiltinFunction::FileSeek => result, + BuiltinFunction::FileSize => result, + BuiltinFunction::FileWriteBytes => result, + BuiltinFunction::FileWriteString => result, + BuiltinFunction::FloatAdd => TypeRef::float(), + BuiltinFunction::FloatCeil => TypeRef::float(), + BuiltinFunction::FloatDiv => TypeRef::float(), + BuiltinFunction::FloatEq => TypeRef::boolean(), + BuiltinFunction::FloatFloor => TypeRef::float(), + BuiltinFunction::FloatFromBits => TypeRef::float(), + BuiltinFunction::FloatGe => TypeRef::boolean(), + BuiltinFunction::FloatGt => TypeRef::boolean(), + BuiltinFunction::FloatIsInf => TypeRef::boolean(), + BuiltinFunction::FloatIsNan => TypeRef::boolean(), + BuiltinFunction::FloatLe => TypeRef::boolean(), + BuiltinFunction::FloatLt => TypeRef::boolean(), + BuiltinFunction::FloatMod => TypeRef::float(), + BuiltinFunction::FloatMul => TypeRef::float(), + BuiltinFunction::FloatRound => TypeRef::float(), + BuiltinFunction::FloatSub => TypeRef::float(), + BuiltinFunction::FloatToBits => TypeRef::int(), + BuiltinFunction::FloatToInt => TypeRef::int(), + BuiltinFunction::FloatToString => TypeRef::string(), + BuiltinFunction::ChannelReceive => TypeRef::Any, + BuiltinFunction::ChannelReceiveUntil => result, + BuiltinFunction::ChannelDrop => TypeRef::nil(), + BuiltinFunction::ChannelWait => TypeRef::nil(), + BuiltinFunction::ChannelNew => TypeRef::Any, + BuiltinFunction::ChannelSend => TypeRef::nil(), + BuiltinFunction::ChannelTryReceive => result, + BuiltinFunction::IntAdd => TypeRef::int(), + BuiltinFunction::IntBitAnd => TypeRef::int(), + BuiltinFunction::IntBitNot => TypeRef::int(), + BuiltinFunction::IntBitOr => TypeRef::int(), + BuiltinFunction::IntBitXor => TypeRef::int(), + BuiltinFunction::IntDiv => TypeRef::int(), + BuiltinFunction::IntEq => TypeRef::boolean(), + BuiltinFunction::IntGe => TypeRef::boolean(), + BuiltinFunction::IntGt => TypeRef::boolean(), + BuiltinFunction::IntLe => TypeRef::boolean(), + BuiltinFunction::IntLt => TypeRef::boolean(), + BuiltinFunction::IntRem => TypeRef::int(), + BuiltinFunction::IntMul => TypeRef::int(), + BuiltinFunction::IntPow => TypeRef::int(), + BuiltinFunction::IntRotateLeft => TypeRef::int(), + BuiltinFunction::IntRotateRight => TypeRef::int(), + BuiltinFunction::IntShl => TypeRef::int(), + BuiltinFunction::IntShr => TypeRef::int(), + BuiltinFunction::IntSub => TypeRef::int(), + BuiltinFunction::IntToFloat => TypeRef::float(), + BuiltinFunction::IntToString => TypeRef::string(), + BuiltinFunction::IntUnsignedShr => TypeRef::int(), + BuiltinFunction::IntWrappingAdd => TypeRef::int(), + BuiltinFunction::IntWrappingMul => TypeRef::int(), + BuiltinFunction::IntWrappingSub => TypeRef::int(), + BuiltinFunction::Moved => TypeRef::nil(), + BuiltinFunction::ObjectEq => TypeRef::boolean(), + BuiltinFunction::Panic => TypeRef::Never, + BuiltinFunction::PathAccessedAt => result, + BuiltinFunction::PathCreatedAt => result, + BuiltinFunction::PathExists => TypeRef::boolean(), + BuiltinFunction::PathIsDirectory => TypeRef::boolean(), + BuiltinFunction::PathIsFile => TypeRef::boolean(), + BuiltinFunction::PathModifiedAt => result, + BuiltinFunction::PathExpand => result, + BuiltinFunction::ProcessStackFrameLine => TypeRef::int(), + BuiltinFunction::ProcessStackFrameName => TypeRef::string(), + BuiltinFunction::ProcessStackFramePath => TypeRef::string(), + BuiltinFunction::ProcessStacktrace => TypeRef::Any, + BuiltinFunction::ProcessStacktraceDrop => TypeRef::nil(), + BuiltinFunction::ProcessStacktraceLength => TypeRef::int(), + BuiltinFunction::ProcessSuspend => TypeRef::nil(), + BuiltinFunction::RandomBytes => TypeRef::byte_array(), + BuiltinFunction::RandomDrop => TypeRef::nil(), + BuiltinFunction::RandomFloat => TypeRef::float(), + BuiltinFunction::RandomFloatRange => TypeRef::float(), + BuiltinFunction::RandomFromInt => TypeRef::Any, + BuiltinFunction::RandomInt => TypeRef::int(), + BuiltinFunction::RandomIntRange => TypeRef::int(), + BuiltinFunction::RandomNew => TypeRef::Any, + BuiltinFunction::SocketAccept => result, + BuiltinFunction::SocketAddressPairAddress => TypeRef::string(), + BuiltinFunction::SocketAddressPairDrop => TypeRef::nil(), + BuiltinFunction::SocketAddressPairPort => TypeRef::int(), + BuiltinFunction::SocketBind => result, + BuiltinFunction::SocketConnect => result, + BuiltinFunction::SocketDrop => TypeRef::nil(), + BuiltinFunction::SocketListen => result, + BuiltinFunction::SocketLocalAddress => result, + BuiltinFunction::SocketNew => result, + BuiltinFunction::SocketPeerAddress => result, + BuiltinFunction::SocketRead => result, + BuiltinFunction::SocketReceiveFrom => result, + BuiltinFunction::SocketSendBytesTo => result, + BuiltinFunction::SocketSendStringTo => result, + BuiltinFunction::SocketSetBroadcast => result, + BuiltinFunction::SocketSetKeepalive => result, + BuiltinFunction::SocketSetLinger => result, + BuiltinFunction::SocketSetNodelay => result, + BuiltinFunction::SocketSetOnlyV6 => result, + BuiltinFunction::SocketSetRecvSize => result, + BuiltinFunction::SocketSetReuseAddress => result, + BuiltinFunction::SocketSetReusePort => result, + BuiltinFunction::SocketSetSendSize => result, + BuiltinFunction::SocketSetTtl => result, + BuiltinFunction::SocketShutdownRead => result, + BuiltinFunction::SocketShutdownReadWrite => result, + BuiltinFunction::SocketShutdownWrite => result, + BuiltinFunction::SocketTryClone => result, + BuiltinFunction::SocketWriteBytes => result, + BuiltinFunction::SocketWriteString => result, + BuiltinFunction::StderrFlush => TypeRef::nil(), + BuiltinFunction::StderrWriteBytes => result, + BuiltinFunction::StderrWriteString => result, + BuiltinFunction::StdinRead => result, + BuiltinFunction::StdoutFlush => TypeRef::nil(), + BuiltinFunction::StdoutWriteBytes => result, + BuiltinFunction::StdoutWriteString => result, + BuiltinFunction::StringByte => TypeRef::int(), + BuiltinFunction::StringCharacters => TypeRef::Any, + BuiltinFunction::StringCharactersDrop => TypeRef::nil(), + BuiltinFunction::StringCharactersNext => result, + BuiltinFunction::StringConcat => TypeRef::string(), + BuiltinFunction::StringConcatArray => TypeRef::string(), + BuiltinFunction::StringDrop => TypeRef::nil(), + BuiltinFunction::StringEq => TypeRef::boolean(), + BuiltinFunction::StringSize => TypeRef::int(), + BuiltinFunction::StringSliceBytes => TypeRef::string(), + BuiltinFunction::StringToByteArray => TypeRef::byte_array(), + BuiltinFunction::StringToFloat => result, + BuiltinFunction::StringToInt => result, + BuiltinFunction::StringToLower => TypeRef::string(), + BuiltinFunction::StringToUpper => TypeRef::string(), + BuiltinFunction::TimeMonotonic => TypeRef::int(), + BuiltinFunction::TimeSystem => TypeRef::float(), + BuiltinFunction::TimeSystemOffset => TypeRef::int(), + BuiltinFunction::HashKey0 => TypeRef::int(), + BuiltinFunction::HashKey1 => TypeRef::int(), + } } } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct BuiltinFunctionId(usize); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum BuiltinConstant { + Arch, + Os, + Abi, +} -impl BuiltinFunctionId { - pub fn kind(self, db: &Database) -> BuiltinFunctionKind { - self.get(db).kind +impl BuiltinConstant { + pub fn mapping() -> HashMap { + vec![BuiltinConstant::Arch, BuiltinConstant::Os, BuiltinConstant::Abi] + .into_iter() + .fold(HashMap::new(), |mut map, cons| { + map.insert(cons.name().to_string(), cons); + map + }) } - pub fn return_type(self, db: &Database) -> TypeRef { - self.get(db).return_type - } - - pub fn throw_type(self, db: &Database) -> TypeRef { - self.get(db).throw_type + pub fn name(self) -> &'static str { + match self { + BuiltinConstant::Arch => "_INKO_ARCH", + BuiltinConstant::Os => "_INKO_OS", + BuiltinConstant::Abi => "_INKO_ABI", + } } - fn get(self, db: &Database) -> &BuiltinFunction { - &db.builtin_functions[self.0] + pub fn return_type(self) -> TypeRef { + match self { + BuiltinConstant::Arch => TypeRef::string(), + BuiltinConstant::Os => TypeRef::string(), + BuiltinConstant::Abi => TypeRef::string(), + } } } @@ -2262,21 +2470,8 @@ pub enum MethodSource { /// The method is directly defined for a type. Direct, - /// The method is defined using a regular trait implementation. - Implementation(TraitInstance), - - /// The method is defined using a bounded trait implementation. - BoundedImplementation(TraitInstance), -} - -impl MethodSource { - pub fn implementation(bounded: bool, instance: TraitInstance) -> Self { - if bounded { - Self::BoundedImplementation(instance) - } else { - Self::Implementation(instance) - } - } + /// The method is defined using a trait implementation. + Implementation(TraitInstance, MethodId), } pub enum MethodLookup { @@ -2307,7 +2502,7 @@ pub struct Method { visibility: Visibility, type_parameters: IndexMap, arguments: Arguments, - throw_type: TypeRef, + bounds: TypeBounds, return_type: TypeRef, source: MethodSource, main: bool, @@ -2316,13 +2511,6 @@ pub struct Method { /// `Self`). receiver: TypeRef, - /// The type to use for `Self` in this method. - /// - /// This differs from the receiver type. For example, for a static method - /// the receiver type is the class, while the type of `Self` is an instance - /// of the class. - self_type: Option, - /// The fields this method has access to, along with their types. field_types: HashMap, } @@ -2354,12 +2542,11 @@ impl Method { kind, visibility, type_parameters: IndexMap::new(), + bounds: TypeBounds::new(), arguments: Arguments::new(), - throw_type: TypeRef::Never, return_type: TypeRef::Unknown, source: MethodSource::Direct, receiver: TypeRef::Unknown, - self_type: None, field_types: HashMap::new(), main: false, } @@ -2367,7 +2554,7 @@ impl Method { } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub struct MethodId(usize); +pub struct MethodId(pub usize); impl MethodId { pub fn named_type(self, db: &Database, name: &str) -> Option { @@ -2377,6 +2564,10 @@ impl MethodId { .map(|&id| Symbol::TypeParameter(id)) } + pub fn type_parameters(self, db: &Database) -> Vec { + self.get(db).type_parameters.values().clone() + } + pub fn new_type_parameter( self, db: &mut Database, @@ -2392,18 +2583,12 @@ impl MethodId { self.get_mut(db).receiver = receiver; } - pub fn set_self_type(self, db: &mut Database, typ: TypeId) { - self.get_mut(db).self_type = Some(typ); - } - pub fn receiver(self, db: &Database) -> TypeRef { self.get(db).receiver } - pub fn self_type(self, db: &Database) -> TypeId { - self.get(db).self_type.expect( - "The method's Self type must be set before it can be obtained", - ) + pub fn receiver_id(self, db: &Database) -> TypeId { + self.get(db).receiver.type_id(db).unwrap() } pub fn source(self, db: &Database) -> MethodSource { @@ -2414,94 +2599,6 @@ impl MethodId { self.get_mut(db).source = source; } - pub fn type_check( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db); - let theirs = with.get(db); - - if ours.kind != theirs.kind { - return false; - } - - if ours.visibility != theirs.visibility { - return false; - } - - if ours.name != theirs.name { - return false; - } - - // These checks are performed in separate methods so we can avoid - // borrowing conflicts of the type database. - self.type_check_type_parameters(db, with, context) - && self.type_check_arguments(db, with, context) - && self.type_check_throw_type(db, with, context) - && self.type_check_return_type(db, with, context) - } - - fn type_check_type_parameters( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db); - let theirs = with.get(db); - - if ours.type_parameters.len() != theirs.type_parameters.len() { - return false; - } - - ours.type_parameters - .values() - .clone() - .into_iter() - .zip(theirs.type_parameters.values().clone().into_iter()) - .all(|(ours, theirs)| { - ours.type_check_with_type_parameter(db, theirs, context, false) - }) - } - - fn type_check_arguments( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).arguments.clone(); - let theirs = with.get(db).arguments.clone(); - - ours.type_check(db, &theirs, context, true) - } - - fn type_check_return_type( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).return_type; - let theirs = with.get(db).return_type; - - ours.type_check(db, theirs, context, true) - } - - fn type_check_throw_type( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).throw_type; - let theirs = with.get(db).throw_type; - - ours.type_check(db, theirs, context, true) - } - pub fn name(self, db: &Database) -> &String { &self.get(db).name } @@ -2613,7 +2710,7 @@ impl MethodId { self.get(db).field_types.values().cloned().collect() } - pub fn add_argument(&self, db: &mut Database, argument: Argument) { + pub fn add_argument(self, db: &mut Database, argument: Argument) { self.get_mut(db).arguments.new_argument( argument.name.clone(), argument.value_type, @@ -2621,14 +2718,22 @@ impl MethodId { ); } - pub fn set_main(&self, db: &mut Database) { + pub fn set_main(self, db: &mut Database) { self.get_mut(db).main = true; } - pub fn is_main(&self, db: &Database) -> bool { + pub fn is_main(self, db: &Database) -> bool { self.get(db).main } + pub fn bounds(self, db: &Database) -> &TypeBounds { + &self.get(db).bounds + } + + pub fn set_bounds(self, db: &mut Database, bounds: TypeBounds) { + self.get_mut(db).bounds = bounds; + } + fn get(self, db: &Database) -> &Method { &db.methods[self.0] } @@ -2652,68 +2757,15 @@ impl Block for MethodId { var } - fn set_throw_type(&self, db: &mut Database, typ: TypeRef) { - self.get_mut(db).throw_type = typ; - } - fn set_return_type(&self, db: &mut Database, typ: TypeRef) { self.get_mut(db).return_type = typ; } - fn throw_type(&self, db: &Database) -> TypeRef { - self.get(db).throw_type - } - fn return_type(&self, db: &Database) -> TypeRef { self.get(db).return_type } } -impl FormatType for MethodId { - fn format_type(&self, buffer: &mut TypeFormatter) { - let block = self.get(buffer.db); - - buffer.write("fn "); - - if block.visibility == Visibility::Public { - buffer.write("pub "); - } - - match block.kind { - MethodKind::Async => buffer.write("async "), - MethodKind::AsyncMutable => buffer.write("async mut "), - MethodKind::Static => buffer.write("static "), - MethodKind::Moving => buffer.write("move "), - MethodKind::Mutable | MethodKind::Destructor => { - buffer.write("mut ") - } - _ => {} - } - - buffer.write(&block.name); - - if block.type_parameters.len() > 0 { - buffer.write(" ["); - - for (index, param) in - block.type_parameters.values().iter().enumerate() - { - if index > 0 { - buffer.write(", "); - } - - param.format_type(buffer); - } - - buffer.write("]"); - } - - buffer.arguments(&block.arguments, true); - buffer.throw_type(block.throw_type); - buffer.return_type(block.return_type); - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum Receiver { /// The receiver is explicit (e.g. `foo.bar()`) @@ -2753,7 +2805,6 @@ pub struct CallInfo { pub id: MethodId, pub receiver: Receiver, pub returns: TypeRef, - pub throws: TypeRef, pub dynamic: bool, } @@ -2762,18 +2813,17 @@ pub struct ClosureCallInfo { pub id: ClosureId, pub expected_arguments: Vec, pub returns: TypeRef, - pub throws: TypeRef, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct BuiltinCallInfo { - pub id: BuiltinFunctionId, + pub id: BuiltinFunction, pub returns: TypeRef, - pub throws: TypeRef, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct FieldInfo { + pub class: ClassId, pub id: FieldId, pub variable_type: TypeRef, } @@ -2782,7 +2832,7 @@ pub struct FieldInfo { pub enum CallKind { Unknown, Call(CallInfo), - ClosureCall(ClosureCallInfo), + CallClosure(ClosureCallInfo), GetField(FieldInfo), SetField(FieldInfo), } @@ -2799,6 +2849,7 @@ pub enum IdentifierKind { pub enum ConstantKind { Unknown, Constant(ConstantId), + Builtin(BuiltinConstant), Method(CallInfo), } @@ -2810,6 +2861,40 @@ pub enum ConstantPatternKind { Int(ConstantId), } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ThrowKind { + Unknown, + Option(TypeRef), + Result(TypeRef, TypeRef), +} + +impl ThrowKind { + pub fn throw_type_name(self, db: &Database, ok: TypeRef) -> String { + match self { + ThrowKind::Unknown => "?".to_string(), + ThrowKind::Option(_) => { + format!("Option[{}]", format::format_type(db, ok)) + } + ThrowKind::Result(_, err) => { + format!( + "Result[{}, {}]", + format::format_type(db, ok), + format::format_type(db, err) + ) + } + } + } + + pub fn as_uni(self, db: &Database) -> ThrowKind { + match self { + ThrowKind::Result(ok, err) if err.is_owned(db) => { + ThrowKind::Result(ok, err.as_uni(db)) + } + kind => kind, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Symbol { Class(ClassId), @@ -2867,7 +2952,7 @@ impl Module { let class_id = Class::alloc( db, name.to_string(), - ClassKind::Regular, + ClassKind::Module, Visibility::Private, id, ); @@ -2945,12 +3030,6 @@ impl ModuleId { } } -impl FormatType for ModuleId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.write(&self.get(buffer.db).name.to_string()); - } -} - /// A local variable. pub struct Variable { /// The user-defined name of the variable. @@ -3003,7 +3082,6 @@ impl VariableId { /// Unlike variables, constants can't be assigned new values. They are also /// limited to values of a select few types. pub struct Constant { - /// The ID of the constant local to its module. id: u16, module: ModuleId, name: String, @@ -3091,7 +3169,6 @@ pub struct Closure { /// The type of `self` as captured by the closure. captured_self_type: Option, arguments: Arguments, - throw_type: TypeRef, return_type: TypeRef, } @@ -3102,7 +3179,7 @@ impl Closure { Self::add(db, closure) } - fn add(db: &mut Database, closure: Closure) -> ClosureId { + pub(crate) fn add(db: &mut Database, closure: Closure) -> ClosureId { let id = db.closures.len(); db.closures.push(closure); @@ -3115,7 +3192,6 @@ impl Closure { captured_self_type: None, captured: HashSet::new(), arguments: Arguments::new(), - throw_type: TypeRef::Never, return_type: TypeRef::Unknown, } } @@ -3183,8 +3259,13 @@ impl ClosureId { pub fn can_infer_as_uni(self, db: &Database) -> bool { let closure = self.get(db); + let allow_captures = if closure.moving { + closure.captured.iter().all(|v| v.value_type(db).is_sendable(db)) + } else { + closure.captured.is_empty() + }; - if !closure.captured.is_empty() { + if !allow_captures { return false; } @@ -3195,110 +3276,13 @@ impl ClosureId { } } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::Closure(with) => { - self.type_check_arguments(db, with, context) - && self.type_check_throw_type(db, with, context, subtyping) - && self.type_check_return_type(db, with, context, subtyping) - } - // Implementing traits for closures with a specific signature (e.g. - // `impl ToString for do -> X`) isn't supported. Even if it was, it - // probably wouldn't be useful. Implementing traits for all closures - // isn't supported either, again because it isn't really useful. - // - // For this reason, we only consider a closures compatible with a - // type parameter if the parameter has no requirements. This still - // allows you to e.g. put a bunch of lambdas in an Array. - TypeId::TypeParameter(id) => id.get(db).requirements.is_empty(), - _ => false, - } - } - - fn type_check_arguments( - self, - db: &mut Database, - with: ClosureId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).arguments.clone(); - let theirs = with.get(db).arguments.clone(); - - ours.type_check(db, &theirs, context, false) - } - - fn type_check_return_type( - self, - db: &mut Database, - with: ClosureId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - let ours = self.get(db).return_type; - let theirs = with.get(db).return_type; - - ours.type_check(db, theirs, context, subtyping) - } - - fn type_check_throw_type( - self, - db: &mut Database, - with: ClosureId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - let ours = self.get(db).throw_type; - let theirs = with.get(db).throw_type; - - ours.type_check(db, theirs, context, subtyping) - } - - fn get(self, db: &Database) -> &Closure { + pub(crate) fn get(self, db: &Database) -> &Closure { &db.closures[self.0] } fn get_mut(self, db: &mut Database) -> &mut Closure { &mut db.closures[self.0] } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - let mut new_func = self.get(db).clone(); - - for arg in new_func.arguments.mapping.values_mut() { - arg.value_type = arg.value_type.as_rigid_type(db, bounds); - } - - new_func.throw_type = new_func.throw_type.as_rigid_type(db, bounds); - new_func.return_type = new_func.return_type.as_rigid_type(db, bounds); - - Closure::add(db, new_func) - } - - fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> Self { - let mut new_func = self.get(db).clone(); - - for arg in new_func.arguments.mapping.values_mut() { - arg.value_type = arg.value_type.inferred(db, context, immutable); - } - - new_func.throw_type = - new_func.throw_type.inferred(db, context, immutable); - new_func.return_type = - new_func.return_type.inferred(db, context, immutable); - - Closure::add(db, new_func) - } } impl Block for ClosureId { @@ -3315,41 +3299,15 @@ impl Block for ClosureId { var } - fn set_throw_type(&self, db: &mut Database, typ: TypeRef) { - self.get_mut(db).throw_type = typ; - } - fn set_return_type(&self, db: &mut Database, typ: TypeRef) { self.get_mut(db).return_type = typ; } - fn throw_type(&self, db: &Database) -> TypeRef { - self.get(db).throw_type - } - fn return_type(&self, db: &Database) -> TypeRef { self.get(db).return_type } } -impl FormatType for ClosureId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.descend(|buffer| { - let fun = self.get(buffer.db); - - if fun.moving { - buffer.write("fn move"); - } else { - buffer.write("fn"); - } - - buffer.arguments(&fun.arguments, false); - buffer.throw_type(fun.throw_type); - buffer.return_type(fun.return_type); - }); - } -} - /// A reference to a type. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum TypeRef { @@ -3380,8 +3338,7 @@ pub enum TypeRef { /// A type that signals something never happens. /// - /// When used as a return type, it means a method never returns. When used - /// as an (explicit) throw type, it means the method never throws. + /// When used as a return type, it means a method never returns. Never, /// A value that could be anything _including_ non-managed objects. @@ -3399,18 +3356,6 @@ pub enum TypeRef { /// transferred. RefAny, - /// The `Self` type. - OwnedSelf, - - /// The `uni Self` type. - UniSelf, - - /// The `ref Self` type. - RefSelf, - - /// The `mut Self` type. - MutSelf, - /// A value indicating a typing error. /// /// This type is produced whenever a type couldn't be produced, for example @@ -3427,38 +3372,6 @@ pub enum TypeRef { } impl TypeRef { - fn mut_or_ref(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::Ref(id) - } else { - TypeRef::Mut(id) - } - } - - fn mut_or_ref_uni(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::RefUni(id) - } else { - TypeRef::MutUni(id) - } - } - - fn owned_or_ref(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::Ref(id) - } else { - TypeRef::Owned(id) - } - } - - fn uni_or_ref(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::RefUni(id) - } else { - TypeRef::Uni(id) - } - } - pub fn nil() -> TypeRef { TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(ClassId( NIL_ID, @@ -3495,33 +3408,18 @@ impl TypeRef { ))) } - pub fn array(db: &mut Database, value: TypeRef) -> TypeRef { - let array_class = ClassId::array(); - let mut arguments = TypeArguments::new(); - let param = array_class.type_parameters(db)[0]; - - arguments.assign(param, value); - - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::generic( - db, - array_class, - arguments, - ))) - } - pub fn module(id: ModuleId) -> TypeRef { TypeRef::Owned(TypeId::Module(id)) } - pub fn placeholder(db: &mut Database) -> TypeRef { - TypeRef::Placeholder(TypePlaceholder::alloc(db)) + pub fn placeholder( + db: &mut Database, + required: Option, + ) -> TypeRef { + TypeRef::Placeholder(TypePlaceholder::alloc(db, required)) } - pub fn type_id( - self, - db: &Database, - self_type: TypeId, - ) -> Result { + pub fn type_id(self, db: &Database) -> Result { match self { TypeRef::Owned(id) | TypeRef::Uni(id) @@ -3530,23 +3428,15 @@ impl TypeRef { | TypeRef::RefUni(id) | TypeRef::MutUni(id) | TypeRef::Infer(id) => Ok(id), - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => Ok(self_type), TypeRef::Placeholder(id) => { - id.value(db).ok_or(self).and_then(|t| t.type_id(db, self_type)) + id.value(db).ok_or(self).and_then(|t| t.type_id(db)) } _ => Err(self), } } - pub fn closure_id( - self, - db: &Database, - self_type: TypeId, - ) -> Option { - if let Ok(TypeId::Closure(id)) = self.type_id(db, self_type) { + pub fn closure_id(self, db: &Database) -> Option { + if let Ok(TypeId::Closure(id)) = self.type_id(db) { Some(id) } else { None @@ -3608,8 +3498,6 @@ impl TypeRef { TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::Infer(_) - | TypeRef::UniSelf - | TypeRef::OwnedSelf | TypeRef::Any => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_owned_or_uni(db)) @@ -3620,10 +3508,7 @@ impl TypeRef { pub fn is_owned(self, db: &Database) -> bool { match self { - TypeRef::Owned(_) - | TypeRef::Infer(_) - | TypeRef::OwnedSelf - | TypeRef::Any => true, + TypeRef::Owned(_) | TypeRef::Infer(_) | TypeRef::Any => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_owned(db)) } @@ -3669,19 +3554,50 @@ impl TypeRef { } } - pub fn is_self_type(self, db: &Database) -> bool { + pub fn is_generic(self, db: &Database) -> bool { match self { - TypeRef::OwnedSelf | TypeRef::MutSelf | TypeRef::RefSelf => true, + TypeRef::Owned(TypeId::TraitInstance(ins)) + | TypeRef::Uni(TypeId::TraitInstance(ins)) + | TypeRef::Ref(TypeId::TraitInstance(ins)) + | TypeRef::Mut(TypeId::TraitInstance(ins)) + | TypeRef::RefUni(TypeId::TraitInstance(ins)) + | TypeRef::MutUni(TypeId::TraitInstance(ins)) => { + ins.instance_of.is_generic(db) + } + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Uni(TypeId::ClassInstance(ins)) + | TypeRef::Ref(TypeId::ClassInstance(ins)) + | TypeRef::Mut(TypeId::ClassInstance(ins)) + | TypeRef::RefUni(TypeId::ClassInstance(ins)) + | TypeRef::MutUni(TypeId::ClassInstance(ins)) => { + ins.instance_of.is_generic(db) + } TypeRef::Placeholder(id) => { - id.value(db).map_or(false, |v| v.is_self_type(db)) + id.value(db).map_or(false, |v| v.is_generic(db)) } _ => false, } } + pub fn type_arguments(self, db: &Database) -> TypeArguments { + match self.type_id(db) { + Ok(TypeId::TraitInstance(ins)) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db).clone() + } + Ok(TypeId::ClassInstance(ins)) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db).clone() + } + _ => TypeArguments::new(), + } + } + pub fn is_uni(self, db: &Database) -> bool { match self { - TypeRef::Uni(_) | TypeRef::UniSelf => true, + TypeRef::Uni(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_uni(db)) } @@ -3691,10 +3607,7 @@ impl TypeRef { pub fn require_sendable_arguments(self, db: &Database) -> bool { match self { - TypeRef::Uni(_) - | TypeRef::RefUni(_) - | TypeRef::MutUni(_) - | TypeRef::UniSelf => true, + TypeRef::Uni(_) | TypeRef::RefUni(_) | TypeRef::MutUni(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.require_sendable_arguments(db)) } @@ -3704,7 +3617,7 @@ impl TypeRef { pub fn is_ref(self, db: &Database) -> bool { match self { - TypeRef::Ref(_) | TypeRef::RefSelf => true, + TypeRef::Ref(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_ref(db)) } @@ -3714,7 +3627,7 @@ impl TypeRef { pub fn is_mut(self, db: &Database) -> bool { match self { - TypeRef::Mut(_) | TypeRef::MutSelf => true, + TypeRef::Mut(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_ref(db)) } @@ -3722,12 +3635,26 @@ impl TypeRef { } } + pub fn is_mutable(self, db: &Database) -> bool { + match self { + TypeRef::Owned(_) + | TypeRef::Uni(_) + | TypeRef::Mut(_) + | TypeRef::Infer(_) + | TypeRef::Error + | TypeRef::Unknown + | TypeRef::Never => true, + TypeRef::Placeholder(id) => { + id.value(db).map_or(true, |v| v.is_mutable(db)) + } + _ => false, + } + } + pub fn use_reference_counting(self, db: &Database) -> bool { match self { TypeRef::Ref(_) - | TypeRef::RefSelf | TypeRef::Mut(_) - | TypeRef::MutSelf | TypeRef::RefUni(_) | TypeRef::MutUni(_) => true, TypeRef::Placeholder(id) => { @@ -3737,38 +3664,34 @@ impl TypeRef { } } - pub fn use_atomic_reference_counting( - self, - db: &Database, - self_type: TypeId, - ) -> bool { - self.class_id(db, self_type) - .map_or(false, |id| id.0 == STRING_ID || id.kind(db).is_async()) + pub fn use_atomic_reference_counting(self, db: &Database) -> bool { + self.class_id(db).map_or(false, |id| id.is_atomic(db)) + } + + pub fn is_bool(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::boolean()) } - pub fn is_bool(self, db: &Database, self_type: TypeId) -> bool { - self.is_instance_of(db, ClassId::boolean(), self_type) + pub fn is_int(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::int()) } - pub fn is_string(self, db: &Database, self_type: TypeId) -> bool { - self.is_instance_of(db, ClassId::string(), self_type) + pub fn is_string(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::string()) } - pub fn is_nil(self, db: &Database, self_type: TypeId) -> bool { - self.is_instance_of(db, ClassId::nil(), self_type) + pub fn is_nil(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::nil()) } pub fn allow_moving(self) -> bool { - matches!(self, TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::OwnedSelf) + matches!(self, TypeRef::Owned(_) | TypeRef::Uni(_)) } pub fn allow_mutating(self) -> bool { matches!( self, TypeRef::Mut(_) - | TypeRef::MutSelf - | TypeRef::OwnedSelf - | TypeRef::UniSelf | TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::MutUni(_) @@ -3791,10 +3714,7 @@ impl TypeRef { } match self { - TypeRef::Uni(_) - | TypeRef::UniSelf - | TypeRef::Never - | TypeRef::Error => true, + TypeRef::Uni(_) | TypeRef::Never | TypeRef::Error => true, TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.is_sendable(db)) } @@ -3809,7 +3729,6 @@ impl TypeRef { match self { TypeRef::Uni(_) - | TypeRef::UniSelf | TypeRef::Never | TypeRef::Any | TypeRef::Error => true, @@ -3838,10 +3757,6 @@ impl TypeRef { } } - pub fn is_async(self, db: &Database, self_type: TypeId) -> bool { - self.class_id(db, self_type).map_or(false, |id| id.kind(db).is_async()) - } - pub fn cast_according_to(self, other: Self, db: &Database) -> Self { if other.is_uni(db) && self.is_value_type(db) { self.as_uni(db) @@ -3873,9 +3788,6 @@ impl TypeRef { TypeRef::Ref(id) } TypeRef::Uni(id) => TypeRef::RefUni(id), - TypeRef::OwnedSelf => TypeRef::RefSelf, - TypeRef::MutSelf => TypeRef::RefSelf, - TypeRef::UniSelf => TypeRef::RefSelf, TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_ref(db)) } @@ -3883,23 +3795,52 @@ impl TypeRef { } } - pub fn as_mut(self, db: &Database) -> Self { + pub fn allow_as_ref(self, db: &Database) -> bool { match self { - TypeRef::Owned(id) | TypeRef::Infer(id) => TypeRef::Mut(id), - TypeRef::Uni(id) => TypeRef::MutUni(id), - TypeRef::OwnedSelf | TypeRef::UniSelf => TypeRef::MutSelf, + TypeRef::Any => true, + TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Ref(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(self, |v| v.as_mut(db)) + id.value(db).map_or(false, |v| v.allow_as_ref(db)) } - _ => self, + _ => false, } } - pub fn as_ref_uni(self, db: &Database) -> Self { + pub fn allow_as_mut(self, db: &Database) -> bool { match self { - TypeRef::Uni(id) => TypeRef::RefUni(id), + TypeRef::Any => true, + TypeRef::Owned(TypeId::RigidTypeParameter(id)) => id.is_mutable(db), + TypeRef::Owned(_) | TypeRef::Mut(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(self, |v| v.as_ref_uni(db)) + id.value(db).map_or(false, |v| v.allow_as_mut(db)) + } + _ => false, + } + } + + pub fn as_mut(self, db: &Database) -> Self { + match self { + TypeRef::Owned(TypeId::RigidTypeParameter(id)) => { + if id.is_mutable(db) { + TypeRef::Mut(TypeId::RigidTypeParameter(id)) + } else { + self + } + } + TypeRef::Owned(id) => TypeRef::Mut(id), + TypeRef::Uni(id) => TypeRef::MutUni(id), + TypeRef::Placeholder(id) => { + id.value(db).map_or(self, |v| v.as_mut(db)) + } + _ => self, + } + } + + pub fn as_ref_uni(self, db: &Database) -> Self { + match self { + TypeRef::Uni(id) => TypeRef::RefUni(id), + TypeRef::Placeholder(id) => { + id.value(db).map_or(self, |v| v.as_ref_uni(db)) } _ => self, } @@ -3922,7 +3863,6 @@ impl TypeRef { | TypeRef::Uni(id) | TypeRef::Mut(id) | TypeRef::Ref(id) => TypeRef::Uni(id), - TypeRef::OwnedSelf | TypeRef::UniSelf => TypeRef::UniSelf, TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_uni(db)) } @@ -3937,9 +3877,6 @@ impl TypeRef { | TypeRef::Mut(id) | TypeRef::RefUni(id) | TypeRef::MutUni(id) => TypeRef::Owned(id), - TypeRef::UniSelf | TypeRef::MutSelf | TypeRef::RefSelf => { - TypeRef::OwnedSelf - } TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_owned(db)) } @@ -3947,192 +3884,7 @@ impl TypeRef { } } - /// Replaces temporary types with their inferred types. - pub fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> TypeRef { - if context.depth == MAX_TYPE_DEPTH { - return TypeRef::Unknown; - } - - context.depth += 1; - - let result = match self { - TypeRef::OwnedSelf => TypeRef::owned_or_ref( - self.infer_self_type_id(db, context), - immutable, - ), - TypeRef::RefSelf => { - TypeRef::Ref(self.infer_self_type_id(db, context)) - } - TypeRef::MutSelf => TypeRef::mut_or_ref( - self.infer_self_type_id(db, context), - immutable, - ), - TypeRef::UniSelf => TypeRef::uni_or_ref( - self.infer_self_type_id(db, context), - immutable, - ), - // Owned and Infer variants are treated the same, because: - // - // 1. For non-type parameters, Infer(T) is the same as Owned(T) - // (simply because we never use Infer for anything but type - // parameters). - // 2. For a type parameter, in both cases we can just use the - // assigned value if there is any, as said value determines the - // ownership. In case of an owned parameter non-owned values - // can't be assigned to it anyway. - TypeRef::Owned(id) | TypeRef::Infer(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::owned_or_ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::owned_or_ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref(db) - } else { - typ - } - } - TypeId::Closure(fid) => TypeRef::owned_or_ref( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::Uni(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::uni_or_ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::uni_or_ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref(db) - } else { - typ - } - } - TypeId::Closure(fid) => TypeRef::uni_or_ref( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::Ref(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::Ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - ), - TypeId::TraitInstance(tid) => TypeRef::Ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - ), - TypeId::TypeParameter(pid) => self - .infer_type_parameter(pid, db, context, immutable) - .as_ref(db), - TypeId::Closure(fid) => TypeRef::Ref(TypeId::Closure( - fid.inferred(db, context, immutable), - )), - _ => self, - }, - TypeRef::RefUni(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::RefUni( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - ), - TypeId::TraitInstance(tid) => TypeRef::RefUni( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - ), - TypeId::TypeParameter(pid) => self - .infer_type_parameter(pid, db, context, immutable) - .as_ref_uni(db), - TypeId::Closure(fid) => TypeRef::RefUni(TypeId::Closure( - fid.inferred(db, context, immutable), - )), - _ => self, - }, - TypeRef::Mut(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::mut_or_ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::mut_or_ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref(db) - } else { - typ.as_mut(db) - } - } - TypeId::Closure(fid) => TypeRef::mut_or_ref( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::MutUni(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::mut_or_ref_uni( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::mut_or_ref_uni( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref_uni(db) - } else { - typ.as_mut_uni(db) - } - } - TypeId::Closure(fid) => TypeRef::mut_or_ref_uni( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::Placeholder(id) => id - .value(db) - .map(|t| t.inferred(db, context, immutable)) - .unwrap_or_else( - || if immutable { self.as_ref(db) } else { self }, - ), - _ => self, - }; - - context.depth -= 1; - result - } - - pub fn as_enum_instance( - self, - db: &Database, - self_type: TypeId, - ) -> Option { + pub fn as_enum_instance(self, db: &Database) -> Option { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) @@ -4142,17 +3894,6 @@ impl TypeRef { { Some(ins) } - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => match self_type { - TypeId::ClassInstance(ins) - if ins.instance_of.kind(db).is_enum() => - { - Some(ins) - } - _ => None, - }, _ => None, } } @@ -4198,102 +3939,19 @@ impl TypeRef { } } - fn is_regular_type_parameter(self) -> bool { - matches!( - self, - TypeRef::Owned(TypeId::TypeParameter(_)) - | TypeRef::Uni(TypeId::TypeParameter(_)) - | TypeRef::Ref(TypeId::TypeParameter(_)) - | TypeRef::Mut(TypeId::TypeParameter(_)) - | TypeRef::Infer(TypeId::TypeParameter(_)) - | TypeRef::RefUni(TypeId::TypeParameter(_)) - | TypeRef::MutUni(TypeId::TypeParameter(_)) - ) - } - - pub fn is_compatible_with_type_parameter( - self, - db: &mut Database, - parameter: TypeParameterId, - context: &mut TypeContext, - ) -> bool { - parameter - .requirements(db) - .into_iter() - .all(|r| self.implements_trait_instance(db, r, context)) - } - - pub fn allow_cast_to( - self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - ) -> bool { - // Casting to/from Any is dangerous but necessary to make the standard - // library work. - if self == TypeRef::Any || with == TypeRef::Any { - return true; - } - - self.type_check_directly(db, with, context, true) - } - pub fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - match self { - TypeRef::Owned(id) => TypeRef::Owned(id.as_rigid_type(db, bounds)), - TypeRef::Uni(id) => TypeRef::Uni(id.as_rigid_type(db, bounds)), - TypeRef::Ref(id) => TypeRef::Ref(id.as_rigid_type(db, bounds)), - TypeRef::Mut(id) => TypeRef::Mut(id.as_rigid_type(db, bounds)), - TypeRef::Infer(id) => TypeRef::Owned(id.as_rigid_type(db, bounds)), - _ => self, - } - } - - pub fn implements_trait_instance( - self, - db: &mut Database, - trait_type: TraitInstance, - context: &mut TypeContext, - ) -> bool { - match self { - TypeRef::Any => false, - TypeRef::Error => true, - TypeRef::Never => true, - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => context - .self_type - .implements_trait_instance(db, trait_type, context), - TypeRef::Owned(id) - | TypeRef::Uni(id) - | TypeRef::Ref(id) - | TypeRef::Mut(id) - | TypeRef::Infer(id) => { - id.implements_trait_instance(db, trait_type, context) - } - TypeRef::Placeholder(id) => id.value(db).map_or(true, |v| { - v.implements_trait_instance(db, trait_type, context) - }), - _ => false, - } + TypeResolver::new(db, &TypeArguments::new(), bounds) + .with_rigid(true) + .resolve(self) } pub fn is_value_type(self, db: &Database) -> bool { match self { - TypeRef::Owned(TypeId::ClassInstance(ins)) - if ins.instance_of.kind(db).is_async() => - { - true - } TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Ref(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { - matches!( - ins.instance_of.0, - INT_ID | FLOAT_ID | STRING_ID | BOOLEAN_ID | NIL_ID - ) + ins.instance_of().get(db).value_type } TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.is_value_type(db)) @@ -4308,7 +3966,8 @@ impl TypeRef { | TypeRef::Ref(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { - matches!(ins.instance_of.0, BOOLEAN_ID | NIL_ID) + ins.instance_of.kind(db).is_extern() + || matches!(ins.instance_of.0, BOOLEAN_ID | NIL_ID) } TypeRef::Owned(TypeId::Module(_)) => true, TypeRef::Owned(TypeId::Class(_)) => true, @@ -4330,682 +3989,181 @@ impl TypeRef { | TypeRef::Mut(id) | TypeRef::RefUni(id) | TypeRef::MutUni(id) - | TypeRef::Infer(id) => { - let args = match id { - TypeId::ClassInstance(ins) - if ins.instance_of.is_generic(db) => - { - ins.type_arguments(db) - } - TypeId::TraitInstance(ins) - if ins.instance_of.is_generic(db) => - { - ins.type_arguments(db) - } - _ => return true, - }; - - args.mapping.values().all(|v| v.is_inferred(db)) + | TypeRef::Infer(id) => match id { + TypeId::ClassInstance(ins) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db) + .mapping + .values() + .all(|v| v.is_inferred(db)) + } + TypeId::TraitInstance(ins) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db) + .mapping + .values() + .all(|v| v.is_inferred(db)) + } + TypeId::Closure(id) => { + id.arguments(db) + .into_iter() + .all(|arg| arg.value_type.is_inferred(db)) + && id.return_type(db).is_inferred(db) + } + _ => true, + }, + TypeRef::Placeholder(id) => { + id.value(db).map_or(false, |v| v.is_inferred(db)) } - TypeRef::Placeholder(id) => id.value(db).is_some(), _ => true, } } - pub fn implements_trait_id( - self, - db: &Database, - trait_id: TraitId, - self_type: TypeId, - ) -> bool { + pub fn class_id(self, db: &Database) -> Option { match self { - TypeRef::Any => false, - TypeRef::Error => false, - TypeRef::Never => false, - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => self_type.implements_trait_id(db, trait_id), - TypeRef::Owned(id) - | TypeRef::Uni(id) - | TypeRef::Ref(id) - | TypeRef::Mut(id) - | TypeRef::Infer(id) => id.implements_trait_id(db, trait_id), - _ => false, + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Uni(TypeId::ClassInstance(ins)) + | TypeRef::Ref(TypeId::ClassInstance(ins)) + | TypeRef::Mut(TypeId::ClassInstance(ins)) => Some(ins.instance_of), + TypeRef::Placeholder(p) => p.value(db).and_then(|v| v.class_id(db)), + _ => None, } } - pub fn class_id(self, db: &Database, self_type: TypeId) -> Option { + pub fn throw_kind(self, db: &Database) -> ThrowKind { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) | TypeRef::Ref(TypeId::ClassInstance(ins)) - | TypeRef::Mut(TypeId::ClassInstance(ins)) => Some(ins.instance_of), - TypeRef::OwnedSelf | TypeRef::RefSelf | TypeRef::MutSelf => { - match self_type { - TypeId::ClassInstance(ins) => Some(ins.instance_of), - _ => None, + | TypeRef::Mut(TypeId::ClassInstance(ins)) => { + let opt_class = db.class_in_module(OPTION_MODULE, OPTION_CLASS); + let res_class = db.class_in_module(RESULT_MODULE, RESULT_CLASS); + let params = ins.instance_of.type_parameters(db); + + if ins.instance_of == res_class { + let args = ins.type_arguments(db); + let ok = args.get(params[0]).unwrap(); + let err = args.get(params[1]).unwrap(); + + ThrowKind::Result(ok, err) + } else if ins.instance_of == opt_class { + let args = ins.type_arguments(db); + let some = args.get(params[0]).unwrap(); + + ThrowKind::Option(some) + } else { + ThrowKind::Unknown } } TypeRef::Placeholder(p) => { - p.value(db).and_then(|v| v.class_id(db, self_type)) + p.value(db).map_or(ThrowKind::Unknown, |v| v.throw_kind(db)) } - _ => None, + _ => ThrowKind::Unknown, } } - pub fn type_check( - self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - // We special-case type parameters on the right-hand side here, that way - // we don't need to cover this case for all the various TypeRef variants - // individually. - match with { - TypeRef::Owned(TypeId::TypeParameter(pid)) - | TypeRef::Uni(TypeId::TypeParameter(pid)) - | TypeRef::Infer(TypeId::TypeParameter(pid)) - | TypeRef::RefUni(TypeId::TypeParameter(pid)) - | TypeRef::MutUni(TypeId::TypeParameter(pid)) - | TypeRef::Mut(TypeId::TypeParameter(pid)) - | TypeRef::Ref(TypeId::TypeParameter(pid)) => self - .type_check_with_type_parameter( - db, with, pid, context, subtyping, - ), - TypeRef::Placeholder(id) => { - if let Some(assigned) = id.value(db) { - self.type_check_directly(db, assigned, context, subtyping) - } else if let TypeRef::Placeholder(ours) = self { - // Assigning a placeholder to an unassigned placeholder - // isn't useful, and can break type inference when returning - // empty generic types in e.g. a closure (as this will - // compare them to a type placeholder). - // - // Instead, we track our placeholder in the one we're - // comparing with, ensuring our placeholder is also assigned - // when the one we're comparing with is assigned a value. - id.add_depending(db, ours); - true - } else { - id.assign(db, self); - true - } - } - _ => self.type_check_directly(db, with, context, subtyping), - } + fn is_instance_of(self, db: &Database, id: ClassId) -> bool { + self.class_id(db) == Some(id) } +} - fn type_check_with_type_parameter( - self, - db: &mut Database, - with: TypeRef, - param: TypeParameterId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if let Some(assigned) = context.type_arguments.get(param) { - if let TypeRef::Placeholder(placeholder) = assigned { - let mut rhs = with; - let mut update = true; - - if let Some(val) = placeholder.value(db) { - rhs = val; - - // A placeholder may be assigned to a regular type - // parameter (not a rigid one). In this case we want to - // update the placeholder value to `self`. An example of - // where this can happen is the following: - // - // class Stack[V] { @values: Array[V] } - // Stack { @values = [] } - // - // Here `[]` is of type `Array[P]` where P is a placeholder. - // When assigned to `@values`, we end up assigning V to P, - // and P to V. - // - // If the parameter is rigid we have to leave it as-is, as - // inferring the types further is unsafe. - update = matches!( - val, - TypeRef::Owned(TypeId::TypeParameter(_)) - | TypeRef::Uni(TypeId::TypeParameter(_)) - | TypeRef::Ref(TypeId::TypeParameter(_)) - | TypeRef::Mut(TypeId::TypeParameter(_)) - | TypeRef::Infer(TypeId::TypeParameter(_)) - ); - } - - rhs = rhs.cast_according_to(with, db); +/// An ID pointing to a type. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum TypeId { + Class(ClassId), + Trait(TraitId), + Module(ModuleId), + ClassInstance(ClassInstance), + TraitInstance(TraitInstance), + TypeParameter(TypeParameterId), + RigidTypeParameter(TypeParameterId), + Closure(ClosureId), +} - let compat = - self.type_check_directly(db, rhs, context, subtyping); +impl TypeId { + pub fn named_type(self, db: &Database, name: &str) -> Option { + match self { + TypeId::Module(id) => id.symbol(db, name), + TypeId::Trait(id) => id.named_type(db, name), + TypeId::Class(id) => id.named_type(db, name), + TypeId::ClassInstance(id) => id.named_type(db, name), + TypeId::TraitInstance(id) => id.named_type(db, name), + _ => None, + } + } - if compat && update { - placeholder.assign(db, self); - } + pub fn lookup_method( + self, + db: &Database, + name: &str, + module: ModuleId, + allow_type_private: bool, + ) -> MethodLookup { + if let Some(id) = self.method(db, name) { + let kind = id.kind(db); + let is_ins = !matches!( + self, + TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) + ); - return compat; + if is_ins && kind == MethodKind::Static { + MethodLookup::StaticOnInstance + } else if !is_ins && kind != MethodKind::Static { + MethodLookup::InstanceOnStatic + } else if self.can_call(db, id, module, allow_type_private) { + MethodLookup::Ok(id) + } else { + MethodLookup::Private } - - return self.type_check_directly( - db, - assigned.cast_according_to(with, db), - context, - subtyping, - ); + } else { + MethodLookup::None } + } - if self.type_check_directly(db, with, context, subtyping) { - context.type_arguments.assign(param, self); - - return true; + pub fn method(self, db: &Database, name: &str) -> Option { + match self { + TypeId::Class(id) => id.method(db, name), + TypeId::Trait(id) => id.method(db, name), + TypeId::Module(id) => id.method(db, name), + TypeId::ClassInstance(id) => id.method(db, name), + TypeId::TraitInstance(id) => id.method(db, name), + TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { + id.method(db, name) + } + TypeId::Closure(_) => None, } + } - false + pub fn use_dynamic_dispatch(self) -> bool { + matches!( + self, + TypeId::TraitInstance(_) + | TypeId::TypeParameter(_) + | TypeId::RigidTypeParameter(_) + ) } - fn type_check_with_type_placeholder( - self, - db: &mut Database, - with: TypePlaceholderId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if let Some(assigned) = with.value(db) { - self.type_check(db, assigned, context, subtyping) + pub fn has_destructor(self, db: &Database) -> bool { + if let TypeId::ClassInstance(id) = self { + id.instance_of().has_destructor(db) } else { - with.assign(db, self); - true + false } } - fn type_check_directly( + fn can_call( self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - subtyping: bool, + db: &Database, + method: MethodId, + module: ModuleId, + allow_type_private: bool, ) -> bool { - // This case is the same for all variants of `self`, so we handle it - // here once. - if let TypeRef::Placeholder(id) = with { - return self - .type_check_with_type_placeholder(db, id, context, subtyping); - } - - match self { - TypeRef::Owned(our_id) => match with { - TypeRef::Owned(their_id) | TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Ref(their_id) | TypeRef::Mut(their_id) - if self.is_value_type(db) => - { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Any | TypeRef::RefAny | TypeRef::Error => true, - TypeRef::OwnedSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - _ => false, - }, - TypeRef::Uni(our_id) => match with { - TypeRef::Owned(their_id) - | TypeRef::Infer(their_id) - | TypeRef::Uni(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Any | TypeRef::RefAny | TypeRef::Error => true, - TypeRef::UniSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - _ => false, - }, - TypeRef::RefUni(our_id) => match with { - TypeRef::RefUni(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - _ => false, - }, - TypeRef::MutUni(our_id) => match with { - TypeRef::RefUni(their_id) | TypeRef::MutUni(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - _ => false, - }, - TypeRef::Ref(our_id) => match with { - TypeRef::Ref(their_id) | TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Owned(their_id) | TypeRef::Uni(their_id) - if self.is_value_type(db) => - { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - TypeRef::RefSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - _ => false, - }, - TypeRef::Mut(our_id) => match with { - TypeRef::Ref(their_id) | TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Mut(their_id) => { - our_id.type_check(db, their_id, context, false) - } - TypeRef::Owned(their_id) | TypeRef::Uni(their_id) - if self.is_value_type(db) => - { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - TypeRef::RefSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - TypeRef::MutSelf => { - our_id.type_check(db, context.self_type, context, false) - } - _ => false, - }, - TypeRef::Infer(our_id) => match with { - TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - _ => false, - }, - // Since a Never can't actually be passed around, it's compatible - // with everything else. This allows for code like this: - // - // try foo else panic - // - // Where `panic` would return a `Never`. - TypeRef::Never => true, - TypeRef::OwnedSelf => match with { - TypeRef::Owned(their_id) | TypeRef::Infer(their_id) => context - .self_type - .type_check(db, their_id, context, subtyping), - TypeRef::Any - | TypeRef::RefAny - | TypeRef::Error - | TypeRef::OwnedSelf => true, - _ => false, - }, - TypeRef::RefSelf => match with { - TypeRef::Ref(their_id) | TypeRef::Infer(their_id) => context - .self_type - .type_check(db, their_id, context, subtyping), - TypeRef::Error | TypeRef::RefSelf => true, - _ => false, - }, - TypeRef::MutSelf => match with { - TypeRef::Mut(their_id) | TypeRef::Infer(their_id) => { - context.self_type.type_check(db, their_id, context, false) - } - TypeRef::Error | TypeRef::MutSelf => true, - _ => false, - }, - TypeRef::UniSelf => match with { - TypeRef::Owned(their_id) - | TypeRef::Uni(their_id) - | TypeRef::Infer(their_id) => { - context.self_type.type_check(db, their_id, context, false) - } - TypeRef::Any - | TypeRef::RefAny - | TypeRef::Error - | TypeRef::UniSelf - | TypeRef::OwnedSelf => true, - _ => false, - }, - // Type errors are compatible with all other types to prevent a - // cascade of type errors. - TypeRef::Error => true, - TypeRef::Any => { - matches!(with, TypeRef::Any | TypeRef::RefAny | TypeRef::Error) - } - TypeRef::RefAny => matches!(with, TypeRef::RefAny | TypeRef::Error), - TypeRef::Placeholder(id) => { - if let Some(assigned) = id.value(db) { - return assigned.type_check(db, with, context, subtyping); - } - - if !with.is_regular_type_parameter() { - // This is best explained with an example. Consider the - // following code: - // - // class Stack[X] { - // @values: Array[X] - // - // static fn new -> Self { - // Self { @values = [] } - // } - // } - // - // When the array is created, it's type is `Array[?]` where - // `?` is a placeholder. When assigned to `Array[X]`, we end - // up comparing the placeholder to `X`. The type of `X` in - // this case is `Infer(X)`, because we don't know the - // ownership at runtime. - // - // The return type is expected to be a rigid type (i.e. - // literally `Stack[X]` and not e.g. `Stack[Int]`). This - // creates a problem: Infer() isn't compatible with a rigid - // type parameter, so the above code would produce a type - // error. - // - // In addition, it introduces a cycle of `X -> ? -> X` - // that's not needed. - // - // To prevent both from happening, we _don't_ assign to the - // placeholder if the assigned value is a regular type - // parameter. Regular type parameters can't occur outside of - // method signatures, as they are either turned into rigid - // parameters or replaced with placeholders. - id.assign(db, with); - } - - true - } - _ => false, - } - } - - fn infer_self_type_id( - self, - db: &mut Database, - context: &TypeContext, - ) -> TypeId { - // Self types always refer to instances of a type, so if - // `context.self_type` is a class or trait, we need to turn it into an - // instance. - match context.self_type { - TypeId::Class(id) => { - let ins = if id.is_generic(db) { - let args = context - .type_arguments - .assigned_or_placeholders(db, id.type_parameters(db)); - - ClassInstance::generic(db, id, args) - } else { - ClassInstance::new(id) - }; - - TypeId::ClassInstance(ins) - } - TypeId::Trait(id) => { - let ins = if id.is_generic(db) { - let args = context - .type_arguments - .assigned_or_placeholders(db, id.type_parameters(db)); - - TraitInstance::generic(db, id, args) - } else { - TraitInstance::new(id) - }; - - TypeId::TraitInstance(ins) - } - val => val, - } - } - - fn infer_type_parameter( - self, - type_parameter: TypeParameterId, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> TypeRef { - if let Some(arg) = context.type_arguments.get(type_parameter) { - // Given a case of `A -> placeholder -> A`, this prevents us from - // recursing back into this code and eventually blowing up the - // stack. - if let TypeRef::Placeholder(id) = arg { - if id.value(db).map_or(false, |v| v == self) { - return arg; - } - } - - if arg == self { - return self; - } - - return arg.inferred(db, context, immutable); - } - - if let TypeId::TraitInstance(ins) = context.self_type { - if let Some(arg) = ins - .instance_of - .get(db) - .inherited_type_arguments - .get(type_parameter) - { - return arg.inferred(db, context, immutable); - } - } - - TypeRef::placeholder(db) - } - - fn format_self_type(self, buffer: &mut TypeFormatter) { - if let Some(val) = buffer.self_type { - val.format_type(buffer); - } else { - buffer.write("Self"); - } - } - - fn is_instance_of( - self, - db: &Database, - id: ClassId, - self_type: TypeId, - ) -> bool { - self.class_id(db, self_type) == Some(id) - } -} - -impl FormatType for TypeRef { - fn format_type(&self, buffer: &mut TypeFormatter) { - match self { - TypeRef::Owned(id) | TypeRef::Infer(id) => id.format_type(buffer), - TypeRef::Uni(id) => { - buffer.write_ownership("uni "); - id.format_type(buffer); - } - TypeRef::RefUni(id) => { - buffer.write_ownership("ref uni "); - id.format_type(buffer); - } - TypeRef::MutUni(id) => { - buffer.write_ownership("mut uni "); - id.format_type(buffer); - } - TypeRef::Ref(id) => { - buffer.write_ownership("ref "); - id.format_type(buffer); - } - TypeRef::Mut(id) => { - buffer.write_ownership("mut "); - id.format_type(buffer); - } - TypeRef::Never => buffer.write("Never"), - TypeRef::Any => buffer.write("Any"), - TypeRef::RefAny => buffer.write("ref Any"), - TypeRef::OwnedSelf => { - self.format_self_type(buffer); - } - TypeRef::RefSelf => { - buffer.write_ownership("ref "); - self.format_self_type(buffer); - } - TypeRef::MutSelf => { - buffer.write_ownership("mut "); - self.format_self_type(buffer); - } - TypeRef::UniSelf => { - buffer.write_ownership("uni "); - self.format_self_type(buffer); - } - TypeRef::Error => buffer.write(""), - TypeRef::Unknown => buffer.write(""), - TypeRef::Placeholder(id) => id.format_type(buffer), - }; - } -} - -/// An ID pointing to a type. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum TypeId { - Class(ClassId), - Trait(TraitId), - Module(ModuleId), - ClassInstance(ClassInstance), - TraitInstance(TraitInstance), - TypeParameter(TypeParameterId), - RigidTypeParameter(TypeParameterId), - Closure(ClosureId), -} - -impl TypeId { - pub fn named_type(self, db: &Database, name: &str) -> Option { - match self { - TypeId::Module(id) => id.symbol(db, name), - TypeId::Trait(id) => id.named_type(db, name), - TypeId::Class(id) => id.named_type(db, name), - TypeId::ClassInstance(id) => id.named_type(db, name), - TypeId::TraitInstance(id) => id.named_type(db, name), - _ => None, - } - } - - pub fn lookup_method( - self, - db: &Database, - name: &str, - module: ModuleId, - allow_type_private: bool, - ) -> MethodLookup { - if let Some(id) = self.method(db, name) { - let kind = id.kind(db); - let is_ins = !matches!( - self, - TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) - ); - - if is_ins && kind == MethodKind::Static { - MethodLookup::StaticOnInstance - } else if !is_ins && kind != MethodKind::Static { - MethodLookup::InstanceOnStatic - } else if self.can_call(db, id, module, allow_type_private) { - MethodLookup::Ok(id) - } else { - MethodLookup::Private - } - } else { - MethodLookup::None - } - } - - pub fn method(self, db: &Database, name: &str) -> Option { - match self { - TypeId::Class(id) => id.method(db, name), - TypeId::Trait(id) => id.method(db, name), - TypeId::Module(id) => id.method(db, name), - TypeId::ClassInstance(id) => id.method(db, name), - TypeId::TraitInstance(id) => id.method(db, name), - TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { - id.method(db, name) - } - TypeId::Closure(_) => None, - } - } - - pub fn implements_trait_instance( - self, - db: &mut Database, - trait_type: TraitInstance, - context: &mut TypeContext, - ) -> bool { - match self { - TypeId::ClassInstance(id) => { - id.type_check_with_trait_instance(db, trait_type, context, true) - } - TypeId::TraitInstance(id) => { - id.implements_trait_instance(db, trait_type, context) - } - TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { - id.type_check_with_trait_instance(db, trait_type, context, true) - } - _ => false, - } - } - - pub fn use_dynamic_dispatch(self) -> bool { - matches!( - self, - TypeId::TraitInstance(_) - | TypeId::TypeParameter(_) - | TypeId::RigidTypeParameter(_) - ) - } - - pub fn has_destructor(self, db: &Database) -> bool { - if let TypeId::ClassInstance(id) = self { - id.instance_of().has_destructor(db) - } else { - false - } - } - - fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { - match self { - TypeId::ClassInstance(id) => id.implements_trait_id(db, trait_id), - TypeId::TraitInstance(id) => id.implements_trait_id(db, trait_id), - TypeId::TypeParameter(id) => id - .requirements(db) - .iter() - .any(|req| req.implements_trait_id(db, trait_id)), - _ => false, - } - } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - match self { - TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) => self, - TypeId::ClassInstance(ins) => { - TypeId::ClassInstance(ins.as_rigid_type(db, bounds)) - } - TypeId::TraitInstance(ins) => { - TypeId::TraitInstance(ins.as_rigid_type(db, bounds)) - } - TypeId::TypeParameter(ins) => ins.as_rigid_type(bounds), - TypeId::RigidTypeParameter(_) => self, - TypeId::Closure(ins) => { - TypeId::Closure(ins.as_rigid_type(db, bounds)) - } - } - } - - fn can_call( - self, - db: &Database, - method: MethodId, - module: ModuleId, - allow_type_private: bool, - ) -> bool { - let m = method.get(db); - - if m.kind == MethodKind::Destructor { - return false; + let m = method.get(db); + + if m.kind == MethodKind::Destructor { + return false; } match m.visibility { @@ -5014,53 +4172,6 @@ impl TypeId { Visibility::TypePrivate => allow_type_private, } } - - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match self { - TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) => { - self == with - } - TypeId::ClassInstance(ins) => { - ins.type_check(db, with, context, subtyping) - } - TypeId::TraitInstance(ins) => { - ins.type_check(db, with, context, subtyping) - } - TypeId::TypeParameter(ins) => { - ins.type_check(db, with, context, subtyping) - } - TypeId::RigidTypeParameter(our_ins) => match with { - TypeId::RigidTypeParameter(their_ins) => our_ins == their_ins, - _ => our_ins.type_check(db, with, context, subtyping), - }, - TypeId::Closure(ins) => { - ins.type_check(db, with, context, subtyping) - } - } - } -} - -impl FormatType for TypeId { - fn format_type(&self, buffer: &mut TypeFormatter) { - match self { - TypeId::Class(id) => id.format_type(buffer), - TypeId::Trait(id) => id.format_type(buffer), - TypeId::Module(id) => id.format_type(buffer), - TypeId::ClassInstance(ins) => ins.format_type(buffer), - TypeId::TraitInstance(id) => id.format_type(buffer), - TypeId::TypeParameter(id) => id.format_type(buffer), - TypeId::RigidTypeParameter(id) => { - id.format_type_without_argument(buffer); - } - TypeId::Closure(id) => id.format_type(buffer), - } - } } /// A database of all Inko types. @@ -5076,7 +4187,8 @@ pub struct Database { closures: Vec, variables: Vec, constants: Vec, - builtin_functions: IndexMap, + builtin_functions: HashMap, + builtin_constants: HashMap, type_placeholders: Vec, variants: Vec, @@ -5085,6 +4197,8 @@ pub struct Database { /// For executables this will be set based on the file that is built/run. /// When just type-checking a project, this may be left as a None. main_module: Option, + main_method: Option, + main_class: Option, } impl Database { @@ -5094,14 +4208,14 @@ impl Database { module_mapping: HashMap::new(), traits: Vec::new(), classes: vec![ - Class::regular(INT_NAME.to_string()), - Class::regular(FLOAT_NAME.to_string()), - Class::regular(STRING_NAME.to_string()), + Class::value_type(INT_NAME.to_string()), + Class::value_type(FLOAT_NAME.to_string()), + Class::atomic(STRING_NAME.to_string()), Class::regular(ARRAY_NAME.to_string()), - Class::regular(BOOLEAN_NAME.to_string()), - Class::regular(NIL_NAME.to_string()), + Class::value_type(BOOLEAN_NAME.to_string()), + Class::value_type(NIL_NAME.to_string()), Class::regular(BYTE_ARRAY_NAME.to_string()), - Class::regular(FUTURE_NAME.to_string()), + Class::atomic(CHANNEL_NAME.to_string()), Class::tuple(TUPLE1_NAME.to_string()), Class::tuple(TUPLE2_NAME.to_string()), Class::tuple(TUPLE3_NAME.to_string()), @@ -5110,6 +4224,7 @@ impl Database { Class::tuple(TUPLE6_NAME.to_string()), Class::tuple(TUPLE7_NAME.to_string()), Class::tuple(TUPLE8_NAME.to_string()), + Class::external(RESULT_NAME.to_string()), ], type_parameters: Vec::new(), type_arguments: Vec::new(), @@ -5118,10 +4233,13 @@ impl Database { closures: Vec::new(), variables: Vec::new(), constants: Vec::new(), - builtin_functions: IndexMap::new(), + builtin_functions: BuiltinFunction::mapping(), + builtin_constants: BuiltinConstant::mapping(), type_placeholders: Vec::new(), variants: Vec::new(), main_module: None, + main_method: None, + main_class: None, } } @@ -5134,7 +4252,7 @@ impl Database { BOOLEAN_NAME => Some(ClassId(BOOLEAN_ID)), NIL_NAME => Some(ClassId(NIL_ID)), BYTE_ARRAY_NAME => Some(ClassId(BYTE_ARRAY_ID)), - FUTURE_NAME => Some(ClassId(FUTURE_ID)), + CHANNEL_NAME => Some(ClassId(CHANNEL_ID)), TUPLE1_NAME => Some(ClassId(TUPLE1_ID)), TUPLE2_NAME => Some(ClassId(TUPLE2_ID)), TUPLE3_NAME => Some(ClassId(TUPLE3_ID)), @@ -5147,8 +4265,12 @@ impl Database { } } - pub fn builtin_function(&self, name: &str) -> Option { - self.builtin_functions.index_of(name).map(BuiltinFunctionId) + pub fn builtin_function(&self, name: &str) -> Option { + self.builtin_functions.get(name).cloned() + } + + pub fn builtin_constant(&self, name: &str) -> Option { + self.builtin_constants.get(name).cloned() } pub fn module(&self, name: &str) -> ModuleId { @@ -5200,16 +4322,37 @@ impl Database { pub fn main_module(&self) -> Option<&ModuleName> { self.main_module.as_ref() } + + pub fn set_main_method(&mut self, id: MethodId) { + self.main_method = Some(id); + } + + pub fn main_method(&self) -> Option { + self.main_method + } + + pub fn set_main_class(&mut self, id: ClassId) { + self.main_class = Some(id); + } + + pub fn main_class(&self) -> Option { + self.main_class + } } #[cfg(test)] mod tests { use super::*; + use crate::test::{ + immutable, instance, mutable, new_parameter, owned, placeholder, rigid, + uni, + }; use std::mem::size_of; #[test] fn test_type_sizes() { assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 24); } #[test] @@ -5254,69 +4397,6 @@ mod tests { assert_eq!(id.requirements(&db), vec![requirement]); } - #[test] - fn test_type_parameter_id_type_check() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let p1 = TypeParameter::alloc(&mut db, "A".to_string()); - let p2 = TypeParameter::alloc(&mut db, "B".to_string()); - let p3 = TypeParameter::alloc(&mut db, "C".to_string()); - let mut ctx = TypeContext::new(self_type); - - assert!(p1.type_check( - &mut db, - TypeId::TypeParameter(p2), - &mut ctx, - false - )); - assert!(!p1.type_check( - &mut db, - TypeId::RigidTypeParameter(p3), - &mut ctx, - false - )); - } - - #[test] - fn test_type_parameter_id_type_check_with_requirements() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let p1 = TypeParameter::alloc(&mut db, "A".to_string()); - let p2 = TypeParameter::alloc(&mut db, "B".to_string()); - - p1.add_requirements(&mut db, vec![TraitInstance::new(to_s)]); - p2.add_requirements(&mut db, vec![TraitInstance::new(to_s)]); - - let mut ctx = TypeContext::new(self_type); - - assert!(p1.type_check( - &mut db, - TypeId::TypeParameter(p2), - &mut ctx, - false - )); - } - #[test] fn test_type_arguments_assign() { let mut targs = TypeArguments::new(); @@ -5404,348 +4484,9 @@ mod tests { } #[test] - fn test_trait_instance_type_check_with_generic_trait_instance() { + fn test_class_alloc() { let mut db = Database::new(); - let trait_a = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); - let trait_b = Trait::alloc( - &mut db, - "B".to_string(), - ModuleId(0), - Visibility::Private, - ); - - let param1 = trait_a.new_type_parameter(&mut db, "A".to_string()); - - trait_b.new_type_parameter(&mut db, "A".to_string()); - - let mut ins1_args = TypeArguments::new(); - let mut ins2_args = TypeArguments::new(); - let mut ins3_args = TypeArguments::new(); - - ins1_args.assign(param1, TypeRef::Any); - ins2_args.assign(param1, TypeRef::Any); - ins3_args.assign(param1, TypeRef::Never); - - let ins1 = TraitInstance::generic(&mut db, trait_a, ins1_args); - let ins2 = TraitInstance::generic(&mut db, trait_a, ins2_args); - let ins3 = TraitInstance::generic(&mut db, trait_a, ins3_args); - let ins4 = - TraitInstance::generic(&mut db, trait_a, TypeArguments::new()); - let ins5 = - TraitInstance::generic(&mut db, trait_b, TypeArguments::new()); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins1.type_check( - &mut db, - TypeId::TraitInstance(ins2), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::TraitInstance(ins3), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::TraitInstance(ins4), - &mut ctx, - false - )); - assert!(!ins4.type_check( - &mut db, - TypeId::TraitInstance(ins5), - &mut ctx, - false - )); - } - - #[test] - fn test_trait_instance_type_check_with_generic_trait_as_required_trait() { - let mut db = Database::new(); - let trait_b = Trait::alloc( - &mut db, - "B".to_string(), - ModuleId(0), - Visibility::Private, - ); - let trait_c = Trait::alloc( - &mut db, - "C".to_string(), - ModuleId(0), - Visibility::Private, - ); - let ins_b = - TraitInstance::generic(&mut db, trait_b, TypeArguments::new()); - let ins_c = TypeId::TraitInstance(TraitInstance::generic( - &mut db, - trait_c, - TypeArguments::new(), - )); - - { - let req = - TraitInstance::generic(&mut db, trait_c, TypeArguments::new()); - - trait_b.add_required_trait(&mut db, req); - } - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins_b.type_check(&mut db, ins_c, &mut ctx, true)); - } - - #[test] - fn test_trait_instance_type_check_with_regular_trait() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let requirement = TraitInstance::new(to_string); - - debug.add_required_trait(&mut db, requirement); - - let debug_ins = TraitInstance::new(debug); - let to_string_ins = - TypeId::TraitInstance(TraitInstance::new(to_string)); - let to_int_ins = TypeId::TraitInstance(TraitInstance::new(to_int)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(debug_ins.type_check(&mut db, to_string_ins, &mut ctx, true)); - assert!(!debug_ins.type_check(&mut db, to_int_ins, &mut ctx, true)); - } - - #[test] - fn test_trait_instance_type_check_with_rigid_type_parameter() { - let mut db = Database::new(); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let param = TypeParameter::alloc(&mut db, "A".to_string()); - let param_ins = TypeId::RigidTypeParameter(param); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!to_s_ins.type_check(&mut db, param_ins, &mut ctx, false)); - } - - #[test] - fn test_trait_instance_type_check_with_type_parameter() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - let param3 = TypeParameter::alloc(&mut db, "C".to_string()); - let debug_ins = TraitInstance::new(debug); - let to_string_ins = TraitInstance::new(to_string); - - debug.add_required_trait(&mut db, to_string_ins); - param2.add_requirements(&mut db, vec![debug_ins]); - param3.add_requirements(&mut db, vec![to_string_ins]); - - let to_int_ins = TraitInstance::new(to_int); - let param1_ins = TypeId::TypeParameter(param1); - let param2_ins = TypeId::TypeParameter(param2); - let param3_ins = TypeId::TypeParameter(param3); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(debug_ins.type_check(&mut db, param1_ins, &mut ctx, true)); - assert!(debug_ins.type_check(&mut db, param2_ins, &mut ctx, true)); - assert!(debug_ins.type_check(&mut db, param3_ins, &mut ctx, true)); - assert!(!to_int_ins.type_check(&mut db, param2_ins, &mut ctx, true)); - } - - #[test] - fn test_trait_instance_type_check_with_other_variants() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let debug_ins = TraitInstance::new(debug); - let closure = TypeId::Closure(Closure::alloc(&mut db, false)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!debug_ins.type_check(&mut db, closure, &mut ctx, false)); - } - - #[test] - fn test_trait_instance_format_type_with_regular_trait() { - let mut db = Database::new(); - let trait_id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); - let trait_ins = TraitInstance::new(trait_id); - - assert_eq!(format_type(&db, trait_ins), "A".to_string()); - } - - #[test] - fn test_trait_instance_format_type_with_generic_trait() { - let mut db = Database::new(); - let trait_id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param1 = trait_id.new_type_parameter(&mut db, "A".to_string()); - - trait_id.new_type_parameter(&mut db, "B".to_string()); - - let mut targs = TypeArguments::new(); - - targs.assign(param1, TypeRef::Any); - - let trait_ins = TraitInstance::generic(&mut db, trait_id, targs); - - assert_eq!(format_type(&db, trait_ins), "ToString[Any, B]"); - } - - #[test] - fn test_trait_instance_as_rigid_type_with_regular_trait() { - let mut db = Database::new(); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let bounds = TypeBounds::new(); - let rigid = to_s_ins.as_rigid_type(&mut db, &bounds); - - assert_eq!(rigid, to_s_ins); - } - - #[test] - fn test_trait_instance_as_rigid_type_with_generic_trait() { - let mut db = Database::new(); - let to_a = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param1 = to_a.new_type_parameter(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "A".to_string()); - let mut args = TypeArguments::new(); - - args.assign(param1, TypeRef::Owned(TypeId::TypeParameter(param2))); - - let to_a_ins = TraitInstance::generic(&mut db, to_a, args); - let bounds = TypeBounds::new(); - let rigid = to_a_ins.as_rigid_type(&mut db, &bounds); - let old_arg = to_a_ins.type_arguments(&db).get(param1).unwrap(); - let new_arg = rigid.type_arguments(&db).get(param1).unwrap(); - - assert_ne!(old_arg, new_arg); - assert_eq!(new_arg, TypeParameterId(1).as_owned_rigid()); - } - - #[test] - fn test_class_alloc() { - let mut db = Database::new(); - let id = Class::alloc( + let id = Class::alloc( &mut db, "A".to_string(), ClassKind::Regular, @@ -5844,2925 +4585,258 @@ mod tests { assert_eq!(ins.type_arguments, 0); } - #[test] - fn test_class_instance_generic() { - let mut db = Database::new(); - let id = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let ins1 = ClassInstance::generic(&mut db, id, TypeArguments::new()); - let ins2 = ClassInstance::generic(&mut db, id, TypeArguments::new()); - - assert_eq!(ins1.instance_of.0, FIRST_USER_CLASS_ID); - assert_eq!(ins1.type_arguments, 0); - - assert_eq!(ins2.instance_of.0, FIRST_USER_CLASS_ID); - assert_eq!(ins2.type_arguments, 1); - } - - #[test] - fn test_class_instance_type_check_with_class_instance() { - let mut db = Database::new(); - let cls1 = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let cls2 = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let ins1 = ClassInstance::new(cls1); - let ins2 = ClassInstance::new(cls1); - let ins3 = ClassInstance::new(cls2); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins1), - &mut ctx, - false - )); - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins2), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::ClassInstance(ins3), - &mut ctx, - false - )); - } - - #[test] - fn test_class_instance_type_check_with_generic_class_instance() { - let mut db = Database::new(); - let class_a = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_b = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - - let param1 = class_a.new_type_parameter(&mut db, "A".to_string()); - - class_b.new_type_parameter(&mut db, "A".to_string()); - - let mut ins1_args = TypeArguments::new(); - let mut ins2_args = TypeArguments::new(); - let mut ins3_args = TypeArguments::new(); - - ins1_args.assign(param1, TypeRef::Any); - ins2_args.assign(param1, TypeRef::Any); - ins3_args.assign(param1, TypeRef::Never); - - let ins1 = ClassInstance::generic(&mut db, class_a, ins1_args); - let ins2 = ClassInstance::generic(&mut db, class_a, ins2_args); - let ins3 = ClassInstance::generic(&mut db, class_a, ins3_args); - let ins4 = - ClassInstance::generic(&mut db, class_a, TypeArguments::new()); - let ins5 = - ClassInstance::generic(&mut db, class_b, TypeArguments::new()); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins2), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::ClassInstance(ins3), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::ClassInstance(ins4), - &mut ctx, - false - )); - assert!(!ins4.type_check( - &mut db, - TypeId::ClassInstance(ins5), - &mut ctx, - false - )); - } - - #[test] - fn test_class_instance_type_check_with_empty_type_arguments() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - let ins1 = ClassInstance::generic(&mut db, array, TypeArguments::new()); - let ins2 = { - let mut args = TypeArguments::new(); - - args.assign(param, TypeRef::Any); - ClassInstance::generic(&mut db, array, args) - }; - - let stype = TypeId::ClassInstance(ins1); - let mut ctx = TypeContext::new(stype); - - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins2), - &mut ctx, - false - )); - assert!(!ins2.type_check( - &mut db, - TypeId::ClassInstance(ins1), - &mut ctx, - false - )); - } - - #[test] - fn test_class_instance_type_check_with_trait_instance() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - let string_ins = ClassInstance::new(string); - let to_int_ins = TypeId::TraitInstance(TraitInstance::new(to_int)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(string_ins.type_check( - &mut db, - TypeId::TraitInstance(to_string_ins), - &mut ctx, - true - )); - - assert!(!string_ins.type_check(&mut db, to_int_ins, &mut ctx, true)); - } - - #[test] - fn test_class_instance_type_check_with_generic_trait_instance() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let owned_string = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let owned_int = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))); - let equal = Trait::alloc( - &mut db, - "Equal".to_string(), - ModuleId(0), - Visibility::Private, - ); - let equal_param = equal.new_type_parameter(&mut db, "T".to_string()); - - let equal_string = { - let mut type_args = TypeArguments::new(); - - type_args.assign(equal_param, owned_string); - TraitInstance::generic(&mut db, equal, type_args) - }; - - let equal_int = { - let mut type_args = TypeArguments::new(); - - type_args.assign(equal_param, owned_int); - TraitInstance::generic(&mut db, equal, type_args) - }; - - let equal_any = { - let mut type_args = TypeArguments::new(); - - type_args.assign(equal_param, TypeRef::Any); - TraitInstance::generic(&mut db, equal, type_args) - }; - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: equal_string, - bounds: TypeBounds::new(), - }, - ); - - let string_ins = ClassInstance::new(string); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - // String -> Equal[String] is OK because String implements - // Equal[String]. - assert!(string_ins.type_check( - &mut db, - TypeId::TraitInstance(equal_string), - &mut ctx, - true - )); - - // String -> Equal[Any] is OK, as Equal[String] is compatible with - // Equal[Any] (but not the other way around). - assert!(string_ins.type_check( - &mut db, - TypeId::TraitInstance(equal_any), - &mut ctx, - true - )); - - // String -> Equal[Int] is not OK, as Equal[Int] isn't implemented by - // String. - assert!(!string_ins.type_check( - &mut db, - TypeId::TraitInstance(equal_int), - &mut ctx, - true - )); - } - - #[test] - fn test_class_instance_type_check_with_trait_instance_with_bounds() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let float = Class::alloc( - &mut db, - "Float".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let mut to_string_impl = TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }; - - let bound_param = TypeParameter::alloc(&mut db, "T".to_string()); - - bound_param.add_requirements(&mut db, vec![to_string_ins]); - to_string_impl.bounds.set(param, bound_param); - array.add_trait_implementation(&mut db, to_string_impl); - - int.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - let empty_array = - ClassInstance::generic(&mut db, array, TypeArguments::new()); - - let int_array = { - let mut args = TypeArguments::new(); - - args.assign( - param, - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))), - ); - - ClassInstance::generic(&mut db, array, args) - }; - - let float_array = { - let mut args = TypeArguments::new(); - - args.assign( - param, - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new( - float, - ))), - ); - - ClassInstance::generic(&mut db, array, args) - }; - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - let to_string_type = TypeId::TraitInstance(to_string_ins); - - assert!(!empty_array.type_check( - &mut db, - to_string_type, - &mut ctx, - true - )); - assert!(!float_array.type_check( - &mut db, - to_string_type, - &mut ctx, - true - )); - assert!(int_array.type_check(&mut db, to_string_type, &mut ctx, true)); - } - - #[test] - fn test_class_instance_type_check_with_type_parameter() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int_ins = TraitInstance::new(to_int); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - let param3 = TypeParameter::alloc(&mut db, "C".to_string()); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - param2.add_requirements(&mut db, vec![to_string_ins]); - param3.add_requirements(&mut db, vec![to_int_ins]); - - let string_ins = ClassInstance::new(string); - let param1_type = TypeId::TypeParameter(param1); - let param2_type = TypeId::TypeParameter(param2); - let param3_type = TypeId::TypeParameter(param3); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - // String -> A is OK, as A has no requirements. - assert!(string_ins.type_check(&mut db, param1_type, &mut ctx, true)); - - // String -> B is OK, as ToString is implemented by String. - assert!(string_ins.type_check(&mut db, param2_type, &mut ctx, true)); - - // String -> C is not OK, as ToInt isn't implemented. - assert!(!string_ins.type_check(&mut db, param3_type, &mut ctx, true)); - } - - #[test] - fn test_class_instance_type_check_with_rigid_type_parameter() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = ClassInstance::new(string); - let param = TypeParameter::alloc(&mut db, "A".to_string()); - let param_ins = TypeId::RigidTypeParameter(param); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!string_ins.type_check(&mut db, param_ins, &mut ctx, false)); - } - - #[test] - fn test_class_instance_type_check_with_function() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let closure = TypeId::Closure(Closure::alloc(&mut db, false)); - let string_ins = ClassInstance::new(string); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!string_ins.type_check(&mut db, closure, &mut ctx, false)); - } - - #[test] - fn test_class_instance_as_rigid_type_with_regular_trait() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = ClassInstance::new(string); - let bounds = TypeBounds::new(); - let rigid = string_ins.as_rigid_type(&mut db, &bounds); - - assert_eq!(rigid, string_ins); - } - - #[test] - fn test_class_instance_as_rigid_type_with_generic_trait() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param1 = array.new_type_parameter(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "A".to_string()); - let mut args = TypeArguments::new(); - - args.assign(param1, TypeRef::Owned(TypeId::TypeParameter(param2))); - - let to_a_ins = ClassInstance::generic(&mut db, array, args); - let bounds = TypeBounds::new(); - let rigid = to_a_ins.as_rigid_type(&mut db, &bounds); - let old_arg = to_a_ins.type_arguments(&db).get(param1).unwrap(); - let new_arg = rigid.type_arguments(&db).get(param1).unwrap(); - - assert_ne!(old_arg, new_arg); - assert_eq!(new_arg, TypeParameterId(1).as_owned_rigid()); - } - - #[test] - fn test_method_alloc() { - let mut db = Database::new(); - let id = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Moving, - ); - - assert_eq!(id.0, 0); - assert_eq!(&db.methods[0].name, &"foo".to_string()); - assert_eq!(db.methods[0].kind, MethodKind::Moving); - } - - #[test] - fn test_method_id_named_type() { - let mut db = Database::new(); - let method = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let param = method.new_type_parameter(&mut db, "A".to_string()); - - assert_eq!( - method.named_type(&db, "A"), - Some(Symbol::TypeParameter(param)) - ); - } - - #[test] - fn test_method_id_format_type_with_instance_method() { - let mut db = Database::new(); - let class_a = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_b = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_c = Class::alloc( - &mut db, - "C".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_d = Class::alloc( - &mut db, - "D".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - let ins_a = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); - - let ins_b = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); - - let ins_c = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_c))); - - let ins_d = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); - - block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); - block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); - block.set_throw_type(&mut db, ins_c); - block.set_return_type(&mut db, ins_d); - - assert_eq!(format_type(&db, block), "fn foo (a: A, b: B) !! C -> D"); - } - - #[test] - fn test_method_id_format_type_with_moving_method() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Moving, - ); - - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn move foo -> Any"); - } - - #[test] - fn test_method_id_format_type_with_type_parameters() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Static, - ); - - block.new_type_parameter(&mut db, "A".to_string()); - block.new_type_parameter(&mut db, "B".to_string()); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn static foo [A, B] -> Any"); - } - - #[test] - fn test_method_id_format_type_with_static_method() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Static, - ); - - block.new_argument( - &mut db, - "a".to_string(), - TypeRef::Any, - TypeRef::Any, - ); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn static foo (a: Any) -> Any"); - } - - #[test] - fn test_method_id_format_type_with_async_method() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Async, - ); - - block.new_argument( - &mut db, - "a".to_string(), - TypeRef::Any, - TypeRef::Any, - ); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn async foo (a: Any) -> Any"); - } - - #[test] - fn test_method_id_type_check_with_different_name() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "b".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_different_visibility() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Public, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_different_kind() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Static, - ); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_different_param_count() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m2.new_type_parameter(&mut db, "T".to_string()); - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_params() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - - m1.new_type_parameter(&mut db, "T".to_string()); - - m2.new_type_parameter(&mut db, "T".to_string()) - .add_requirements(&mut db, vec![to_s_ins]); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_arg_types() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m3 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = - TypeRef::Owned(TypeId::TraitInstance(TraitInstance::new(to_s))); - - m1.new_argument(&mut db, "a".to_string(), to_s_ins, to_s_ins); - m3.new_argument(&mut db, "a".to_string(), to_s_ins, to_s_ins); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - m3.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - assert!(!m2.type_check(&mut db, m3, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_arg_names() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.new_argument(&mut db, "a".to_string(), TypeRef::Any, TypeRef::Any); - m2.new_argument(&mut db, "b".to_string(), TypeRef::Any, TypeRef::Any); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_throw_type() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.set_throw_type(&mut db, TypeRef::Any); - m1.set_return_type(&mut db, TypeRef::Any); - - m2.set_throw_type(&mut db, TypeRef::Never); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_return_type() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Never); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_compatible_method() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.new_type_parameter(&mut db, "T".to_string()); - m1.new_argument(&mut db, "a".to_string(), TypeRef::Any, TypeRef::Any); - m1.set_throw_type(&mut db, TypeRef::Any); - m1.set_return_type(&mut db, TypeRef::Any); - - m2.new_type_parameter(&mut db, "T".to_string()); - m2.new_argument(&mut db, "a".to_string(), TypeRef::Any, TypeRef::Any); - m2.set_throw_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_module_alloc() { - let mut db = Database::new(); - let name = ModuleName::new("foo"); - let id = Module::alloc(&mut db, name.clone(), "foo.inko".into()); - - assert_eq!(id.0, 0); - assert_eq!(&db.modules[0].name, &name); - assert_eq!(&db.modules[0].file, &PathBuf::from("foo.inko")); - } - - #[test] - fn test_module_id_file() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - assert_eq!(id.file(&db), PathBuf::from("test.inko")); - } - - #[test] - fn test_module_id_symbol() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - - assert_eq!(id.symbol(&db, "A"), Some(Symbol::Module(id))); - } - - #[test] - fn test_module_id_symbols() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - - assert_eq!( - id.symbols(&db), - vec![("A".to_string(), Symbol::Module(id))] - ); - } - - #[test] - fn test_module_id_symbol_exists() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - - assert!(id.symbol_exists(&db, "A")); - assert!(!id.symbol_exists(&db, "B")); - } - - #[test] - fn test_function_closure() { - let mut db = Database::new(); - let id = Closure::alloc(&mut db, false); - - assert_eq!(id.0, 0); - } - - #[test] - fn test_closure_id_format_type_never_throws() { - let mut db = Database::new(); - let block = Closure::alloc(&mut db, false); - - block.set_throw_type(&mut db, TypeRef::Never); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn -> Any"); - } - - #[test] - fn test_closure_id_format_type_never_returns() { - let mut db = Database::new(); - let block = Closure::alloc(&mut db, false); - - block.set_return_type(&mut db, TypeRef::Never); - - assert_eq!(format_type(&db, block), "fn -> Never"); - } - - #[test] - fn test_closure_id_type_check_with_empty_closure() { - let mut db = Database::new(); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - - closure1.set_return_type(&mut db, TypeRef::Any); - closure2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_arguments() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - let closure3 = Closure::alloc(&mut db, false); - - closure1.new_argument(&mut db, "a".to_string(), string_ins, string_ins); - closure1.set_return_type(&mut db, TypeRef::Any); - - closure2.new_argument(&mut db, "x".to_string(), string_ins, string_ins); - closure2.set_return_type(&mut db, TypeRef::Any); - - closure3.new_argument(&mut db, "a".to_string(), string_ins, string_ins); - closure3.new_argument(&mut db, "b".to_string(), string_ins, string_ins); - closure3.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure3), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_throw_type() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - let closure3 = Closure::alloc(&mut db, false); - let closure4 = Closure::alloc(&mut db, false); - - closure1.set_throw_type(&mut db, string_ins); - closure1.set_return_type(&mut db, TypeRef::Any); - - closure2.set_throw_type(&mut db, string_ins); - closure2.set_return_type(&mut db, TypeRef::Any); - - closure3.set_throw_type(&mut db, int_ins); - closure3.set_return_type(&mut db, TypeRef::Any); - - closure4.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - assert!(closure4.type_check( - &mut db, - TypeId::Closure(closure1), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure3), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure4), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_return_type() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - let closure3 = Closure::alloc(&mut db, false); - let closure4 = Closure::alloc(&mut db, false); - - closure1.set_return_type(&mut db, string_ins); - closure2.set_return_type(&mut db, string_ins); - closure3.set_return_type(&mut db, int_ins); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure3), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure4), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_type_parameter() { - let mut db = Database::new(); - let closure = Closure::alloc(&mut db, false); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - - param2.add_requirements(&mut db, vec![to_string_ins]); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure.type_check( - &mut db, - TypeId::TypeParameter(param1), - &mut ctx, - false - )); - assert!(!closure.type_check( - &mut db, - TypeId::TypeParameter(param2), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_as_rigid_type_with_regular_function() { - let mut db = Database::new(); - let closure = Closure::alloc(&mut db, false); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let to_s_type = TypeRef::Owned(TypeId::TraitInstance(to_s_ins)); - - closure.new_argument(&mut db, "a".to_string(), to_s_type, to_s_type); - - let bounds = TypeBounds::new(); - let new_closure = closure.as_rigid_type(&mut db, &bounds); - - assert_eq!(new_closure, ClosureId(1)); - } - - #[test] - fn test_closure_id_as_rigid_type_with_generic_function() { - let mut db = Database::new(); - let closure = Closure::alloc(&mut db, false); - let param = TypeParameter::alloc(&mut db, "T".to_string()); - let param_type = TypeRef::Owned(TypeId::TypeParameter(param)); - - closure.new_argument(&mut db, "a".to_string(), param_type, param_type); - closure.set_throw_type(&mut db, param_type); - closure.set_return_type(&mut db, param_type); - - let bounds = TypeBounds::new(); - let new_closure = closure.as_rigid_type(&mut db, &bounds); - - assert_ne!(closure, new_closure); - - let new_arg = new_closure.get(&db).arguments.get("a").unwrap(); - - assert_eq!(new_arg.value_type, param.as_owned_rigid(),); - assert_eq!( - new_closure.throw_type(&db), - TypeParameterId(0).as_owned_rigid() - ); - assert_eq!( - new_closure.return_type(&db), - TypeParameterId(0).as_owned_rigid() - ); - } - - #[test] - fn test_type_ref_type_check_with_owned() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let string_typ = TypeRef::Owned(string_ins); - let string_ref_typ = TypeRef::Ref(string_ins); - let int_typ = TypeRef::Owned(int_ins); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check(&mut db, string_typ, &mut ctx, false)); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check(&mut db, TypeRef::Any, &mut ctx, false)); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check( - &mut db, - TypeRef::OwnedSelf, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check( - &mut db, - string_ref_typ, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check( - &mut db, - TypeRef::Unknown, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check(&mut db, int_typ, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_ref() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let string_typ = TypeRef::Ref(string_ins); - let mut ctx_int = TypeContext::new(int_ins); - let mut ctx_str = TypeContext::new(string_ins); - - assert!(string_typ.type_check( - &mut db, - string_typ, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::Error, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx_str, - false - )); - assert!(!string_typ.type_check( - &mut db, - TypeRef::Owned(string_ins), - &mut ctx_int, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_mut() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let string_typ = TypeRef::Mut(string_ins); - let mut ctx_int = TypeContext::new(int_ins); - let mut ctx_str = TypeContext::new(string_ins); - - assert!(string_typ.type_check( - &mut db, - string_typ, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::Error, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx_str, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::MutSelf, - &mut ctx_str, - false - )); - assert!(!string_typ.type_check( - &mut db, - TypeRef::Owned(string_ins), - &mut ctx_int, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_mut_trait() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = TraitInstance::new(Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - )); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string, - bounds: TypeBounds::new(), - }, - ); - - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let string_typ = TypeRef::Mut(string_ins); - let mut ctx = TypeContext::new(string_ins); - - assert!(string_typ.type_check( - &mut db, - TypeRef::Ref(TypeId::TraitInstance(to_string)), - &mut ctx, - true - )); - - assert!(!string_typ.type_check( - &mut db, - TypeRef::Mut(TypeId::TraitInstance(to_string)), - &mut ctx, - true - )); - } - - #[test] - fn test_type_ref_type_check_with_infer() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let param = TypeParameter::alloc(&mut db, "T".to_string()); - let param_ins = TypeId::TypeParameter(param); - let param_typ = TypeRef::Infer(param_ins); - - let mut ctx = TypeContext::new(int_ins); - assert!(param_typ.type_check(&mut db, param_typ, &mut ctx, false)); - - let mut ctx = TypeContext::new(int_ins); - assert!(param_typ.type_check(&mut db, TypeRef::Error, &mut ctx, false)); - - let mut ctx = TypeContext::new(param_ins); - assert!(!param_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(int_ins); - assert!(!param_typ.type_check( - &mut db, - TypeRef::Owned(param_ins), - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_never() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(TypeRef::Never.type_check( - &mut db, - TypeRef::Never, - &mut ctx, - false - )); - assert!(TypeRef::Never.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(TypeRef::Never.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_any() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let param = TypeRef::Owned(TypeId::TypeParameter( - TypeParameter::alloc(&mut db, "T".to_string()), - )); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(TypeRef::Any.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - assert!(TypeRef::Any.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(!TypeRef::Any.type_check(&mut db, param, &mut ctx, false)); - assert!(!TypeRef::Any.type_check( - &mut db, - TypeRef::OwnedSelf, - &mut ctx, - false - )); - assert!(!TypeRef::Any.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx, - false - )); - assert!(!TypeRef::Any.type_check(&mut db, string_ins, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_owned_self() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - - let mut ctx = TypeContext::new(string_ins); - assert!(TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Owned(string_ins), - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Ref(string_ins), - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_ref_self() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let string_ref = TypeRef::Ref(string_ins); - let mut ctx = TypeContext::new(string_ins); - - assert!( - TypeRef::RefSelf.type_check(&mut db, string_ref, &mut ctx, false) - ); - assert!(TypeRef::RefSelf.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(!TypeRef::RefSelf.type_check( - &mut db, - TypeRef::OwnedSelf, - &mut ctx, - false - )); - assert!(!TypeRef::RefSelf.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - assert!(!TypeRef::RefSelf.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_error() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(TypeRef::Error.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(TypeRef::Error.type_check( - &mut db, - TypeRef::Never, - &mut ctx, - false - )); - assert!(TypeRef::Error.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_unknown() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - let unknown = TypeRef::Unknown; - - assert!(!unknown.type_check(&mut db, unknown, &mut ctx, false)); - assert!(!unknown.type_check(&mut db, TypeRef::Error, &mut ctx, false)); - assert!(!unknown.type_check(&mut db, TypeRef::Never, &mut ctx, false)); - assert!(!unknown.type_check(&mut db, TypeRef::Any, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_type_parameter() { - let mut db = Database::new(); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - let trait_id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let ins = TypeId::TraitInstance(TraitInstance::new(trait_id)); - let ins_type = TypeRef::Owned(ins); - - param1.add_requirements(&mut db, vec![TraitInstance::new(trait_id)]); - - { - let mut ctx = TypeContext::new(ins); - - assert!(!ins_type - .is_compatible_with_type_parameter(&mut db, param1, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(ins_type - .is_compatible_with_type_parameter(&mut db, param2, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(TypeRef::OwnedSelf - .is_compatible_with_type_parameter(&mut db, param2, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(TypeRef::RefSelf - .is_compatible_with_type_parameter(&mut db, param2, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(TypeRef::Owned(TypeId::TypeParameter(param1)) - .type_check(&mut db, ins_type, &mut ctx, false)); - } - } - - #[test] - fn test_type_ref_type_check_with_assigned_type_parameter() { - let mut db = Database::new(); - let param = TypeRef::Owned(TypeId::TypeParameter( - TypeParameter::alloc(&mut db, "A".to_string()), - )); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_i = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let to_i_ins = TraitInstance::new(to_i); - let typ1 = TypeRef::Owned(TypeId::TraitInstance(to_s_ins)); - let typ2 = TypeRef::Owned(TypeId::TraitInstance(to_i_ins)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, param, &mut ctx, false)); - assert_eq!(ctx.type_arguments.mapping.len(), 1); - assert!(!typ2.type_check(&mut db, param, &mut ctx, false)); - } - - #[test] - fn test_type_ref_implements_trait_with_class_instance() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let mut ctx = TypeContext::new(string_ins); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - assert!(TypeRef::Owned(string_ins).implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!(TypeRef::Ref(string_ins).implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!(TypeRef::OwnedSelf.implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!(TypeRef::RefSelf.implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - } - - #[test] - fn test_type_ref_implements_trait_with_trait_instance() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_foo = Trait::alloc( - &mut db, - "ToFoo".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let debug_ins = TypeId::TraitInstance(TraitInstance::new(debug)); - let to_foo_ins = TypeId::TraitInstance(TraitInstance::new(to_foo)); - - debug.add_required_trait(&mut db, to_s_ins); - - { - let req = TraitInstance::new(debug); - - to_foo.add_required_trait(&mut db, req); - } - - let mut ctx = TypeContext::new(debug_ins); - - assert!(TypeRef::Owned(debug_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::Owned(to_foo_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::Ref(debug_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::Infer(debug_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::OwnedSelf - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::RefSelf - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - } - - #[test] - fn test_type_ref_implements_trait_with_type_parameter() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_foo = Trait::alloc( - &mut db, - "ToFoo".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string_ins = TraitInstance::new(to_string); - let debug_ins = TraitInstance::new(debug); - let to_foo_ins = TraitInstance::new(to_foo); - let param_ins = TypeRef::Owned(TypeId::TypeParameter(param)); - - debug.add_required_trait(&mut db, to_string_ins); - param.add_requirements(&mut db, vec![debug_ins]); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!( - param_ins.implements_trait_instance(&mut db, debug_ins, &mut ctx) - ); - assert!(param_ins.implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!( - !param_ins.implements_trait_instance(&mut db, to_foo_ins, &mut ctx) - ); - } - - #[test] - fn test_type_ref_implements_trait_with_other_variants() { - let mut db = Database::new(); - let trait_type = Trait::alloc( - &mut db, - "ToA".to_string(), - ModuleId(0), - Visibility::Private, - ); - let ins = TraitInstance::new(trait_type); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(!TypeRef::Any.implements_trait_instance(&mut db, ins, &mut ctx)); - assert!( - !TypeRef::Unknown.implements_trait_instance(&mut db, ins, &mut ctx) - ); - assert!( - TypeRef::Error.implements_trait_instance(&mut db, ins, &mut ctx) - ); - assert!( - TypeRef::Never.implements_trait_instance(&mut db, ins, &mut ctx) - ); - } - - #[test] - fn test_type_ref_type_name() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let param = TypeId::TypeParameter(TypeParameter::alloc( - &mut db, - "T".to_string(), - )); - - assert_eq!( - format_type(&db, TypeRef::Owned(string_ins)), - "String".to_string() - ); - assert_eq!(format_type(&db, TypeRef::Infer(param)), "T".to_string()); - assert_eq!( - format_type(&db, TypeRef::Ref(string_ins)), - "ref String".to_string() - ); - assert_eq!(format_type(&db, TypeRef::Never), "Never".to_string()); - assert_eq!(format_type(&db, TypeRef::Any), "Any".to_string()); - assert_eq!(format_type(&db, TypeRef::OwnedSelf), "Self".to_string()); - assert_eq!(format_type(&db, TypeRef::RefSelf), "ref Self".to_string()); - assert_eq!(format_type(&db, TypeRef::Error), "".to_string()); - assert_eq!(format_type(&db, TypeRef::Unknown), "".to_string()); - } - - #[test] - fn test_type_id_named_type_with_class() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - - assert_eq!( - TypeId::Class(array).named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - } - - #[test] - fn test_type_id_named_type_with_trait() { - let mut db = Database::new(); - let to_array = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param = to_array.new_type_parameter(&mut db, "T".to_string()); - - assert_eq!( - TypeId::Trait(to_array).named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - } - - #[test] - fn test_type_id_named_type_with_module() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let module = - Module::alloc(&mut db, ModuleName::new("foo"), "foo.inko".into()); - - let symbol = Symbol::Class(string); - let type_id = TypeId::Module(module); - - module.new_symbol(&mut db, "String".to_string(), symbol); - - assert_eq!(type_id.named_type(&db, "String"), Some(symbol)); - assert!(type_id.named_type(&db, "Foo").is_none()); - } - - #[test] - fn test_type_id_named_type_with_class_instance() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - let ins = TypeId::ClassInstance(ClassInstance::generic( - &mut db, - array, - TypeArguments::new(), - )); - - assert_eq!( - ins.named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - assert!(ins.named_type(&db, "E").is_none()); - } - - #[test] - fn test_type_id_named_type_with_trait_instance() { - let mut db = Database::new(); - let to_array = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param = to_array.new_type_parameter(&mut db, "T".to_string()); - let ins = TypeId::TraitInstance(TraitInstance::generic( - &mut db, - to_array, - TypeArguments::new(), - )); - - assert_eq!( - ins.named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - assert!(ins.named_type(&db, "E").is_none()); - } - - #[test] - fn test_type_id_named_type_with_type_parameter() { - let mut db = Database::new(); - let param = TypeId::TypeParameter(TypeParameter::alloc( - &mut db, - "T".to_string(), - )); - - assert!(param.named_type(&db, "T").is_none()); - } - - #[test] - fn test_type_id_named_type_with_function() { - let mut db = Database::new(); - let block = TypeId::Closure(Closure::alloc(&mut db, false)); - - assert!(block.named_type(&db, "T").is_none()); - } - - #[test] - fn test_type_ref_type_check_with_class() { - let mut db = Database::new(); - let typ1 = TypeRef::Owned(TypeId::Class(ClassId(0))); - let typ2 = TypeRef::Owned(TypeId::Class(ClassId(1))); - let typ3 = TypeRef::Owned(TypeId::Trait(TraitId(0))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, typ1, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ2, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_trait() { - let mut db = Database::new(); - let typ1 = TypeRef::Owned(TypeId::Trait(TraitId(0))); - let typ2 = TypeRef::Owned(TypeId::Trait(TraitId(1))); - let typ3 = TypeRef::Owned(TypeId::Class(ClassId(0))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, typ1, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ2, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_module() { - let mut db = Database::new(); - let typ1 = TypeRef::Owned(TypeId::Module(ModuleId(0))); - let typ2 = TypeRef::Owned(TypeId::Module(ModuleId(1))); - let typ3 = TypeRef::Owned(TypeId::Class(ClassId(0))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, typ1, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ2, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_class_instance() { - let mut db = Database::new(); - let cls1 = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let cls2 = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let ins1 = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(cls1))); - let ins2 = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(cls1))); - let ins3 = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(cls2))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(ins1.type_check(&mut db, ins1, &mut ctx, false)); - assert!(ins1.type_check(&mut db, ins2, &mut ctx, false)); - assert!(!ins1.type_check(&mut db, ins3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_trait_instance() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let requirement = TraitInstance::new(to_string); - - debug.add_required_trait(&mut db, requirement); - - let debug_ins = - TypeRef::Owned(TypeId::TraitInstance(TraitInstance::new(debug))); - let to_string_ins = TypeRef::Owned(TypeId::TraitInstance( - TraitInstance::new(to_string), - )); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(debug_ins.type_check(&mut db, to_string_ins, &mut ctx, true)); - } - - #[test] - fn test_type_ref_type_check_with_function() { - let mut db = Database::new(); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - - closure1.set_return_type(&mut db, TypeRef::Any); - closure2.set_return_type(&mut db, TypeRef::Any); - - let closure1_type = TypeRef::Owned(TypeId::Closure(closure1)); - let closure2_type = TypeRef::Owned(TypeId::Closure(closure2)); - - let int = Class::alloc( + #[test] + fn test_class_instance_generic() { + let mut db = Database::new(); + let id = Class::alloc( &mut db, - "Int".to_string(), + "A".to_string(), ClassKind::Regular, Visibility::Private, ModuleId(0), ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); + let ins1 = ClassInstance::generic(&mut db, id, TypeArguments::new()); + let ins2 = ClassInstance::generic(&mut db, id, TypeArguments::new()); - assert!(closure1_type.type_check( - &mut db, - closure2_type, - &mut ctx, - false - )); + assert_eq!(ins1.instance_of.0, FIRST_USER_CLASS_ID); + assert_eq!(ins1.type_arguments, 0); + + assert_eq!(ins2.instance_of.0, FIRST_USER_CLASS_ID); + assert_eq!(ins2.type_arguments, 1); } #[test] - fn test_type_id_format_type_with_class() { + fn test_method_alloc() { let mut db = Database::new(); - let id = TypeId::Class(Class::alloc( + let id = Method::alloc( &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, ModuleId(0), - )); + "foo".to_string(), + Visibility::Private, + MethodKind::Moving, + ); - assert_eq!(format_type(&db, id), "String"); + assert_eq!(id.0, 0); + assert_eq!(&db.methods[0].name, &"foo".to_string()); + assert_eq!(db.methods[0].kind, MethodKind::Moving); } #[test] - fn test_type_id_format_type_with_trait() { + fn test_method_id_named_type() { let mut db = Database::new(); - let id = TypeId::Trait(Trait::alloc( + let method = Method::alloc( &mut db, - "ToString".to_string(), ModuleId(0), + "foo".to_string(), Visibility::Private, - )); + MethodKind::Instance, + ); + let param = method.new_type_parameter(&mut db, "A".to_string()); - assert_eq!(format_type(&db, id), "ToString"); + assert_eq!( + method.named_type(&db, "A"), + Some(Symbol::TypeParameter(param)) + ); } #[test] - fn test_type_id_format_type_with_module() { + fn test_module_alloc() { let mut db = Database::new(); - let id = TypeId::Module(Module::alloc( - &mut db, - ModuleName::new("foo::bar"), - "foo/bar.inko".into(), - )); + let name = ModuleName::new("foo"); + let id = Module::alloc(&mut db, name.clone(), "foo.inko".into()); - assert_eq!(format_type(&db, id), "foo::bar"); + assert_eq!(id.0, 0); + assert_eq!(&db.modules[0].name, &name); + assert_eq!(&db.modules[0].file, &PathBuf::from("foo.inko")); } #[test] - fn test_type_id_format_type_with_class_instance() { + fn test_module_id_file() { let mut db = Database::new(); - let id = Class::alloc( + let id = Module::alloc( &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let ins = TypeId::ClassInstance(ClassInstance::new(id)); - assert_eq!(format_type(&db, ins), "String"); + assert_eq!(id.file(&db), PathBuf::from("test.inko")); } #[test] - fn test_type_id_format_type_with_tuple_instance() { + fn test_module_id_symbol() { let mut db = Database::new(); - let id = Class::alloc( + let id = Module::alloc( &mut db, - "MyTuple".to_string(), - ClassKind::Tuple, - Visibility::Private, - ModuleId(0), + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let param1 = id.new_type_parameter(&mut db, "A".to_string()); - let param2 = id.new_type_parameter(&mut db, "B".to_string()); - let mut args = TypeArguments::new(); - - args.assign(param1, TypeRef::Any); - args.assign(param2, TypeRef::Never); - let ins = - TypeId::ClassInstance(ClassInstance::generic(&mut db, id, args)); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - assert_eq!(format_type(&db, ins), "(Any, Never)"); + assert_eq!(id.symbol(&db, "A"), Some(Symbol::Module(id))); } #[test] - fn test_type_id_format_type_with_trait_instance() { + fn test_module_id_symbols() { let mut db = Database::new(); - let id = Trait::alloc( + let id = Module::alloc( &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let ins = TypeId::TraitInstance(TraitInstance::new(id)); - assert_eq!(format_type(&db, ins), "ToString"); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); + + assert_eq!( + id.symbols(&db), + vec![("A".to_string(), Symbol::Module(id))] + ); } #[test] - fn test_type_id_format_type_with_generic_class_instance() { + fn test_module_id_symbol_exists() { let mut db = Database::new(); - let id = Class::alloc( + let id = Module::alloc( &mut db, - "Future".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let param1 = id.new_type_parameter(&mut db, "T".to_string()); - id.new_type_parameter(&mut db, "E".to_string()); - - let mut targs = TypeArguments::new(); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - targs.assign(param1, TypeRef::Any); + assert!(id.symbol_exists(&db, "A")); + assert!(!id.symbol_exists(&db, "B")); + } - let ins = - TypeId::ClassInstance(ClassInstance::generic(&mut db, id, targs)); + #[test] + fn test_function_closure() { + let mut db = Database::new(); + let id = Closure::alloc(&mut db, false); - assert_eq!(format_type(&db, ins), "Future[Any, E]"); + assert_eq!(id.0, 0); } #[test] - fn test_type_id_format_type_with_generic_trait_instance() { + fn test_type_id_named_type_with_class() { let mut db = Database::new(); - let id = Trait::alloc( + let array = Class::alloc( &mut db, - "ToFoo".to_string(), - ModuleId(0), + "Array".to_string(), + ClassKind::Regular, Visibility::Private, + ModuleId(0), ); - let param1 = id.new_type_parameter(&mut db, "T".to_string()); - - id.new_type_parameter(&mut db, "E".to_string()); - - let mut targs = TypeArguments::new(); - - targs.assign(param1, TypeRef::Any); - - let ins = - TypeId::TraitInstance(TraitInstance::generic(&mut db, id, targs)); + let param = array.new_type_parameter(&mut db, "T".to_string()); - assert_eq!(format_type(&db, ins), "ToFoo[Any, E]"); + assert_eq!( + TypeId::Class(array).named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) + ); } #[test] - fn test_type_id_format_type_with_type_parameter() { + fn test_type_id_named_type_with_trait() { let mut db = Database::new(); - let param1 = TypeParameter::alloc(&mut db, "T".to_string()); - let param2 = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int = Trait::alloc( + let to_array = Trait::alloc( &mut db, - "ToInt".to_string(), + "ToArray".to_string(), ModuleId(0), Visibility::Private, ); - let param1_ins = TypeId::TypeParameter(param1); - let param2_ins = TypeId::TypeParameter(param2); - let to_string_ins = TraitInstance::new(to_string); - let to_int_ins = TraitInstance::new(to_int); - - param1.add_requirements(&mut db, vec![to_string_ins]); - param2.add_requirements(&mut db, vec![to_string_ins, to_int_ins]); + let param = to_array.new_type_parameter(&mut db, "T".to_string()); - assert_eq!(format_type(&db, param1_ins), "T: ToString"); - assert_eq!(format_type(&db, param2_ins), "T: ToString + ToInt"); + assert_eq!( + TypeId::Trait(to_array).named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) + ); } #[test] - fn test_type_id_format_type_with_rigid_type_parameter() { + fn test_type_id_named_type_with_module() { let mut db = Database::new(); - let param1 = TypeParameter::alloc(&mut db, "T".to_string()); - let param2 = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string = Trait::alloc( + let string = Class::alloc( &mut db, - "ToString".to_string(), - ModuleId(0), + "String".to_string(), + ClassKind::Regular, Visibility::Private, - ); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), ModuleId(0), - Visibility::Private, ); - let param1_ins = TypeId::RigidTypeParameter(param1); - let param2_ins = TypeId::RigidTypeParameter(param2); - let to_string_ins = TraitInstance::new(to_string); - let to_int_ins = TraitInstance::new(to_int); + let module = + Module::alloc(&mut db, ModuleName::new("foo"), "foo.inko".into()); + + let symbol = Symbol::Class(string); + let type_id = TypeId::Module(module); - param1.add_requirements(&mut db, vec![to_string_ins]); - param2.add_requirements(&mut db, vec![to_string_ins, to_int_ins]); + module.new_symbol(&mut db, "String".to_string(), symbol); - assert_eq!(format_type(&db, param1_ins), "T: ToString"); - assert_eq!(format_type(&db, param2_ins), "T: ToString + ToInt"); + assert_eq!(type_id.named_type(&db, "String"), Some(symbol)); + assert!(type_id.named_type(&db, "Foo").is_none()); } #[test] - fn test_type_id_format_type_with_closure() { + fn test_type_id_named_type_with_class_instance() { let mut db = Database::new(); - let class_a = Class::alloc( + let array = Class::alloc( &mut db, - "A".to_string(), + "Array".to_string(), ClassKind::Regular, Visibility::Private, ModuleId(0), ); - let class_b = Class::alloc( + let param = array.new_type_parameter(&mut db, "T".to_string()); + let ins = TypeId::ClassInstance(ClassInstance::generic( &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), + array, + TypeArguments::new(), + )); + + assert_eq!( + ins.named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) ); - let class_c = Class::alloc( + assert!(ins.named_type(&db, "E").is_none()); + } + + #[test] + fn test_type_id_named_type_with_trait_instance() { + let mut db = Database::new(); + let to_array = Trait::alloc( &mut db, - "C".to_string(), - ClassKind::Regular, - Visibility::Private, + "ToArray".to_string(), ModuleId(0), - ); - let class_d = Class::alloc( - &mut db, - "D".to_string(), - ClassKind::Regular, Visibility::Private, - ModuleId(0), ); - let block = Closure::alloc(&mut db, true); - - let ins_a = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); - - let ins_b = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); - - let ins_c = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_c))); - - let ins_d = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); - - block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); - block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); - block.set_throw_type(&mut db, ins_c); - block.set_return_type(&mut db, ins_d); - - let block_ins = TypeId::Closure(block); + let param = to_array.new_type_parameter(&mut db, "T".to_string()); + let ins = TypeId::TraitInstance(TraitInstance::generic( + &mut db, + to_array, + TypeArguments::new(), + )); - assert_eq!(format_type(&db, block_ins), "fn move (A, B) !! C -> D"); + assert_eq!( + ins.named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) + ); + assert!(ins.named_type(&db, "E").is_none()); } #[test] - fn test_type_id_implements_trait_with_other_variants() { + fn test_type_id_named_type_with_type_parameter() { let mut db = Database::new(); - let closure = TypeId::Closure(Closure::alloc(&mut db, false)); - let debug = Trait::alloc( + let param = TypeId::TypeParameter(TypeParameter::alloc( &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let debug_ins = TraitInstance::new(debug); + "T".to_string(), + )); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); + assert!(param.named_type(&db, "T").is_none()); + } - assert!( - !closure.implements_trait_instance(&mut db, debug_ins, &mut ctx) - ); + #[test] + fn test_type_id_named_type_with_function() { + let mut db = Database::new(); + let block = TypeId::Closure(Closure::alloc(&mut db, false)); + + assert!(block.named_type(&db, "T").is_none()); } #[test] @@ -8776,7 +4850,7 @@ mod tests { assert_eq!(&db.classes[4].name, BOOLEAN_NAME); assert_eq!(&db.classes[5].name, NIL_NAME); assert_eq!(&db.classes[6].name, BYTE_ARRAY_NAME); - assert_eq!(&db.classes[7].name, FUTURE_NAME); + assert_eq!(&db.classes[7].name, CHANNEL_NAME); } #[test] @@ -8796,11 +4870,19 @@ mod tests { db.module("foo"); } + #[test] + fn test_class_id_is_builtin() { + assert!(ClassId::int().is_builtin()); + assert!(!ClassId::tuple8().is_builtin()); + assert!(!ClassId(42).is_builtin()); + } + #[test] fn test_type_placeholder_id_assign() { let mut db = Database::new(); - let p1 = TypePlaceholder::alloc(&mut db); - let p2 = TypePlaceholder::alloc(&mut db); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let p1 = TypePlaceholder::alloc(&mut db, Some(param)); + let p2 = TypePlaceholder::alloc(&mut db, Some(param)); p1.assign(&db, TypeRef::Any); p2.assign(&db, TypeRef::Placeholder(p2)); @@ -8808,4 +4890,91 @@ mod tests { assert_eq!(p1.value(&db), Some(TypeRef::Any)); assert!(p2.value(&db).is_none()); } + + #[test] + fn test_type_placeholder_id_resolve() { + let mut db = Database::new(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, None); + let var3 = TypePlaceholder::alloc(&mut db, None); + + var1.assign(&db, TypeRef::Any); + var2.assign(&db, TypeRef::Placeholder(var1)); + var3.assign(&db, TypeRef::Placeholder(var2)); + + assert_eq!(var1.value(&db), Some(TypeRef::Any)); + assert_eq!(var2.value(&db), Some(TypeRef::Any)); + assert_eq!(var3.value(&db), Some(TypeRef::Any)); + } + + #[test] + fn test_type_ref_allow_as_ref() { + let mut db = Database::new(); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + let param = new_parameter(&mut db, "A"); + + var.assign(&db, owned(instance(int))); + + assert!(owned(instance(int)).allow_as_ref(&db)); + assert!(mutable(instance(int)).allow_as_ref(&db)); + assert!(immutable(instance(int)).allow_as_ref(&db)); + assert!(placeholder(var).allow_as_ref(&db)); + assert!(owned(rigid(param)).allow_as_ref(&db)); + assert!(TypeRef::Any.allow_as_ref(&db)); + assert!(!uni(instance(int)).allow_as_ref(&db)); + } + + #[test] + fn test_type_ref_allow_as_mut() { + let mut db = Database::new(); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "A"); + + param2.set_mutable(&mut db); + var.assign(&db, owned(instance(int))); + + assert!(owned(instance(int)).allow_as_mut(&db)); + assert!(mutable(instance(int)).allow_as_mut(&db)); + assert!(placeholder(var).allow_as_mut(&db)); + assert!(TypeRef::Any.allow_as_mut(&db)); + assert!(owned(rigid(param2)).allow_as_mut(&db)); + assert!(!immutable(instance(int)).allow_as_mut(&db)); + assert!(!owned(rigid(param1)).allow_as_mut(&db)); + assert!(!uni(instance(int)).allow_as_mut(&db)); + } + + #[test] + fn test_type_ref_as_ref() { + let mut db = Database::new(); + let int = ClassId::int(); + let param = new_parameter(&mut db, "A"); + + assert_eq!(owned(instance(int)).as_ref(&db), immutable(instance(int))); + assert_eq!( + uni(instance(int)).as_ref(&db), + TypeRef::RefUni(instance(int)) + ); + assert_eq!(owned(rigid(param)).as_ref(&db), immutable(rigid(param))); + } + + #[test] + fn test_type_ref_as_mut() { + let mut db = Database::new(); + let int = ClassId::int(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "A"); + + param2.set_mutable(&mut db); + + assert_eq!(owned(instance(int)).as_mut(&db), mutable(instance(int))); + assert_eq!( + uni(instance(int)).as_mut(&db), + TypeRef::MutUni(instance(int)) + ); + assert_eq!(owned(rigid(param1)).as_mut(&db), owned(rigid(param1))); + assert_eq!(owned(rigid(param2)).as_mut(&db), mutable(rigid(param2))); + } } diff --git a/types/src/module_name.rs b/types/src/module_name.rs index 2b38d9b00..4d2bf7054 100644 --- a/types/src/module_name.rs +++ b/types/src/module_name.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf, MAIN_SEPARATOR}; const MAIN_MODULE: &str = "main"; const SOURCE_EXT: &str = "inko"; -const SEPARATOR: &str = "::"; +pub const SEPARATOR: &str = "::"; /// The fully qualified name of a module. #[derive(Eq, PartialEq, Hash, Clone, Ord, PartialOrd)] @@ -50,6 +50,10 @@ impl ModuleName { path } + pub fn normalized_name(&self) -> String { + self.value.replace(SEPARATOR, "_") + } + pub fn as_str(&self) -> &str { self.value.as_str() } @@ -131,4 +135,12 @@ mod tests { assert_eq!(format!("{:?}", name), "ModuleName(foo::bar)".to_string()); } + + #[test] + fn test_normalized_name() { + assert_eq!( + ModuleName::new("std::foo::bar").normalized_name(), + "std_foo_bar" + ); + } } diff --git a/types/src/resolve.rs b/types/src/resolve.rs new file mode 100644 index 000000000..c18b85cb0 --- /dev/null +++ b/types/src/resolve.rs @@ -0,0 +1,804 @@ +//! Resolving abstract types into concrete types. +use crate::either::Either; +use crate::{ + ClassInstance, Closure, Database, TraitId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameterId, TypeRef, +}; +use std::collections::HashMap; + +/// A type that takes an abstract type and resolves it into a more concrete +/// type. +/// +/// For example, if a method has any type parameter bounds then this type +/// ensures any regular type parameters are turned into their corresponding +/// bounds. +pub struct TypeResolver<'a> { + db: &'a mut Database, + + /// A cache of types we've already resolved. + /// + /// This cache is used to handle recursive types, such as a type parameter + /// assigned to a placeholder that's assigned to itself. + cached: HashMap, + + /// The type arguments to use when resolving type parameters. + type_arguments: &'a TypeArguments, + + /// Any type parameters that have additional bounds set. + /// + /// If a type parameter is present in this structure, it's bounded version + /// is produced when resolving the parameter. + bounds: &'a TypeBounds, + + /// If resolved types should be made immutable or not. + immutable: bool, + + /// When set to true, non-rigid type parameters are turned into rigid + /// parameters instead of placeholders. + rigid: bool, + + /// The surrounding trait definition, if any. + /// + /// If present it's used to remap inherited type parameters to their correct + /// types. + surrounding_trait: Option, +} + +impl<'a> TypeResolver<'a> { + pub fn new( + db: &'a mut Database, + type_arguments: &'a TypeArguments, + bounds: &'a TypeBounds, + ) -> TypeResolver<'a> { + TypeResolver { + db, + type_arguments, + bounds, + immutable: false, + rigid: false, + surrounding_trait: None, + cached: HashMap::new(), + } + } + + pub fn with_immutable(mut self, immutable: bool) -> TypeResolver<'a> { + self.immutable = immutable; + self + } + + pub fn with_rigid(mut self, rigid: bool) -> TypeResolver<'a> { + self.rigid = rigid; + self + } + + pub fn resolve(&mut self, value: TypeRef) -> TypeRef { + if let Some(&cached) = self.cached.get(&value) { + return cached; + } + + // To handle recursive types we have to add the raw value first, then + // later update it with the resolved value. If we don't do this we'd + // just end up recursing into this method indefinitely. This also + // ensures we handle type parameters assigned to themselves, without + // needing extra logic. + self.cached.insert(value, value); + + let resolved = match value { + TypeRef::Owned(id) | TypeRef::Infer(id) => { + match self.resolve_type_id(id) { + Either::Left(res) => self.owned(res), + Either::Right(typ) => typ, + } + } + TypeRef::Ref(id) => match self.resolve_type_id(id) { + Either::Left(res) => TypeRef::Ref(res), + Either::Right(TypeRef::Owned(typ) | TypeRef::Mut(typ)) => { + TypeRef::Ref(typ) + } + Either::Right(TypeRef::Uni(typ) | TypeRef::MutUni(typ)) => { + TypeRef::RefUni(typ) + } + Either::Right(typ) => typ, + }, + TypeRef::Mut(id) => match self.resolve_type_id(id) { + Either::Left(res) => self.mutable(res), + Either::Right(TypeRef::Owned(typ)) => self.mutable(typ), + Either::Right(TypeRef::Uni(typ)) => self.mutable_uni(typ), + Either::Right(typ) => typ, + }, + TypeRef::Uni(id) => match self.resolve_type_id(id) { + Either::Left(res) => self.uni(res), + Either::Right(TypeRef::Owned(typ)) => self.uni(typ), + Either::Right(typ) => typ, + }, + TypeRef::RefUni(id) => match self.resolve_type_id(id) { + Either::Left(res) => TypeRef::RefUni(res), + Either::Right(typ) => typ, + }, + TypeRef::MutUni(id) => match self.resolve_type_id(id) { + Either::Left(res) => self.mutable_uni(res), + Either::Right(typ) => typ, + }, + // If a placeholder is unassigned we need to return it as-is. This + // way future use of the placeholder allows us to infer the current + // type. + TypeRef::Placeholder(id) => { + id.value(self.db).map(|v| self.resolve(v)).unwrap_or(value) + } + _ => value, + }; + + // No point in hashing again if the value is the same. + if value != resolved { + self.cached.insert(value, resolved); + } + + resolved + } + + fn resolve_type_id(&mut self, id: TypeId) -> Either { + match id { + TypeId::ClassInstance(ins) => { + let base = ins.instance_of; + + if !base.is_generic(self.db) { + return Either::Left(id); + } + + let mut args = ins.type_arguments(self.db).clone(); + + self.resolve_arguments(&mut args); + + Either::Left(TypeId::ClassInstance(ClassInstance::generic( + self.db, base, args, + ))) + } + TypeId::TraitInstance(ins) => { + let base = ins.instance_of; + + if !base.is_generic(self.db) { + return Either::Left(id); + } + + let mut args = ins.type_arguments(self.db).clone(); + + self.resolve_arguments(&mut args); + + Either::Left(TypeId::TraitInstance(TraitInstance::generic( + self.db, base, args, + ))) + } + TypeId::TypeParameter(pid) => { + let pid = self.remap_type_parameter(pid); + + match self.resolve_type_parameter(pid) { + Some(val) => Either::Right(val), + _ if self.rigid => { + Either::Left(TypeId::RigidTypeParameter(pid)) + } + _ => { + Either::Right(TypeRef::placeholder(self.db, Some(pid))) + } + } + } + TypeId::RigidTypeParameter(pid) => Either::Left( + TypeId::RigidTypeParameter(self.remap_type_parameter(pid)), + ), + TypeId::Closure(id) => { + let mut new = id.get(self.db).clone(); + let immutable = self.immutable; + + // The ownership of the closure's arguments and return type + // shouldn't be changed, instead the ability to use the closure + // in the first place is restricted by the type checker where + // needede. + self.immutable = false; + + for arg in new.arguments.mapping.values_mut() { + arg.value_type = self.resolve(arg.value_type); + } + + new.return_type = self.resolve(new.return_type); + self.immutable = immutable; + Either::Left(TypeId::Closure(Closure::add(self.db, new))) + } + _ => Either::Left(id), + } + } + + fn resolve_arguments(&mut self, arguments: &mut TypeArguments) { + for value in arguments.mapping.values_mut() { + *value = self.resolve(*value); + } + } + + fn resolve_type_parameter( + &mut self, + id: TypeParameterId, + ) -> Option { + // Type arguments are always mapped using the original type parameters. + // This way if we have a bounded parameter we can easily look up the + // corresponding argument. + let key = id.original(self.db).unwrap_or(id); + + if let Some(arg) = self.type_arguments.get(key) { + return Some(self.resolve(arg)); + } + + // Inside a trait we may end up referring to type parameters from a + // required trait. In this case we recursively resolve the type + // parameter chain until reaching the final type. + if let Some(arg) = self + .surrounding_trait + .and_then(|t| t.get(self.db).inherited_type_arguments.get(key)) + { + return Some(self.resolve(arg)); + } + + None + } + + fn remap_type_parameter(&self, id: TypeParameterId) -> TypeParameterId { + self.bounds.get(id).unwrap_or(id) + } + + fn owned(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::Ref(id) + } else { + TypeRef::Owned(id) + } + } + + fn uni(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::RefUni(id) + } else { + TypeRef::Uni(id) + } + } + + fn mutable(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::Ref(id) + } else { + TypeRef::Mut(id) + } + } + + fn mutable_uni(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::RefUni(id) + } else { + TypeRef::MutUni(id) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::{ + closure, generic_instance_id, generic_trait_instance, + generic_trait_instance_id, immutable, immutable_uni, infer, instance, + mutable, mutable_uni, new_parameter, new_trait, owned, parameter, + placeholder, rigid, type_arguments, type_bounds, uni, + }; + use crate::{Block, ClassId, Closure, TypePlaceholder, TypePlaceholderId}; + + fn resolve( + db: &mut Database, + type_arguments: &TypeArguments, + bounds: &TypeBounds, + source: TypeRef, + ) -> TypeRef { + TypeResolver::new(db, type_arguments, bounds).resolve(source) + } + + fn resolve_immutable( + db: &mut Database, + type_arguments: &TypeArguments, + bounds: &TypeBounds, + source: TypeRef, + ) -> TypeRef { + TypeResolver::new(db, type_arguments, bounds) + .with_immutable(true) + .resolve(source) + } + + #[test] + fn test_owned() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(instance(string))), + owned(instance(string)) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, owned(instance(string))), + immutable(instance(string)) + ); + } + + #[test] + fn test_infer() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, infer(instance(string))), + owned(instance(string)) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, infer(instance(string))), + immutable(instance(string)) + ); + } + + #[test] + fn test_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, uni(instance(string))), + uni(instance(string)) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, uni(instance(string))), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_ref() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(instance(string))), + immutable(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + immutable(instance(string)) + ), + immutable(instance(string)) + ); + } + + #[test] + fn test_ref_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable_uni(instance(string))), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + immutable_uni(instance(string)) + ), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_mut() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(instance(string))), + mutable(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable(instance(string)) + ), + immutable(instance(string)) + ); + } + + #[test] + fn test_mut_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable_uni(instance(string))), + mutable_uni(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable_uni(instance(string)) + ), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_placeholder() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, None); + + var1.assign(&db, owned(instance(string))); + + assert_eq!( + resolve(&mut db, &args, &bounds, placeholder(var1)), + owned(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, placeholder(var2)), + placeholder(var2) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, placeholder(var1)), + immutable(instance(string)) + ); + } + + #[test] + fn test_type_parameter() { + let mut db = Database::new(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let args = type_arguments(vec![(param1, owned(instance(string)))]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(param1))), + owned(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(rigid(param1))), + owned(rigid(param1)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, infer(parameter(param1))), + owned(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + infer(parameter(param1)) + ), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(rigid(param2))), + owned(rigid(param2)) + ); + } + + #[test] + fn test_type_parameter_as_reference() { + let mut db = Database::new(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let args = type_arguments(vec![ + (param1, owned(instance(string))), + (param2, immutable(instance(string))), + ]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param1))), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param2))), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param1))), + mutable(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable(parameter(param1)) + ), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param2))), + immutable(instance(string)) + ); + } + + #[test] + fn test_type_parameter_as_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let args = type_arguments(vec![ + (param1, uni(instance(string))), + (param2, immutable_uni(instance(string))), + ]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param1))), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param2))), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param1))), + mutable_uni(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable(parameter(param1)) + ), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param2))), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_type_parameter_surrounding_trait() { + let mut db = Database::new(); + let string = ClassId::string(); + let to_foo = new_trait(&mut db, "ToFoo"); + let to_bar = new_trait(&mut db, "ToBar"); + let foo_param = to_foo.new_type_parameter(&mut db, "A".to_string()); + let bar_param = to_bar.new_type_parameter(&mut db, "B".to_string()); + + { + let ins = generic_trait_instance( + &mut db, + to_foo, + vec![owned(parameter(bar_param))], + ); + + // ToBar[B]: ToFoo[B] + to_bar.add_required_trait(&mut db, ins); + } + + let args = type_arguments(vec![(bar_param, owned(instance(string)))]); + let bounds = TypeBounds::new(); + let mut resolver = TypeResolver::new(&mut db, &args, &bounds); + + resolver.surrounding_trait = Some(to_bar); + + assert_eq!( + resolver.resolve(owned(parameter(foo_param))), + owned(instance(string)) + ); + } + + #[test] + fn test_generic_class() { + let mut db = Database::new(); + let array = ClassId::array(); + let string = ClassId::string(); + let param = new_parameter(&mut db, "A"); + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let args = type_arguments(vec![(param, owned(instance(string)))]); + let bounds = TypeBounds::new(); + let input = owned(generic_instance_id( + &mut db, + array, + vec![owned(parameter(param))], + )); + + let arg = match resolve(&mut db, &args, &bounds, input) { + TypeRef::Owned(TypeId::ClassInstance(ins)) => { + ins.type_arguments(&db).get(array_param).unwrap() + } + _ => TypeRef::Unknown, + }; + + assert_eq!(arg, owned(instance(string))); + } + + #[test] + fn test_generic_class_with_parameter_chain() { + let mut db = Database::new(); + let array = ClassId::array(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let param3 = new_parameter(&mut db, "C"); + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let args = type_arguments(vec![ + (param1, owned(parameter(param2))), + (param2, owned(parameter(param3))), + (param3, owned(instance(string))), + ]); + let bounds = TypeBounds::new(); + let input = owned(generic_instance_id( + &mut db, + array, + vec![owned(parameter(param1))], + )); + + let arg = match resolve(&mut db, &args, &bounds, input) { + TypeRef::Owned(TypeId::ClassInstance(ins)) => { + ins.type_arguments(&db).get(array_param).unwrap() + } + _ => TypeRef::Unknown, + }; + + assert_eq!(arg, owned(instance(string))); + } + + #[test] + fn test_generic_trait() { + let mut db = Database::new(); + let to_foo = new_trait(&mut db, "ToFoo"); + let string = ClassId::string(); + let param = new_parameter(&mut db, "A"); + let trait_param = to_foo.new_type_parameter(&mut db, "T".to_string()); + let args = type_arguments(vec![(param, owned(instance(string)))]); + let bounds = TypeBounds::new(); + let input = owned(generic_trait_instance_id( + &mut db, + to_foo, + vec![owned(parameter(param))], + )); + + let arg = match resolve(&mut db, &args, &bounds, input) { + TypeRef::Owned(TypeId::TraitInstance(ins)) => { + ins.type_arguments(&db).get(trait_param).unwrap() + } + _ => TypeRef::Unknown, + }; + + assert_eq!(arg, owned(instance(string))); + } + + #[test] + fn test_closure() { + let mut db = Database::new(); + let fun = Closure::alloc(&mut db, false); + let param = new_parameter(&mut db, "T"); + + fun.set_return_type(&mut db, owned(parameter(param))); + fun.new_argument( + &mut db, + "a".to_string(), + owned(rigid(param)), + infer(parameter(param)), + ); + + let args = type_arguments(vec![(param, TypeRef::Any)]); + let bounds = TypeBounds::new(); + let output = match resolve(&mut db, &args, &bounds, owned(closure(fun))) + { + TypeRef::Owned(TypeId::Closure(id)) => id, + _ => panic!("Expected the resolved value to be a closure"), + }; + + assert_eq!(output.return_type(&db), TypeRef::Any); + assert_eq!(output.arguments(&db)[0].value_type, TypeRef::Any); + } + + #[test] + fn test_recursive() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let var = TypePlaceholder::alloc(&mut db, None); + + var.assign(&db, owned(parameter(param))); + + let args = type_arguments(vec![(param, placeholder(var))]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(param))), + owned(parameter(param)) + ); + } + + #[test] + fn test_bounded_parameter() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let bound = new_parameter(&mut db, "A"); + + bound.set_original(&mut db, param); + + let args = type_arguments(vec![(param, TypeRef::Any)]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(bound))), + TypeRef::Any, + ); + } + + #[test] + fn test_type_bounds() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let bound = new_parameter(&mut db, "A"); + + bound.set_original(&mut db, param); + + let args = TypeArguments::new(); + let bounds = type_bounds(vec![(param, bound)]); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(rigid(param))), + owned(rigid(bound)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(param))), + placeholder(TypePlaceholderId(0)) + ); + + assert_eq!(TypePlaceholderId(0).required(&db), Some(bound)); + } +} diff --git a/types/src/test.rs b/types/src/test.rs new file mode 100644 index 000000000..e532a3bae --- /dev/null +++ b/types/src/test.rs @@ -0,0 +1,165 @@ +use crate::{ + Class, ClassId, ClassInstance, ClassKind, ClosureId, Database, ModuleId, + Trait, TraitId, TraitImplementation, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameter, TypeParameterId, TypePlaceholderId, + TypeRef, Visibility, +}; + +pub(crate) fn new_class(db: &mut Database, name: &str) -> ClassId { + Class::alloc( + db, + name.to_string(), + ClassKind::Regular, + Visibility::Public, + ModuleId(0), + ) +} + +pub(crate) fn new_extern_class(db: &mut Database, name: &str) -> ClassId { + Class::alloc( + db, + name.to_string(), + ClassKind::Extern, + Visibility::Public, + ModuleId(0), + ) +} + +pub(crate) fn new_trait(db: &mut Database, name: &str) -> TraitId { + Trait::alloc(db, name.to_string(), ModuleId(0), Visibility::Public) +} + +pub(crate) fn new_parameter(db: &mut Database, name: &str) -> TypeParameterId { + TypeParameter::alloc(db, name.to_string()) +} + +pub(crate) fn implement( + db: &mut Database, + instance: TraitInstance, + class: ClassId, +) { + class.add_trait_implementation( + db, + TraitImplementation { instance, bounds: TypeBounds::new() }, + ); +} + +pub(crate) fn owned(id: TypeId) -> TypeRef { + TypeRef::Owned(id) +} + +pub(crate) fn uni(id: TypeId) -> TypeRef { + TypeRef::Uni(id) +} + +pub(crate) fn immutable_uni(id: TypeId) -> TypeRef { + TypeRef::RefUni(id) +} + +pub(crate) fn mutable_uni(id: TypeId) -> TypeRef { + TypeRef::MutUni(id) +} + +pub(crate) fn infer(id: TypeId) -> TypeRef { + TypeRef::Infer(id) +} + +pub(crate) fn immutable(id: TypeId) -> TypeRef { + TypeRef::Ref(id) +} + +pub(crate) fn mutable(id: TypeId) -> TypeRef { + TypeRef::Mut(id) +} + +pub(crate) fn placeholder(id: TypePlaceholderId) -> TypeRef { + TypeRef::Placeholder(id) +} + +pub(crate) fn instance(class: ClassId) -> TypeId { + TypeId::ClassInstance(ClassInstance::new(class)) +} + +pub(crate) fn parameter(id: TypeParameterId) -> TypeId { + TypeId::TypeParameter(id) +} + +pub(crate) fn rigid(id: TypeParameterId) -> TypeId { + TypeId::RigidTypeParameter(id) +} + +pub(crate) fn closure(id: ClosureId) -> TypeId { + TypeId::Closure(id) +} + +pub(crate) fn generic_instance_id( + db: &mut Database, + class: ClassId, + arguments: Vec, +) -> TypeId { + let mut args = TypeArguments::new(); + + for (param, arg) in + class.type_parameters(db).into_iter().zip(arguments.into_iter()) + { + args.assign(param, arg); + } + + TypeId::ClassInstance(ClassInstance::generic(db, class, args)) +} + +pub(crate) fn generic_trait_instance_id( + db: &mut Database, + trait_id: TraitId, + arguments: Vec, +) -> TypeId { + TypeId::TraitInstance(generic_trait_instance(db, trait_id, arguments)) +} + +pub(crate) fn generic_trait_instance( + db: &mut Database, + trait_id: TraitId, + arguments: Vec, +) -> TraitInstance { + let mut args = TypeArguments::new(); + + for (param, arg) in + trait_id.type_parameters(db).into_iter().zip(arguments.into_iter()) + { + args.assign(param, arg); + } + + TraitInstance::generic(db, trait_id, args) +} + +pub(crate) fn trait_instance(trait_id: TraitId) -> TraitInstance { + TraitInstance::new(trait_id) +} + +pub(crate) fn trait_instance_id(trait_id: TraitId) -> TypeId { + TypeId::TraitInstance(trait_instance(trait_id)) +} + +pub(crate) fn type_arguments( + pairs: Vec<(TypeParameterId, TypeRef)>, +) -> TypeArguments { + let mut args = TypeArguments::new(); + + for (param, typ) in pairs { + args.assign(param, typ); + } + + args +} + +pub(crate) fn type_bounds( + pairs: Vec<(TypeParameterId, TypeParameterId)>, +) -> TypeBounds { + let mut bounds = TypeBounds::new(); + + for (param, bound) in pairs { + bounds.set(param, bound); + } + + bounds +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml deleted file mode 100644 index 82dc4f1fc..000000000 --- a/vm/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "vm" -version = "0.10.0" # VERSION -authors = ["Yorick Peterse "] -edition = "2021" -build = "build.rs" -license = "MPL-2.0" - -[lib] -doctest = false - -[features] -libffi-system = ["libffi/system"] - -[dependencies] -libloading = "^0.7" -libffi = "^3.0" -crossbeam-utils = "^0.8" -crossbeam-queue = "^0.3" -libc = "^0.2" -ahash = "^0.8" -rand = { version = "^0.8", features = ["default", "small_rng"] } -polling = "^2.0" -unicode-segmentation = "^1.8" -bytecode = { path = "../bytecode" } - -[dependencies.socket2] -version = "^0.4" -features = ["all"] - -[target.'cfg(windows)'.dependencies.windows-sys] -version = "^0.36" -features = [ - "Win32_Foundation", - "Win32_Networking_WinSock", - "Win32_System_Time", - "Win32_System_SystemInformation", - "Win32_System_SystemServices", -] diff --git a/vm/src/builtin_functions.rs b/vm/src/builtin_functions.rs deleted file mode 100644 index a7d03e3c9..000000000 --- a/vm/src/builtin_functions.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! A registry of builtin functions that can be called in Inko source code. -use crate::mem::Pointer; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::io::Read; - -mod array; -mod byte_array; -mod env; -mod ffi; -mod float; -mod fs; -mod hasher; -mod process; -mod random; -mod socket; -mod stdio; -mod string; -mod sys; -mod time; - -/// A builtin function that can be called from Inko source code. -pub(crate) type BuiltinFunction = fn( - &State, - &mut Thread, - ProcessPointer, - &[Pointer], -) -> Result; - -/// Reads a number of bytes from a buffer into a Vec. -pub(crate) fn read_into( - stream: &mut T, - output: &mut Vec, - size: i64, -) -> Result { - let read = if size > 0 { - stream.take(size as u64).read_to_end(output)? - } else { - stream.read_to_end(output)? - }; - - Ok(read as i64) -} - -/// A collection of builtin functions. -pub(crate) struct BuiltinFunctions { - functions: Vec, -} - -impl BuiltinFunctions { - /// Creates a collection of builtin functions and registers all functions - /// that Inko ships with. - pub(crate) fn new() -> Self { - Self { - functions: vec![ - byte_array::byte_array_drain_to_string, - byte_array::byte_array_to_string, - sys::child_process_drop, - sys::child_process_spawn, - sys::child_process_stderr_close, - sys::child_process_stderr_read, - sys::child_process_stdin_close, - sys::child_process_stdin_flush, - sys::child_process_stdin_write_bytes, - sys::child_process_stdin_write_string, - sys::child_process_stdout_close, - sys::child_process_stdout_read, - sys::child_process_try_wait, - sys::child_process_wait, - env::env_arguments, - env::env_executable, - env::env_get, - env::env_get_working_directory, - env::env_home_directory, - env::env_platform, - env::env_set_working_directory, - env::env_temp_directory, - env::env_variables, - ffi::ffi_function_attach, - ffi::ffi_function_call, - ffi::ffi_function_drop, - ffi::ffi_library_drop, - ffi::ffi_library_open, - ffi::ffi_pointer_address, - ffi::ffi_pointer_attach, - ffi::ffi_pointer_from_address, - ffi::ffi_pointer_read, - ffi::ffi_pointer_write, - ffi::ffi_type_alignment, - ffi::ffi_type_size, - fs::directory_create, - fs::directory_create_recursive, - fs::directory_list, - fs::directory_remove, - fs::directory_remove_recursive, - fs::file_copy, - fs::file_drop, - fs::file_flush, - fs::file_open_append_only, - fs::file_open_read_append, - fs::file_open_read_only, - fs::file_open_read_write, - fs::file_open_write_only, - fs::file_read, - fs::file_remove, - fs::file_seek, - fs::file_size, - fs::file_write_bytes, - fs::file_write_string, - fs::path_accessed_at, - fs::path_created_at, - fs::path_exists, - fs::path_is_directory, - fs::path_is_file, - fs::path_modified_at, - hasher::hasher_drop, - hasher::hasher_new, - hasher::hasher_to_hash, - hasher::hasher_write_int, - process::process_stacktrace_drop, - process::process_call_frame_line, - process::process_call_frame_name, - process::process_call_frame_path, - process::process_stacktrace, - random::random_bytes, - random::random_float, - random::random_float_range, - random::random_int, - random::random_int_range, - socket::socket_accept_ip, - socket::socket_accept_unix, - socket::socket_address_pair_address, - socket::socket_address_pair_drop, - socket::socket_address_pair_port, - socket::socket_allocate_ipv4, - socket::socket_allocate_ipv6, - socket::socket_allocate_unix, - socket::socket_bind, - socket::socket_connect, - socket::socket_drop, - socket::socket_get_broadcast, - socket::socket_get_keepalive, - socket::socket_get_linger, - socket::socket_get_nodelay, - socket::socket_get_only_v6, - socket::socket_get_recv_size, - socket::socket_get_reuse_address, - socket::socket_get_reuse_port, - socket::socket_get_send_size, - socket::socket_get_ttl, - socket::socket_listen, - socket::socket_local_address, - socket::socket_peer_address, - socket::socket_read, - socket::socket_receive_from, - socket::socket_send_bytes_to, - socket::socket_send_string_to, - socket::socket_set_broadcast, - socket::socket_set_keepalive, - socket::socket_set_linger, - socket::socket_set_nodelay, - socket::socket_set_only_v6, - socket::socket_set_recv_size, - socket::socket_set_reuse_address, - socket::socket_set_reuse_port, - socket::socket_set_send_size, - socket::socket_set_ttl, - socket::socket_shutdown_read, - socket::socket_shutdown_read_write, - socket::socket_shutdown_write, - socket::socket_try_clone, - socket::socket_write_bytes, - socket::socket_write_string, - stdio::stderr_flush, - stdio::stderr_write_bytes, - stdio::stderr_write_string, - stdio::stdin_read, - stdio::stdout_flush, - stdio::stdout_write_bytes, - stdio::stdout_write_string, - string::string_to_byte_array, - string::string_to_float, - string::string_to_int, - string::string_to_lower, - string::string_to_upper, - time::time_monotonic, - time::time_system, - time::time_system_offset, - sys::cpu_cores, - string::string_characters, - string::string_characters_next, - string::string_characters_drop, - string::string_concat_array, - array::array_reserve, - array::array_capacity, - process::process_stacktrace_length, - float::float_to_bits, - float::float_from_bits, - random::random_new, - random::random_from_int, - random::random_drop, - string::string_slice_bytes, - byte_array::byte_array_slice, - byte_array::byte_array_append, - byte_array::byte_array_copy_from, - byte_array::byte_array_resize, - ], - } - } - - pub(crate) fn get(&self, index: u16) -> BuiltinFunction { - self.functions[index as usize] - } -} diff --git a/vm/src/builtin_functions/array.rs b/vm/src/builtin_functions/array.rs deleted file mode 100644 index e84cd8a70..000000000 --- a/vm/src/builtin_functions/array.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::mem::{Array, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn array_reserve( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let array = unsafe { args[0].get_mut::() }; - let size = unsafe { Int::read(args[1]) }; - - array.value_mut().reserve(size as usize); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn array_capacity( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let array = unsafe { args[0].get::() }; - let cap = array.value().capacity() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), cap)) -} diff --git a/vm/src/builtin_functions/byte_array.rs b/vm/src/builtin_functions/byte_array.rs deleted file mode 100644 index a3fd6138c..000000000 --- a/vm/src/builtin_functions/byte_array.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::immutable_string::ImmutableString; -use crate::mem::{ByteArray, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::cmp::min; - -pub(crate) fn byte_array_to_string( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes_ref = unsafe { args[0].get_mut::() }; - let string = ImmutableString::from_utf8(bytes_ref.value().clone()); - let res = InkoString::from_immutable_string( - state.permanent_space.string_class(), - string, - ); - - Ok(res) -} - -pub(crate) fn byte_array_drain_to_string( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes_ref = unsafe { args[0].get_mut::() }; - let string = ImmutableString::from_utf8(bytes_ref.take_bytes()); - let res = InkoString::from_immutable_string( - state.permanent_space.string_class(), - string, - ); - - Ok(res) -} - -pub(crate) fn byte_array_slice( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes = unsafe { args[0].get::() }; - let start = unsafe { Int::read(args[1]) } as usize; - let len = unsafe { Int::read(args[2]) } as usize; - let end = min((start + len) as usize, bytes.value().len()); - - Ok(ByteArray::alloc( - state.permanent_space.byte_array_class(), - bytes.value()[start..end].to_vec(), - )) -} - -pub(crate) fn byte_array_append( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let target = unsafe { args[0].get_mut::() }; - let source = unsafe { args[1].get_mut::() }; - - target.value_mut().append(source.value_mut()); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn byte_array_copy_from( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let target = unsafe { args[0].get_mut::() }; - let source = unsafe { args[1].get_mut::() }; - let start = unsafe { Int::read(args[2]) } as usize; - let len = unsafe { Int::read(args[3]) } as usize; - let end = min((start + len) as usize, source.value().len()); - let slice = &source.value()[start..end]; - let amount = slice.len() as i64; - - target.value_mut().extend_from_slice(slice); - Ok(Int::alloc(state.permanent_space.int_class(), amount)) -} - -pub(crate) fn byte_array_resize( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes = unsafe { args[0].get_mut::() }; - let size = unsafe { Int::read(args[1]) as usize }; - let filler = unsafe { Int::read(args[2]) as u8 }; - - bytes.value_mut().resize(size, filler); - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/env.rs b/vm/src/builtin_functions/env.rs deleted file mode 100644 index 940e8ab99..000000000 --- a/vm/src/builtin_functions/env.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Functions for setting/getting environment and operating system data. -use crate::mem::{Array, Pointer, String as InkoString}; -use crate::platform; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::env; -use std::path::PathBuf; - -pub(crate) fn env_get( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let var_name = unsafe { InkoString::read(&arguments[0]) }; - let result = state - .environment - .get(var_name) - .cloned() - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(result) -} - -pub(crate) fn env_variables( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let names = state - .environment - .keys() - .map(|key| { - InkoString::alloc(state.permanent_space.string_class(), key.clone()) - }) - .collect(); - - Ok(Array::alloc(state.permanent_space.array_class(), names)) -} - -pub(crate) fn env_home_directory( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - // Rather than performing all sorts of magical incantations to get the home - // directory, we're just going to require that these environment variables - // are set. - let var = if cfg!(windows) { - state.environment.get("USERPROFILE") - } else { - state.environment.get("HOME") - }; - - // If the home is explicitly set to an empty string we still ignore it, - // because there's no scenario in which Some("") is useful. - let result = var - .cloned() - .filter(|p| unsafe { !InkoString::read(p).is_empty() }) - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(result) -} - -pub(crate) fn env_temp_directory( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let path = canonalize(env::temp_dir().to_string_lossy().into_owned()); - - Ok(InkoString::alloc(state.permanent_space.string_class(), path)) -} - -pub(crate) fn env_get_working_directory( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let path = env::current_dir() - .map(|path| path.to_string_lossy().into_owned()) - .map(canonalize)?; - - Ok(InkoString::alloc(state.permanent_space.string_class(), path)) -} - -pub(crate) fn env_set_working_directory( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let dir = unsafe { InkoString::read(&arguments[0]) }; - - env::set_current_dir(dir)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn env_arguments( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Array::alloc( - state.permanent_space.array_class(), - state.arguments.clone(), - )) -} - -pub(crate) fn env_platform( - _: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Pointer::int(platform::operating_system())) -} - -pub(crate) fn env_executable( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let path = env::current_exe()?.to_string_lossy().into_owned(); - - Ok(InkoString::alloc(state.permanent_space.string_class(), path)) -} - -fn canonalize(path: String) -> String { - PathBuf::from(&path) - .canonicalize() - .map(|p| p.to_string_lossy().into_owned()) - .unwrap_or(path) -} diff --git a/vm/src/builtin_functions/ffi.rs b/vm/src/builtin_functions/ffi.rs deleted file mode 100644 index 3566ce37f..000000000 --- a/vm/src/builtin_functions/ffi.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! Functions for interacting with C code from Inko. -use crate::ffi::{ - type_alignment, type_size, Function, Library, Pointer as ForeignPointer, -}; -use crate::mem::{Array, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn ffi_library_open( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let names = unsafe { arguments[0].get::() }.value(); - let result = Library::from_pointers(names) - .map(Pointer::boxed) - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(result) -} - -pub(crate) fn ffi_function_attach( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let func = unsafe { - let lib = arguments[0].get::(); - let name = InkoString::read(&arguments[1]); - let args = arguments[2].get::().value(); - let rtype = arguments[3]; - - Function::attach(lib, name, args, rtype)? - }; - - Ok(func.map(Pointer::boxed).unwrap_or_else(Pointer::undefined_singleton)) -} - -pub(crate) fn ffi_function_call( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let func = unsafe { arguments[0].get::() }; - let args = &arguments[1..]; - - Ok(unsafe { func.call(state, args)? }) -} - -pub(crate) fn ffi_pointer_attach( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let lib = unsafe { arguments[0].get::() }; - let name = unsafe { InkoString::read(&arguments[1]) }; - let raw_ptr = unsafe { - lib.get(name) - .map(|ptr| Pointer::new(ptr.as_ptr())) - .unwrap_or_else(Pointer::undefined_singleton) - }; - - Ok(raw_ptr) -} - -pub(crate) fn ffi_pointer_read( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let ptr = ForeignPointer::new(arguments[0].as_ptr() as _); - let kind = arguments[1]; - let offset = unsafe { Int::read(arguments[2]) as usize }; - let result = unsafe { ptr.with_offset(offset).read_as(state, kind)? }; - - Ok(result) -} - -pub(crate) fn ffi_pointer_write( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let ptr = ForeignPointer::new(arguments[0].as_ptr() as _); - let kind = arguments[1]; - let offset = unsafe { Int::read(arguments[2]) as usize }; - let value = arguments[3]; - - unsafe { - ptr.with_offset(offset).write_as(kind, value)?; - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn ffi_pointer_from_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let addr = unsafe { Int::read(arguments[0]) }; - - Ok(Pointer::new(addr as _)) -} - -pub(crate) fn ffi_pointer_address( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let addr = arguments[0].as_ptr() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), addr)) -} - -pub(crate) fn ffi_type_size( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - - type_size(kind).map_err(|e| e.into()) -} - -pub(crate) fn ffi_type_alignment( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - - type_alignment(kind).map_err(|e| e.into()) -} - -pub(crate) fn ffi_library_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn ffi_function_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/float.rs b/vm/src/builtin_functions/float.rs deleted file mode 100644 index afc38a70a..000000000 --- a/vm/src/builtin_functions/float.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::mem::{Float, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn float_to_bits( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bits = unsafe { Float::read(args[0]) }.to_bits() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), bits)) -} - -pub(crate) fn float_from_bits( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bits = unsafe { Int::read(args[0]) } as u64; - - Ok(Float::alloc(state.permanent_space.float_class(), f64::from_bits(bits))) -} diff --git a/vm/src/builtin_functions/fs.rs b/vm/src/builtin_functions/fs.rs deleted file mode 100644 index 16e9c30e2..000000000 --- a/vm/src/builtin_functions/fs.rs +++ /dev/null @@ -1,379 +0,0 @@ -//! Functions for working with the file system. -//! -//! Files aren't allocated onto the Inko heap. Instead, we allocate them using -//! Rust's allocator and convert them into an Inko pointer. Dropping a file -//! involves turning that pointer back into a File, then dropping the Rust -//! object. -//! -//! This approach means the VM doesn't need to know anything about what objects -//! to use for certain files, how to store file paths, etc; instead we can keep -//! all that in the standard library. -use crate::builtin_functions::read_into; -use crate::mem::{Array, ByteArray, Float, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::fs::{self, File, OpenOptions}; -use std::io::{Seek, SeekFrom, Write}; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub(crate) fn file_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn file_seek( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let offset = unsafe { Int::read(arguments[1]) }; - let seek = if offset < 0 { - SeekFrom::End(offset) - } else { - SeekFrom::Start(offset as u64) - }; - - let result = thread.blocking(|| file.seek(seek))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), result)) -} - -pub(crate) fn file_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - - thread.blocking(|| file.flush())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn file_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let input = unsafe { InkoString::read(&arguments[1]).as_bytes() }; - let written = thread.blocking(|| file.write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), written)) -} - -pub(crate) fn file_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let input = unsafe { arguments[1].get::() }; - let written = thread.blocking(|| file.write(input.value()))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), written)) -} - -pub(crate) fn file_copy( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let src = unsafe { InkoString::read(&arguments[0]) }; - let dst = unsafe { InkoString::read(&arguments[1]) }; - let copied = thread.blocking(|| fs::copy(src, dst))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), copied)) -} - -pub(crate) fn file_size( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let size = thread.blocking(|| fs::metadata(path))?.len() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn file_remove( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::remove_file(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn path_created_at( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path))?; - let time = system_time_to_timestamp(meta.created()?); - - Ok(Float::alloc(state.permanent_space.float_class(), time)) -} - -pub(crate) fn path_modified_at( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path))?; - let time = system_time_to_timestamp(meta.modified()?); - - Ok(Float::alloc(state.permanent_space.float_class(), time)) -} - -pub(crate) fn path_accessed_at( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path))?; - let time = system_time_to_timestamp(meta.accessed()?); - - Ok(Float::alloc(state.permanent_space.float_class(), time)) -} - -pub(crate) fn path_is_file( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path)); - - if meta.map(|m| m.is_file()).unwrap_or(false) { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn path_is_directory( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path)); - - if meta.map(|m| m.is_dir()).unwrap_or(false) { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn path_exists( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path)); - - if meta.is_ok() { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn file_open_read_only( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.read(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_write_only( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.write(true).truncate(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_append_only( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.append(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_read_write( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.read(true).write(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_read_append( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.read(true).append(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let buff = unsafe { arguments[1].get_mut::() }; - let size = unsafe { Int::read(arguments[2]) } as i64; - let read = thread.blocking(|| read_into(file, buff.value_mut(), size))?; - - Ok(Int::alloc(state.permanent_space.int_class(), read)) -} - -pub(crate) fn directory_create( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::create_dir(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_create_recursive( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::create_dir_all(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_remove( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::remove_dir(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_remove_recursive( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::remove_dir_all(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_list( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let mut paths = Vec::new(); - - for entry in thread.blocking(|| fs::read_dir(path))? { - let entry = entry?; - let path = entry.path().to_string_lossy().to_string(); - let pointer = - InkoString::alloc(state.permanent_space.string_class(), path); - - paths.push(pointer); - } - - Ok(Array::alloc(state.permanent_space.array_class(), paths)) -} - -fn open_file( - thread: &mut Thread, - options: OpenOptions, - path_ptr: Pointer, -) -> Result { - let path = unsafe { InkoString::read(&path_ptr) }; - - thread - .blocking(|| options.open(path)) - .map(Pointer::boxed) - .map_err(RuntimeError::from) -} - -fn system_time_to_timestamp(time: SystemTime) -> f64 { - let duration = if time < UNIX_EPOCH { - UNIX_EPOCH.duration_since(time) - } else { - time.duration_since(UNIX_EPOCH) - }; - - duration.unwrap().as_secs_f64() -} diff --git a/vm/src/builtin_functions/hasher.rs b/vm/src/builtin_functions/hasher.rs deleted file mode 100644 index 1fd2113a3..000000000 --- a/vm/src/builtin_functions/hasher.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Functions for hashing objects. -use crate::hasher::Hasher; -use crate::mem::{Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::hash::BuildHasher as _; - -pub(crate) fn hasher_new( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let hasher = state.hash_state.build_hasher(); - - Ok(Pointer::boxed(Hasher::new(hasher))) -} - -pub(crate) fn hasher_write_int( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let hasher = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) }; - - hasher.write_int(value); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn hasher_to_hash( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let hasher = unsafe { arguments[0].get_mut::() }; - let value = hasher.finish(); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn hasher_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/process.rs b/vm/src/builtin_functions/process.rs deleted file mode 100644 index c38929666..000000000 --- a/vm/src/builtin_functions/process.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Functions for Inko processes. -use crate::location_table::Location; -use crate::mem::{Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn process_stacktrace( - _: &State, - _: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let skip = unsafe { Int::read(arguments[0]) as usize }; - let raw_trace = process.stacktrace(); - let len = raw_trace.len(); - let limit = if skip > 0 { len.saturating_sub(skip) } else { len }; - let trace: Vec = raw_trace.into_iter().take(limit).collect(); - - Ok(Pointer::boxed(trace)) -} - -pub(crate) fn process_call_frame_name( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }; - let index = unsafe { Int::read(arguments[1]) as usize }; - - Ok(trace[index].name) -} - -pub(crate) fn process_call_frame_path( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }; - let index = unsafe { Int::read(arguments[1]) as usize }; - - Ok(trace[index].file) -} - -pub(crate) fn process_call_frame_line( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }; - let index = unsafe { Int::read(arguments[1]) as usize }; - - Ok(trace[index].line) -} - -pub(crate) fn process_stacktrace_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { arguments[0].drop_boxed::>() } - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn process_stacktrace_length( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }.len(); - - Ok(Int::alloc(state.permanent_space.int_class(), trace as i64)) -} diff --git a/vm/src/builtin_functions/random.rs b/vm/src/builtin_functions/random.rs deleted file mode 100644 index 0559b4b46..000000000 --- a/vm/src/builtin_functions/random.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Functions for generating random numbers. -use crate::mem::{ByteArray, Float, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng}; - -pub(crate) fn random_int( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - - Ok(Int::alloc(state.permanent_space.int_class(), rng.gen())) -} - -pub(crate) fn random_float( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - - Ok(Float::alloc(state.permanent_space.float_class(), rng.gen())) -} - -pub(crate) fn random_int_range( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - let min = unsafe { Int::read(arguments[1]) }; - let max = unsafe { Int::read(arguments[2]) }; - let val = if min < max { rng.gen_range(min..max) } else { 0 }; - - Ok(Int::alloc(state.permanent_space.int_class(), val)) -} - -pub(crate) fn random_float_range( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - let min = unsafe { Float::read(arguments[1]) }; - let max = unsafe { Float::read(arguments[2]) }; - let val = if min < max { rng.gen_range(min..max) } else { 0.0 }; - - Ok(Float::alloc(state.permanent_space.float_class(), val)) -} - -pub(crate) fn random_bytes( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - let size = unsafe { Int::read(arguments[1]) } as usize; - let mut bytes = vec![0; size]; - - rng.try_fill(&mut bytes[..]).map_err(|e| e.to_string())?; - Ok(ByteArray::alloc(state.permanent_space.byte_array_class(), bytes)) -} - -pub(crate) fn random_new( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let mut seed: ::Seed = Default::default(); - - thread.rng.fill(&mut seed); - - let rng = Pointer::boxed(StdRng::from_seed(seed)); - - Ok(rng) -} - -pub(crate) fn random_from_int( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let seed = unsafe { Int::read(args[0]) } as u64; - let rng = Pointer::boxed(StdRng::seed_from_u64(seed)); - - Ok(rng) -} - -pub(crate) fn random_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - unsafe { args[0].drop_boxed::() }; - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/socket.rs b/vm/src/builtin_functions/socket.rs deleted file mode 100644 index 7ee449f5d..000000000 --- a/vm/src/builtin_functions/socket.rs +++ /dev/null @@ -1,670 +0,0 @@ -//! Functions for working with non-blocking sockets. -use crate::mem::{ByteArray, Int, Pointer, String as InkoString}; -use crate::network_poller::Interest; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::scheduler::timeouts::Timeout; -use crate::socket::Socket; -use crate::state::State; -use std::io::Write; - -fn ret( - result: Result, - state: &State, - thread: &Thread, - process: ProcessPointer, - socket: &mut Socket, - interest: Interest, - deadline: Pointer, -) -> Result { - if !matches!(result, Err(ref err) if err.should_poll()) { - return result; - } - - // We must keep the process' state lock open until everything is registered, - // otherwise a timeout thread may reschedule the process (i.e. the timeout - // is very short) before we finish registering the socket with a poller. - let mut proc_state = process.state(); - let nanos = unsafe { Int::read(deadline) }; - - // A deadline of -1 signals that we should wait indefinitely. - if nanos >= 0 { - let time = Timeout::from_nanos_deadline(state, nanos as u64); - - proc_state.waiting_for_io(Some(time.clone())); - state.timeout_worker.suspend(process, time); - } else { - proc_state.waiting_for_io(None); - } - - socket.register(state, process, thread.network_poller, interest)?; - result -} - -fn handle_timeout( - state: &State, - socket: &mut Socket, - process: ProcessPointer, -) -> Result<(), RuntimeError> { - if process.timeout_expired() { - // The socket is still registered at this point, so we have to - // deregister first. If we don't and suspend for another IO operation, - // the poller could end up rescheduling the process multiple times (as - // there are multiple events still in flight for the process). - socket.deregister(state); - - return Err(RuntimeError::timed_out()); - } - - Ok(()) -} - -pub(crate) fn socket_allocate_ipv4( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - let socket = Socket::ipv4(kind)?; - - Ok(Pointer::boxed(socket)) -} - -pub(crate) fn socket_allocate_ipv6( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - let socket = Socket::ipv6(kind)?; - - Ok(Pointer::boxed(socket)) -} - -pub(crate) fn socket_allocate_unix( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - let socket = Socket::unix(kind)?; - - Ok(Pointer::boxed(socket)) -} - -pub(crate) fn socket_write_string( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let input = unsafe { InkoString::read(&arguments[1]).as_bytes() }; - let deadline = arguments[2]; - let res = sock - .write(input) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)) - .map_err(RuntimeError::from); - - ret(res, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_write_bytes( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let input = unsafe { arguments[1].get::() }.value(); - let deadline = arguments[2]; - let res = sock - .write(input) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)) - .map_err(RuntimeError::from); - - ret(res, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_read( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { arguments[1].get_mut::() }.value_mut(); - let amount = unsafe { Int::read(arguments[2]) } as usize; - let deadline = arguments[3]; - let result = sock - .read(buffer, amount) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_listen( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let backlog = unsafe { Int::read(arguments[1]) } as i32; - - sock.listen(backlog)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_bind( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let addr = unsafe { InkoString::read(&arguments[1]) }; - let port = unsafe { Int::read(arguments[2]) } as u16; - - // POSX states that bind(2) _can_ produce EINPROGRESS, but in practise it - // seems no system out there actually does this. - sock.bind(addr, port).map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_connect( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let addr = unsafe { InkoString::read(&arguments[1]) }; - let port = unsafe { Int::read(arguments[2]) } as u16; - let deadline = arguments[3]; - let result = sock.connect(addr, port).map(|_| Pointer::nil_singleton()); - - ret(result, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_accept_ip( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let deadline = arguments[1]; - - handle_timeout(state, sock, process)?; - - let result = sock.accept().map(Pointer::boxed); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_accept_unix( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let deadline = arguments[1]; - - handle_timeout(state, sock, process)?; - - let result = sock.accept().map(Pointer::boxed); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_receive_from( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { arguments[1].get_mut::() }.value_mut(); - let amount = unsafe { Int::read(arguments[2]) } as usize; - let deadline = arguments[3]; - let result = sock - .recv_from(buffer, amount) - .map(|(addr, port)| allocate_address_pair(state, addr, port)); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_send_bytes_to( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { arguments[1].get::() }.value(); - let address = unsafe { InkoString::read(&arguments[2]) }; - let port = unsafe { Int::read(arguments[3]) } as u16; - let deadline = arguments[4]; - let result = sock - .send_to(buffer, address, port) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)); - - ret(result, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_send_string_to( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { InkoString::read(&arguments[1]).as_bytes() }; - let address = unsafe { InkoString::read(&arguments[2]) }; - let port = unsafe { Int::read(arguments[3]) } as u16; - let deadline = arguments[4]; - let result = sock - .send_to(buffer, address, port) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)); - - ret(result, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_shutdown_read( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.shutdown_read().map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_shutdown_write( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.shutdown_write().map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_shutdown_read_write( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.shutdown_read_write().map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_local_address( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get::() }; - - sock.local_address() - .map(|(addr, port)| allocate_address_pair(state, addr, port)) -} - -pub(crate) fn socket_peer_address( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get::() }; - - sock.peer_address() - .map(|(addr, port)| allocate_address_pair(state, addr, port)) -} - -pub(crate) fn socket_get_ttl( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let value = unsafe { arguments[0].get::() }.ttl()? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn socket_get_only_v6( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.only_v6()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_nodelay( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get_mut::() }.nodelay()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_broadcast( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.broadcast()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_linger( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let value = unsafe { arguments[0].get::() }.linger()?; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn socket_get_recv_size( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - Ok(Int::alloc( - state.permanent_space.int_class(), - unsafe { arguments[0].get::() }.recv_buffer_size()? as i64, - )) -} - -pub(crate) fn socket_get_send_size( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - Ok(Int::alloc( - state.permanent_space.int_class(), - unsafe { arguments[0].get::() }.send_buffer_size()? as i64, - )) -} - -pub(crate) fn socket_get_keepalive( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let value = unsafe { arguments[0].get::() }.keepalive()?; - - if value { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_reuse_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.reuse_address()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_reuse_port( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.reuse_port()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_set_ttl( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) } as u32; - - sock.set_ttl(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_only_v6( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.set_only_v6(arguments[1] == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_nodelay( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.set_nodelay(arguments[1] == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_broadcast( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.set_broadcast(arguments[1] == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_linger( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read_u64(arguments[1]) }; - - sock.set_linger(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_recv_size( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) } as usize; - - sock.set_recv_buffer_size(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_send_size( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) } as usize; - - sock.set_send_buffer_size(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_keepalive( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = arguments[1] == Pointer::true_singleton(); - - sock.set_keepalive(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_reuse_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = arguments[1]; - - sock.set_reuse_address(value == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_reuse_port( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = arguments[1]; - - sock.set_reuse_port(value == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_try_clone( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get::() }; - let clone = sock.try_clone()?; - - Ok(Pointer::boxed(clone)) -} - -pub(crate) fn socket_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { arguments[0].drop_boxed::() }; - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_address_pair_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let pair = unsafe { arguments[0].get::<(Pointer, Pointer)>() }; - - Ok(pair.0) -} - -pub(crate) fn socket_address_pair_port( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let pair = unsafe { arguments[0].get::<(Pointer, Pointer)>() }; - - Ok(pair.1) -} - -pub(crate) fn socket_address_pair_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { Pointer::drop_boxed::<(Pointer, Pointer)>(arguments[0]) }; - - Ok(Pointer::nil_singleton()) -} - -fn allocate_address_pair(state: &State, addr: String, port: i64) -> Pointer { - let addr = InkoString::alloc(state.permanent_space.string_class(), addr); - let port = Int::alloc(state.permanent_space.int_class(), port); - - Pointer::boxed((addr, port)) -} diff --git a/vm/src/builtin_functions/stdio.rs b/vm/src/builtin_functions/stdio.rs deleted file mode 100644 index b9904d5c9..000000000 --- a/vm/src/builtin_functions/stdio.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Functions for working with the standard input/output streams. -use crate::builtin_functions::read_into; -use crate::mem::{ByteArray, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::io::Write; -use std::io::{stderr, stdin, stdout}; - -pub(crate) fn stdout_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { InkoString::read(&arguments[0]).as_bytes() }; - let size = thread.blocking(|| stdout().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stdout_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { arguments[0].get::() }.value(); - let size = thread.blocking(|| stdout().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stderr_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { InkoString::read(&arguments[0]).as_bytes() }; - let size = thread.blocking(|| stderr().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stderr_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { arguments[0].get::() }.value(); - let size = thread.blocking(|| stderr().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stdout_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - thread.blocking(|| stdout().flush())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn stderr_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - thread.blocking(|| stderr().flush())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn stdin_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let buff = unsafe { arguments[0].get_mut::() }.value_mut(); - let size = unsafe { Int::read(arguments[1]) }; - let result = thread.blocking(|| read_into(&mut stdin(), buff, size))?; - - Ok(Int::alloc(state.permanent_space.int_class(), result)) -} diff --git a/vm/src/builtin_functions/string.rs b/vm/src/builtin_functions/string.rs deleted file mode 100644 index 36edd7143..000000000 --- a/vm/src/builtin_functions/string.rs +++ /dev/null @@ -1,191 +0,0 @@ -use crate::mem::{Array, ByteArray, Float, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::cmp::min; -use unicode_segmentation::{Graphemes, UnicodeSegmentation}; - -pub(crate) fn string_to_lower( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let lower = unsafe { InkoString::read(&args[0]).to_lowercase() }; - let res = InkoString::alloc(state.permanent_space.string_class(), lower); - - Ok(res) -} - -pub(crate) fn string_to_upper( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let upper = unsafe { InkoString::read(&args[0]).to_uppercase() }; - let res = InkoString::alloc(state.permanent_space.string_class(), upper); - - Ok(res) -} - -pub(crate) fn string_to_byte_array( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes = unsafe { InkoString::read(&args[0]).as_bytes().to_vec() }; - let res = ByteArray::alloc(state.permanent_space.byte_array_class(), bytes); - - Ok(res) -} - -pub(crate) fn string_to_float( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let start = unsafe { Int::read(args[1]) }; - let end = unsafe { Int::read(args[2]) }; - let slice = if start >= 0 && end >= 0 { - &string[start as usize..end as usize] - } else { - string - }; - - let parsed = match slice { - "Infinity" => Ok(f64::INFINITY), - "-Infinity" => Ok(f64::NEG_INFINITY), - _ => slice.parse::(), - }; - - let res = parsed - .map(|val| Float::alloc(state.permanent_space.float_class(), val)) - .unwrap_or_else(|_| Pointer::undefined_singleton()); - - Ok(res) -} - -pub(crate) fn string_to_int( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let radix = unsafe { Int::read(args[1]) }; - let start = unsafe { Int::read(args[2]) }; - let end = unsafe { Int::read(args[3]) }; - - if !(2..=36).contains(&radix) { - return Err(RuntimeError::Panic(format!( - "The radix '{}' is invalid", - radix - ))); - } - - let slice = if start >= 0 && end >= 0 { - &string[start as usize..end as usize] - } else { - string - }; - - // Rust doesn't handle parsing strings like "-0x4a3f043013b2c4d1" out of the - // box. - let parsed = if radix == 16 { - if let Some(tail) = string.strip_prefix("-0x") { - i64::from_str_radix(tail, 16).map(|v| 0_i64.wrapping_sub(v)) - } else { - i64::from_str_radix(slice, 16) - } - } else { - i64::from_str_radix(slice, radix as u32) - }; - - let res = parsed - .map(|val| Int::alloc(state.permanent_space.int_class(), val)) - .unwrap_or_else(|_| Pointer::undefined_singleton()); - - Ok(res) -} - -pub(crate) fn string_characters( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let iter = Pointer::boxed(string.graphemes(true)); - - Ok(iter) -} - -pub(crate) fn string_characters_next( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let iter = unsafe { args[0].get_mut::() }; - let class = state.permanent_space.string_class(); - let res = iter - .next() - .map(|v| InkoString::alloc(class, v.to_string())) - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(res) -} - -pub(crate) fn string_characters_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - unsafe { args[0].drop_boxed::() }; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn string_concat_array( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let array = unsafe { args[0].get::() }.value(); - let mut buffer = String::new(); - - for str_ptr in array.iter() { - buffer.push_str(unsafe { InkoString::read(str_ptr) }); - } - - Ok(InkoString::alloc(state.permanent_space.string_class(), buffer)) -} - -pub(crate) fn string_slice_bytes( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let start = unsafe { Int::read(args[1]) }; - let len = unsafe { Int::read(args[2]) }; - let end = min((start + len) as usize, string.len()); - - let new_string = if start < 0 || len <= 0 || start as usize >= end { - String::new() - } else { - String::from_utf8_lossy( - &string.as_bytes()[start as usize..end as usize], - ) - .into_owned() - }; - - Ok(InkoString::alloc(state.permanent_space.string_class(), new_string)) -} diff --git a/vm/src/builtin_functions/sys.rs b/vm/src/builtin_functions/sys.rs deleted file mode 100644 index 53f8fa625..000000000 --- a/vm/src/builtin_functions/sys.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! Functions for working with the underlying system. -use crate::builtin_functions::read_into; -use crate::mem::{Array, ByteArray, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::io::Write; -use std::process::{Child, Command, Stdio}; -use std::thread::available_parallelism; - -pub(crate) fn child_process_spawn( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let program = unsafe { InkoString::read(&arguments[0]) }; - let args = unsafe { arguments[1].get::() }.value(); - let env = unsafe { arguments[2].get::() }.value(); - let stdin = unsafe { Int::read(arguments[3]) }; - let stdout = unsafe { Int::read(arguments[4]) }; - let stderr = unsafe { Int::read(arguments[5]) }; - let directory = unsafe { InkoString::read(&arguments[6]) }; - let mut cmd = Command::new(program); - - for ptr in args { - cmd.arg(unsafe { InkoString::read(ptr) }); - } - - for pair in env.chunks(2) { - unsafe { - cmd.env(InkoString::read(&pair[0]), InkoString::read(&pair[1])); - } - } - - cmd.stdin(stdio_for(stdin)); - cmd.stdout(stdio_for(stdout)); - cmd.stderr(stdio_for(stderr)); - - if !directory.is_empty() { - cmd.current_dir(directory); - } - - thread.blocking(|| Ok(Pointer::boxed(cmd.spawn()?))) -} - -pub(crate) fn child_process_wait( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let status = thread.blocking(|| child.wait())?; - let code = status.code().unwrap_or(0) as i64; - - Ok(Pointer::int(code)) -} - -pub(crate) fn child_process_try_wait( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let status = child.try_wait()?; - let code = status.map(|s| s.code().unwrap_or(0)).unwrap_or(-1) as i64; - - Ok(Pointer::int(code)) -} - -pub(crate) fn child_process_stdout_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let buff = unsafe { arguments[1].get_mut::() }.value_mut(); - let size = unsafe { Int::read(arguments[2]) }; - let value = child - .stdout - .as_mut() - .map(|stream| thread.blocking(|| read_into(stream, buff, size))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn child_process_stderr_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let buff = unsafe { arguments[1].get_mut::() }.value_mut(); - let size = unsafe { Int::read(arguments[2]) }; - let value = child - .stderr - .as_mut() - .map(|stream| thread.blocking(|| read_into(stream, buff, size))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn child_process_stdin_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let input = unsafe { arguments[1].get::() }.value(); - let value = child - .stdin - .as_mut() - .map(|stream| thread.blocking(|| stream.write(input))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value as i64)) -} - -pub(crate) fn child_process_stdin_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let input = unsafe { InkoString::read(&arguments[1]) }; - let value = child - .stdin - .as_mut() - .map(|stream| thread.blocking(|| stream.write(input.as_bytes()))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value as i64)) -} - -pub(crate) fn child_process_stdin_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child - .stdin - .as_mut() - .map(|stream| thread.blocking(|| stream.flush())) - .unwrap_or(Ok(()))?; - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_stdout_close( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child.stdout.take(); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_stderr_close( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child.stderr.take(); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_stdin_close( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child.stdin.take(); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn cpu_cores( - _: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let cores = available_parallelism().map(|v| v.get()).unwrap_or(1); - - Ok(Pointer::int(cores as i64)) -} - -fn stdio_for(value: i64) -> Stdio { - match value { - 1 => Stdio::inherit(), - 2 => Stdio::piped(), - _ => Stdio::null(), - } -} diff --git a/vm/src/builtin_functions/time.rs b/vm/src/builtin_functions/time.rs deleted file mode 100644 index a52b803a4..000000000 --- a/vm/src/builtin_functions/time.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Functions for system and monotonic clocks. -use crate::mem::{Float, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::mem::MaybeUninit; - -#[cfg(not(windows))] -fn utc() -> f64 { - unsafe { - let mut ts = MaybeUninit::uninit(); - - if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { - panic!("clock_gettime() failed"); - } - - let ts = ts.assume_init(); - - ts.tv_sec as f64 + (ts.tv_nsec as f64 / 1_000_000_000.0) - } -} - -#[cfg(not(windows))] -fn offset() -> i64 { - unsafe { - extern "C" { - fn tzset(); - } - - let ts = { - let mut ts = MaybeUninit::uninit(); - - if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { - panic!("clock_gettime() failed"); - } - - ts.assume_init() - }; - - let mut tm = MaybeUninit::uninit(); - - // localtime_r() doesn't necessarily call tzset() for us. - tzset(); - - // While localtime_r() may call setenv() internally, this is not a - // problem as Inko caches environment variables upon startup. If an FFI - // call ends up racing with the setenv() call, that's a problem for the - // FFI code. - if libc::localtime_r(&ts.tv_sec, tm.as_mut_ptr()).is_null() { - panic!("localtime_r() failed"); - } - - tm.assume_init().tm_gmtoff - } -} - -#[cfg(windows)] -fn utc() -> f64 { - use windows_sys::Win32::System::SystemInformation::GetSystemTimeAsFileTime; - - unsafe { - let ft = { - let mut ft = MaybeUninit::uninit(); - - GetSystemTimeAsFileTime(ft.as_mut_ptr()); - ft.assume_init() - }; - - let intervals_per_sec = 10_000_000; - let intervals_to_unix = 11_644_473_600 * intervals_per_sec; - let win_time = - i64::from(ft.dwHighDateTime) << 32 | i64::from(ft.dwLowDateTime); - - (win_time - intervals_to_unix) as f64 / intervals_per_sec as f64 - } -} - -#[cfg(windows)] -fn offset() -> i64 { - use windows_sys::Win32::System::SystemServices::{ - TIME_ZONE_ID_DAYLIGHT, TIME_ZONE_ID_STANDARD, TIME_ZONE_ID_UNKNOWN, - }; - use windows_sys::Win32::System::Time::GetTimeZoneInformation; - - unsafe { - let mut tz = MaybeUninit::uninit(); - let bias = match GetTimeZoneInformation(tz.as_mut_ptr()) { - TIME_ZONE_ID_UNKNOWN => tz.assume_init().Bias as i64, - TIME_ZONE_ID_STANDARD => { - let tz = tz.assume_init(); - - tz.Bias as i64 + tz.StandardBias as i64 - } - TIME_ZONE_ID_DAYLIGHT => { - let tz = tz.assume_init(); - - tz.Bias as i64 + tz.DaylightBias as i64 - } - _ => 0, - }; - - // The bias (in minutes) is the result of `UTC - local time`, so if - // you're ahead of UTC the bias is negative. - bias * -60 - } -} - -pub(crate) fn time_monotonic( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - // An i64 gives us roughly 292 years of time. That should be more than - // enough for a monotonic clock, as an Inko program is unlikely to run for - // that long. - let nanos = state.start_time.elapsed().as_nanos() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), nanos)) -} - -pub(crate) fn time_system( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Float::alloc(state.permanent_space.float_class(), utc())) -} - -pub(crate) fn time_system_offset( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Int::alloc(state.permanent_space.int_class(), offset())) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::time::{SystemTime, UNIX_EPOCH}; - - #[test] - fn test_utc() { - let expected = - SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64(); - let given = utc(); - - // We can't assert equality, for there may be time between the two - // function calls. We also can't assert the utc() time is greater in the - // event of clock changes. Instead we just assert the two times are - // within 5 seconds of each other, which should be sufficient. - assert!((given - expected).abs() < 5.0); - } -} diff --git a/vm/src/chunk.rs b/vm/src/chunk.rs deleted file mode 100644 index 6686beb5d..000000000 --- a/vm/src/chunk.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Fixed-size chunks of memory. -use std::alloc::{self, Layout}; -use std::mem; -use std::ops::Drop; -use std::ptr; - -/// A fixed-size amount of memory. -/// -/// Chunks are a bit like a Vec, but with far fewer features and no bounds -/// checking. This makes them useful for cases where reads and writes are very -/// frequent, and an external source (e.g. a compiler) verifies if these -/// operations are within bounds. -/// -/// A Chunk does not drop the values stored within, simply because we don't need -/// this at this time. -pub(crate) struct Chunk { - ptr: *mut T, - capacity: usize, -} - -unsafe fn layout_for(capacity: usize) -> Layout { - Layout::from_size_align_unchecked( - mem::size_of::() * capacity, - mem::align_of::(), - ) -} - -#[cfg_attr(feature = "cargo-clippy", allow(clippy::len_without_is_empty))] -impl Chunk { - pub(crate) fn new(capacity: usize) -> Self { - if capacity == 0 { - return Chunk { ptr: ptr::null_mut(), capacity: 0 }; - } - - let layout = unsafe { layout_for::(capacity) }; - let ptr = unsafe { alloc::alloc_zeroed(layout) as *mut T }; - - if ptr.is_null() { - alloc::handle_alloc_error(layout); - } - - Chunk { ptr, capacity } - } - - pub(crate) fn len(&self) -> usize { - self.capacity - } - - pub(crate) unsafe fn get(&self, index: usize) -> &T { - &*self.ptr.add(index) - } - - pub(crate) unsafe fn set(&mut self, index: usize, value: T) { - self.ptr.add(index).write(value); - } -} - -impl Drop for Chunk { - fn drop(&mut self) { - unsafe { - if !self.ptr.is_null() { - alloc::dealloc( - self.ptr as *mut u8, - layout_for::(self.len()), - ); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::Pointer; - - #[test] - fn test_empty_chunk() { - let chunk = Chunk::<()>::new(0); - - assert_eq!(chunk.len(), 0); - assert!(chunk.ptr.is_null()); - } - - #[test] - fn test_len() { - let chunk = Chunk::::new(4); - - assert_eq!(chunk.len(), 4); - } - - #[test] - fn test_get_set() { - let mut chunk = Chunk::::new(2); - - unsafe { - chunk.set(0, Pointer::int(5)); - - assert!(*chunk.get(0) == Pointer::int(5)); - } - } -} diff --git a/vm/src/execution_context.rs b/vm/src/execution_context.rs deleted file mode 100644 index 225c69434..000000000 --- a/vm/src/execution_context.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Process Execution Contexts -//! -//! An execution context contains the registers, bindings, and other information -//! needed by a process in order to execute bytecode. -use crate::mem::MethodPointer; -use crate::mem::Pointer; -use crate::registers::Registers; - -/// A single call frame, its variables, registers, and more. -pub(crate) struct ExecutionContext { - /// The code that we're currently running. - pub method: MethodPointer, - - /// The parent execution context. - pub parent: Option>, - - /// The index of the instruction to store prior to suspending a process. - pub index: usize, - - /// The registers for this context. - registers: Registers, -} - -/// Struct for iterating over an ExecutionContext and its parent contexts. -pub(crate) struct ExecutionContextIterator<'a> { - current: Option<&'a ExecutionContext>, -} - -impl ExecutionContext { - pub(crate) fn new(method: MethodPointer) -> Self { - ExecutionContext { - registers: Registers::new(method.registers), - method, - parent: None, - index: 0, - } - } - - /// Returns the method that is being executed. - pub(crate) fn method(&self) -> MethodPointer { - self.method - } - - /// Returns the value of a single register. - pub(crate) fn get_register(&self, register: u16) -> Pointer { - self.registers.get(register) - } - - /// Sets the value of a single register. - pub(crate) fn set_register(&mut self, register: u16, value: Pointer) { - self.registers.set(register, value); - } - - /// Returns an iterator for traversing the context chain, including the - /// current context. - pub(crate) fn contexts(&self) -> ExecutionContextIterator { - ExecutionContextIterator { current: Some(self) } - } -} - -impl<'a> Iterator for ExecutionContextIterator<'a> { - type Item = &'a ExecutionContext; - - fn next(&mut self) -> Option<&'a ExecutionContext> { - if let Some(ctx) = self.current { - if let Some(parent) = ctx.parent.as_ref() { - self.current = Some(&**parent); - } else { - self.current = None; - } - - return Some(ctx); - } - - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::Method; - use crate::test::empty_method; - use std::mem; - - fn with_context(callback: F) { - let method = empty_method(); - let ctx = ExecutionContext::new(method); - - callback(ctx); - Method::drop_and_deallocate(method); - } - - #[test] - fn test_new() { - let method = empty_method(); - let ctx = ExecutionContext::new(method); - - assert!(ctx.method.as_ptr() == method.as_ptr()); - assert!(ctx.parent.is_none()); - assert_eq!(ctx.index, 0); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_for_method() { - let method = empty_method(); - let ctx = ExecutionContext::new(method); - - assert!(ctx.method.as_ptr() == method.as_ptr()); - assert!(ctx.parent.is_none()); - assert_eq!(ctx.index, 0); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_get_set_register() { - with_context(|mut ctx| { - let ptr = Pointer::int(42); - - ctx.set_register(0, ptr); - - assert_eq!(ctx.get_register(0), ptr); - }); - } - - #[test] - fn test_contexts() { - with_context(|ctx| { - let mut iter = ctx.contexts(); - - assert!(iter.next().is_some()); - assert!(iter.next().is_none()); - }); - } - - #[test] - fn test_type_size() { - assert_eq!(mem::size_of::(), 40); - } -} diff --git a/vm/src/ffi.rs b/vm/src/ffi.rs deleted file mode 100644 index abcc27226..000000000 --- a/vm/src/ffi.rs +++ /dev/null @@ -1,731 +0,0 @@ -use crate::mem::{ - ByteArray, Float, Int, Pointer as InkoPointer, String as InkoString, -}; -use crate::state::State; -use libffi::low::{ - call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, - prep_cif, types, CodePtr, Error as FFIError, -}; -use std::convert::Into; -use std::ffi::{CStr, OsStr}; -use std::fmt::{Debug, Display}; -use std::mem; -use std::os::raw::{c_char, c_double, c_float, c_int, c_long, c_short, c_void}; -use std::ptr; - -#[repr(i64)] -#[derive(Copy, Clone)] -enum Type { - /// The numeric identifier of the C `void` type. - Void, - - /// The numeric identifier of the C `void*` type. - Pointer, - - /// The numeric identifier of the C `double` type. - F64, - - /// The numeric identifier of the C `float` type. - F32, - - /// The numeric identifier of the C `signed char` type. - I8, - - /// The numeric identifier of the C `short` type. - I16, - - /// The numeric identifier of the C `int` type. - I32, - - /// The numeric identifier of the C `long` type. - I64, - - /// The numeric identifier of the C `unsigned char` type. - U8, - - /// The numeric identifier of the C `unsigned short` type. - U16, - - /// The numeric identifier of the C `unsigned int` type. - U32, - - /// The numeric identifier of the C `unsigned long` type. - U64, - - /// The numeric identifier for the C `const char*` type. - String, - - /// The numeric identifier for a C `const char*` type that should be read - /// into a byte array. - ByteArray, - - /// The numeric identifier of the C `size_t` type. - SizeT, -} - -impl Type { - fn from_i64(value: i64) -> Result { - match value { - 0 => Ok(Type::Void), - 1 => Ok(Type::Pointer), - 2 => Ok(Type::F64), - 3 => Ok(Type::F32), - 4 => Ok(Type::I8), - 5 => Ok(Type::I16), - 6 => Ok(Type::I32), - 7 => Ok(Type::I64), - 8 => Ok(Type::U8), - 9 => Ok(Type::U16), - 10 => Ok(Type::U32), - 11 => Ok(Type::U64), - 12 => Ok(Type::String), - 13 => Ok(Type::ByteArray), - 14 => Ok(Type::SizeT), - _ => Err(format!("The type identifier '{}' is invalid", value)), - } - } - - unsafe fn as_ffi_type(&self) -> *mut ffi_type { - match self { - Type::Void => &mut types::void as *mut _, - Type::Pointer => &mut types::pointer as *mut _, - Type::F64 => &mut types::double as *mut _, - Type::F32 => &mut types::float as *mut _, - Type::I8 => &mut types::sint8 as *mut _, - Type::I16 => &mut types::sint16 as *mut _, - Type::I32 => &mut types::sint32 as *mut _, - Type::I64 => &mut types::sint64 as *mut _, - Type::U8 => &mut types::uint8 as *mut _, - Type::U16 => &mut types::uint16 as *mut _, - Type::U32 => &mut types::uint32 as *mut _, - Type::U64 => &mut types::uint64 as *mut _, - Type::String => &mut types::pointer as *mut _, - Type::ByteArray => &mut types::pointer as *mut _, - Type::SizeT => match mem::size_of::() { - 64 => &mut types::uint64 as *mut _, - 32 => &mut types::uint32 as *mut _, - 8 => &mut types::uint8 as *mut _, - _ => &mut types::uint16 as *mut _, - }, - } - } -} - -/// A C library, such as libc. -/// -/// This is currently a thin wrapper around libloading's Library structure, -/// allowing us to decouple the rest of the VM code from libloading. -pub(crate) struct Library { - inner: libloading::Library, -} - -/// A pointer to an FFI type. -pub(crate) type TypePointer = *mut ffi_type; - -/// A raw C pointer. -pub(crate) type RawPointer = *mut c_void; - -/// A wrapper around a C pointer. -#[derive(Clone, Copy)] -#[repr(transparent)] -pub(crate) struct Pointer { - inner: RawPointer, -} - -unsafe impl Send for Pointer {} - -/// A function with a fixed number of arguments. -pub(crate) struct Function { - /// The pointer to the function to call. - pointer: Pointer, - - /// The CIF (Call Interface) to use for this function. - cif: ffi_cif, - - /// The argument types of the function. - argument_types: Vec, - - /// The raw FFI types of the arguments. - /// - /// A CIF maintains a reference to this array, so we have to keep it around. - argument_ffi_types: Vec, - - /// The return type of the function. - return_type: Type, -} - -/// Returns the size of a type ID. -/// -/// The size of the type is returned as a tagged integer. -pub(crate) fn type_size(id: i64) -> Result { - let size = unsafe { - match Type::from_i64(id)? { - Type::Void => types::void.size, - Type::Pointer | Type::String | Type::ByteArray => { - types::pointer.size - } - Type::F64 => types::double.size, - Type::F32 => types::float.size, - Type::I8 => types::sint8.size, - Type::I16 => types::sint16.size, - Type::I32 => types::sint32.size, - Type::I64 => types::sint64.size, - Type::U8 => types::uint8.size, - Type::U16 => types::uint16.size, - Type::U32 => types::uint32.size, - Type::U64 => types::uint64.size, - Type::SizeT => mem::size_of::(), - } - }; - - Ok(InkoPointer::int(size as i64)) -} - -/// Returns the alignment of a type ID. -/// -/// The alignment of the type is returned as a tagged integer. -pub(crate) fn type_alignment(id: i64) -> Result { - let size = unsafe { - match Type::from_i64(id)? { - Type::Void => types::void.alignment, - Type::Pointer | Type::String | Type::ByteArray => { - types::pointer.alignment - } - Type::F64 => types::double.alignment, - Type::F32 => types::float.alignment, - Type::I8 => types::sint8.alignment, - Type::I16 => types::sint16.alignment, - Type::I32 => types::sint32.alignment, - Type::I64 => types::sint64.alignment, - Type::U8 => types::uint8.alignment, - Type::U16 => types::uint16.alignment, - Type::U32 => types::uint32.alignment, - Type::U64 => types::uint64.alignment, - Type::SizeT => mem::align_of::() as u16, - } - }; - - Ok(InkoPointer::int(i64::from(size))) -} - -/// A value of some sort to be passed to a C function. -pub(crate) enum Argument { - F32(f32), - F64(f64), - I16(i16), - I32(i32), - I64(i64), - I8(i8), - Pointer(RawPointer), - U16(u16), - U32(u32), - U64(u64), - U8(u8), - Void, -} - -impl Argument { - // Creates a new Argument wrapping the value of `ptr` according to the needs - // of the FFI type specified in `ffi_type`. - unsafe fn wrap( - ffi_type: Type, - ptr: InkoPointer, - ) -> Result { - let argument = match ffi_type { - Type::Pointer => Argument::Pointer(ptr.as_ptr() as RawPointer), - Type::String => Argument::Pointer( - ptr.get::().value().as_c_char_pointer() - as RawPointer, - ), - Type::ByteArray => Argument::Pointer( - ptr.get::().value().as_ptr() as RawPointer, - ), - Type::Void => Argument::Void, - Type::F32 => Argument::F32(Float::read(ptr) as f32), - Type::F64 => Argument::F64(Float::read(ptr)), - Type::I8 => Argument::I8(Int::read(ptr) as i8), - Type::I16 => Argument::I16(Int::read(ptr) as i16), - Type::I32 => Argument::I32(Int::read(ptr) as i32), - Type::I64 => Argument::I64(Int::read(ptr) as i64), - Type::U8 => Argument::U8(Int::read(ptr) as u8), - Type::U16 => Argument::U16(Int::read(ptr) as u16), - Type::U32 => Argument::U32(Int::read(ptr) as u32), - Type::U64 => Argument::U64(Int::read(ptr) as u64), - Type::SizeT => match mem::size_of::() { - 64 => Argument::U64(Int::read(ptr) as u64), - 32 => Argument::U32(Int::read(ptr) as u32), - 8 => Argument::U8(Int::read(ptr) as u8), - _ => Argument::U16(Int::read(ptr) as u16), - }, - }; - - Ok(argument) - } - - /// Returns a C pointer to the wrapped value. - fn as_c_pointer(&mut self) -> RawPointer { - match self { - Argument::Pointer(ref mut val) => { - // When passing a pointer we shouldn't pass the pointer - // directly, instead we want a pointer to the pointer to pass to - // the underlying C function. - val as *mut RawPointer as RawPointer - } - Argument::Void => ptr::null_mut() as RawPointer, - Argument::F32(ref mut val) => val as *mut _ as RawPointer, - Argument::F64(ref mut val) => val as *mut _ as RawPointer, - Argument::I8(ref mut val) => val as *mut _ as RawPointer, - Argument::I16(ref mut val) => val as *mut _ as RawPointer, - Argument::I32(ref mut val) => val as *mut _ as RawPointer, - Argument::I64(ref mut val) => val as *mut _ as RawPointer, - Argument::U8(ref mut val) => val as *mut _ as RawPointer, - Argument::U16(ref mut val) => val as *mut _ as RawPointer, - Argument::U32(ref mut val) => val as *mut _ as RawPointer, - Argument::U64(ref mut val) => val as *mut _ as RawPointer, - } - } -} - -impl Library { - /// Opens a library using one or more possible names, stored as pointers to - /// heap allocated objects. - pub(crate) fn from_pointers(search_for: &[InkoPointer]) -> Option { - let names = search_for - .iter() - .map(|n| unsafe { InkoString::read(n) }) - .collect::>(); - - Self::open(&names) - } - - /// Opens a library using one or more possible names. - pub(crate) fn open + Debug + Display>( - search_for: &[P], - ) -> Option { - for name in search_for { - if let Ok(library) = unsafe { libloading::Library::new(name) } - .map(|inner| Library { inner }) - { - return Some(library); - } - } - - None - } - - /// Obtains a pointer to a symbol. - /// - /// This method is unsafe because the pointer could be of any type, thus it - /// is up to the caller to make sure the result is used appropriately. - pub(crate) unsafe fn get(&self, name: &str) -> Option { - self.inner - .get(name.as_bytes()) - .map(|sym: libloading::Symbol| Pointer::new(*sym)) - .ok() - } -} - -impl Pointer { - pub(crate) fn new(inner: RawPointer) -> Self { - Pointer { inner } - } - - /// Reads the value of this pointer into a particular type, based on the - /// integer specified in `kind`. - pub(crate) unsafe fn read_as( - self, - state: &State, - kind: InkoPointer, - ) -> Result { - let int = Int::read(kind); - let pointer = match Type::from_i64(int)? { - Type::Pointer => { - let ptr: RawPointer = self.read(); - - InkoPointer::new(ptr as *mut u8) - } - Type::Void => InkoPointer::new(ptr::null_mut()), - Type::String => { - let string = self.read_cstr().to_string_lossy().into_owned(); - - InkoString::alloc(state.permanent_space.string_class(), string) - } - Type::ByteArray => { - let bytes = self.read_cstr().to_bytes().to_vec(); - - ByteArray::alloc( - state.permanent_space.byte_array_class(), - bytes, - ) - } - Type::F64 => self.read_float::(state), - Type::F32 => self.read_float::(state), - Type::I8 | Type::U8 => self.read_signed_integer::(state), - Type::I16 | Type::U16 => self.read_signed_integer::(state), - Type::I32 | Type::U32 => self.read_signed_integer::(state), - Type::I64 | Type::U64 => self.read_signed_integer::(state), - Type::SizeT => match mem::size_of::() { - 64 => self.read_signed_integer::(state), - 32 => self.read_signed_integer::(state), - 8 => self.read_signed_integer::(state), - _ => self.read_signed_integer::(state), - }, - }; - - Ok(pointer) - } - - /// Writes a value to the underlying pointer. - pub(crate) unsafe fn write_as( - self, - kind: InkoPointer, - value: InkoPointer, - ) -> Result<(), String> { - let int = Int::read(kind); - - match Type::from_i64(int)? { - Type::String => { - let string = value.get::().value(); - - ptr::copy( - string.as_c_char_pointer(), - self.inner as *mut c_char, - string.len_with_null_byte(), - ); - } - Type::ByteArray => { - let byte_array = value.get::().value(); - - ptr::copy( - byte_array.as_ptr(), - self.inner as *mut _, - byte_array.len(), - ); - } - Type::Pointer => self.write(value.as_ptr() as RawPointer), - Type::Void => self.write(ptr::null_mut() as RawPointer), - Type::F64 => self.write(Float::read(value)), - Type::F32 => self.write(Float::read(value) as f32), - Type::I8 => self.write(Int::read(value) as i8), - Type::I16 => self.write(Int::read(value) as i16), - Type::I32 => self.write(Int::read(value) as i32), - Type::I64 => self.write(Int::read(value) as i64), - Type::U8 => self.write(Int::read(value) as u8), - Type::U16 => self.write(Int::read(value) as u16), - Type::U32 => self.write(Int::read(value) as u32), - Type::U64 => self.write(Int::read(value) as u64), - Type::SizeT => self.write(Int::read(value) as usize), - }; - - Ok(()) - } - - /// Returns a new Pointer, optionally starting at the given offset. - /// - /// The `offset` argument is the offset in _bytes_, not the number of - /// elements (unlike Rust's `pointer::offset`). - pub(crate) fn with_offset(self, offset_bytes: usize) -> Self { - let inner = (self.inner as usize + offset_bytes) as RawPointer; - - Pointer::new(inner) - } - - /// Returns the underlying pointer. - pub(crate) fn as_ptr(self) -> *mut u8 { - self.inner as _ - } - - unsafe fn read(self) -> R { - ptr::read(self.inner as *mut R) - } - - unsafe fn write(self, value: T) { - ptr::write(self.inner as *mut T, value); - } - - unsafe fn read_signed_integer>( - self, - state: &State, - ) -> InkoPointer { - Int::alloc(state.permanent_space.int_class(), self.read::().into()) - } - - unsafe fn read_float>(self, state: &State) -> InkoPointer { - Float::alloc( - state.permanent_space.float_class(), - self.read::().into(), - ) - } - - unsafe fn read_cstr<'a>(self) -> &'a CStr { - CStr::from_ptr(self.inner as *mut c_char) - } -} - -impl Function { - pub(crate) unsafe fn attach( - library: &Library, - name: &str, - arguments: &[InkoPointer], - return_type: InkoPointer, - ) -> Result, String> { - let func_ptr = if let Some(sym) = library.get(name) { - sym - } else { - return Ok(None); - }; - - let rtype = Type::from_i64(Int::read(return_type))?; - let mut args = Vec::with_capacity(arguments.len()); - - for ptr in arguments { - args.push(Type::from_i64(Int::read(*ptr))?); - } - - Self::create(func_ptr, args, rtype).map(Some) - } - - unsafe fn create( - pointer: Pointer, - argument_types: Vec, - return_type: Type, - ) -> Result { - let argument_ffi_types: Vec = - argument_types.iter().map(|t| t.as_ffi_type()).collect(); - let mut func = Function { - pointer, - cif: Default::default(), - argument_types, - argument_ffi_types, - return_type, - }; - - let result = prep_cif( - &mut func.cif, - ABI, - func.argument_types.len(), - return_type.as_ffi_type(), - func.argument_ffi_types.as_mut_ptr(), - ); - - match result { - Ok(_) => Ok(func), - Err(FFIError::Typedef) => { - Err("The type representation is invalid or unsupported" - .to_string()) - } - Err(FFIError::Abi) => { - Err("The ABI is invalid or unsupported".to_string()) - } - } - } - - pub(crate) unsafe fn call( - &self, - state: &State, - arg_ptrs: &[InkoPointer], - ) -> Result { - if arg_ptrs.len() != self.argument_types.len() { - return Err(format!( - "Invalid number of arguments, expected {} but got {}", - self.argument_types.len(), - arg_ptrs.len() - )); - } - - let mut arguments = Vec::with_capacity(arg_ptrs.len()); - - for (index, arg) in arg_ptrs.iter().enumerate() { - arguments.push(Argument::wrap(self.argument_types[index], *arg)?); - } - - // libffi expects an array of _pointers_ to the arguments to pass, - // instead of an array containing the arguments directly. The pointers - // and the values they point to must outlive the FFI call, otherwise we - // may end up passing pointers to invalid memory. - let mut argument_pointers: Vec = - arguments.iter_mut().map(Argument::as_c_pointer).collect(); - - // libffi requires a mutable pointer to the CIF, but "self" is immutable - // since we never actually modify the current function. To work around - // this we manually cast to a mutable pointer. - let cif_ptr = &self.cif as *const _ as *mut _; - let fun_ptr = CodePtr::from_ptr(self.pointer.inner); - let args_ptr = argument_pointers.as_mut_ptr(); - let result = match self.return_type { - Type::Void => { - ffi_call::(cif_ptr, fun_ptr, args_ptr); - InkoPointer::nil_singleton() - } - Type::Pointer => { - InkoPointer::new(ffi_call(cif_ptr, fun_ptr, args_ptr)) - } - Type::F64 | Type::F32 => { - let result: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); - - Float::alloc(state.permanent_space.float_class(), result as f64) - } - Type::I8 - | Type::I16 - | Type::I32 - | Type::I64 - | Type::U8 - | Type::U16 - | Type::U32 - | Type::U64 - | Type::SizeT => { - let result: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); - - Int::alloc(state.permanent_space.int_class(), result as i64) - } - Type::String => { - let result = - CStr::from_ptr(ffi_call(cif_ptr, fun_ptr, args_ptr)) - .to_string_lossy() - .into_owned(); - - InkoString::alloc(state.permanent_space.string_class(), result) - } - Type::ByteArray => { - let result = - CStr::from_ptr(ffi_call(cif_ptr, fun_ptr, args_ptr)) - .to_bytes(); - - ByteArray::alloc( - state.permanent_space.byte_array_class(), - result.into(), - ) - } - }; - - Ok(result) - } -} - -#[cfg(all( - test, - any(target_os = "macos", target_os = "linux", target_os = "windows") -))] -mod tests { - use super::*; - use crate::mem::Pointer as InkoPointer; - use crate::mem::String as InkoString; - use crate::test::setup; - - extern "C" { - fn calloc(amount: usize, size: usize) -> RawPointer; - fn free(pointer: RawPointer); - } - - #[cfg(target_os = "macos")] - const LIBM: &'static str = "libm.dylib"; - - #[cfg(target_os = "linux")] - const LIBM: &str = "libm.so.6"; - - #[cfg(target_os = "windows")] - const LIBM: &'static str = "msvcrt.dll"; - - #[test] - fn test_library_new() { - assert!(Library::open(&[LIBM]).is_some()); - } - - #[test] - fn test_library_get() { - let lib = Library::open(&[LIBM]).unwrap(); - let sym = unsafe { lib.get("floor") }; - - assert!(sym.is_some()); - } - - #[test] - fn test_function_new() { - let lib = Library::open(&[LIBM]).unwrap(); - - unsafe { - let sym = lib.get("floor").unwrap(); - - let fun = Function::create(sym, vec![Type::F64], Type::F64); - - assert!(fun.is_ok()); - } - } - - #[test] - fn test_function_from_pointers() { - let state = setup(); - let name = InkoString::alloc( - state.permanent_space.string_class(), - LIBM.to_string(), - ); - - let names = vec![name]; - let lib = Library::from_pointers(&names); - - assert!(lib.is_some()); - - unsafe { - InkoString::drop_and_deallocate(name); - } - } - - #[test] - fn test_function_call() { - let lib = Library::open(&[LIBM]).unwrap(); - let state = setup(); - let arg = Float::alloc(state.permanent_space.float_class(), 3.15); - - unsafe { - let sym = lib.get("floor").unwrap(); - let fun = - Function::create(sym, vec![Type::F64], Type::F64).unwrap(); - let res = fun.call(&state, &[arg]).unwrap(); - - assert_eq!(Float::read(res), 3.0); - arg.free(); - res.free(); - } - } - - #[test] - fn test_pointer_read_and_write() { - let state = setup(); - - unsafe { - let ptr = Pointer::new(calloc(1, 3)); - let kind = InkoPointer::int(12); - let val = InkoString::alloc( - state.permanent_space.string_class(), - "ab".to_string(), - ); - - ptr.write_as(kind, val).unwrap(); - - let result = ptr.read_as(&state, kind); - - free(ptr.inner); - - assert!(result.is_ok()); - - let new_string = result.unwrap(); - - assert_eq!(InkoString::read(&new_string), "ab"); - - InkoString::drop_and_deallocate(val); - InkoString::drop_and_deallocate(new_string); - } - } -} - -#[cfg(test)] -mod tests_for_all_platforms { - use super::*; - - #[test] - fn test_library_new_invalid() { - let lib = Library::open(&["inko-test-1", "inko-test-2"]); - - assert!(lib.is_none()); - } -} diff --git a/vm/src/hasher.rs b/vm/src/hasher.rs deleted file mode 100644 index e11ddce49..000000000 --- a/vm/src/hasher.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Types and methods for hashing objects. -use ahash::AHasher; -use std::hash::{Hash, Hasher as _}; -use std::i64; - -#[derive(Clone)] -pub(crate) struct Hasher { - hasher: AHasher, -} - -impl Hasher { - pub(crate) fn new(hasher: AHasher) -> Self { - Hasher { hasher } - } - - pub(crate) fn write_int(&mut self, value: i64) { - value.hash(&mut self.hasher); - } - - pub(crate) fn finish(&mut self) -> i64 { - self.hasher.finish() as i64 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ahash::RandomState; - use std::hash::BuildHasher as _; - use std::mem::size_of; - - #[test] - fn test_write_int() { - let state = RandomState::new(); - let mut hasher1 = Hasher::new(state.build_hasher()); - let mut hasher2 = Hasher::new(state.build_hasher()); - - hasher1.write_int(10); - hasher2.write_int(10); - - assert_eq!(hasher1.finish(), hasher2.finish()); - } - - #[test] - fn test_mem_size() { - assert!(size_of::() <= 48); - } -} diff --git a/vm/src/image.rs b/vm/src/image.rs deleted file mode 100644 index cb3404747..000000000 --- a/vm/src/image.rs +++ /dev/null @@ -1,1076 +0,0 @@ -//! Loading of Inko bytecode images. -//! -//! Various chunks of bytecode, such as numbers, are encoded using -//! little-endian. Since most conventional CPUs use little-endian, using the -//! same endianness means we don't have to flip bits around on these CPUs. -use crate::chunk::Chunk; -use crate::config::Config; -use crate::indexes::{ClassIndex, MethodIndex}; -use crate::location_table::LocationTable; -use crate::mem::{Class, ClassPointer, Method, Module, ModulePointer, Pointer}; -use crate::permanent_space::{ - MethodCounts, PermanentSpace, ARRAY_CLASS, BOOLEAN_CLASS, BYTE_ARRAY_CLASS, - FLOAT_CLASS, FUTURE_CLASS, INT_CLASS, NIL_CLASS, STRING_CLASS, -}; -use bytecode::{ - Instruction, Opcode, CONST_ARRAY, CONST_FLOAT, CONST_INTEGER, CONST_STRING, - SIGNATURE_BYTES, VERSION, -}; -use std::f64; -use std::fs::File; -use std::io::{BufReader, Read}; -use std::str; - -macro_rules! read_slice { - ($stream:expr, $amount:expr) => {{ - let mut buffer: [u8; $amount] = [0; $amount]; - - $stream - .read_exact(&mut buffer) - .map_err(|e| format!("Failed to read {} bytes: {}", $amount, e))?; - - buffer - }}; -} - -macro_rules! read_vec { - ($stream:expr, $amount:expr) => {{ - let mut buffer: Vec = vec![0; $amount]; - - $stream.read_exact(&mut buffer).map_err(|e| e.to_string())?; - - buffer - }}; -} - -macro_rules! read_byte { - ($stream: expr) => {{ - read_slice!($stream, 1)[0] - }}; -} - -/// The number of bytes to buffer for every read from a bytecode file. -const BUFFER_SIZE: usize = 32 * 1024; - -/// A parsed bytecode image. -pub struct Image { - /// The ID of the first class to run. - pub(crate) entry_class: ClassPointer, - - /// The index of the entry module to run. - pub(crate) entry_method: MethodIndex, - - /// The space to use for allocating permanent objects. - pub(crate) permanent_space: PermanentSpace, - - /// Configuration settings to use when parsing and running bytecode. - pub(crate) config: Config, -} - -impl Image { - /// Loads a bytecode image from a file. - pub fn load_file(config: Config, path: &str) -> Result { - let file = File::open(path).map_err(|e| e.to_string())?; - - Self::load(config, &mut BufReader::with_capacity(BUFFER_SIZE, file)) - } - - pub fn load_bytes(config: Config, bytes: Vec) -> Result { - Self::load( - config, - &mut BufReader::with_capacity(BUFFER_SIZE, bytes.as_slice()), - ) - } - - /// Loads a bytecode image from a stream of bytes. - fn load(config: Config, stream: &mut R) -> Result { - // This is a simply/naive check to make sure we're _probably_ loading an - // Inko bytecode image; instead of something random like a PNG file. - if read_slice!(stream, 4) != SIGNATURE_BYTES { - return Err("The bytecode signature is invalid".to_string()); - } - - let version = read_byte!(stream); - - if version != VERSION { - return Err(format!( - "The bytecode version {} is not supported", - version - )); - } - - let modules = read_u32(stream)?; - let classes = read_u32(stream)?; - let space = PermanentSpace::new( - modules, - classes, - read_builtin_method_counts(stream)?, - ); - - let entry_class = ClassIndex::new(read_u32(stream)?); - let entry_method = read_u16(stream)?; - - // Now we can load the bytecode for all modules, including the entry - // module. The order in which the modules are returned is unspecified. - read_modules(modules as usize, &space, stream)?; - - Ok(Image { - entry_class: unsafe { space.get_class(entry_class) }, - entry_method: MethodIndex::new(entry_method), - permanent_space: space, - config, - }) - } -} - -fn read_builtin_method_counts( - stream: &mut R, -) -> Result { - let int_class = read_u16(stream)?; - let float_class = read_u16(stream)?; - let string_class = read_u16(stream)?; - let array_class = read_u16(stream)?; - let boolean_class = read_u16(stream)?; - let nil_class = read_u16(stream)?; - let byte_array_class = read_u16(stream)?; - let future_class = read_u16(stream)?; - let counts = MethodCounts { - int_class, - float_class, - string_class, - array_class, - boolean_class, - nil_class, - byte_array_class, - future_class, - }; - - Ok(counts) -} - -fn read_modules( - num_modules: usize, - space: &PermanentSpace, - stream: &mut R, -) -> Result<(), String> { - for _ in 0..num_modules { - let amount = read_u64(stream)? as usize; - let chunk = read_vec!(stream, amount); - - read_module(space, &mut &chunk[..])?; - } - - Ok(()) -} - -fn read_module( - space: &PermanentSpace, - stream: &mut R, -) -> Result { - let index = read_u32(stream)?; - let constants = read_constants(space, stream)?; - let class_index = read_u32(stream)?; - - read_classes(space, stream, &constants)?; - - let mod_class = unsafe { space.get_class(ClassIndex::new(class_index)) }; - let module = Module::alloc(mod_class); - - unsafe { space.add_module(index, module)? }; - - Ok(module) -} - -fn read_string(stream: &mut R) -> Result { - let size = read_u32(stream)? as usize; - let buff = read_vec!(stream, size); - - String::from_utf8(buff).map_err(|e| e.to_string()) -} - -fn read_constant_array( - space: &PermanentSpace, - stream: &mut R, -) -> Result, String> { - let amount = read_u16(stream)? as usize; - let mut values = Vec::with_capacity(amount); - - for _ in 0..amount { - values.push(read_constant(space, stream)?); - } - - Ok(values) -} - -fn read_u8(stream: &mut R) -> Result { - Ok(read_byte!(stream)) -} - -fn read_u16(stream: &mut R) -> Result { - let buff = read_slice!(stream, 2); - - Ok(u16::from_le_bytes(buff)) -} - -fn read_u32(stream: &mut R) -> Result { - let buff = read_slice!(stream, 4); - - Ok(u32::from_le_bytes(buff)) -} - -fn read_i64(stream: &mut R) -> Result { - let buff = read_slice!(stream, 8); - - Ok(i64::from_le_bytes(buff)) -} - -fn read_u64(stream: &mut R) -> Result { - let buff = read_slice!(stream, 8); - - Ok(u64::from_le_bytes(buff)) -} - -fn read_f64(stream: &mut R) -> Result { - let buff = read_slice!(stream, 8); - let int = u64::from_le_bytes(buff); - - Ok(f64::from_bits(int)) -} - -fn read_instruction( - stream: &mut R, - constants: &Chunk, -) -> Result { - let opcode = Opcode::from_byte(read_u8(stream)?)?; - let mut args = [0, 0, 0, 0, 0]; - - for index in 0..opcode.arity() { - args[index] = read_u16(stream)?; - } - - let mut ins = Instruction::new(opcode, args); - - // GetConstant instructions are rewritten such that the pointer to the value - // is encoded directly into its arguments. - if let Opcode::GetConstant = opcode { - let ptr = unsafe { *constants.get(ins.u32_arg(1, 2) as usize) }; - let addr = ptr.as_ptr() as u64; - let bytes = u64::to_le_bytes(addr); - - ins.arguments[1] = u16::from_le_bytes([bytes[0], bytes[1]]); - ins.arguments[2] = u16::from_le_bytes([bytes[2], bytes[3]]); - ins.arguments[3] = u16::from_le_bytes([bytes[4], bytes[5]]); - ins.arguments[4] = u16::from_le_bytes([bytes[6], bytes[7]]); - } - - Ok(ins) -} - -fn read_instructions( - stream: &mut R, - constants: &Chunk, -) -> Result, String> { - let amount = read_u32(stream)? as usize; - let mut buff = Vec::with_capacity(amount); - - for _ in 0..amount { - buff.push(read_instruction(stream, constants)?); - } - - Ok(buff) -} - -fn read_classes( - space: &PermanentSpace, - stream: &mut R, - constants: &Chunk, -) -> Result<(), String> { - let amount = read_u16(stream)?; - - for _ in 0..amount { - read_class(space, stream, constants)?; - } - - Ok(()) -} - -fn read_class( - space: &PermanentSpace, - stream: &mut R, - constants: &Chunk, -) -> Result<(), String> { - let index = read_u32(stream)?; - let process_class = read_u8(stream)? == 1; - let name = read_string(stream)?; - let fields = read_u8(stream)? as usize; - let method_slots = read_u16(stream)?; - let class = match index as usize { - INT_CLASS => space.int_class(), - FLOAT_CLASS => space.float_class(), - STRING_CLASS => space.string_class(), - ARRAY_CLASS => space.array_class(), - BOOLEAN_CLASS => space.boolean_class(), - NIL_CLASS => space.nil_class(), - BYTE_ARRAY_CLASS => space.byte_array_class(), - FUTURE_CLASS => space.future_class(), - _ => { - let new_class = if process_class { - Class::process(name, fields, method_slots) - } else { - Class::object(name, fields, method_slots) - }; - - unsafe { space.add_class(index, new_class)? }; - new_class - } - }; - - read_methods(stream, class, constants)?; - Ok(()) -} - -fn read_method( - stream: &mut R, - class: ClassPointer, - constants: &Chunk, -) -> Result<(), String> { - let index = read_u16(stream)?; - let hash = read_u32(stream)?; - let registers = read_u16(stream)?; - let instructions = read_instructions(stream, constants)?; - let locations = read_location_table(stream, constants)?; - let jump_tables = read_jump_tables(stream)?; - let method = - Method::alloc(hash, registers, instructions, locations, jump_tables); - - unsafe { - class.set_method(MethodIndex::new(index), method); - } - - Ok(()) -} - -fn read_location_table( - stream: &mut R, - constants: &Chunk, -) -> Result { - let mut table = LocationTable::new(); - - for _ in 0..read_u16(stream)? { - let index = read_u32(stream)?; - let line = read_u16(stream)?; - let file = read_constant_index(stream, constants)?; - let name = read_constant_index(stream, constants)?; - - table.add_entry(index, line, file, name); - } - - Ok(table) -} - -fn read_jump_tables( - stream: &mut R, -) -> Result>, String> { - let num_tables = read_u16(stream)? as usize; - let mut tables = Vec::with_capacity(num_tables); - - for _ in 0..num_tables { - let num_entries = read_u16(stream)? as usize; - let mut table = Vec::with_capacity(num_entries); - - for _ in 0..num_entries { - table.push(read_u32(stream)? as usize); - } - - tables.push(table); - } - - Ok(tables) -} - -fn read_methods( - stream: &mut R, - class: ClassPointer, - constants: &Chunk, -) -> Result<(), String> { - let amount = read_u16(stream)? as usize; - - for _ in 0..amount { - read_method(stream, class, constants)?; - } - - Ok(()) -} - -fn read_constants( - space: &PermanentSpace, - stream: &mut R, -) -> Result, String> { - let amount = read_u32(stream)? as usize; - let mut buff = Chunk::new(amount); - - for _ in 0..amount { - let index = read_u32(stream)? as usize; - - unsafe { - buff.set(index, read_constant(space, stream)?); - } - } - - Ok(buff) -} - -fn read_constant_index( - stream: &mut R, - constants: &Chunk, -) -> Result { - let index = read_u32(stream)? as usize; - - if index >= constants.len() { - return Err(format!( - "The constant index {} is out of bounds (number of constants: {})", - index, - constants.len() - )); - } - - Ok(unsafe { *constants.get(index) }) -} - -fn read_constant( - space: &PermanentSpace, - stream: &mut R, -) -> Result { - let const_type = read_u8(stream)?; - let value = match const_type { - CONST_INTEGER => space.allocate_int(read_i64(stream)?), - CONST_FLOAT => space.allocate_float(read_f64(stream)?), - CONST_STRING => space.allocate_string(read_string(stream)?), - CONST_ARRAY => { - space.allocate_array(read_constant_array(space, stream)?) - } - _ => { - return Err(format!("The constant type {} is invalid", const_type)) - } - }; - - Ok(value) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::indexes::{ClassIndex, MethodIndex}; - use crate::mem::{Float, Int, String as InkoString}; - use crate::test::OwnedClass; - use bytecode::Opcode; - use std::u64; - - fn pack_signature(buffer: &mut Vec) { - buffer.extend_from_slice(&SIGNATURE_BYTES); - } - - fn pack_u8(buffer: &mut Vec, value: u8) { - buffer.push(value); - } - - fn pack_u16(buffer: &mut Vec, value: u16) { - let num = u16::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_u32(buffer: &mut Vec, value: u32) { - let num = u32::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_u64(buffer: &mut Vec, value: u64) { - let num = u64::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_i64(buffer: &mut Vec, value: i64) { - let num = i64::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_f64(buffer: &mut Vec, value: f64) { - pack_u64(buffer, value.to_bits()); - } - - fn pack_string(buffer: &mut Vec, string: &str) { - pack_u32(buffer, string.len() as u32); - - buffer.extend_from_slice(string.as_bytes()); - } - - fn pack_version(buffer: &mut Vec) { - buffer.push(VERSION); - } - - macro_rules! reader { - ($buff: expr) => { - &mut $buff.as_slice() - }; - } - - #[test] - fn test_load_empty() { - let config = Config::new(); - let buffer = Vec::new(); - let output = - Image::load(config, &mut BufReader::new(buffer.as_slice())); - - assert!(output.is_err()); - } - - #[test] - fn test_load_invalid_signature() { - let config = Config::new(); - let mut buffer = Vec::new(); - - pack_string(&mut buffer, "cats"); - - let output = - Image::load(config, &mut BufReader::new(buffer.as_slice())); - - assert!(output.is_err()); - } - - #[test] - fn test_load_invalid_version() { - let config = Config::new(); - let mut buffer = Vec::new(); - - pack_signature(&mut buffer); - - buffer.push(VERSION + 1); - - let output = - Image::load(config, &mut BufReader::new(buffer.as_slice())); - - assert!(output.is_err()); - } - - #[test] - fn test_load_valid() { - let config = Config::new(); - let mut image = Vec::new(); - let mut chunk = Vec::new(); - - pack_u32(&mut chunk, 0); // module index - - // The module's constants - pack_u32(&mut chunk, 3); - - pack_u32(&mut chunk, 0); - pack_u8(&mut chunk, CONST_STRING); - pack_string(&mut chunk, "new_counter"); - - pack_u32(&mut chunk, 1); - pack_u8(&mut chunk, CONST_STRING); - pack_string(&mut chunk, "main.inko"); - - pack_u32(&mut chunk, 2); - pack_u8(&mut chunk, CONST_STRING); - pack_string(&mut chunk, "add"); - - // The (global) index of the module's class. - pack_u32(&mut chunk, 8); - - // Classes defined in the module - pack_u16(&mut chunk, 2); // class count - - pack_u32(&mut chunk, 8); // index - pack_u8(&mut chunk, 0); - pack_string(&mut chunk, "main"); - pack_u8(&mut chunk, 0); - pack_u16(&mut chunk, 1); // Method slot count - - // The methods - pack_u16(&mut chunk, 1); - pack_u16(&mut chunk, 0); - pack_u32(&mut chunk, 456); - pack_u16(&mut chunk, 0); - - // The method instructions - pack_u32(&mut chunk, 1); - pack_u8(&mut chunk, 93); - pack_u16(&mut chunk, 2); - - // The location table - pack_u16(&mut chunk, 0); - - // The jump tables - pack_u16(&mut chunk, 0); - - pack_u32(&mut chunk, 9); // index - pack_u8(&mut chunk, 0); - pack_string(&mut chunk, "Counter"); - pack_u8(&mut chunk, 1); - pack_u16(&mut chunk, 1); // Method slot count - - // The methods of the class - pack_u16(&mut chunk, 1); - pack_u16(&mut chunk, 0); - pack_u32(&mut chunk, 123); - pack_u16(&mut chunk, 2); - - // The method instructions - pack_u32(&mut chunk, 1); - pack_u8(&mut chunk, 93); - pack_u16(&mut chunk, 2); - - // The location table - pack_u16(&mut chunk, 0); - - // The jump tables - pack_u16(&mut chunk, 0); - - // Image header - pack_signature(&mut image); - pack_version(&mut image); - - pack_u32(&mut image, 1); // Number of modules - pack_u32(&mut image, 2); // Number of classes - - // Built-in method counts - pack_u16(&mut image, 1); // Int - pack_u16(&mut image, 4); // Float - pack_u16(&mut image, 5); // String - pack_u16(&mut image, 6); // Array - pack_u16(&mut image, 9); // Bool - pack_u16(&mut image, 10); // NilType - pack_u16(&mut image, 11); // ByteArray - pack_u16(&mut image, 13); // Future - - // Entry class and method - pack_u32(&mut image, 0); - pack_u16(&mut image, 42); - - // The number of bytes for this module. - pack_u64(&mut image, chunk.len() as u64); - image.append(&mut chunk); - - let image = - Image::load(config, &mut BufReader::new(image.as_slice())).unwrap(); - - let entry_method: u16 = image.entry_method.into(); - let perm = &image.permanent_space; - - assert_eq!(perm.int_class().method_slots, 1); - assert_eq!(perm.float_class().method_slots, 4); - assert_eq!(perm.string_class().method_slots, 5); - assert_eq!(perm.array_class().method_slots, 6); - assert_eq!(perm.boolean_class().method_slots, 9); - assert_eq!(perm.nil_class().method_slots, 10); - assert_eq!(perm.byte_array_class().method_slots, 11); - assert_eq!(perm.future_class().method_slots, 13); - - assert!( - image.entry_class == unsafe { perm.get_class(ClassIndex::new(0)) } - ); - assert_eq!(entry_method, 42); - - let module_class = unsafe { perm.get_class(ClassIndex::new(8)) }; - let counter_class = unsafe { perm.get_class(ClassIndex::new(9)) }; - - assert_eq!(&module_class.name, &"main"); - assert_eq!(module_class.method_slots, 1); - - assert_eq!(&counter_class.name, &"Counter"); - assert_eq!(counter_class.method_slots, 1); - - let add_meth = unsafe { counter_class.get_method(MethodIndex::new(0)) }; - let new_meth = unsafe { module_class.get_method(MethodIndex::new(0)) }; - - assert_eq!(add_meth.hash, 123); - assert_eq!(add_meth.registers, 2); - assert_eq!(add_meth.instructions[0].opcode, Opcode::Return); - assert_eq!(add_meth.instructions[0].arg(0), 2); - - assert_eq!(new_meth.hash, 456); - assert_eq!(new_meth.registers, 0); - assert_eq!(new_meth.instructions[0].opcode, Opcode::Return); - assert_eq!(new_meth.instructions[0].arg(0), 2); - } - - #[test] - fn test_read_string() { - let mut buffer = Vec::new(); - - pack_string(&mut buffer, "inko"); - - let output = read_string(reader!(buffer)).unwrap(); - - assert_eq!(output, "inko".to_string()); - } - - #[test] - fn test_read_string_too_large() { - let mut buffer = Vec::new(); - - pack_u32(&mut buffer, u32::MAX); - - let output = read_string(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_string_longer_than_size() { - let mut buffer = Vec::new(); - - pack_u32(&mut buffer, 2); - pack_signature(&mut buffer); - - let output = read_string(reader!(buffer)).unwrap(); - - assert_eq!(output, "in".to_string()); - } - - #[test] - fn test_read_string_invalid_utf8() { - let mut buffer = Vec::new(); - - pack_u32(&mut buffer, 4); - pack_u8(&mut buffer, 0); - pack_u8(&mut buffer, 159); - pack_u8(&mut buffer, 146); - pack_u8(&mut buffer, 150); - - let output = read_string(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_string_empty() { - let buffer = Vec::new(); - let output = read_string(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_u8() { - let mut buffer = Vec::new(); - - pack_u8(&mut buffer, 2); - - let output = read_u8(reader!(buffer)).unwrap(); - - assert_eq!(output, 2); - } - - #[test] - fn test_read_u8_empty() { - let buffer = Vec::new(); - let output = read_u8(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_u16() { - let mut buffer = Vec::new(); - - pack_u16(&mut buffer, 2); - - let output = read_u16(reader!(buffer)).unwrap(); - - assert_eq!(output, 2); - } - - #[test] - fn test_read_u16_empty() { - let buffer = Vec::new(); - let output = read_u16(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_i64() { - let mut buffer = Vec::new(); - - pack_i64(&mut buffer, 2); - - let output = read_i64(reader!(buffer)).unwrap(); - - assert_eq!(output, 2); - } - - #[test] - fn test_read_i64_empty() { - let buffer = Vec::new(); - let output = read_i64(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_f64() { - let mut buffer = Vec::new(); - - pack_f64(&mut buffer, 2.123456); - - let output = read_f64(reader!(buffer)).unwrap(); - - assert!((2.123456 - output).abs() < 0.00001); - } - - #[test] - fn test_read_f64_empty() { - let buffer = Vec::new(); - let output = read_f64(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_instruction() { - let mut buffer = Vec::new(); - let mut constants = Chunk::new(2); - let const1 = Pointer::int(10); - let const2 = Pointer::int(20); - - unsafe { - constants.set(0, const1); - constants.set(1, const2); - } - - let u32_bytes = u32::to_le_bytes(1); - let arg1 = u16::from_le_bytes([u32_bytes[0], u32_bytes[1]]); - let arg2 = u16::from_le_bytes([u32_bytes[2], u32_bytes[3]]); - - pack_u8(&mut buffer, 49); - pack_u16(&mut buffer, 14); - pack_u16(&mut buffer, arg1); - pack_u16(&mut buffer, arg2); - - let ins = read_instruction(reader!(buffer), &constants).unwrap(); - - assert_eq!(ins.opcode, Opcode::GetConstant); - assert_eq!(ins.arg(0), 14); - assert_eq!(ins.u64_arg(1, 2, 3, 4), const2.as_ptr() as u64); - } - - #[test] - fn test_read_instructions() { - let mut buffer = Vec::new(); - let constants = Chunk::new(0); - - pack_u32(&mut buffer, 1); - pack_u8(&mut buffer, 0); - pack_u16(&mut buffer, 0); - pack_u16(&mut buffer, 2); - pack_u16(&mut buffer, 4); - - let ins = read_instructions(reader!(buffer), &constants).unwrap(); - - assert_eq!(ins.len(), 1); - assert_eq!(ins[0].opcode, Opcode::Allocate); - assert_eq!(ins[0].arg(0), 0); - assert_eq!(ins[0].arg(1), 2); - assert_eq!(ins[0].arg(2), 4); - } - - #[test] - fn test_read_class() { - let class_index = 8; - let mut buffer = Vec::new(); - let constants = Chunk::new(0); - let perm = PermanentSpace::new(1, 1, MethodCounts::default()); - - pack_u32(&mut buffer, class_index); - pack_u8(&mut buffer, 0); - pack_string(&mut buffer, "A"); - pack_u8(&mut buffer, 1); - pack_u16(&mut buffer, 0); - pack_u16(&mut buffer, 0); - - assert!(read_class(&perm, reader!(buffer), &constants).is_ok()); - - let class = unsafe { perm.get_class(ClassIndex::new(class_index)) }; - - assert_eq!(&class.name, &"A"); - } - - #[test] - fn test_read_class_with_process_class() { - let class_index = 8; - let mut buffer = Vec::new(); - let constants = Chunk::new(0); - let perm = PermanentSpace::new(1, 1, MethodCounts::default()); - - pack_u32(&mut buffer, class_index); - pack_u8(&mut buffer, 1); - pack_string(&mut buffer, "A"); - pack_u8(&mut buffer, 1); - pack_u16(&mut buffer, 0); - pack_u16(&mut buffer, 0); - - assert!(read_class(&perm, reader!(buffer), &constants).is_ok()); - - let class = unsafe { perm.get_class(ClassIndex::new(class_index)) }; - - assert_eq!(&class.name, &"A"); - } - - #[test] - fn test_read_class_with_builtin_class() { - let mut buffer = Vec::new(); - let counts = MethodCounts { int_class: 1, ..Default::default() }; - let perm = PermanentSpace::new(0, 0, counts); - let mut constants = Chunk::new(1); - - unsafe { - constants.set(0, perm.allocate_string("add".to_string())); - } - - pack_u32(&mut buffer, 0); - pack_u8(&mut buffer, 2); - pack_string(&mut buffer, "A"); - pack_u8(&mut buffer, 0); - pack_u16(&mut buffer, 1); - - pack_u16(&mut buffer, 0); - pack_u32(&mut buffer, 123); - pack_u16(&mut buffer, 0); - - // The instructions - pack_u32(&mut buffer, 0); - - // The location table - pack_u16(&mut buffer, 0); - - // The jump tables - pack_u16(&mut buffer, 0); - - assert!(read_class(&perm, reader!(buffer), &constants).is_ok()); - assert_eq!(perm.int_class().method_slots, 1); - } - - #[test] - fn test_read_method() { - let mut buffer = Vec::new(); - let mut constants = Chunk::new(2); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - let class = OwnedClass::new(Class::alloc("A".to_string(), 1, 0)); - - unsafe { constants.set(0, perm.allocate_string("add".to_string())) }; - unsafe { - constants.set(1, perm.allocate_string("test.inko".to_string())) - }; - - pack_u16(&mut buffer, 0); - pack_u32(&mut buffer, 123); - pack_u16(&mut buffer, 3); - - // The instructions - pack_u32(&mut buffer, 0); - - // The location table - pack_u16(&mut buffer, 1); // entries - pack_u32(&mut buffer, 0); - pack_u16(&mut buffer, 14); - pack_u32(&mut buffer, 1); - pack_u32(&mut buffer, 0); - - // The jump tables - pack_u16(&mut buffer, 1); - pack_u16(&mut buffer, 2); - pack_u32(&mut buffer, 4); - pack_u32(&mut buffer, 8); - - read_method(reader!(buffer), *class, &constants).unwrap(); - - let method = unsafe { class.get_method(MethodIndex::new(0)) }; - - assert_eq!(method.hash, 123); - assert_eq!(method.registers, 3); - - let location = method.locations.get(0).unwrap(); - - assert_eq!(unsafe { InkoString::read(&location.name) }, "add"); - assert_eq!(unsafe { InkoString::read(&location.file) }, "test.inko"); - assert_eq!(location.line, Pointer::int(14)); - assert_eq!(method.jump_tables, vec![vec![4, 8]]); - } - - #[test] - fn test_read_methods() { - let mut buffer = Vec::new(); - let mut constants = Chunk::new(1); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - let class = OwnedClass::new(Class::alloc("A".to_string(), 2, 1)); - - unsafe { constants.set(0, perm.allocate_string("add".to_string())) }; - - pack_u16(&mut buffer, 1); // The number of methods to read - - pack_u16(&mut buffer, 1); - pack_u32(&mut buffer, 123); - pack_u16(&mut buffer, 1); - - // The instructions - pack_u32(&mut buffer, 0); - - // The location table - pack_u16(&mut buffer, 0); - - // The jump tables - pack_u16(&mut buffer, 0); - - assert!(read_methods(reader!(buffer), *class, &constants).is_ok()); - } - - #[test] - fn test_read_constants() { - let mut buffer = Vec::new(); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - pack_u32(&mut buffer, 3); - - pack_u32(&mut buffer, 0); - pack_u8(&mut buffer, CONST_INTEGER); - pack_i64(&mut buffer, -2); - - pack_u32(&mut buffer, 1); - pack_u8(&mut buffer, CONST_FLOAT); - pack_f64(&mut buffer, 2.0); - - pack_u32(&mut buffer, 2); - pack_u8(&mut buffer, CONST_STRING); - pack_string(&mut buffer, "inko"); - - let output = read_constants(&perm, reader!(buffer)).unwrap(); - - assert_eq!(output.len(), 3); - - unsafe { - assert_eq!(Int::read(*output.get(0)), -2); - assert_eq!(Float::read(*output.get(1)), 2.0); - assert_eq!(InkoString::read(output.get(2)), "inko"); - } - } - - #[test] - fn test_read_constant_invalid() { - let mut buffer = Vec::new(); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - pack_u8(&mut buffer, 255); - - assert!(read_constant(&perm, reader!(buffer)).is_err()); - } -} diff --git a/vm/src/indexes.rs b/vm/src/indexes.rs deleted file mode 100644 index a30232bce..000000000 --- a/vm/src/indexes.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Types for indexing various tables such as method tables. - -/// An index used for accessing classes. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct ClassIndex(u32); - -impl ClassIndex { - pub(crate) fn new(index: u32) -> Self { - Self(index) - } -} - -impl From for u32 { - fn from(index: ClassIndex) -> u32 { - index.0 - } -} - -impl From for usize { - fn from(index: ClassIndex) -> usize { - index.0 as usize - } -} - -/// An index used for accessing methods. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct MethodIndex(u16); - -impl MethodIndex { - pub(crate) fn new(index: u16) -> Self { - Self(index) - } -} - -impl From for u16 { - fn from(index: MethodIndex) -> u16 { - index.0 - } -} - -impl From for usize { - fn from(index: MethodIndex) -> usize { - index.0 as usize - } -} - -/// An index used for accessing fields. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct FieldIndex(u8); - -impl FieldIndex { - pub(crate) fn new(index: u8) -> Self { - Self(index) - } -} - -impl From for u8 { - fn from(index: FieldIndex) -> u8 { - index.0 - } -} - -impl From for usize { - fn from(index: FieldIndex) -> usize { - index.0 as usize - } -} diff --git a/vm/src/instructions/array.rs b/vm/src/instructions/array.rs deleted file mode 100644 index bab4ef803..000000000 --- a/vm/src/instructions/array.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! VM functions for working with Inko arrays. -use crate::mem::Pointer; -use crate::mem::{Array, Int}; -use crate::process::TaskPointer; -use crate::state::State; - -#[inline(always)] -pub(crate) fn allocate(state: &State, mut task: TaskPointer) -> Pointer { - // We use this approach so our array's capacity isn't dictated by the - // stack's capacity. - let mut values = Vec::with_capacity(task.stack.len()); - - values.append(&mut task.stack); - Array::alloc(state.permanent_space.array_class(), values) -} - -#[inline(always)] -pub(crate) fn push(array_ptr: Pointer, value_ptr: Pointer) { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - - vector.push(value_ptr); -} - -#[inline(always)] -pub(crate) fn pop(array_ptr: Pointer) -> Pointer { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - - vector.pop().unwrap_or_else(Pointer::undefined_singleton) -} - -#[inline(always)] -pub(crate) fn set( - array_ptr: Pointer, - index_ptr: Pointer, - value_ptr: Pointer, -) -> Pointer { - unsafe { - let array = array_ptr.get_mut::(); - let vector = array.value_mut(); - let index = Int::read(index_ptr) as usize; - let index_ref = vector.get_unchecked_mut(index); - let old_value = *index_ref; - - *index_ref = value_ptr; - old_value - } -} - -#[inline(always)] -pub(crate) fn get(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - unsafe { - let array = array_ptr.get::(); - let vector = array.value(); - let index = Int::read(index_ptr) as usize; - - *vector.get_unchecked(index) - } -} - -#[inline(always)] -pub(crate) fn remove(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - let index = unsafe { Int::read(index_ptr) as usize }; - - vector.remove(index) -} - -#[inline(always)] -pub(crate) fn length(state: &State, pointer: Pointer) -> Pointer { - let array = unsafe { pointer.get::() }; - let vector = array.value(); - - Int::alloc(state.permanent_space.int_class(), vector.len() as i64) -} - -#[inline(always)] -pub(crate) fn clear(array_ptr: Pointer) { - unsafe { array_ptr.get_mut::() }.value_mut().clear(); -} - -#[inline(always)] -pub(crate) fn drop(array_ptr: Pointer) { - unsafe { - Array::drop(array_ptr); - } -} diff --git a/vm/src/instructions/builtin_functions.rs b/vm/src/instructions/builtin_functions.rs deleted file mode 100644 index bb2b9794c..000000000 --- a/vm/src/instructions/builtin_functions.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! VM functions for handling builtin functions. -use crate::mem::Pointer; -use crate::process::{ProcessPointer, TaskPointer}; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -#[inline(always)] -pub(crate) fn call( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - task: TaskPointer, - func_index: u16, -) -> Result { - let func = state.builtin_functions.get(func_index); - - // We keep the arguments as-is, as the function may suspend the current - // process and require retrying the current instruction. - let args = &task.stack; - - func(state, thread, process, args) -} diff --git a/vm/src/instructions/byte_array.rs b/vm/src/instructions/byte_array.rs deleted file mode 100644 index 6b4f72793..000000000 --- a/vm/src/instructions/byte_array.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! VM functions for working with Inko byte arrays. -use crate::mem::Pointer; -use crate::mem::{ByteArray, Int}; -use crate::state::State; - -#[inline(always)] -pub(crate) fn allocate(state: &State) -> Pointer { - ByteArray::alloc(state.permanent_space.byte_array_class(), Vec::new()) -} - -#[inline(always)] -pub(crate) fn push(array_ptr: Pointer, value_ptr: Pointer) { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - let value = unsafe { Int::read(value_ptr) } as u8; - - vector.push(value); -} - -#[inline(always)] -pub(crate) fn pop(array_ptr: Pointer) -> Pointer { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - - if let Some(value) = vector.pop() { - Pointer::int(value as i64) - } else { - Pointer::undefined_singleton() - } -} - -#[inline(always)] -pub(crate) fn set( - _: &State, - array_ptr: Pointer, - index_ptr: Pointer, - value_ptr: Pointer, -) -> Pointer { - unsafe { - let byte_array = array_ptr.get_mut::(); - let bytes = byte_array.value_mut(); - let index = Int::read(index_ptr) as usize; - let index_ref = bytes.get_unchecked_mut(index); - let old_value = *index_ref; - - *index_ref = Int::read(value_ptr) as u8; - Pointer::int(old_value as i64) - } -} - -#[inline(always)] -pub(crate) fn get(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - unsafe { - let byte_array = array_ptr.get::(); - let bytes = byte_array.value(); - let index = Int::read(index_ptr) as usize; - - Pointer::int(*bytes.get_unchecked(index) as i64) - } -} - -#[inline(always)] -pub(crate) fn remove(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - let byte_array = unsafe { array_ptr.get_mut::() }; - let bytes = byte_array.value_mut(); - let index = unsafe { Int::read(index_ptr) as usize }; - - Pointer::int(bytes.remove(index) as i64) -} - -#[inline(always)] -pub(crate) fn length(state: &State, array_ptr: Pointer) -> Pointer { - let byte_array = unsafe { array_ptr.get::() }; - let bytes = byte_array.value(); - - Int::alloc(state.permanent_space.int_class(), bytes.len() as i64) -} - -#[inline(always)] -pub(crate) fn equals( - compare_ptr: Pointer, - compare_with_ptr: Pointer, -) -> Pointer { - let compare = unsafe { compare_ptr.get::() }; - let compare_with = unsafe { compare_with_ptr.get::() }; - - if compare.value() == compare_with.value() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn clear(pointer: Pointer) { - let bytes = unsafe { pointer.get_mut::() }; - - bytes.value_mut().clear(); -} - -#[inline(always)] -pub(crate) fn clone(state: &State, pointer: Pointer) -> Pointer { - let bytes = unsafe { pointer.get_mut::() }.value().clone(); - - ByteArray::alloc(state.permanent_space.byte_array_class(), bytes) -} - -#[inline(always)] -pub(crate) fn drop(pointer: Pointer) { - unsafe { - ByteArray::drop(pointer); - } -} diff --git a/vm/src/instructions/float.rs b/vm/src/instructions/float.rs deleted file mode 100644 index 8d8d8c0d4..000000000 --- a/vm/src/instructions/float.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! VM functions for working with Inko floats. -use crate::mem::Pointer; -use crate::mem::{Float, Int, String as InkoString}; -use crate::state::State; - -/// The maximum difference between two floats for them to be considered equal, -/// as expressed in "Units in the Last Place" (ULP). -const ULP_DIFF: i64 = 1; - -#[inline(always)] -pub(crate) fn add( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left + right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn mul( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left * right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn div( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left / right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn sub( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left - right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn modulo(state: &State, left: Pointer, right: Pointer) -> Pointer { - let lhs = unsafe { Float::read(left) }; - let rhs = unsafe { Float::read(right) }; - let value = ((lhs % rhs) + rhs) % rhs; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn eq(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - // For float equality we use ULPs. See - // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ - // for more details. - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left == right { - // Handle cases such as `-0.0 == 0.0`. - return Pointer::true_singleton(); - } - - if left.is_sign_positive() != right.is_sign_positive() { - return Pointer::false_singleton(); - } - - if left.is_nan() || right.is_nan() { - return Pointer::false_singleton(); - } - - let left_bits = left.to_bits() as i64; - let right_bits = right.to_bits() as i64; - let diff = left_bits.wrapping_sub(right_bits); - - if (-ULP_DIFF..=ULP_DIFF).contains(&diff) { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn lt(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left < right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn gt(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left > right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn ge(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left >= right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn le(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left <= right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn clone(state: &State, ptr: Pointer) -> Pointer { - if ptr.is_permanent() { - return ptr; - } - - let value = unsafe { Float::read(ptr) }; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn ceil(state: &State, pointer: Pointer) -> Pointer { - let float = unsafe { Float::read(pointer) }; - let value = float.ceil(); - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn floor(state: &State, pointer: Pointer) -> Pointer { - let float = unsafe { Float::read(pointer) }; - let value = float.floor(); - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn round( - state: &State, - pointer: Pointer, - precision_ptr: Pointer, -) -> Pointer { - let float = unsafe { Float::read(pointer) }; - let precision = unsafe { Int::read(precision_ptr) }; - let result = if precision == 0 { - float.round() - } else if precision <= i64::from(u32::MAX) { - let power = 10.0_f64.powi(precision as i32); - let multiplied = float * power; - - // Certain very large numbers (e.g. f64::MAX) would produce Infinity - // when multiplied with the power. In this case we just return the input - // float directly. - if multiplied.is_finite() { - multiplied.round() / power - } else { - float - } - } else { - float - }; - - Float::alloc(state.permanent_space.float_class(), result) -} - -#[inline(always)] -pub(crate) fn to_int(state: &State, pointer: Pointer) -> Pointer { - let float = unsafe { Float::read(pointer) }; - - Int::alloc(state.permanent_space.int_class(), float as i64) -} - -#[inline(always)] -pub(crate) fn to_string(state: &State, pointer: Pointer) -> Pointer { - let value = unsafe { Float::read(pointer) }; - let string = if value.is_infinite() && value.is_sign_positive() { - "Infinity".to_string() - } else if value.is_infinite() { - "-Infinity".to_string() - } else if value.is_nan() { - "NaN".to_string() - } else { - format!("{:?}", value) - }; - - InkoString::alloc(state.permanent_space.string_class(), string) -} - -#[inline(always)] -pub(crate) fn is_nan(pointer: Pointer) -> Pointer { - if unsafe { Float::read(pointer) }.is_nan() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn is_inf(pointer: Pointer) -> Pointer { - if unsafe { Float::read(pointer) }.is_infinite() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} diff --git a/vm/src/instructions/future.rs b/vm/src/instructions/future.rs deleted file mode 100644 index 5b8b5d953..000000000 --- a/vm/src/instructions/future.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! VM functions for working with Inko futures. -use crate::mem::{Array, Int, Pointer}; -use crate::process::{Future, FutureResult, ProcessPointer}; -use crate::scheduler::timeouts::Timeout; -use crate::state::State; -use std::time::Duration; - -#[inline(always)] -pub(crate) fn get( - _: &State, - mut process: ProcessPointer, - future_ptr: Pointer, -) -> bool { - let fut = unsafe { future_ptr.get::() }; - - match fut.get(process, None) { - FutureResult::Returned(val) => { - process.set_return_value(val); - true - } - FutureResult::Thrown(val) => { - process.set_throw_value(val); - true - } - FutureResult::None => false, - } -} - -#[inline(always)] -pub(crate) fn get_for( - state: &State, - mut process: ProcessPointer, - future_ptr: Pointer, - time_ptr: Pointer, -) -> bool { - if process.timeout_expired() { - state.timeout_worker.increase_expired_timeouts(); - process.set_return_value(Pointer::undefined_singleton()); - - return true; - } - - let nanos = unsafe { Int::read_u64(time_ptr) }; - let timeout = Timeout::with_rc(Duration::from_nanos(nanos)); - let fut_ref = unsafe { future_ptr.get::() }; - - match fut_ref.get(process, Some(timeout.clone())) { - FutureResult::Returned(val) => { - process.set_return_value(val); - true - } - FutureResult::Thrown(val) => { - process.set_throw_value(val); - true - } - FutureResult::None => { - state.timeout_worker.suspend(process, timeout); - false - } - } -} - -#[inline(always)] -pub(crate) fn drop(pointer: Pointer) -> Pointer { - unsafe { - let result = pointer.get::().disconnect(); - - Future::drop(pointer); - result - } -} - -#[inline(always)] -pub(crate) fn poll( - state: &State, - process: ProcessPointer, - pending_ptr: Pointer, -) -> Option { - let pending = unsafe { pending_ptr.get_mut::() }; - let mut ready = Vec::new(); - - if pending.value().is_empty() { - // If the input is already empty we can just skip all the work below. - // This way polling an empty Array won't result in the process hanging - // forever. - return Some(Array::alloc(state.permanent_space.array_class(), ready)); - } - - let mut locks: Vec<_> = pending - .value() - .iter() - .map(|p| (*p, unsafe { p.get::() }.lock())) - .collect(); - - pending.value_mut().clear(); - - for (p, lock) in &mut locks { - // We _must_ ensure the consumer is cleared. If we don't, the following - // could happen: - // - // 1. We return a bunch of ready futures, but a few are not ready yet. - // 2. We wait for an unrelated future. - // 3. One of the futures is now ready and reschedules us. - // 4. All hell breaks loose. - lock.consumer = None; - - let target = - if lock.has_result() { &mut ready } else { pending.value_mut() }; - - target.push(*p); - } - - if ready.is_empty() { - process.state().waiting_for_future(None); - - for (_, lock) in &mut locks { - lock.consumer = Some(process); - } - - None - } else { - Some(Array::alloc(state.permanent_space.array_class(), ready)) - } -} diff --git a/vm/src/instructions/general.rs b/vm/src/instructions/general.rs deleted file mode 100644 index 6ad3dece0..000000000 --- a/vm/src/instructions/general.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! VM functions for which no better category/module exists. -use crate::execution_context::ExecutionContext; -use crate::indexes::{ClassIndex, FieldIndex, MethodIndex}; -use crate::mem::{Class, Header, Int, Object, Pointer}; -use crate::process::TaskPointer; -use crate::state::State; -use bytecode::{REF_ATOMIC, REF_OWNED, REF_PERMANENT, REF_REF}; - -#[inline(always)] -pub(crate) fn allocate(state: &State, idx: u32) -> Pointer { - let index = ClassIndex::new(idx); - let class = unsafe { state.permanent_space.get_class(index) }; - - Object::alloc(class) -} - -#[inline(always)] -pub(crate) fn get_field(receiver: Pointer, index: u16) -> Pointer { - unsafe { receiver.get::().get_field(FieldIndex::new(index as u8)) } -} - -#[inline(always)] -pub(crate) fn set_field(receiver: Pointer, index: u16, value: Pointer) { - unsafe { - receiver - .get_mut::() - .set_field(FieldIndex::new(index as u8), value); - } -} - -#[inline(always)] -pub(crate) fn equals(compare: Pointer, compare_with: Pointer) -> Pointer { - if compare == compare_with { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn call_virtual( - state: &State, - mut task: TaskPointer, - receiver: Pointer, - method_idx: u16, -) { - let class = Class::of(&state.permanent_space, receiver); - let method = unsafe { class.get_method(MethodIndex::new(method_idx)) }; - - task.push_context(ExecutionContext::new(method)); -} - -#[inline(always)] -pub(crate) fn call_static( - state: &State, - mut task: TaskPointer, - class_idx: u32, - method_idx: u16, -) { - let class = - unsafe { state.permanent_space.get_class(ClassIndex::new(class_idx)) }; - let method = unsafe { class.get_method(MethodIndex::new(method_idx)) }; - - task.push_context(ExecutionContext::new(method)); -} - -#[inline(always)] -pub(crate) fn call_dynamic( - state: &State, - mut task: TaskPointer, - receiver: Pointer, - hash: u32, -) { - let class = Class::of(&state.permanent_space, receiver); - let method = unsafe { class.get_hashed_method(hash) }; - - task.push_context(ExecutionContext::new(method)); -} - -#[inline(always)] -pub(crate) fn exit(state: &State, status_ptr: Pointer) -> Result<(), String> { - let status = unsafe { Int::read(status_ptr) as i32 }; - - state.set_exit_status(status); - state.terminate(); - Ok(()) -} - -#[inline(always)] -pub(crate) fn increment(pointer: Pointer) -> Pointer { - if !pointer.is_local_heap_object() { - return pointer; - } - - let header = unsafe { pointer.get_mut::
() }; - - if header.is_atomic() { - header.increment_atomic(); - pointer - } else { - header.increment(); - pointer.as_ref() - } -} - -#[inline(always)] -pub(crate) fn decrement(pointer: Pointer) { - if !pointer.is_local_heap_object() { - return; - } - - unsafe { pointer.get_mut::
() }.decrement(); -} - -#[inline(always)] -pub(crate) fn decrement_atomic(pointer: Pointer) -> Pointer { - if !pointer.is_local_heap_object() { - return Pointer::false_singleton(); - } - - let header = unsafe { pointer.get_mut::
() }; - - if header.decrement_atomic() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn check_refs(pointer: Pointer) -> Result<(), String> { - if !pointer.is_local_heap_object() { - return Ok(()); - } - - let header = unsafe { pointer.get::
() }; - let refs = header.references(); - - if refs == 0 { - return Ok(()); - } - - Err(format!( - "Can't drop a value of type '{}' as it still has {} references", - &header.class.name, refs - )) -} - -#[inline(always)] -pub(crate) fn ref_kind(pointer: Pointer) -> Pointer { - if !pointer.is_local_heap_object() { - Pointer::int(REF_PERMANENT as i64) - } else if unsafe { pointer.get_mut::
().is_atomic() } { - Pointer::int(REF_ATOMIC as i64) - } else if pointer.is_ref() { - Pointer::int(REF_REF as i64) - } else { - Pointer::int(REF_OWNED as i64) - } -} - -#[inline(always)] -pub(crate) fn free(ptr: Pointer) { - if ptr.is_local_heap_object() { - unsafe { - ptr.free(); - } - } -} - -#[inline(always)] -pub(crate) fn is_undefined(pointer: Pointer) -> Pointer { - if pointer == Pointer::undefined_singleton() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} diff --git a/vm/src/instructions/integer.rs b/vm/src/instructions/integer.rs deleted file mode 100644 index 86b0bbaae..000000000 --- a/vm/src/instructions/integer.rs +++ /dev/null @@ -1,326 +0,0 @@ -// VM functions for working with Inko integers. -use crate::mem::Pointer; -use crate::mem::{Float, Int, String as InkoString}; -use crate::numeric::Modulo; -use crate::state::State; -use std::ops::{BitAnd, BitOr, BitXor}; - -macro_rules! overflow_error { - ($left: expr, $right: expr) => {{ - return Err(format!( - "Integer overflow, left: {}, right: {}", - $left, $right - )); - }}; -} - -macro_rules! int_overflow_op { - ($kind: ident, $left: expr, $right: expr, $op: ident) => {{ - let left = unsafe { $kind::read($left) }; - let right = unsafe { $kind::read($right) }; - - if let Some(result) = left.$op(right) { - result - } else { - overflow_error!(left, right); - } - }}; -} - -macro_rules! int_op { - ($left: expr, $right: expr, $op: ident) => {{ - let left = unsafe { Int::read($left) }; - let right = unsafe { Int::read($right) }; - - left.$op(right) - }}; -} - -macro_rules! int_shift { - ($kind: ident, $left: expr, $right: expr, $op: ident) => {{ - let left = unsafe { $kind::read($left) }; - let right = unsafe { $kind::read($right) }; - - if let Some(result) = left.$op(right as u32) { - result - } else { - overflow_error!(left, right); - } - }}; -} - -macro_rules! int_bool { - ($kind: ident, $left: expr, $right: expr, $op: tt) => {{ - let left = unsafe { $kind::read($left) }; - let right = unsafe { $kind::read($right) }; - - if left $op right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } - }}; -} - -#[inline(always)] -pub(crate) fn add( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_add); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn wrapping_add( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.wrapping_add(rhs); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn div( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - // This implements floored division, rather than rounding towards zero. This - // makes division work more natural when using negative numbers. - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - - if rhs == 0 { - return Err(format!( - "Integer division failed, left: {}, right: {}", - lhs, rhs - )); - } - - // Taken from the upcoming div_floor() implementation in the standard - // library: https://github.com/rust-lang/rust/pull/88582. - let d = lhs / rhs; - let r = lhs % rhs; - let value = - if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) { d - 1 } else { d }; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn mul( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_mul); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn wrapping_mul( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.wrapping_mul(rhs); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn sub( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_sub); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn wrapping_sub( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.wrapping_sub(rhs); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn modulo( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_modulo); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn and(state: &State, left: Pointer, right: Pointer) -> Pointer { - let value = int_op!(left, right, bitand); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn or(state: &State, left: Pointer, right: Pointer) -> Pointer { - let value = int_op!(left, right, bitor); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn xor(state: &State, left: Pointer, right: Pointer) -> Pointer { - let value = int_op!(left, right, bitxor); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn not(state: &State, ptr: Pointer) -> Pointer { - let value = unsafe { !Int::read(ptr) }; - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn rotate_left( - state: &State, - left: Pointer, - right: Pointer, -) -> Pointer { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) as u32 }; - let value = lhs.rotate_left(rhs); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn rotate_right( - state: &State, - left: Pointer, - right: Pointer, -) -> Pointer { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) as u32 }; - let value = lhs.rotate_right(rhs); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn shl( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_shift!(Int, left, right, checked_shl); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn shr( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_shift!(Int, left, right, checked_shr); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn unsigned_shr( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) as u64 }; - let rhs = unsafe { Int::read(right) }; - let res = if let Some(result) = lhs.checked_shr(rhs as u32) { - result as i64 - } else { - overflow_error!(lhs, rhs) - }; - - Ok(Int::alloc(state.permanent_space.int_class(), res)) -} - -#[inline(always)] -pub(crate) fn pow(state: &State, left: Pointer, right: Pointer) -> Pointer { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.pow(rhs as u32); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn lt(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, <) -} - -#[inline(always)] -pub(crate) fn gt(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, >) -} - -#[inline(always)] -pub(crate) fn eq(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, ==) -} - -#[inline(always)] -pub(crate) fn ge(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, >=) -} - -#[inline(always)] -pub(crate) fn le(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, <=) -} - -#[inline(always)] -pub(crate) fn clone(state: &State, ptr: Pointer) -> Pointer { - if ptr.is_tagged_int() { - return ptr; - } - - let value = unsafe { Int::read(ptr) }; - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn to_float(state: &State, pointer: Pointer) -> Pointer { - let value = unsafe { Int::read(pointer) } as f64; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn to_string(state: &State, pointer: Pointer) -> Pointer { - let value = unsafe { Int::read(pointer) }.to_string(); - - InkoString::alloc(state.permanent_space.string_class(), value) -} diff --git a/vm/src/instructions/mod.rs b/vm/src/instructions/mod.rs deleted file mode 100644 index 802f953f1..000000000 --- a/vm/src/instructions/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod array; -pub mod builtin_functions; -pub mod byte_array; -pub mod float; -pub mod future; -pub mod general; -pub mod integer; -pub mod process; -pub mod string; diff --git a/vm/src/instructions/process.rs b/vm/src/instructions/process.rs deleted file mode 100644 index f487315b9..000000000 --- a/vm/src/instructions/process.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! VM functions for working with Inko processes. -use crate::indexes::{ClassIndex, FieldIndex, MethodIndex}; -use crate::mem::{Int, Pointer}; -use crate::process::{ - Future, FutureState, Process, ProcessPointer, RescheduleRights, - TaskPointer, Write, WriteResult, -}; -use crate::scheduler::process::Thread; -use crate::scheduler::timeouts::Timeout; -use crate::state::State; -use std::time::Duration; - -const SEND_ERROR: &str = "Processes can't send messages to themselves, \ - as this could result in deadlocks"; - -#[inline(always)] -pub(crate) fn allocate(state: &State, class_idx: u32) -> Pointer { - let class_index = ClassIndex::new(class_idx); - let class = unsafe { state.permanent_space.get_class(class_index) }; - let process = Process::alloc(class); - - process.as_pointer() -} - -#[inline(always)] -pub(crate) fn send_message( - state: &State, - thread: &mut Thread, - mut task: TaskPointer, - sender: ProcessPointer, - receiver_ptr: Pointer, - method: u16, - wait: bool, -) -> Result { - let mut receiver = unsafe { ProcessPointer::from_pointer(receiver_ptr) }; - - if sender == receiver { - return Err(SEND_ERROR.to_string()); - } - - let args = task.take_arguments(); - - match receiver.send_message(MethodIndex::new(method), sender, args, wait) { - RescheduleRights::AcquiredWithTimeout => { - state.timeout_worker.increase_expired_timeouts(); - } - RescheduleRights::Acquired => {} - _ => return Ok(false), - } - - let switch = if wait { - // When awaiting the result immediately we want to keep latency as small - // as possible. To achieve this we reschedule the receiver (if allowed) - // onto the current worker with a high priority. - thread.schedule_priority(receiver); - true - } else { - thread.schedule(receiver); - false - }; - - Ok(switch) -} - -#[inline(always)] -pub(crate) fn send_async_message( - state: &State, - thread: &mut Thread, - sender: ProcessPointer, - mut task: TaskPointer, - receiver_ptr: Pointer, - method: u16, -) -> Result { - let mut receiver = unsafe { ProcessPointer::from_pointer(receiver_ptr) }; - - if sender == receiver { - return Err(SEND_ERROR.to_string()); - } - - let fut_state = FutureState::new(); - let fut = - Future::alloc(state.permanent_space.future_class(), fut_state.clone()); - let args = task.take_arguments(); - - match receiver.send_async_message(MethodIndex::new(method), fut_state, args) - { - RescheduleRights::AcquiredWithTimeout => { - state.timeout_worker.increase_expired_timeouts(); - thread.schedule(receiver); - } - RescheduleRights::Acquired => { - thread.schedule(receiver); - } - _ => {} - } - - Ok(fut) -} - -#[inline(always)] -pub(crate) fn suspend( - state: &State, - mut process: ProcessPointer, - time_ptr: Pointer, -) { - let nanos = unsafe { Int::read_u64(time_ptr) }; - let timeout = Timeout::with_rc(Duration::from_nanos(nanos)); - - process.suspend(timeout.clone()); - state.timeout_worker.suspend(process, timeout); -} - -#[inline(always)] -pub(crate) fn get_field(process: Pointer, index: u16) -> Pointer { - unsafe { process.get::().get_field(FieldIndex::new(index as u8)) } -} - -#[inline(always)] -pub(crate) fn set_field(process: Pointer, index: u16, value: Pointer) { - unsafe { - process - .get_mut::() - .set_field(FieldIndex::new(index as u8), value) - } -} - -#[inline(always)] -pub(crate) fn write_result( - state: &State, - thread: &mut Thread, - task: TaskPointer, - result: Pointer, - thrown: bool, -) -> Pointer { - match &task.write { - Write::Discard => Pointer::false_singleton(), - Write::Direct(mut rec) => { - if thrown { - rec.set_throw_value(result); - } else { - rec.set_return_value(result); - } - - thread.schedule(rec); - Pointer::true_singleton() - } - Write::Future(fut) => match fut.write(result, thrown) { - WriteResult::Continue => Pointer::true_singleton(), - WriteResult::Reschedule(consumer) => { - thread.schedule(consumer); - Pointer::true_singleton() - } - WriteResult::RescheduleWithTimeout(consumer) => { - state.timeout_worker.increase_expired_timeouts(); - thread.schedule(consumer); - Pointer::true_singleton() - } - WriteResult::Discard => Pointer::false_singleton(), - }, - } -} diff --git a/vm/src/instructions/string.rs b/vm/src/instructions/string.rs deleted file mode 100644 index 2118dddda..000000000 --- a/vm/src/instructions/string.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! VM functions for working with Inko strings. -use crate::mem::Pointer; -use crate::mem::{Int, String as InkoString}; -use crate::process::TaskPointer; -use crate::state::State; - -#[inline(always)] -pub(crate) fn equals(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - if left_ptr.is_permanent() && right_ptr.is_permanent() { - if left_ptr == right_ptr { - return Pointer::true_singleton(); - } else { - return Pointer::false_singleton(); - } - } - - let left = unsafe { InkoString::read(&left_ptr) }; - let right = unsafe { InkoString::read(&right_ptr) }; - - if left == right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn size(state: &State, ptr: Pointer) -> Pointer { - let string = unsafe { InkoString::read(&ptr) }; - let length = string.len() as i64; - - Int::alloc(state.permanent_space.int_class(), length) -} - -#[inline(always)] -pub(crate) fn concat(state: &State, mut task: TaskPointer) -> Pointer { - let mut buffer = String::new(); - - for ptr in task.take_arguments() { - buffer.push_str(unsafe { InkoString::read(&ptr) }); - } - - InkoString::alloc(state.permanent_space.string_class(), buffer) -} - -#[inline(always)] -pub(crate) fn byte(str_ptr: Pointer, index_ptr: Pointer) -> Pointer { - let byte = unsafe { - let string = InkoString::read(&str_ptr); - let index = Int::read(index_ptr) as usize; - - i64::from(*string.as_bytes().get_unchecked(index)) - }; - - Pointer::int(byte) -} - -#[inline(always)] -pub(crate) fn drop(pointer: Pointer) { - // Permanent Strings can be used as if they were regular owned Strings, so - // we must make sure not to drop these. - if pointer.is_permanent() { - return; - } - - unsafe { - InkoString::drop(pointer); - } -} diff --git a/vm/src/location_table.rs b/vm/src/location_table.rs deleted file mode 100644 index 8d116a728..000000000 --- a/vm/src/location_table.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Mapping of instruction offsets to their source locations. -use crate::mem::Pointer; - -/// A single entry in a location table. -pub(crate) struct Entry { - /// The instruction index. - index: u32, - - /// The source line number. - /// - /// We don't use line number offsets, as line numbers are not monotonically - /// increasing due to code inlining. For example, entry A may refer to line - /// 4 in file A, while entry B may refer to line 2 in file D. - /// - /// This means files are limited to (2^16)-1 lines. This should be fine in - /// practise, as files with that many lines are never a good idea. - line: u16, - - /// The file (as a permanent string) the instruction originates from. - file: Pointer, - - /// The name of the method (as a permanent string) the instruction - /// originates from. - name: Pointer, -} - -/// A location resolved using a location table. -#[derive(Eq, PartialEq, Debug)] -pub(crate) struct Location { - pub(crate) name: Pointer, - pub(crate) file: Pointer, - pub(crate) line: Pointer, -} - -/// A table that maps instruction offsets to their source locations. -/// -/// This table is used to obtain stack traces. The setup here is based on the -/// line number tables found in Python. -/// -/// File paths and scope names are stored separately from entries. This ensures -/// entries take up as little space as possible. -pub(crate) struct LocationTable { - entries: Vec, -} - -impl LocationTable { - pub(crate) fn new() -> Self { - Self { entries: Vec::new() } - } - - pub(crate) fn add_entry( - &mut self, - index: u32, - line: u16, - file: Pointer, - name: Pointer, - ) { - self.entries.push(Entry { index, line, file, name }); - } - - pub(crate) fn get(&self, index: u32) -> Option { - for entry in self.entries.iter() { - if entry.index == index { - let name = entry.name; - let file = entry.file; - let line = Pointer::int(entry.line as i64); - - return Some(Location { name, file, line }); - } - } - - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::mem::size_of; - - #[test] - fn test_type_sizes() { - assert_eq!(size_of::(), 24); - } - - #[test] - fn test_location_table_get() { - let mut table = LocationTable::new(); - let file = Pointer::int(1); - let name = Pointer::int(2); - - table.add_entry(2, 1, file, name); - table.add_entry(1, 2, file, name); - table.add_entry(1, 3, file, name); - - assert!(table.get(0).is_none()); - assert_eq!( - table.get(1), - Some(Location { name, file, line: Pointer::int(2) }) - ); - assert_eq!( - table.get(2), - Some(Location { name, file, line: Pointer::int(1) }) - ); - assert!(table.get(3).is_none()); - } -} diff --git a/vm/src/machine.rs b/vm/src/machine.rs deleted file mode 100644 index 0ce28ef1c..000000000 --- a/vm/src/machine.rs +++ /dev/null @@ -1,1138 +0,0 @@ -use crate::execution_context::ExecutionContext; -use crate::image::Image; -use crate::instructions::array; -use crate::instructions::builtin_functions; -use crate::instructions::byte_array; -use crate::instructions::float; -use crate::instructions::future; -use crate::instructions::general; -use crate::instructions::integer; -use crate::instructions::process; -use crate::instructions::string; -use crate::mem::{Int, Pointer, String as InkoString}; -use crate::network_poller::Worker as NetworkPollerWorker; -use crate::process::{Process, ProcessPointer, TaskPointer}; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use bytecode::Instruction; -use bytecode::Opcode; -use std::fmt::Write; -use std::thread; - -macro_rules! reset { - ($task: expr, $loop_state: expr) => {{ - $loop_state = unsafe { LoopState::new($task) }; - }}; -} - -/// The state of an interpreter loop, such as the context being executed. -/// -/// We use a structure here so we don't need to pass around tons of variables -/// just to support suspending/resuming processes in the right place. -struct LoopState<'a> { - instructions: &'a [Instruction], - index: usize, - context: &'a mut ExecutionContext, -} - -impl<'a> LoopState<'a> { - /// Returns a new LoopState for the given task. - /// - /// This method is unsafe as we perform multiple borrows of data related to - /// the task. This is necessary to prevent additional/needless pointer reads - /// in the interpreter loop. Unfortunately, there's no way of expressing - /// this in safe Rust code, hence the use of unsafe code in this method. - /// - /// Because of this code, care must be taken to reset the loop state at the - /// right time. For example, when a new method is scheduled the state must - /// be reset; otherwise we'll continue running the current method. - /// - /// If there's a better way of going about this (without incurring runtime - /// overhead) we'd love to adopt that. Unfortunately, as of July 2021 this - /// is the best we can do :< - unsafe fn new(mut task: TaskPointer) -> Self { - let context = &mut *(&mut *task.context as *mut ExecutionContext); - let method = context.method(); - - // Accessing instructions using `context.method().instructions` would - // incur a pointer read for every instruction, followed by the offset to - // determine the current instruction. By storing the slice we can avoid - // the pointer read. - let instructions = &*(method.instructions.as_slice() as *const _); - - LoopState { index: context.index, instructions, context } - } - - fn rewind(&mut self) { - self.context.index = self.index - 1; - } - - fn save(&mut self) { - self.context.index = self.index; - } -} - -pub struct Machine<'a> { - /// The shared virtual machine state, such as the process pools and built-in - /// types. - pub(crate) state: &'a State, -} - -impl<'a> Machine<'a> { - pub(crate) fn new(state: &'a State) -> Self { - Machine { state } - } - - /// Boots up the VM and all its thread pools. - /// - /// This method blocks the calling thread until the Inko program terminates. - pub fn boot(image: Image, arguments: &[String]) -> Result { - let state = State::new(image.config, image.permanent_space, arguments); - let entry_class = image.entry_class; - let entry_method = - unsafe { entry_class.get_method(image.entry_method) }; - - { - let state = state.clone(); - - thread::Builder::new() - .name("timeout worker".to_string()) - .spawn(move || state.timeout_worker.run(&state.scheduler)) - .unwrap(); - } - - { - for id in 0..state.network_pollers.len() { - let state = state.clone(); - - thread::Builder::new() - .name(format!("netpoll {}", id)) - .spawn(move || NetworkPollerWorker::new(id, state).run()) - .unwrap(); - } - } - - state.scheduler.run(&*state, entry_class, entry_method); - Ok(state.current_exit_status()) - } - - pub(crate) fn run(&self, thread: &mut Thread, mut process: ProcessPointer) { - // When there's no task to run, clients will try to reschedule the - // process after sending it a message. This means we (here) don't need - // to do anything extra. - if let Some(task) = process.task_to_run() { - if let Err(message) = self.run_task(thread, process, task) { - self.panic(process, &message); - } - } else if process.finish_task() { - thread.schedule(process); - } - } - - fn run_task( - &self, - thread: &mut Thread, - mut process: ProcessPointer, - mut task: TaskPointer, - ) -> Result<(), String> { - let mut reductions = self.state.config.reductions as i32; - let mut state; - - reset!(task, state); - - 'ins_loop: loop { - let ins = unsafe { state.instructions.get_unchecked(state.index) }; - - state.index += 1; - - match ins.opcode { - Opcode::Allocate => { - let reg = ins.arg(0); - let idx = ins.u32_arg(1, 2); - let res = general::allocate(self.state, idx); - - state.context.set_register(reg, res); - } - Opcode::ArrayAllocate => { - let reg = ins.arg(0); - let res = array::allocate(self.state, task); - - state.context.set_register(reg, res); - } - Opcode::GetTrue => { - let reg = ins.arg(0); - let res = Pointer::true_singleton(); - - state.context.set_register(reg, res); - } - Opcode::GetFalse => { - let reg = ins.arg(0); - let res = Pointer::false_singleton(); - - state.context.set_register(reg, res); - } - Opcode::Return => { - let res = state.context.get_register(ins.arg(0)); - - process.set_return_value(res); - - // Once we're at the top-level _and_ we have no more - // instructions to process, we'll write the result to a - // future and bail out the execution loop. - if task.pop_context() { - break 'ins_loop; - } - - reset!(task, state); - } - Opcode::Branch => { - let val = state.context.get_register(ins.arg(0)); - let if_true = ins.arg(1) as usize; - let if_false = ins.arg(2) as usize; - - state.index = if val == Pointer::true_singleton() { - if_true - } else { - if_false - }; - } - Opcode::Goto => { - state.index = ins.arg(0) as usize; - } - Opcode::BranchResult => { - let if_ok = ins.arg(0) as usize; - let if_err = ins.arg(1) as usize; - - state.index = if process.thrown() { if_err } else { if_ok }; - } - Opcode::IntAdd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::add(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntWrappingAdd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::wrapping_add(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntDiv => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::div(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntMul => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::mul(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntWrappingMul => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::wrapping_mul(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntSub => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::sub(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntWrappingSub => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::wrapping_sub(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntMod => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::modulo(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntBitAnd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::and(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntBitOr => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::or(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntBitXor => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::xor(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntBitNot => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = integer::not(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::IntRotateLeft => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::rotate_left(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntRotateRight => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::rotate_right(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntShl => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::shl(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntShr => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::shr(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntUnsignedShr => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::unsigned_shr(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntLt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::lt(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntGt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::gt(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::eq(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntGe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::ge(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntLe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::le(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatAdd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::add(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatMul => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::mul(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatDiv => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::div(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatSub => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::sub(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatMod => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::modulo(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatLt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::lt(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatGt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::gt(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::eq(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatGe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::ge(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatLe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::le(a, b); - - state.context.set_register(reg, res); - } - Opcode::ArraySet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let val = state.context.get_register(ins.arg(3)); - let res = array::set(ary, idx, val); - - state.context.set_register(reg, res); - } - Opcode::ArrayPush => { - let ary = state.context.get_register(ins.arg(0)); - let val = state.context.get_register(ins.arg(1)); - - array::push(ary, val); - } - Opcode::ArrayPop => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = array::pop(ary); - - state.context.set_register(reg, res); - } - Opcode::ArrayGet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = array::get(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ArrayRemove => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = array::remove(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ArrayLength => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = array::length(self.state, ary); - - state.context.set_register(reg, res); - } - Opcode::StringEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = string::equals(a, b); - - state.context.set_register(reg, res); - } - Opcode::StringSize => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = string::size(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::SetField => { - let rec = state.context.get_register(ins.arg(0)); - let idx = ins.arg(1); - let val = state.context.get_register(ins.arg(2)); - - general::set_field(rec, idx, val); - } - Opcode::GetField => { - let reg = ins.arg(0); - let rec = state.context.get_register(ins.arg(1)); - let idx = ins.arg(2); - let res = general::get_field(rec, idx); - - state.context.set_register(reg, res); - } - Opcode::ProcessAllocate => { - let reg = ins.arg(0); - let idx = ins.u32_arg(1, 2); - let res = process::allocate(self.state, idx); - - state.context.set_register(reg, res); - } - Opcode::ProcessSend => { - let rec = state.context.get_register(ins.arg(0)); - let method = ins.arg(1); - let wait = ins.arg(2) == 1; - - state.save(); - - let switch = process::send_message( - self.state, thread, task, process, rec, method, wait, - )?; - - if switch { - return Ok(()); - } - } - Opcode::ProcessSendAsync => { - let reg = ins.arg(0); - let rec = state.context.get_register(ins.arg(1)); - let method = ins.arg(2); - - // We save in case of a panic, otherwise we may be missing - // stack frames in the resulting stack trace. - state.save(); - - let res = process::send_async_message( - self.state, thread, process, task, rec, method, - )?; - - state.context.set_register(reg, res); - } - Opcode::ProcessWriteResult => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = process::write_result( - self.state, - thread, - task, - val, - ins.arg(2) == 1, - ); - - state.context.set_register(reg, res); - } - Opcode::ProcessSuspend => { - let time = state.context.get_register(ins.arg(0)); - - state.save(); - process::suspend(self.state, process, time); - - return Ok(()); - } - Opcode::ProcessGetField => { - let reg = ins.arg(0); - let rec = state.context.get_register(ins.arg(1)); - let idx = ins.arg(2); - let res = process::get_field(rec, idx); - - state.context.set_register(reg, res); - } - Opcode::ProcessSetField => { - let rec = state.context.get_register(ins.arg(0)); - let idx = ins.arg(1); - let val = state.context.get_register(ins.arg(2)); - - process::set_field(rec, idx, val); - } - Opcode::ObjectEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = general::equals(a, b); - - state.context.set_register(reg, res); - } - Opcode::GetNil => { - let res = Pointer::nil_singleton(); - - state.context.set_register(ins.arg(0), res); - } - Opcode::GetUndefined => { - let res = Pointer::undefined_singleton(); - - state.context.set_register(ins.arg(0), res); - } - Opcode::GetConstant => { - let reg = ins.arg(0); - let addr = ins.u64_arg(1, 2, 3, 4); - let res = Pointer::new(addr as *mut u8); - - state.context.set_register(reg, res); - } - Opcode::Throw => { - let value = state.context.get_register(ins.arg(0)); - let unwind = ins.arg(1) == 1; - - process.set_throw_value(value); - - if unwind && task.pop_context() { - break 'ins_loop; - } - - reset!(task, state); - } - Opcode::MoveRegister => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - - state.context.set_register(reg, val); - } - Opcode::Panic => { - let msg = state.context.get_register(ins.arg(0)); - - return Err(unsafe { InkoString::read(&msg).to_string() }); - } - Opcode::Exit => { - let status = state.context.get_register(ins.arg(0)); - - general::exit(self.state, status)?; - - // This is just a best-case effort to clean up the current - // process. If it still has unfinished tasks or live - // objects, those are all left as-is. - Process::drop_and_deallocate(process); - - return Ok(()); - } - Opcode::StringConcat => { - let reg = ins.arg(0); - let res = string::concat(self.state, task); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayAllocate => { - let reg = ins.arg(0); - let res = byte_array::allocate(self.state); - - state.context.set_register(reg, res); - } - Opcode::ByteArraySet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let val = state.context.get_register(ins.arg(3)); - let res = byte_array::set(self.state, ary, idx, val); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayGet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = byte_array::get(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayPush => { - let ary = state.context.get_register(ins.arg(0)); - let val = state.context.get_register(ins.arg(1)); - - byte_array::push(ary, val); - } - Opcode::ByteArrayPop => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = byte_array::pop(ary); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayRemove => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = byte_array::remove(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayLength => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = byte_array::length(self.state, ary); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayEquals => { - let reg = ins.arg(0); - let cmp = state.context.get_register(ins.arg(1)); - let cmp_with = state.context.get_register(ins.arg(2)); - let res = byte_array::equals(cmp, cmp_with); - - state.context.set_register(reg, res); - } - Opcode::StringByte => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = string::byte(val, idx); - - state.context.set_register(reg, res); - } - Opcode::MoveResult => { - let reg = ins.arg(0); - let res = process.move_result(); - - state.context.set_register(reg, res); - } - Opcode::BuiltinFunctionCall => { - let func = ins.arg(0); - - // When an operation would block, the file descriptor/thing - // is already registered, and the process may already be - // running again in another thread. This means that when a - // WouldBlock is produced it is not safe to access any - // process data. - // - // To ensure blocking operations are retried properly, we - // _first_ set the instruction index, then advance it again - // if it is safe to do so. - state.rewind(); - - match builtin_functions::call( - self.state, thread, process, task, func, - ) { - Ok(val) => { - state.save(); - task.clear_arguments(); - process.set_return_value(val); - } - Err(RuntimeError::Panic(msg)) => { - task.clear_arguments(); - - return Err(msg); - } - Err(RuntimeError::Error(value)) => { - state.save(); - task.clear_arguments(); - process.set_throw_value(value); - } - Err(RuntimeError::WouldBlock) => { - // *DO NOT* use the task or process at this point, - // as it may have been invalidated if the process is - // already running again in another thread. - return Ok(()); - } - } - - // If the function took too long to run (e.g. an IO - // operation took too long), we have to give up running the - // process. If we continue running we could mess up whatever - // thread has taken over our queue/work, and we'd be using - // the OS thread even longer than we already have. - // - // We schedule onto the global queue because if another - // thread took over but found no other work, it may have - // gone to sleep. In that case scheduling onto the local - // queue may result in the work never getting picked up - // (e.g. if all other threads are also sleeping). - if thread.backup { - thread.schedule_global(process); - - return Ok(()); - } - } - Opcode::FutureGet => { - let fut = state.context.get_register(ins.arg(0)); - - state.rewind(); - - if future::get(self.state, process, fut) { - state.save(); - } else { - return Ok(()); - } - } - Opcode::FutureGetFor => { - let fut = state.context.get_register(ins.arg(0)); - let time = state.context.get_register(ins.arg(1)); - - state.rewind(); - - if future::get_for(self.state, process, fut, time) { - state.save(); - } else { - return Ok(()); - } - } - Opcode::FuturePoll => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - - state.rewind(); - - if let Some(res) = future::poll(self.state, process, ary) { - state.save(); - state.context.set_register(reg, res); - } else { - return Ok(()); - } - } - Opcode::CallVirtual => { - let rec = state.context.get_register(ins.arg(0)); - let method = ins.arg(1); - - state.save(); - // TODO: remove - if rec.as_ptr().is_null() { - return Err("CallVirtual with NULL".to_string()); - } - general::call_virtual(self.state, task, rec, method); - reset!(task, state); - } - Opcode::CallDynamic => { - let rec = state.context.get_register(ins.arg(0)); - let hash = ins.u32_arg(1, 2); - - state.save(); - general::call_dynamic(self.state, task, rec, hash); - reset!(task, state); - } - Opcode::CallStatic => { - let class = ins.u32_arg(0, 1); - let method = ins.arg(2); - - state.save(); - general::call_static(self.state, task, class, method); - reset!(task, state); - } - Opcode::RefKind => { - let reg = ins.arg(0); - let ptr = state.context.get_register(ins.arg(1)); - let res = general::ref_kind(ptr); - - state.context.set_register(reg, res); - } - Opcode::Increment => { - let reg = ins.arg(0); - let ptr = state.context.get_register(ins.arg(1)); - let res = general::increment(ptr); - - state.context.set_register(reg, res); - } - Opcode::Decrement => { - let ptr = state.context.get_register(ins.arg(0)); - - general::decrement(ptr); - } - Opcode::DecrementAtomic => { - let reg = ins.arg(0); - let ptr = state.context.get_register(ins.arg(1)); - let res = general::decrement_atomic(ptr); - - state.context.set_register(reg, res); - } - Opcode::CheckRefs => { - let ptr = state.context.get_register(ins.arg(0)); - - general::check_refs(ptr)?; - } - Opcode::Free => { - let obj = state.context.get_register(ins.arg(0)); - - general::free(obj); - } - Opcode::IntClone => { - let reg = ins.arg(0); - let obj = state.context.get_register(ins.arg(1)); - let res = integer::clone(self.state, obj); - - state.context.set_register(reg, res); - } - Opcode::FloatClone => { - let reg = ins.arg(0); - let obj = state.context.get_register(ins.arg(1)); - let res = float::clone(self.state, obj); - - state.context.set_register(reg, res); - } - Opcode::Reduce => { - reductions -= ins.arg(0) as i32; - - // We don't need an overflow check here, as a single u16 - // (combined with this check) can't overflow an i32. - if reductions <= 0 { - state.save(); - thread.schedule(process); - - return Ok(()); - } - } - Opcode::ArrayClear => { - let ary = state.context.get_register(ins.arg(0)); - - array::clear(ary); - } - Opcode::ArrayDrop => { - let ary = state.context.get_register(ins.arg(0)); - - array::drop(ary); - } - Opcode::ByteArrayClear => { - let ary = state.context.get_register(ins.arg(0)); - - byte_array::clear(ary) - } - Opcode::ByteArrayClone => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = byte_array::clone(self.state, ary); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayDrop => { - let ary = state.context.get_register(ins.arg(0)); - - byte_array::drop(ary); - } - Opcode::IntToFloat => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = integer::to_float(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::IntToString => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = integer::to_string(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::IntPow => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::pow(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatCeil => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::ceil(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatFloor => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::floor(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatRound => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let prec = state.context.get_register(ins.arg(2)); - let res = float::round(self.state, val, prec); - - state.context.set_register(reg, res); - } - Opcode::FloatToInt => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::to_int(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatToString => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::to_string(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatIsNan => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::is_nan(val); - - state.context.set_register(reg, res); - } - Opcode::FloatIsInf => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::is_inf(val); - - state.context.set_register(reg, res); - } - Opcode::FutureDrop => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = future::drop(val); - - state.context.set_register(reg, res); - } - Opcode::StringDrop => { - let val = state.context.get_register(ins.arg(0)); - - string::drop(val); - } - Opcode::IsUndefined => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = general::is_undefined(val); - - state.context.set_register(reg, res); - } - Opcode::ProcessFinishTask => { - let terminate = ins.arg(0) == 1; - - if terminate { - if process.is_main() { - self.state.terminate(); - } - - // Processes drop/free themselves as this must be - // deferred until all messages (including any - // destructors) have finished running. If we did this in - // a destructor we'd end up releasing memory of a - // process while still using it. - Process::drop_and_deallocate(process); - - return Ok(()); - } - - break 'ins_loop; - } - Opcode::JumpTable => { - let val = state.context.get_register(ins.arg(0)); - let val_idx = unsafe { Int::read(val) } as usize; - let tbl_idx = ins.arg(1) as usize; - - state.index = - state.context.method.jump_tables[tbl_idx][val_idx]; - } - Opcode::Push => { - let val = state.context.get_register(ins.arg(0)); - - task.stack.push(val); - } - Opcode::Pop => { - let reg = ins.arg(0); - - if let Some(val) = task.stack.pop() { - state.context.set_register(reg, val); - } - } - }; - } - - if process.finish_task() { - thread.schedule(process); - } - - Ok(()) - } - - /// Produces an Inko panic (not a Rust panic) and terminates the current - /// program. - /// - /// This function is marked as cold as we expect it to be called rarely, if - /// ever (in a correct program). This should also ensure any branches - /// leading to this function are treated as unlikely. - #[cold] - #[inline(never)] - fn panic(&self, process: ProcessPointer, message: &str) { - let mut buffer = String::new(); - - buffer.push_str("Stack trace (the most recent call comes last):"); - - for location in process.stacktrace() { - unsafe { - let _ = write!( - buffer, - "\n {} line {}, in '{}'", - InkoString::read(&location.file), - Int::read(location.line), - InkoString::read(&location.name) - ); - } - } - - let _ = write!( - buffer, - "\nProcess {:#x} panicked: {}", - process.identifier(), - message - ); - - eprintln!("{}", buffer); - self.state.set_exit_status(1); - self.state.terminate(); - } -} diff --git a/vm/src/mem.rs b/vm/src/mem.rs deleted file mode 100644 index fbbb1b81c..000000000 --- a/vm/src/mem.rs +++ /dev/null @@ -1,1164 +0,0 @@ -use crate::immutable_string::ImmutableString; -use crate::indexes::*; -use crate::location_table::LocationTable; -use crate::permanent_space::PermanentSpace; -use crate::process::Process; -use bytecode::Instruction; -use std::alloc::{alloc, alloc_zeroed, dealloc, handle_alloc_error, Layout}; -use std::mem::{align_of, size_of, swap, transmute}; -use std::ops::Deref; -use std::ptr::drop_in_place; -use std::string::String as RustString; -use std::sync::atomic::{AtomicU16, Ordering}; - -/// The alignment to use for Inko objects. -const ALIGNMENT: usize = align_of::(); - -/// The number of bits to shift for tagged integers. -/// -/// We shift by two bits, limiting tagged integers to 62 bits. This frees up the -/// lower two bits for non tagged values. We need two bits instead of one so we -/// can efficiently tell the various immediate values apart. -const INT_SHIFT_BITS: usize = 2; - -/// The minimum integer value that can be stored as a tagged signed integer. -pub(crate) const MIN_INTEGER: i64 = i64::MIN >> INT_SHIFT_BITS; - -/// The maximum integer value that can be stored as a tagged signed integer. -pub(crate) const MAX_INTEGER: i64 = i64::MAX >> INT_SHIFT_BITS; - -/// The bit set for all immediate values. -const IMMEDIATE_BIT: usize = 0b1; - -/// The mask to use for tagged integers. -const INT_MASK: usize = 0b00_0011; - -/// The mask to use for detecting booleans. -const BOOL_MASK: usize = 0b00_0101; - -/// The address of the singleton `False`. -const FALSE_ADDRESS: usize = 0b00_0101; - -/// The address of the singleton `True`. -const TRUE_ADDRESS: usize = 0b00_1101; - -/// The address of the `Nil` singleton. -const NIL_ADDRESS: usize = 0b00_0001; - -/// The address of the `undefined` singleton. -const UNDEFINED_ADDRESS: usize = 0b00_1001; - -/// The mask to apply for permanent objects. -const PERMANENT_MASK: usize = 0b00_0010; - -/// The mask to apply for references. -const REF_MASK: usize = 0b00_0100; - -/// The mask to use for detecting values that are not immediate or permanent -/// values. -const LOCAL_OWNED_MASK: usize = 0b00_0011; - -/// The mask to use for untagging a pointer. -const UNTAG_MASK: usize = (!0b111) as usize; - -pub(crate) fn allocate(layout: Layout) -> *mut u8 { - unsafe { - let ptr = alloc(layout); - - if ptr.is_null() { - handle_alloc_error(layout); - } else { - ptr - } - } -} - -/// A pointer to an object managed by the Inko runtime. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct Pointer(*mut u8); - -unsafe impl Sync for Pointer {} -unsafe impl Send for Pointer {} - -impl Pointer { - /// Creates a new Pointer from the raw address. - pub(crate) fn new(raw: *mut u8) -> Self { - Self(raw) - } - - /// Creates a pointer to a regular boxed value. - /// - /// This method is intended to be used when we want to pretend a Rust value - /// is an Inko object. This allows exposing of Rust data to Inko, without - /// having to wrap it in a heap object. - pub(crate) fn boxed(value: T) -> Self { - Self::new(Box::into_raw(Box::new(value)) as *mut u8) - } - - pub(crate) fn with_mask(raw: *mut u8, mask: usize) -> Self { - Self::new((raw as usize | mask) as _) - } - - pub(crate) fn int(value: i64) -> Self { - Self::with_mask((value << INT_SHIFT_BITS) as _, INT_MASK) - } - - pub(crate) fn true_singleton() -> Self { - Self::new(TRUE_ADDRESS as _) - } - - pub(crate) fn false_singleton() -> Self { - Self::new(FALSE_ADDRESS as _) - } - - pub(crate) fn nil_singleton() -> Self { - Self::new(NIL_ADDRESS as _) - } - - pub(crate) fn undefined_singleton() -> Self { - Self::new(UNDEFINED_ADDRESS as _) - } - - pub(crate) fn is_regular(self) -> bool { - (self.as_ptr() as usize & IMMEDIATE_BIT) == 0 - } - - pub(crate) fn is_boolean(self) -> bool { - self.mask_is_set(BOOL_MASK) - } - - pub(crate) fn is_tagged_int(self) -> bool { - self.mask_is_set(INT_MASK) - } - - pub(crate) fn is_permanent(self) -> bool { - self.mask_is_set(PERMANENT_MASK) - } - - pub(crate) fn is_ref(self) -> bool { - self.mask_is_set(REF_MASK) - } - - /// Returns a boolean indicating if the pointer points to a non-permanent - /// heap object. - pub(crate) fn is_local_heap_object(self) -> bool { - (self.as_ptr() as usize & LOCAL_OWNED_MASK) == 0 - } - - pub(crate) fn as_permanent(self) -> Pointer { - Self::with_mask(self.as_ptr(), PERMANENT_MASK) - } - - pub(crate) fn as_ref(self) -> Pointer { - Self::with_mask(self.as_ptr(), REF_MASK) - } - - pub(crate) fn as_ptr(self) -> *mut u8 { - self.0 - } - - pub(crate) unsafe fn get<'a, T>(self) -> &'a T { - &*(self.untagged_ptr() as *const T) - } - - pub(crate) unsafe fn get_mut<'a, T>(self) -> &'a mut T { - &mut *(self.untagged_ptr() as *mut T) - } - - /// Drops and deallocates the object this pointer points to. - /// - /// This method is meant to be used only when a Pointer points to a value - /// allocated using Rust's Box type. - pub(crate) unsafe fn drop_boxed(self) { - drop(Box::from_raw(self.as_ptr() as *mut T)); - } - - pub(crate) fn untagged_ptr(self) -> *mut u8 { - (self.as_ptr() as usize & UNTAG_MASK) as _ - } - - pub(crate) unsafe fn as_int(self) -> i64 { - self.as_ptr() as i64 >> INT_SHIFT_BITS - } - - pub(crate) unsafe fn free(self) { - let header = self.get::
(); - let layout = header.class.instance_layout(); - - dealloc(self.untagged_ptr(), layout); - } - - pub(crate) fn mask_is_set(self, mask: usize) -> bool { - (self.as_ptr() as usize & mask) == mask - } -} - -/// The header used by heap allocated objects. -/// -/// The layout is fixed to ensure we can safely assume certain fields are at -/// certain offsets in an object, even when not knowing what type of object -/// we're dealing with. -#[repr(C)] -pub(crate) struct Header { - /// The class of the object. - pub(crate) class: ClassPointer, - - /// A flag indicating the object uses atomic reference counting. - atomic: bool, - - /// The number of references to the object of this header. - /// - /// A 16-bits integer should be enough for every program. - /// - /// Not all objects use reference counting, but we still reserve the space - /// in a header. This makes it easier for generic code to handle both - /// objects that do and don't use reference counting. - references: u16, -} - -impl Header { - pub(crate) fn init(&mut self, class: ClassPointer) { - self.class = class; - self.atomic = false; - self.references = 0; - } - - pub(crate) fn init_atomic(&mut self, class: ClassPointer) { - self.class = class; - self.atomic = true; - - // Atomic values start with a reference count of 1, so - // `decrement_atomic()` returns the correct result for a value for which - // no extra references have been created (instead of overflowing). - self.references = 1; - } - - pub(crate) fn is_atomic(&self) -> bool { - self.atomic - } - - pub(crate) fn references(&self) -> u16 { - self.references - } - - pub(crate) fn increment(&mut self) { - self.references += 1; - } - - pub(crate) fn decrement(&mut self) { - debug_assert_ne!(self.references, 0); - - self.references -= 1; - } - - pub(crate) fn increment_atomic(&self) { - self.references_as_atomic().fetch_add(1, Ordering::AcqRel); - } - - pub(crate) fn decrement_atomic(&self) -> bool { - let old = self.references_as_atomic().fetch_sub(1, Ordering::AcqRel); - - // fetch_sub() overflows, making it harder to detect errors during - // development. - debug_assert_ne!(old, 0); - - old == 1 - } - - fn references_as_atomic(&self) -> &AtomicU16 { - unsafe { transmute::<_, &AtomicU16>(&self.references) } - } -} - -/// A method bound to an object. -/// -/// Methods aren't values and can't be passed around, nor can you call methods -/// on them. As such, methods don't have headers or classes. -#[repr(C)] -pub(crate) struct Method { - /// The hash of this method, used when performing dynamic dispatch. - /// - /// We use a u32 as this is easier to encode into an instruction compared to - /// a u64. - pub(crate) hash: u32, - pub(crate) registers: u16, - pub(crate) instructions: Vec, - pub(crate) locations: LocationTable, - pub(crate) jump_tables: Vec>, -} - -impl Method { - pub(crate) fn drop_and_deallocate(ptr: MethodPointer) { - unsafe { - drop_in_place(ptr.as_ptr()); - dealloc(ptr.as_ptr() as *mut u8, Self::layout()); - } - } - - pub(crate) fn alloc( - hash: u32, - registers: u16, - instructions: Vec, - locations: LocationTable, - jump_tables: Vec>, - ) -> MethodPointer { - unsafe { - let ptr = allocate(Self::layout()) as *mut Self; - let obj = &mut *ptr; - - init!(obj.hash => hash); - init!(obj.registers => registers); - init!(obj.instructions => instructions); - init!(obj.locations => locations); - init!(obj.jump_tables => jump_tables); - - MethodPointer(ptr) - } - } - - unsafe fn layout() -> Layout { - Layout::from_size_align_unchecked( - size_of::(), - align_of::(), - ) - } -} - -/// A pointer to an immutable method. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct MethodPointer(*mut Method); - -impl MethodPointer { - /// Returns the MethodPointer as a regular Pointer. - pub(crate) fn as_ptr(self) -> *mut Method { - self.0 - } -} - -impl Deref for MethodPointer { - type Target = Method; - - fn deref(&self) -> &Method { - unsafe { &*(self.0 as *const Method) } - } -} - -/// An Inko class. -/// -/// Classes come in a variety of sizes, and we don't drop them while the program -/// is running. To make managing memory easier, classes are always allocated -/// using the system allocator. -/// -/// Due to the size of this type being variable, it's used/allocated using the -/// Class type, which acts like an owned pointer to this data. -#[repr(C)] -pub(crate) struct Class { - /// The header of this class. - /// - /// The class pointer in this header will point to this class itself. - header: Header, - - /// The name of the class. - pub(crate) name: RustString, - - /// The size (in bytes) of instances of this class. - pub(crate) instance_size: usize, - - /// The number of method slots this class has. - /// - /// The actual number of methods may be less than this value. - pub(crate) method_slots: u16, - - /// All the methods of this class. - /// - /// Methods are accessed frequently, and we want to do so with as little - /// indirection and as cache-friendly as possible. For this reason we use a - /// flexible array member, instead of a Vec. - /// - /// The length of this table is always a power of two, which means some - /// slots are NULL. - methods: [MethodPointer; 0], -} - -impl Class { - pub(crate) fn drop(ptr: ClassPointer) { - unsafe { - let layout = Self::layout(ptr.method_slots); - let raw_ptr = ptr.as_ptr(); - - drop_in_place(raw_ptr); - dealloc(raw_ptr as *mut u8, layout); - } - } - - pub(crate) fn alloc( - name: RustString, - methods: u16, - size: usize, - ) -> ClassPointer { - let mut class_ptr = unsafe { - let layout = Self::layout(methods); - - // For classes we zero memory out, so unused method slots are set to - // zeroed memory, instead of random garbage. - let ptr = alloc_zeroed(layout) as *mut Class; - - if ptr.is_null() { - handle_alloc_error(layout); - } - - ClassPointer::new(ptr) - }; - let class_ptr_copy = class_ptr; - let class = unsafe { class_ptr.get_mut() }; - - class.header.init(class_ptr_copy); - - init!(class.name => name); - init!(class.instance_size => size); - init!(class.method_slots => methods); - - class_ptr - } - - /// Returns a new class for a regular object. - pub(crate) fn object( - name: RustString, - fields: usize, - methods: u16, - ) -> ClassPointer { - let size = size_of::() + (fields * size_of::()); - - Self::alloc(name, methods, size) - } - - /// Returns a new class for a process. - pub(crate) fn process( - name: RustString, - fields: usize, - methods: u16, - ) -> ClassPointer { - let size = size_of::() + (fields * size_of::()); - - Self::alloc(name, methods, size) - } - - /// Returns a pointer to the class of the given pointer. - pub(crate) fn of(space: &PermanentSpace, ptr: Pointer) -> ClassPointer { - if ptr.is_regular() { - unsafe { ptr.get::
().class } - } else if ptr.is_tagged_int() { - space.int_class() - } else if ptr.is_boolean() { - space.boolean_class() - } else { - space.nil_class() - } - } - - /// Returns the `Layout` for a class itself. - unsafe fn layout(methods: u16) -> Layout { - let size = - size_of::() + (methods as usize * size_of::()); - - Layout::from_size_align_unchecked(size, align_of::()) - } - - pub(crate) unsafe fn instance_layout(&self) -> Layout { - Layout::from_size_align_unchecked(self.instance_size, ALIGNMENT) - } - - pub(crate) fn set_method( - &mut self, - index: MethodIndex, - value: MethodPointer, - ) { - unsafe { self.methods.as_mut_ptr().add(index.into()).write(value) }; - } - - pub(crate) unsafe fn get_method( - &self, - index: MethodIndex, - ) -> MethodPointer { - *self.methods.as_ptr().add(index.into()) - } - - /// Look up a method using hashing. - /// - /// This method is useful for dynamic dispatch, as an exact offset isn't - /// known in such cases. For this to work, each unique method name must have - /// its own unique hash. This method won't work if two different methods - /// have the same hash. - /// - /// In addition, we require that the number of methods in our class is a - /// power of 2, as this allows the use of a bitwise AND instead of the - /// modulo operator. - /// - /// Finally, similar to `get_method()` we expect there to be a method for - /// the given hash. In practise this is always the case as the compiler - /// enforces this, hence we don't check for this explicitly. - /// - /// For more information on this technique, refer to - /// https://thume.ca/2019/07/29/shenanigans-with-hash-tables/. - pub(crate) unsafe fn get_hashed_method( - &self, - input_hash: u32, - ) -> MethodPointer { - let len = (self.method_slots - 1) as u32; - let mut index = input_hash; - - loop { - index &= len; - - // The cast to a u16 is safe here, as the above &= ensures we limit - // the hash value to the method count. - let ptr = self.get_method(MethodIndex::new(index as u16)); - - if ptr.hash == input_hash { - return ptr; - } - - index += 1; - } - } -} - -impl Drop for Class { - fn drop(&mut self) { - for index in 0..self.method_slots { - let method = unsafe { self.get_method(MethodIndex::new(index)) }; - - if method.as_ptr().is_null() { - // Because the table size is always a power of two, some slots - // may be NULL. - continue; - } - - Method::drop_and_deallocate(method); - } - } -} - -/// A pointer to a class. -#[repr(transparent)] -#[derive(Eq, PartialEq, Copy, Clone)] -pub(crate) struct ClassPointer(*mut Class); - -impl ClassPointer { - /// Returns a new ClassPointer from a raw Pointer. - /// - /// This method is unsafe as it doesn't perform any checks to ensure the raw - /// pointer actually points to a class. - pub(crate) unsafe fn new(pointer: *mut Class) -> Self { - Self(pointer) - } - - /// Sets a method in the given index. - pub(crate) unsafe fn set_method( - mut self, - index: MethodIndex, - value: MethodPointer, - ) { - self.get_mut().set_method(index, value); - } - - pub(crate) fn as_ptr(self) -> *mut Class { - self.0 - } - - /// Returns a mutable reference to the underlying class. - /// - /// This method is unsafe because no synchronisation is applied, nor do we - /// guarantee there's only a single writer. - unsafe fn get_mut(&mut self) -> &mut Class { - &mut *(self.0 as *mut Class) - } -} - -impl Deref for ClassPointer { - type Target = Class; - - fn deref(&self) -> &Class { - unsafe { &*(self.0 as *const Class) } - } -} - -/// A resizable array. -#[repr(C)] -pub(crate) struct Array { - header: Header, - value: Vec, -} - -impl Array { - /// Drops the given Array. - /// - /// This method is unsafe as it doesn't check if the object is actually an - /// Array. - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) fn alloc(class: ClassPointer, value: Vec) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - pub(crate) fn value(&self) -> &Vec { - &self.value - } - - pub(crate) fn value_mut(&mut self) -> &mut Vec { - &mut self.value - } -} - -/// A resizable array of bytes. -#[repr(C)] -pub(crate) struct ByteArray { - header: Header, - value: Vec, -} - -impl ByteArray { - /// Drops the given ByteArray. - /// - /// This method is unsafe as it doesn't check if the object is actually an - /// ByteArray. - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) fn alloc(class: ClassPointer, value: Vec) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - pub(crate) fn value(&self) -> &Vec { - &self.value - } - - pub(crate) fn value_mut(&mut self) -> &mut Vec { - &mut self.value - } - - pub(crate) fn take_bytes(&mut self) -> Vec { - let mut bytes = Vec::new(); - - swap(&mut bytes, &mut self.value); - bytes - } -} - -/// A signed 64-bits integer. -#[repr(C)] -pub(crate) struct Int { - header: Header, - value: i64, -} - -impl Int { - pub(crate) fn alloc(class: ClassPointer, value: i64) -> Pointer { - if (MIN_INTEGER..=MAX_INTEGER).contains(&value) { - return Pointer::int(value); - } - - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - pub(crate) unsafe fn read(ptr: Pointer) -> i64 { - if ptr.is_tagged_int() { - ptr.as_int() - } else { - ptr.get::().value - } - } - - pub(crate) unsafe fn read_u64(ptr: Pointer) -> u64 { - let val = Self::read(ptr); - - if val < 0 { - 0 - } else { - val as u64 - } - } -} - -/// A heap allocated float. -#[repr(C)] -pub(crate) struct Float { - header: Header, - value: f64, -} - -impl Float { - pub(crate) fn alloc(class: ClassPointer, value: f64) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - /// Reads the float value from the pointer. - /// - /// If the pointer doesn't actually point to a float, the behaviour is - /// undefined. - pub(crate) unsafe fn read(ptr: Pointer) -> f64 { - ptr.get::().value - } -} - -/// A heap allocated string. -/// -/// Strings use atomic reference counting as they are treated as value types, -/// and this removes the need for cloning the string's contents (at the cost of -/// atomic operations). -#[repr(C)] -pub(crate) struct String { - header: Header, - value: ImmutableString, -} - -impl String { - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) unsafe fn drop_and_deallocate(ptr: Pointer) { - Self::drop(ptr); - ptr.free(); - } - - pub(crate) unsafe fn read(ptr: &Pointer) -> &str { - ptr.get::().value().as_slice() - } - - pub(crate) fn alloc(class: ClassPointer, value: RustString) -> Pointer { - Self::from_immutable_string(class, ImmutableString::from(value)) - } - - pub(crate) fn from_immutable_string( - class: ClassPointer, - value: ImmutableString, - ) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init_atomic(class); - init!(obj.value => value); - ptr - } - - pub(crate) fn value(&self) -> &ImmutableString { - &self.value - } -} - -/// A module containing classes, methods, and code to run. -#[repr(C)] -pub(crate) struct Module { - header: Header, -} - -unsafe impl Send for Module {} - -impl Module { - pub(crate) fn drop_and_deallocate(ptr: ModulePointer) { - unsafe { - drop_in_place(ptr.as_pointer().untagged_ptr() as *mut Self); - ptr.as_pointer().free(); - } - } - - pub(crate) fn alloc(class: ClassPointer) -> ModulePointer { - let ptr = allocate(Layout::new::()); - - unsafe { &mut *(ptr as *mut Self) }.header.init(class); - ModulePointer(ptr) - } - - pub(crate) fn name(&self) -> &RustString { - &self.header.class.name - } -} - -/// A pointer to a module. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct ModulePointer(*mut u8); - -impl ModulePointer { - pub(crate) fn as_pointer(self) -> Pointer { - Pointer::new(self.0).as_permanent() - } -} - -impl Deref for ModulePointer { - type Target = Module; - - fn deref(&self) -> &Module { - unsafe { &*(self.0 as *const Module) } - } -} - -/// A regular object that can store zero or more fields. -/// -/// The size of this object varies based on the number of fields it has to -/// store. -#[repr(C)] -pub(crate) struct Object { - header: Header, - - /// The fields of this object. - /// - /// The length of this flexible array is derived from the number of - /// fields defined in this object's class. - fields: [Pointer; 0], -} - -impl Object { - /// Bump allocates a user-defined object of a variable size. - pub(crate) fn alloc(class: ClassPointer) -> Pointer { - let ptr = Pointer::new(allocate(unsafe { class.instance_layout() })); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - ptr - } - - pub(crate) unsafe fn set_field( - &mut self, - index: FieldIndex, - value: Pointer, - ) { - self.fields.as_mut_ptr().add(index.into()).write(value) - } - - pub(crate) unsafe fn get_field(&self, index: FieldIndex) -> Pointer { - *self.fields.as_ptr().add(index.into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{empty_method, empty_module, OwnedClass}; - use std::mem::{align_of, size_of}; - - #[test] - fn test_type_sizes() { - assert_eq!(size_of::
(), 16); - assert_eq!(size_of::(), 16); // variable, based on the fields - - assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 40); - - assert_eq!(size_of::(), 40); - assert_eq!(size_of::(), 40); - - // Permanent objects - assert_eq!(size_of::(), 80); - assert_eq!(size_of::(), 56); - } - - #[test] - fn test_type_alignments() { - assert_eq!(align_of::
(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - } - - #[test] - fn test_pointer_with_mask() { - let ptr = Pointer::with_mask(0x4 as _, 0b10); - - assert_eq!(ptr.as_ptr() as usize, 0x6); - } - - #[test] - fn test_pointer_integer_tagging() { - unsafe { - assert_eq!(Pointer::int(3).as_int(), 3); - assert_eq!(Pointer::int(0).as_int(), 0); - assert_eq!(Pointer::int(-3).as_int(), -3); - - assert!(Pointer::int(3).is_tagged_int()); - } - } - - #[test] - fn test_pointer_is_permanent_object() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Float::alloc(space.float_class(), 2.4); - - assert!(ptr.as_permanent().is_permanent()); - assert!(!ptr.is_permanent()); - - unsafe { ptr.free() }; - } - - #[test] - fn test_pointer_as_ref() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Float::alloc(space.float_class(), 2.4); - - assert!(!ptr.mask_is_set(REF_MASK)); - assert!(ptr.as_ref().mask_is_set(REF_MASK)); - - unsafe { ptr.free() }; - } - - #[test] - fn test_pointer_is_local_heap_object() { - let space = PermanentSpace::new(0, 0, Default::default()); - let float = Float::alloc(space.float_class(), 8.0); - - assert!(!Pointer::int(42).is_local_heap_object()); - assert!(!Pointer::int(42).is_local_heap_object()); - assert!(!float.as_permanent().is_local_heap_object()); - assert!(!Pointer::true_singleton().is_local_heap_object()); - assert!(!Pointer::false_singleton().is_local_heap_object()); - assert!(float.is_local_heap_object()); - - unsafe { float.free() }; - } - - #[test] - fn test_pointer_get() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Int::alloc(space.int_class(), MAX_INTEGER + 1); - - unsafe { - assert_eq!(ptr.get::().value, MAX_INTEGER + 1); - ptr.free(); - } - } - - #[test] - fn test_pointer_get_mut() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Int::alloc(space.int_class(), MAX_INTEGER + 1); - - unsafe { - ptr.get_mut::().value = MAX_INTEGER; - - assert_eq!(ptr.get::().value, MAX_INTEGER); - ptr.free(); - } - } - - #[test] - fn test_pointer_drop_boxed() { - unsafe { - let ptr = Pointer::boxed(42_u64); - - // This is just a smoke test to make sure dropping a Box doesn't - // crash or leak. - ptr.drop_boxed::(); - } - } - - #[test] - fn test_pointer_untagged_ptr() { - assert_eq!( - Pointer::new(0x8 as _).as_permanent().untagged_ptr(), - 0x8 as *mut u8 - ); - } - - #[test] - fn test_pointer_true_singleton() { - assert_eq!(Pointer::true_singleton().as_ptr() as usize, TRUE_ADDRESS); - } - - #[test] - fn test_pointer_false_singleton() { - assert_eq!(Pointer::false_singleton().as_ptr() as usize, FALSE_ADDRESS); - } - - #[test] - fn test_pointer_nil_singleton() { - assert_eq!(Pointer::nil_singleton().as_ptr() as usize, NIL_ADDRESS); - } - - #[test] - fn test_pointer_undefined_singleton() { - assert_eq!( - Pointer::undefined_singleton().as_ptr() as usize, - UNDEFINED_ADDRESS - ); - } - - #[test] - fn test_pointer_is_regular() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Float::alloc(space.float_class(), 2.4); - - assert!(ptr.is_regular()); - assert!(!Pointer::true_singleton().is_regular()); - assert!(!Pointer::false_singleton().is_regular()); - assert!(!Pointer::nil_singleton().is_regular()); - assert!(!Pointer::undefined_singleton().is_regular()); - assert!(!Pointer::int(42).is_regular()); - - unsafe { ptr.free() }; - } - - #[test] - fn test_pointer_is_tagged_int() { - assert!(!Pointer::true_singleton().is_tagged_int()); - assert!(!Pointer::false_singleton().is_tagged_int()); - assert!(!Pointer::nil_singleton().is_tagged_int()); - assert!(!Pointer::undefined_singleton().is_tagged_int()); - assert!(Pointer::int(42).is_tagged_int()); - assert!(Pointer::int(42).is_tagged_int()); - } - - #[test] - fn test_pointer_is_boolean() { - assert!(Pointer::true_singleton().is_boolean()); - assert!(Pointer::false_singleton().is_boolean()); - assert!(!Pointer::nil_singleton().is_boolean()); - assert!(!Pointer::int(24).is_boolean()); - assert!(!Pointer::int(24).is_boolean()); - } - - #[test] - fn test_class_new() { - let class = Class::alloc("A".to_string(), 0, 24); - - assert_eq!(class.method_slots, 0); - assert_eq!(class.instance_size, 24); - - Class::drop(class); - } - - #[test] - fn test_class_new_object() { - let class = Class::object("A".to_string(), 1, 0); - - assert_eq!(class.method_slots, 0); - assert_eq!(class.instance_size, 24); - - Class::drop(class); - } - - #[test] - fn test_class_new_process() { - let class = Class::process("A".to_string(), 1, 0); - - assert_eq!(class.method_slots, 0); - - Class::drop(class); - } - - #[test] - fn test_class_of() { - let space = PermanentSpace::new(0, 0, Default::default()); - let string = String::alloc(space.string_class(), "A".to_string()); - let perm_string = space.allocate_string("B".to_string()); - - assert!(Class::of(&space, Pointer::int(42)) == space.int_class()); - assert!( - Class::of(&space, Pointer::true_singleton()) - == space.boolean_class() - ); - assert!( - Class::of(&space, Pointer::false_singleton()) - == space.boolean_class() - ); - assert!( - Class::of(&space, Pointer::nil_singleton()) == space.nil_class() - ); - assert!(Class::of(&space, string) == space.string_class()); - assert!(Class::of(&space, perm_string) == space.string_class()); - - unsafe { - String::drop_and_deallocate(string); - }; - } - - #[test] - fn test_class_methods() { - let mod_class = - OwnedClass::new(Class::object("foo_mod".to_string(), 0, 2)); - let module = empty_module(*mod_class); - let foo = empty_method(); - let bar = empty_method(); - let index0 = MethodIndex::new(0); - let index1 = MethodIndex::new(1); - - unsafe { - module.header.class.set_method(index0, foo); - module.header.class.set_method(index1, bar); - - assert_eq!( - module.header.class.get_method(index0).as_ptr(), - foo.as_ptr() - ); - - assert_eq!( - module.header.class.get_method(index1).as_ptr(), - bar.as_ptr() - ); - } - } - - #[test] - fn test_int_read() { - let class = OwnedClass::new(Class::object("Int".to_string(), 0, 0)); - let tagged = Int::alloc(*class, 42); - let max = Int::alloc(*class, i64::MAX); - let min = Int::alloc(*class, i64::MIN); - - assert_eq!(unsafe { Int::read(tagged) }, 42); - assert_eq!(unsafe { Int::read(max) }, i64::MAX); - assert_eq!(unsafe { Int::read(min) }, i64::MIN); - - unsafe { - min.free(); - max.free(); - } - } - - #[test] - fn test_int_read_u64() { - let class = OwnedClass::new(Class::object("Int".to_string(), 0, 0)); - let tagged = Int::alloc(*class, 42); - let max = Int::alloc(*class, i64::MAX); - let min = Int::alloc(*class, i64::MIN); - - assert_eq!(unsafe { Int::read_u64(tagged) }, 42); - assert_eq!(unsafe { Int::read_u64(max) }, i64::MAX as u64); - assert_eq!(unsafe { Int::read_u64(min) }, 0); - - unsafe { - min.free(); - max.free(); - } - } -} diff --git a/vm/src/numeric.rs b/vm/src/numeric.rs deleted file mode 100644 index 3be9392e9..000000000 --- a/vm/src/numeric.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Traits and functions for various numerical operations. - -/// The Modulo trait is used for getting the modulo (instead of remainder) of a -/// number. -pub(crate) trait Modulo { - fn checked_modulo(self, rhs: T) -> Option; -} - -impl Modulo for i64 { - fn checked_modulo(self, rhs: i64) -> Option { - self.checked_rem(rhs) - .and_then(|res| res.checked_add(rhs)) - .and_then(|res| res.checked_rem(rhs)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_modulo_i64() { - assert_eq!((-5_i64).checked_modulo(86_400_i64), Some(86395_i64)); - assert_eq!(i64::MIN.checked_modulo(-1_i64), None); - } -} diff --git a/vm/src/permanent_space.rs b/vm/src/permanent_space.rs deleted file mode 100644 index 4b6f3eb51..000000000 --- a/vm/src/permanent_space.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! Types for memory that stays around permanently. -use crate::chunk::Chunk; -use crate::indexes::ClassIndex; -use crate::mem::{ - Array, ByteArray, Class, ClassPointer, Float, Int, Module, ModulePointer, - Object, Pointer, String as InkoString, -}; -use crate::process::Future; -use ahash::AHashMap; -use std::mem::size_of; -use std::ops::Drop; -use std::sync::Mutex; - -pub(crate) const INT_CLASS: usize = 0; -pub(crate) const FLOAT_CLASS: usize = 1; -pub(crate) const STRING_CLASS: usize = 2; -pub(crate) const ARRAY_CLASS: usize = 3; -pub(crate) const BOOLEAN_CLASS: usize = 4; -pub(crate) const NIL_CLASS: usize = 5; -pub(crate) const BYTE_ARRAY_CLASS: usize = 6; -pub(crate) const FUTURE_CLASS: usize = 7; - -/// The total number of built-in classes. -const BUILTIN_CLASS_COUNT: usize = FUTURE_CLASS + 1; - -/// Allocates a new class, returning a tuple containing the owned pointer and a -/// permanent reference pointer. -macro_rules! class { - ($name: expr, $methods: expr, $size_source: ident) => {{ - Class::alloc($name.to_string(), $methods, size_of::<$size_source>()) - }}; -} - -macro_rules! get_class { - ($space: expr, $index: expr) => { - unsafe { *$space.classes.get($index) } - }; -} - -/// The number of methods used for the various built-in classes. -/// -/// These counts are used to determine how much memory is needed for allocating -/// the various built-in classes. -#[derive(Default)] -pub(crate) struct MethodCounts { - pub int_class: u16, - pub float_class: u16, - pub string_class: u16, - pub array_class: u16, - pub boolean_class: u16, - pub nil_class: u16, - pub byte_array_class: u16, - pub future_class: u16, -} - -/// Memory that sticks around for a program's lifetime (aka permanently). -pub(crate) struct PermanentSpace { - /// All classes defined by the running program. - /// - /// Classes are all stored in the same list, making it easy to efficiently - /// access them from any module; regardless of what module defined the - /// class. - /// - /// We use a Chunk here for the following reasons: - /// - /// 1. The list never grows beyond the size specified in the bytecode. - /// 2. We need to be able to (concurrently set classes in any order while - /// parsing bytecode. - /// 3. We don't want locking. While parsing bytecode we only set values, and - /// after parsing bytecode we only read values. - /// - /// The first N (see the value of BUILTIN_CLASS_COUNT) fields are reserved - /// for built-in classes. Some of these values may be left empty to - /// accomodate for potential future built-in classes. - classes: Chunk, - - /// All modules that are available to the current program. - modules: Chunk, - - /// A map of strings and their heap allocated Inko strings. - /// - /// This map is used to ensure that different occurrences of the same string - /// literal all use the same heap object. - interned_strings: Mutex>, - - /// Permanently allocated objects (excluding classes) to deallocate when the - /// program terminates. - /// - /// This list doesn't include interned strings, as those are stored - /// separately. - permanent_objects: Mutex>, -} - -unsafe impl Sync for PermanentSpace {} - -impl PermanentSpace { - pub(crate) fn new( - modules: u32, - classes: u32, - counts: MethodCounts, - ) -> Self { - let int_class = class!("Int", counts.int_class, Int); - let float_class = class!("Float", counts.float_class, Float); - let str_class = class!("String", counts.string_class, InkoString); - let ary_class = class!("Array", counts.array_class, Array); - let bool_class = class!("Bool", counts.boolean_class, Object); - let nil_class = class!("Nil", counts.nil_class, Object); - let bary_class = - class!("ByteArray", counts.byte_array_class, ByteArray); - - let fut_class = class!("Future", counts.future_class, Future); - let mut classes = Chunk::new(classes as usize + BUILTIN_CLASS_COUNT); - let modules = Chunk::new(modules as usize); - - unsafe { - classes.set(INT_CLASS, int_class); - classes.set(FLOAT_CLASS, float_class); - classes.set(STRING_CLASS, str_class); - classes.set(ARRAY_CLASS, ary_class); - classes.set(BOOLEAN_CLASS, bool_class); - classes.set(NIL_CLASS, nil_class); - classes.set(BYTE_ARRAY_CLASS, bary_class); - classes.set(FUTURE_CLASS, fut_class); - } - - Self { - interned_strings: Mutex::new(AHashMap::default()), - modules, - classes, - permanent_objects: Mutex::new(Vec::new()), - } - } - - pub(crate) fn int_class(&self) -> ClassPointer { - get_class!(self, INT_CLASS) - } - - pub(crate) fn float_class(&self) -> ClassPointer { - get_class!(self, FLOAT_CLASS) - } - - pub(crate) fn string_class(&self) -> ClassPointer { - get_class!(self, STRING_CLASS) - } - - pub(crate) fn array_class(&self) -> ClassPointer { - get_class!(self, ARRAY_CLASS) - } - - pub(crate) fn boolean_class(&self) -> ClassPointer { - get_class!(self, BOOLEAN_CLASS) - } - - pub(crate) fn nil_class(&self) -> ClassPointer { - get_class!(self, NIL_CLASS) - } - - pub(crate) fn byte_array_class(&self) -> ClassPointer { - get_class!(self, BYTE_ARRAY_CLASS) - } - - pub(crate) fn future_class(&self) -> ClassPointer { - get_class!(self, FUTURE_CLASS) - } - - /// Interns a permanent string. - /// - /// If an Inko String has already been allocated for the given Rust String, - /// the existing Inko String is returned; otherwise a new one is created. - pub(crate) fn allocate_string(&self, string: String) -> Pointer { - let mut strings = self.interned_strings.lock().unwrap(); - - if let Some(ptr) = strings.get(&string) { - return *ptr; - } - - let ptr = InkoString::alloc(self.string_class(), string.clone()) - .as_permanent(); - - strings.insert(string, ptr); - ptr - } - - pub(crate) fn allocate_int(&self, value: i64) -> Pointer { - let ptr = Int::alloc(self.int_class(), value); - - if ptr.is_tagged_int() { - ptr - } else { - let ptr = ptr.as_permanent(); - - self.permanent_objects.lock().unwrap().push(ptr); - ptr - } - } - - pub(crate) fn allocate_float(&self, value: f64) -> Pointer { - let ptr = Float::alloc(self.float_class(), value).as_permanent(); - - self.permanent_objects.lock().unwrap().push(ptr); - ptr - } - - pub(crate) fn allocate_array(&self, value: Vec) -> Pointer { - let ptr = Array::alloc(self.array_class(), value).as_permanent(); - - self.permanent_objects.lock().unwrap().push(ptr); - ptr - } - - /// Adds a class in the global class list. - /// - /// This method is unsafe because it allows unsynchronised write access to - /// the class list. In practise this is fine because the bytecode parser - /// doesn't set the same class twice, and we never resize the class list. - /// For the sake of clarity we have marked this method as unsafe anyway. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ref_to_mut))] - pub(crate) unsafe fn add_class( - &self, - raw_index: u32, - class: ClassPointer, - ) -> Result<(), String> { - let index = raw_index as usize; - - if index >= self.classes.len() { - return Err(format!( - "Unable to define class {:?}, as the class index {} is out of bounds", - class.name, - index - )); - } - - let existing = *self.classes.get(index); - - if !existing.as_ptr().is_null() { - return Err(format!( - "Can't store class {:?} in index {}, as it's already used by class {:?}", - class.name, - index, - existing.name - )); - } - - let self_mut = &mut *(self as *const _ as *mut PermanentSpace); - - self_mut.classes.set(index, class); - Ok(()) - } - - /// Adds a module in the global module list. - /// - /// This method is unsafe for the same reasons as - /// `PermanentSpace::add_class()`. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ref_to_mut))] - pub(crate) unsafe fn add_module( - &self, - raw_index: u32, - module: ModulePointer, - ) -> Result<(), String> { - let index = raw_index as usize; - - if index >= self.modules.len() { - return Err(format!( - "Unable to define module {:?}, as the module index {} is out of bounds", - module.name(), - index - )); - } - - let existing = *self.modules.get(index); - - if !existing.as_pointer().untagged_ptr().is_null() { - return Err(format!( - "Can't store module {:?} in index {}, as it's already used by module {:?}", - module.name(), - index, - existing.name() - )); - } - - let self_mut = &mut *(self as *const _ as *mut PermanentSpace); - - self_mut.modules.set(index, module); - Ok(()) - } - - pub unsafe fn get_class(&self, index: ClassIndex) -> ClassPointer { - *self.classes.get(index.into()) - } -} - -impl Drop for PermanentSpace { - fn drop(&mut self) { - unsafe { - for pointer in self.interned_strings.lock().unwrap().values() { - InkoString::drop_and_deallocate(*pointer); - } - - for ptr in self.permanent_objects.lock().unwrap().iter() { - ptr.free(); - } - - for index in 0..self.modules.len() { - let ptr = *self.modules.get(index); - - if !ptr.as_pointer().untagged_ptr().is_null() { - Module::drop_and_deallocate(ptr); - } - } - - for index in 0..self.classes.len() { - let ptr = *self.classes.get(index); - - if !ptr.as_ptr().is_null() { - Class::drop(ptr); - } - } - } - - // The singleton objects can't contain any sub values, so they don't - // need to be dropped explicitly. - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::{ - Array, ByteArray, Float, Int, Object, String as InkoString, - }; - use crate::process::Future; - use std::mem::size_of; - - #[test] - fn test_class_instance_sizes() { - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - assert_eq!(perm.int_class().instance_size, size_of::()); - assert_eq!(perm.float_class().instance_size, size_of::()); - assert_eq!(perm.string_class().instance_size, size_of::()); - assert_eq!(perm.array_class().instance_size, size_of::()); - assert_eq!(perm.boolean_class().instance_size, size_of::()); - assert_eq!(perm.nil_class().instance_size, size_of::()); - assert_eq!( - perm.byte_array_class().instance_size, - size_of::() - ); - assert_eq!(perm.future_class().instance_size, size_of::()); - } -} diff --git a/vm/src/platform.rs b/vm/src/platform.rs deleted file mode 100644 index 57066ec16..000000000 --- a/vm/src/platform.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Basic platform detection. -//! -//! This module provides basic functionality for detecting the underlying -//! platform. - -/// Returns an identifier for the underlying operating system. -pub(crate) fn operating_system() -> i64 { - if cfg!(target_os = "windows") { - 0 - } else if cfg!(target_os = "macos") { - 1 - } else if cfg!(target_os = "ios") { - 2 - } else if cfg!(target_os = "linux") { - 3 - } else if cfg!(target_os = "android") { - 4 - } else if cfg!(target_os = "freebsd") { - 5 - } else if cfg!(target_os = "dragonfly") { - 6 - } else if cfg!(target_os = "bitrig") { - 7 - } else if cfg!(target_os = "openbsd") { - 8 - } else if cfg!(target_os = "netbsd") { - 9 - } else if cfg!(unix) { - 10 - } else { - 11 - } -} - -#[cfg(test)] -mod tests { - macro_rules! test_operating_system { - ($platform: expr, $code: expr) => { - #[cfg(target_os = $platform)] - #[test] - fn test_operating_system() { - assert_eq!(super::operating_system(), $code); - } - }; - } - - test_operating_system!("windows", 0); - test_operating_system!("macos", 1); - test_operating_system!("ios", 2); - test_operating_system!("linux", 3); - test_operating_system!("android", 4); - test_operating_system!("freebsd", 5); - test_operating_system!("dragonfly", 6); - test_operating_system!("bitrig", 7); - test_operating_system!("openbsd", 8); - test_operating_system!("netbsd", 9); - - #[cfg(not(any( - target_os = "windows", - target_os = "macos", - target_os = "ios", - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "bitrig", - target_os = "openbsd", - target_os = "netbsd" - )))] - #[test] - fn test_operating_system() { - assert_eq!(super::operating_system(), 11); - } -} diff --git a/vm/src/process.rs b/vm/src/process.rs deleted file mode 100644 index 40d67d3c5..000000000 --- a/vm/src/process.rs +++ /dev/null @@ -1,1601 +0,0 @@ -use crate::arc_without_weak::ArcWithoutWeak; -use crate::execution_context::ExecutionContext; -use crate::indexes::{FieldIndex, MethodIndex}; -use crate::location_table::Location; -use crate::mem::{allocate, ClassPointer, Header, MethodPointer, Pointer}; -use crate::scheduler::timeouts::Timeout; -use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; -use std::collections::VecDeque; -use std::mem::{align_of, size_of, swap}; -use std::ops::Drop; -use std::ops::{Deref, DerefMut}; -use std::ptr::{copy_nonoverlapping, drop_in_place, NonNull}; -use std::slice; -use std::sync::{Mutex, MutexGuard}; - -/// The shared state of a future. -pub(crate) struct FutureState { - /// The process that's waiting for this future to complete. - pub(crate) consumer: Option, - - /// The result of a message. - /// - /// This defaults to the undefined singleton. - result: Pointer, - - /// A boolean indicating that the result was produced by throwing an error, - /// instead of returning it. - thrown: bool, - - /// A boolean indicating the future is disconnected. - /// - /// This flag is set whenever a reader or writer is dropped. - disconnected: bool, -} - -impl FutureState { - pub(crate) fn new() -> RcFutureState { - let state = Self { - consumer: None, - result: Pointer::undefined_singleton(), - thrown: false, - disconnected: false, - }; - - ArcWithoutWeak::new(Mutex::new(state)) - } - - pub(crate) fn has_result(&self) -> bool { - self.result != Pointer::undefined_singleton() - } -} - -type RcFutureState = ArcWithoutWeak>; - -impl RcFutureState { - /// Writes the result of a message to this future. - /// - /// If the future has been disconnected, an Err is returned that contains - /// the value that we tried to write. - /// - /// The returned tuple contains a value that indicates if a process needs to - /// be rescheduled, and a pointer to the process that sent the message. - pub(crate) fn write(&self, result: Pointer, thrown: bool) -> WriteResult { - let mut future = self.lock().unwrap(); - - if future.disconnected { - return WriteResult::Discard; - } - - future.thrown = thrown; - future.result = result; - - if let Some(consumer) = future.consumer.take() { - match consumer.state.lock().unwrap().try_reschedule_for_future() { - RescheduleRights::Failed => WriteResult::Continue, - RescheduleRights::Acquired => WriteResult::Reschedule(consumer), - RescheduleRights::AcquiredWithTimeout => { - WriteResult::RescheduleWithTimeout(consumer) - } - } - } else { - WriteResult::Continue - } - } - - /// Gets the value from this future. - /// - /// When a None is returned, the consumer must stop running any code. It's - /// then up to a producer to reschedule the consumer when writing a value to - /// the future. - pub(crate) fn get( - &self, - consumer: ProcessPointer, - timeout: Option>, - ) -> FutureResult { - // The locking order is important here: we _must_ lock the future - // _before_ locking the consumer. If we lock the consumer first, we - // could deadlock with processes writing to this future. - let mut future = self.lock().unwrap(); - let mut state = consumer.state.lock().unwrap(); - let result = future.result; - - if result != Pointer::undefined_singleton() { - future.consumer = None; - future.result = Pointer::undefined_singleton(); - - state.status.set_waiting_for_future(false); - - return if future.thrown { - FutureResult::Thrown(result) - } else { - FutureResult::Returned(result) - }; - } - - future.consumer = Some(consumer); - - state.waiting_for_future(timeout); - FutureResult::None - } -} - -/// A method scheduled for execution at some point in the future. -/// -/// The size of this type depends on the number of arguments. Using this -/// approach allows us to keep the size of a message as small as possible, -/// without the need for allocating arguments separately. -#[repr(C)] -struct ScheduledMethodInner { - /// The method to run. - method: MethodIndex, - - /// The number of arguments of this message. - length: u8, - - /// The arguments of the message. - arguments: [Pointer; 0], -} - -/// An owned pointer to a ScheduledMethodInner. -struct ScheduledMethod(NonNull); - -impl ScheduledMethod { - fn new(method: MethodIndex, arguments: Vec) -> Self { - unsafe { - let layout = Self::layout(arguments.len() as u8); - let raw_ptr = alloc(layout) as *mut ScheduledMethodInner; - - if raw_ptr.is_null() { - handle_alloc_error(layout); - } - - let msg = &mut *raw_ptr; - - init!(msg.method => method); - init!(msg.length => arguments.len() as u8); - - copy_nonoverlapping( - arguments.as_ptr(), - msg.arguments.as_mut_ptr(), - arguments.len(), - ); - - Self(NonNull::new_unchecked(raw_ptr)) - } - } - - unsafe fn layout(length: u8) -> Layout { - let size = size_of::() - + (length as usize * size_of::()); - - // Messages are sent often, so we don't want the overhead of size and - // alignment checks. - Layout::from_size_align_unchecked(size, align_of::()) - } -} - -impl Deref for ScheduledMethod { - type Target = ScheduledMethodInner; - - fn deref(&self) -> &ScheduledMethodInner { - unsafe { self.0.as_ref() } - } -} - -impl Drop for ScheduledMethod { - fn drop(&mut self) { - unsafe { - let layout = Self::layout(self.0.as_ref().length); - - drop_in_place(self.0.as_ptr()); - dealloc(self.0.as_ptr() as *mut u8, layout); - } - } -} - -/// A message sent between two processes. -struct Message { - write: Write, - scheduled: ScheduledMethod, -} - -impl Message { - fn new( - method: MethodIndex, - write: Write, - arguments: Vec, - ) -> Message { - let scheduled = ScheduledMethod::new(method, arguments); - - Message { write, scheduled } - } -} - -/// A collection of messages to be processed by a process. -struct Mailbox { - messages: VecDeque, -} - -impl Mailbox { - fn new() -> Self { - Mailbox { messages: VecDeque::new() } - } - - fn send(&mut self, message: Message) { - self.messages.push_back(message); - } - - fn receive(&mut self) -> Option { - self.messages.pop_front() - } -} - -/// A type indicating how the results of a task should be communicated with the -/// consumer. -pub(crate) enum Write { - /// The result of a task is to be discarded. - Discard, - - /// The consumer/sender is suspended and waiting for a result, without using - /// a future. - Direct(ProcessPointer), - - /// The consumer scheduled the message without immediately waiting for it. - /// Instead of writing the result directly to the consumer, we write it to a - /// future. - Future(RcFutureState), -} - -/// A task to run in response to a message. -pub(crate) struct Task { - /// The execution context/call stack of this task. - pub(crate) context: Box, - - /// The stack of arguments for the next instruction. - /// - /// Certain instructions use a variable number of arguments, such as method - /// calls. For these instructions we use a stack, as the VM's instructions - /// are of a fixed size. - /// - /// The ordering of values may differ per instruction: for method calls - /// arguments should be pushed in reverse order, so the first pop() returns - /// the first argument instead of the last one. For other instructions the - /// values are in-order, meaning the first value in the stack is the first - /// value to use. - /// - /// In the past we used to put registers next to each other, and specified - /// the first register and the number of arguments. The VM would then read - /// all arguments in sequence. While this removes the need for a stack, it - /// complicated code generation enough that we decided to move away from it. - pub(crate) stack: Vec, - pub(crate) write: Write, -} - -impl Task { - pub(crate) fn new(context: ExecutionContext, write: Write) -> Self { - Self { context: Box::new(context), write, stack: Vec::new() } - } - - pub(crate) fn take_arguments(&mut self) -> Vec { - let mut stack = Vec::new(); - - swap(&mut stack, &mut self.stack); - stack - } - - pub(crate) fn clear_arguments(&mut self) { - self.stack.clear(); - } - - /// Adds a new execution context to the stack. - /// - /// The parent of the new context is set to the context before the push. - pub(crate) fn push_context(&mut self, new_context: ExecutionContext) { - let mut boxed = Box::new(new_context); - let target = &mut self.context; - - swap(target, &mut boxed); - target.parent = Some(boxed); - } - - /// Pops the current execution context off the stack. - /// - /// If all contexts have been popped, `true` is returned. - pub(crate) fn pop_context(&mut self) -> bool { - let context = &mut self.context; - - if let Some(parent) = context.parent.take() { - *context = parent; - false - } else { - true - } - } - - pub(crate) fn contexts(&self) -> Vec<&ExecutionContext> { - self.context.contexts().collect::>() - } -} - -/// A pointer to a Task. -/// -/// In various places we borrow a Task while also borrowing its fields, or while -/// borrowing fields of the owning Process. This can't be expressed safely in -/// Rust's type system, and working around this requires fiddling with pointer -/// casts and unsafe code. -/// -/// To make this less painful to deal with, instead of using a `&mut Task` in -/// various places we use a `TaskPointer`. This won't give us any extra safety, -/// but at least it's less annoying to deal with. -#[repr(transparent)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct TaskPointer(Pointer); - -impl TaskPointer { - fn new(pointer: &Task) -> Self { - Self(Pointer::new(pointer as *const _ as *mut _)) - } -} - -impl Deref for TaskPointer { - type Target = Task; - - fn deref(&self) -> &Task { - unsafe { &*(self.0.as_ptr() as *mut Task) } - } -} - -impl DerefMut for TaskPointer { - fn deref_mut(&mut self) -> &mut Task { - unsafe { &mut *(self.0.as_ptr() as *mut Task) } - } -} - -/// The status of a process, represented as a set of bits. -pub(crate) struct ProcessStatus { - /// The bits used to indicate the status of the process. - /// - /// Multiple bits may be set in order to combine different statuses. - bits: u8, -} - -impl ProcessStatus { - /// A regular process. - const NORMAL: u8 = 0b00_0000; - - /// The main process. - const MAIN: u8 = 0b00_0001; - - /// The process is waiting for a message. - const WAITING_FOR_MESSAGE: u8 = 0b00_0010; - - /// The process is waiting for a future. - const WAITING_FOR_FUTURE: u8 = 0b00_0100; - - /// The process is waiting for an IO operation to complete. - const WAITING_FOR_IO: u8 = 0b00_1000; - - /// The process is simply sleeping for a certain amount of time. - const SLEEPING: u8 = 0b01_0000; - - /// The process was rescheduled after a timeout expired. - const TIMEOUT_EXPIRED: u8 = 0b10_0000; - - /// The process is waiting for something, or suspended for a period of time. - const WAITING: u8 = - Self::WAITING_FOR_FUTURE | Self::SLEEPING | Self::WAITING_FOR_IO; - - pub(crate) fn new() -> Self { - Self { bits: Self::NORMAL } - } - - fn set_main(&mut self) { - self.update_bits(Self::MAIN, true); - } - - fn is_main(&self) -> bool { - self.bit_is_set(Self::MAIN) - } - - fn set_waiting_for_message(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_MESSAGE, enable); - } - - fn is_waiting_for_message(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_MESSAGE) - } - - fn set_waiting_for_future(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_FUTURE, enable); - } - - fn set_waiting_for_io(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_IO, enable); - } - - fn is_waiting_for_io(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_IO) - } - - fn is_waiting_for_future(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_FUTURE) - } - - fn is_waiting(&self) -> bool { - (self.bits & Self::WAITING) != 0 - } - - fn no_longer_waiting(&mut self) { - self.update_bits(Self::WAITING, false); - } - - fn set_timeout_expired(&mut self, enable: bool) { - self.update_bits(Self::TIMEOUT_EXPIRED, enable) - } - - fn set_sleeping(&mut self, enable: bool) { - self.update_bits(Self::SLEEPING, enable); - } - - fn timeout_expired(&self) -> bool { - self.bit_is_set(Self::TIMEOUT_EXPIRED) - } - - fn update_bits(&mut self, mask: u8, enable: bool) { - self.bits = if enable { self.bits | mask } else { self.bits & !mask }; - } - - fn bit_is_set(&self, bit: u8) -> bool { - self.bits & bit == bit - } -} - -/// An enum describing what rights a thread was given when trying to reschedule -/// a process. -#[derive(Eq, PartialEq, Debug)] -pub(crate) enum RescheduleRights { - /// The rescheduling rights were not obtained. - Failed, - - /// The rescheduling rights were obtained. - Acquired, - - /// The rescheduling rights were obtained, and the process was using a - /// timeout. - AcquiredWithTimeout, -} - -impl RescheduleRights { - pub(crate) fn are_acquired(&self) -> bool { - !matches!(self, RescheduleRights::Failed) - } -} - -/// The shared state of a process. -/// -/// This state is shared by both the process and its clients. -pub(crate) struct ProcessState { - /// The mailbox of this process. - mailbox: Mailbox, - - /// The status of the process. - status: ProcessStatus, - - /// The timeout this process is suspended with, if any. - /// - /// If missing and the process is suspended, it means the process is - /// suspended indefinitely. - timeout: Option>, -} - -impl ProcessState { - pub(crate) fn new() -> Self { - Self { - mailbox: Mailbox::new(), - status: ProcessStatus::new(), - timeout: None, - } - } - - pub(crate) fn has_same_timeout( - &self, - timeout: &ArcWithoutWeak, - ) -> bool { - self.timeout - .as_ref() - .map(|t| t.as_ptr() == timeout.as_ptr()) - .unwrap_or(false) - } - - pub(crate) fn try_reschedule_after_timeout(&mut self) -> RescheduleRights { - if !self.status.is_waiting() { - return RescheduleRights::Failed; - } - - if self.status.is_waiting_for_future() - || self.status.is_waiting_for_io() - { - // We may be suspended for some time without actually waiting for - // anything, in that case we don't want to update the process - // status. - self.status.set_timeout_expired(true); - } - - self.status.no_longer_waiting(); - - if self.timeout.take().is_some() { - RescheduleRights::AcquiredWithTimeout - } else { - RescheduleRights::Acquired - } - } - - pub(crate) fn waiting_for_future( - &mut self, - timeout: Option>, - ) { - self.timeout = timeout; - - self.status.set_waiting_for_future(true); - } - - pub(crate) fn waiting_for_io( - &mut self, - timeout: Option>, - ) { - self.timeout = timeout; - - self.status.set_waiting_for_io(true); - } - - fn try_reschedule_for_message(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_message() { - return RescheduleRights::Failed; - } - - self.status.set_waiting_for_message(false); - RescheduleRights::Acquired - } - - fn try_reschedule_for_future(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_future() { - return RescheduleRights::Failed; - } - - self.status.set_waiting_for_future(false); - - if self.timeout.take().is_some() { - RescheduleRights::AcquiredWithTimeout - } else { - RescheduleRights::Acquired - } - } - - pub(crate) fn try_reschedule_for_io(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_io() { - return RescheduleRights::Failed; - } - - self.status.set_waiting_for_io(false); - - if self.timeout.take().is_some() { - RescheduleRights::AcquiredWithTimeout - } else { - RescheduleRights::Acquired - } - } -} - -/// A lightweight process. -#[repr(C)] -pub(crate) struct Process { - pub(crate) header: Header, - - /// A boolean indicating that the result was thrown rather than returned. - thrown: bool, - - /// The currently running task, if any. - task: Option, - - /// The last value returned or thrown. - result: Pointer, - - /// The internal shared state of the process. - state: Mutex, - - /// The fields of this process. - /// - /// The length of this flexible array is derived from the number of - /// fields defined in this process' class. - fields: [Pointer; 0], -} - -impl Process { - pub(crate) fn drop_and_deallocate(ptr: ProcessPointer) { - unsafe { - drop_in_place(ptr.as_pointer().as_ptr() as *mut Self); - ptr.as_pointer().free(); - } - } - - pub(crate) fn alloc(class: ClassPointer) -> ProcessPointer { - let ptr = Pointer::new(allocate(unsafe { class.instance_layout() })); - let obj = unsafe { ptr.get_mut::() }; - let mut state = ProcessState::new(); - - // Processes start without any messages, so we must ensure their status - // is set accordingly. - state.status.set_waiting_for_message(true); - - obj.header.init_atomic(class); - - init!(obj.thrown => false); - init!(obj.result => Pointer::undefined_singleton()); - init!(obj.state => Mutex::new(state)); - init!(obj.task => None); - - unsafe { ProcessPointer::from_pointer(ptr) } - } - - /// Returns a new Process acting as the main process. - /// - /// This process always runs on the main thread. - pub(crate) fn main( - class: ClassPointer, - method: MethodPointer, - ) -> ProcessPointer { - let mut process = Self::alloc(class); - let mut task = Task::new(ExecutionContext::new(method), Write::Discard); - - task.stack.push(process.as_pointer()); - - process.task = Some(task); - - // The main process always has an initial message, so we need to reset - // this particular status. - process.state().status.set_waiting_for_message(false); - process.set_main(); - process - } - - pub(crate) fn set_main(&mut self) { - self.state.lock().unwrap().status.set_main(); - } - - pub(crate) fn is_main(&self) -> bool { - self.state.lock().unwrap().status.is_main() - } - - /// Suspends this process for a period of time. - /// - /// A process is sleeping when it simply isn't to be scheduled for a while, - /// without waiting for a message, future, or something else. - pub(crate) fn suspend(&mut self, timeout: ArcWithoutWeak) { - let mut state = self.state.lock().unwrap(); - - state.timeout = Some(timeout); - - state.status.set_sleeping(true); - } - - /// Sends a synchronous message to this process. - pub(crate) fn send_message( - &mut self, - method: MethodIndex, - sender: ProcessPointer, - arguments: Vec, - wait: bool, - ) -> RescheduleRights { - let write = if wait { Write::Direct(sender) } else { Write::Discard }; - let message = Message::new(method, write, arguments); - let mut state = self.state.lock().unwrap(); - - state.mailbox.send(message); - state.try_reschedule_for_message() - } - - /// Sends an asynchronous message to this process. - pub(crate) fn send_async_message( - &mut self, - method: MethodIndex, - future: RcFutureState, - arguments: Vec, - ) -> RescheduleRights { - let message = Message::new(method, Write::Future(future), arguments); - let mut state = self.state.lock().unwrap(); - - state.mailbox.send(message); - state.try_reschedule_for_message() - } - - /// Schedules a task to run, if none is scheduled already. - pub(crate) fn task_to_run(&mut self) -> Option { - let mut proc_state = self.state.lock().unwrap(); - - if let Some(task) = self.task.as_ref() { - return Some(TaskPointer::new(task)); - } - - let message = if let Some(message) = proc_state.mailbox.receive() { - message - } else { - proc_state.status.set_waiting_for_message(true); - - return None; - }; - - drop(proc_state); - - let method = - unsafe { self.header.class.get_method(message.scheduled.method) }; - let ctx = ExecutionContext::new(method); - let len = message.scheduled.length as usize; - let values = unsafe { - slice::from_raw_parts(message.scheduled.arguments.as_ptr(), len) - }; - - let mut task = Task::new(ctx, message.write); - - task.stack.extend_from_slice(values); - - self.task = Some(task); - - self.task.as_ref().map(TaskPointer::new) - } - - /// Finishes the exection of a task, and decides what to do next with this - /// process. - /// - /// If the return value is `true`, the process should be rescheduled. - pub(crate) fn finish_task(&mut self) -> bool { - let mut state = self.state.lock().unwrap(); - - self.task.take(); - - if state.mailbox.messages.is_empty() { - state.status.set_waiting_for_message(true); - false - } else { - true - } - } - - pub(crate) unsafe fn set_field( - &mut self, - index: FieldIndex, - value: Pointer, - ) { - self.fields.as_mut_ptr().add(index.into()).write(value); - } - - pub(crate) unsafe fn get_field(&self, index: FieldIndex) -> Pointer { - *self.fields.as_ptr().add(index.into()) - } - - pub(crate) fn timeout_expired(&self) -> bool { - let mut state = self.state.lock().unwrap(); - - if state.status.timeout_expired() { - state.status.set_timeout_expired(false); - true - } else { - false - } - } - - pub(crate) fn state(&self) -> MutexGuard { - self.state.lock().unwrap() - } - - pub(crate) fn stacktrace(&self) -> Vec { - let mut locations = Vec::new(); - - if let Some(task) = self.task.as_ref() { - for context in task.contexts() { - let mut index = context.index; - let ins = &context.method.instructions[index]; - - // When entering methods the index points to the instruction - // _after_ the call. For built-in function calls this isn't the - // case, as we store the current index instead so the call can - // be retried (e.g. when a socket operation) would block. - if index > 0 && !ins.opcode.rewind_before_call() { - index -= 1; - } - - if let Some(loc) = context.method.locations.get(index as u32) { - locations.push(loc); - } - } - - // The most recent frame should come first, not last. - locations.reverse(); - } - - locations - } - - pub(crate) fn thrown(&self) -> bool { - self.thrown - } - - pub(crate) fn set_return_value(&mut self, value: Pointer) { - self.result = value; - } - - pub(crate) fn set_throw_value(&mut self, value: Pointer) { - self.thrown = true; - self.result = value; - } - - pub(crate) fn move_result(&mut self) -> Pointer { - let result = self.result; - - self.result = Pointer::undefined_singleton(); - self.thrown = false; - - result - } -} - -/// A pointer to a process. -#[repr(transparent)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct ProcessPointer(NonNull); - -unsafe impl Sync for ProcessPointer {} -unsafe impl Send for ProcessPointer {} - -impl ProcessPointer { - pub(crate) unsafe fn from_pointer(pointer: Pointer) -> Self { - Self::new(pointer.as_ptr() as *mut Process) - } - - /// Returns a new pointer from a raw Pointer. - pub(crate) unsafe fn new(pointer: *mut Process) -> Self { - Self(NonNull::new_unchecked(pointer)) - } - - pub(crate) fn identifier(self) -> usize { - self.0.as_ptr() as usize - } - - pub(crate) fn as_pointer(self) -> Pointer { - Pointer::new(self.0.as_ptr() as *mut u8) - } -} - -impl Deref for ProcessPointer { - type Target = Process; - - fn deref(&self) -> &Process { - unsafe { &*self.0.as_ptr() } - } -} - -impl DerefMut for ProcessPointer { - fn deref_mut(&mut self) -> &mut Process { - unsafe { &mut *self.0.as_mut() } - } -} - -/// An enum describing the value produced by a future, and how it was produced. -#[derive(PartialEq, Eq, Debug)] -pub(crate) enum FutureResult { - /// No values has been produced so far. - None, - - /// The value was returned. - Returned(Pointer), - - /// The value was thrown and should be thrown again. - Thrown(Pointer), -} - -/// An enum that describes what a producer should do after writing to a future. -#[derive(PartialEq, Eq, Debug)] -pub(crate) enum WriteResult { - /// The future is disconnected, and the writer should discard the value it - /// tried to write. - Discard, - - /// A value was written, but no further action is needed. - Continue, - - /// A value was written, and the given process needs to be rescheduled. - Reschedule(ProcessPointer), - - /// A value was written, the given process needs to be rescheduled, and a - /// timeout needs to be invalidated. - RescheduleWithTimeout(ProcessPointer), -} - -/// Storage for a value to be computed some time in the future. -/// -/// A Future is essentially just a single-producer single-consumer queue, with -/// support for only writing a value once, and only reading it once. Futures are -/// used to send message results between processes. -#[repr(C)] -pub(crate) struct Future { - header: Header, - state: RcFutureState, -} - -impl Future { - pub(crate) fn alloc(class: ClassPointer, state: RcFutureState) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.state => state); - ptr - } - - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) fn get( - &self, - consumer: ProcessPointer, - timeout: Option>, - ) -> FutureResult { - self.state.get(consumer, timeout) - } - - pub(crate) fn lock(&self) -> MutexGuard { - self.state.lock().unwrap() - } - - pub(crate) fn disconnect(&self) -> Pointer { - let mut future = self.state.lock().unwrap(); - let result = future.result; - - future.disconnected = true; - future.result = Pointer::undefined_singleton(); - - result - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::location_table::Location; - use crate::mem::{Class, Method}; - use crate::test::{ - empty_class, empty_method, empty_process_class, new_process, - OwnedClass, OwnedProcess, - }; - use std::time::Duration; - - #[test] - fn test_message_new() { - let method = MethodIndex::new(0); - let future = FutureState::new(); - let write = Write::Future(future); - let message = - Message::new(method, write, vec![Pointer::int(1), Pointer::int(2)]); - - assert_eq!(0_u16, message.scheduled.method.into()); - assert_eq!(message.scheduled.length, 2); - - unsafe { - assert_eq!( - *message.scheduled.arguments.as_ptr().add(0), - Pointer::int(1) - ); - assert_eq!( - *message.scheduled.arguments.as_ptr().add(1), - Pointer::int(2) - ); - } - } - - #[test] - fn test_mailbox_send_receive() { - let method = MethodIndex::new(0); - let future = FutureState::new(); - let write = Write::Future(future); - let message = - Message::new(method, write, vec![Pointer::int(1), Pointer::int(2)]); - let mut mailbox = Mailbox::new(); - - mailbox.send(message); - - let message = mailbox.receive().unwrap(); - - assert_eq!(message.scheduled.length, 2); - } - - #[test] - fn test_process_status_new() { - let status = ProcessStatus::new(); - - assert_eq!(status.bits, 0); - } - - #[test] - fn test_process_status_set_main() { - let mut status = ProcessStatus::new(); - - status.set_main(); - - assert!(status.is_main()); - } - - #[test] - fn test_process_status_set_waiting_for_message() { - let mut status = ProcessStatus::new(); - - status.set_waiting_for_message(true); - assert!(status.is_waiting_for_message()); - - status.set_waiting_for_message(false); - assert!(!status.is_waiting_for_message()); - } - - #[test] - fn test_process_status_set_waiting_for_future() { - let mut status = ProcessStatus::new(); - - status.set_waiting_for_future(true); - assert!(status.is_waiting_for_future()); - - status.set_waiting_for_future(false); - assert!(!status.is_waiting_for_future()); - } - - #[test] - fn test_process_status_is_waiting() { - let mut status = ProcessStatus::new(); - - status.set_sleeping(true); - assert!(status.is_waiting()); - - status.set_sleeping(false); - status.set_waiting_for_future(true); - assert!(status.is_waiting()); - - status.no_longer_waiting(); - - assert!(!status.is_waiting_for_future()); - assert!(!status.is_waiting()); - } - - #[test] - fn test_process_status_timeout_expired() { - let mut status = ProcessStatus::new(); - - status.set_timeout_expired(true); - assert!(status.timeout_expired()); - - status.set_timeout_expired(false); - assert!(!status.timeout_expired()); - } - - #[test] - fn test_reschedule_rights_are_acquired() { - assert!(!RescheduleRights::Failed.are_acquired()); - assert!(RescheduleRights::Acquired.are_acquired()); - assert!(RescheduleRights::AcquiredWithTimeout.are_acquired()); - } - - #[test] - fn test_process_state_has_same_timeout() { - let mut state = ProcessState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - assert!(!state.has_same_timeout(&timeout)); - - state.timeout = Some(timeout.clone()); - - assert!(state.has_same_timeout(&timeout)); - } - - #[test] - fn test_process_state_try_reschedule_after_timeout() { - let mut state = ProcessState::new(); - - assert_eq!( - state.try_reschedule_after_timeout(), - RescheduleRights::Failed - ); - - state.waiting_for_future(None); - - assert_eq!( - state.try_reschedule_after_timeout(), - RescheduleRights::Acquired - ); - - assert!(!state.status.is_waiting_for_future()); - assert!(!state.status.is_waiting()); - - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - state.waiting_for_future(Some(timeout)); - - assert_eq!( - state.try_reschedule_after_timeout(), - RescheduleRights::AcquiredWithTimeout - ); - - assert!(!state.status.is_waiting_for_future()); - assert!(!state.status.is_waiting()); - } - - #[test] - fn test_process_state_waiting_for_future() { - let mut state = ProcessState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - state.waiting_for_future(None); - - assert!(state.status.is_waiting_for_future()); - assert!(state.timeout.is_none()); - - state.waiting_for_future(Some(timeout)); - - assert!(state.status.is_waiting_for_future()); - assert!(state.timeout.is_some()); - } - - #[test] - fn test_process_state_try_reschedule_for_message() { - let mut state = ProcessState::new(); - - assert_eq!( - state.try_reschedule_for_message(), - RescheduleRights::Failed - ); - - state.status.set_waiting_for_message(true); - - assert_eq!( - state.try_reschedule_for_message(), - RescheduleRights::Acquired - ); - assert!(!state.status.is_waiting_for_message()); - } - - #[test] - fn test_process_state_try_reschedule_for_future() { - let mut state = ProcessState::new(); - - assert_eq!(state.try_reschedule_for_future(), RescheduleRights::Failed); - - state.status.set_waiting_for_future(true); - assert_eq!( - state.try_reschedule_for_future(), - RescheduleRights::Acquired - ); - assert!(!state.status.is_waiting_for_future()); - - state.status.set_waiting_for_future(true); - state.timeout = Some(Timeout::with_rc(Duration::from_secs(0))); - - assert_eq!( - state.try_reschedule_for_future(), - RescheduleRights::AcquiredWithTimeout - ); - assert!(!state.status.is_waiting_for_future()); - } - - #[test] - fn test_process_new() { - let class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*class)); - - assert_eq!(process.header.class.as_ptr(), class.as_ptr()); - assert!(process.task.is_none()); - } - - #[test] - fn test_process_main() { - let proc_class = empty_process_class("A"); - let method = empty_method(); - let process = OwnedProcess::new(Process::main(*proc_class, method)); - - assert!(process.is_main()); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_process_set_main() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(!process.is_main()); - - process.set_main(); - assert!(process.is_main()); - } - - #[test] - fn test_process_suspend() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - process.suspend(timeout); - - assert!(process.state().timeout.is_some()); - assert!(process.state().status.is_waiting()); - } - - #[test] - fn test_process_send_message() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let future = FutureState::new(); - let index = MethodIndex::new(0); - - unsafe { proc_class.set_method(index, method) }; - - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, vec![Pointer::int(42)]); - - let mut state = process.state(); - - assert_eq!(state.mailbox.messages.len(), 1); - assert_eq!(state.mailbox.receive().unwrap().scheduled.length, 1); - } - - #[test] - fn test_process_task_to_run_without_a_task() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(process.task_to_run().is_none()); - } - - #[test] - fn test_process_task_to_run_waiting_server() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(process.task_to_run().is_none()); - assert!(process.state().status.is_waiting_for_message()); - assert!(!process.state().status.is_waiting()); - } - - #[test] - fn test_process_task_to_run_with_message() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let future = FutureState::new(); - let index = MethodIndex::new(0); - - unsafe { proc_class.set_method(index, method) }; - - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, vec![Pointer::int(42)]); - - let mut task = process.task_to_run().unwrap(); - - assert!(process.task.is_some()); - assert!(task.stack.pop() == Some(Pointer::int(42))); - } - - #[test] - fn test_process_task_to_run_with_existing_task() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let ctx = ExecutionContext::new(method); - let task = Task::new(ctx, Write::Discard); - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.task = Some(task); - - assert_eq!( - process.task_to_run(), - process.task.as_ref().map(TaskPointer::new) - ); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_finish_task_with_existing_task() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let ctx = ExecutionContext::new(method); - let task = Task::new(ctx, Write::Discard); - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.task = Some(task); - process.finish_task(); - - assert!(process.task.is_none()); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_process_finish_task_without_pending_work() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - process.set_main(); - - assert!(!process.finish_task()); - } - - #[test] - fn test_process_finish_task_with_clients() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(!process.finish_task()); - assert!(process.state().status.is_waiting_for_message()); - } - - #[test] - fn test_process_finish_task_with_messages() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let future = FutureState::new(); - let index = MethodIndex::new(0); - - unsafe { proc_class.set_method(index, method) }; - - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, Vec::new()); - - assert!(process.finish_task()); - } - - #[test] - fn test_process_get_set_field() { - let class = OwnedClass::new(Class::process("A".to_string(), 1, 0)); - let mut process = OwnedProcess::new(Process::alloc(*class)); - let idx = FieldIndex::new(0); - - unsafe { - process.set_field(idx, Pointer::int(4)); - - assert!(process.get_field(idx) == Pointer::int(4)); - } - } - - #[test] - fn test_process_timeout_expired() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - assert!(!process.timeout_expired()); - - process.suspend(timeout); - - assert!(!process.timeout_expired()); - assert!(!process.state().status.timeout_expired()); - } - - #[test] - fn test_process_pointer_identifier() { - let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; - - assert_eq!(ptr.identifier(), 0x4); - } - - #[test] - fn test_process_pointer_as_pointer() { - let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; - - assert_eq!(ptr.as_pointer(), Pointer::new(0x4 as *mut _)); - } - - #[test] - fn test_future_new() { - let fut_class = empty_class("Future"); - let state = FutureState::new(); - let future = Future::alloc(*fut_class, state); - - unsafe { - assert_eq!( - future.get::
().class.as_ptr(), - fut_class.as_ptr() - ); - } - - unsafe { - Future::drop(future); - future.free(); - } - } - - #[test] - fn test_future_write_without_consumer() { - let state = FutureState::new(); - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Continue); - assert!(!state.lock().unwrap().thrown); - } - - #[test] - fn test_future_write_thrown() { - let state = FutureState::new(); - let result = state.write(Pointer::int(42), true); - - assert_eq!(result, WriteResult::Continue); - assert!(state.lock().unwrap().thrown); - } - - #[test] - fn test_future_write_disconnected() { - let state = FutureState::new(); - - state.lock().unwrap().disconnected = true; - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Discard); - } - - #[test] - fn test_future_write_with_consumer() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - - state.lock().unwrap().consumer = Some(*process); - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Continue); - } - - #[test] - fn test_future_write_with_waiting_consumer() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - - process.state().waiting_for_future(None); - state.lock().unwrap().consumer = Some(*process); - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Reschedule(*process)); - } - - #[test] - fn test_future_write_with_waiting_consumer_with_timeout() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - process.state().waiting_for_future(Some(timeout)); - state.lock().unwrap().consumer = Some(*process); - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::RescheduleWithTimeout(*process)); - assert!(!process.state().status.is_waiting_for_future()); - assert!(process.state().timeout.is_none()); - } - - #[test] - fn test_future_get_without_result() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - - assert_eq!(state.get(*process, None), FutureResult::None); - assert_eq!(state.lock().unwrap().consumer, Some(*process)); - assert!(process.state().status.is_waiting_for_future()); - } - - #[test] - fn test_future_get_without_result_with_timeout() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - assert_eq!(state.get(*process, Some(timeout)), FutureResult::None); - assert_eq!(state.lock().unwrap().consumer, Some(*process)); - assert!(process.state().status.is_waiting_for_future()); - assert!(process.state().timeout.is_some()); - } - - #[test] - fn test_future_get_with_result() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let value = Pointer::int(42); - - process.state().waiting_for_future(None); - state.lock().unwrap().result = value; - - assert_eq!(state.get(*process, None), FutureResult::Returned(value)); - assert!(state.lock().unwrap().consumer.is_none()); - assert!(!process.state().status.is_waiting_for_future()); - } - - #[test] - fn test_future_get_with_thrown_result() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let value = Pointer::int(42); - - process.state().waiting_for_future(None); - state.lock().unwrap().result = value; - state.lock().unwrap().thrown = true; - - assert_eq!(state.get(*process, None), FutureResult::Thrown(value)); - assert!(state.lock().unwrap().consumer.is_none()); - assert!(!process.state().status.is_waiting_for_future()); - } - - #[test] - fn test_future_disconnect() { - let fut_class = empty_class("Future"); - let state = FutureState::new(); - let fut = Future::alloc(*fut_class, state.clone()); - let result = unsafe { fut.get::() }.disconnect(); - - assert!(state.lock().unwrap().disconnected); - assert!(result == Pointer::undefined_singleton()); - - unsafe { - Future::drop(fut); - fut.free(); - } - } - - #[test] - fn test_process_stacktrace() { - let proc_class = empty_process_class("B"); - let mut proc = new_process(*proc_class); - let method1 = empty_method(); - let method2 = empty_method(); - let m1 = unsafe { &mut *method1.as_ptr() }; - let m2 = unsafe { &mut *method2.as_ptr() }; - - m1.locations.add_entry(0, 4, Pointer::int(2), Pointer::int(1)); - m2.locations.add_entry(0, 12, Pointer::int(4), Pointer::int(3)); - - let ctx1 = ExecutionContext::new(method1); - let ctx2 = ExecutionContext::new(method2); - let mut task = Task::new(ctx1, Write::Discard); - - task.push_context(ctx2); - - proc.task = Some(task); - - let trace = proc.stacktrace(); - - assert_eq!(trace.len(), 2); - assert_eq!( - trace.get(0), - Some(&Location { - name: Pointer::int(1), - file: Pointer::int(2), - line: Pointer::int(4) - }) - ); - assert_eq!( - trace.get(1), - Some(&Location { - name: Pointer::int(3), - file: Pointer::int(4), - line: Pointer::int(12) - }) - ); - - Method::drop_and_deallocate(method1); - Method::drop_and_deallocate(method2); - } -} diff --git a/vm/src/registers.rs b/vm/src/registers.rs deleted file mode 100644 index 4cd41e816..000000000 --- a/vm/src/registers.rs +++ /dev/null @@ -1,43 +0,0 @@ -///! Virtual machine registers -use crate::chunk::Chunk; -use crate::mem::Pointer; - -/// A collection of virtual machine registers. -pub(crate) struct Registers { - pub values: Chunk, -} - -impl Registers { - /// Creates a new Registers. - pub(crate) fn new(amount: u16) -> Registers { - Registers { values: Chunk::new(amount as usize) } - } - - /// Sets the value of the given register. - pub(crate) fn set(&mut self, register: u16, value: Pointer) { - unsafe { self.values.set(register as usize, value) }; - } - - /// Returns the value of a register. - pub(crate) fn get(&self, register: u16) -> Pointer { - unsafe { *self.values.get(register as usize) } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::Pointer; - - #[test] - fn test_set_get() { - let mut register = Registers::new(6); - let pointer = Pointer::new(0x4 as *mut u8); - - register.set(0, pointer); - assert!(register.get(0) == pointer); - - register.set(5, pointer); - assert!(register.get(5) == pointer); - } -} diff --git a/vm/src/runtime_error.rs b/vm/src/runtime_error.rs deleted file mode 100644 index 992c1b391..000000000 --- a/vm/src/runtime_error.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Errors that can be produced at VM runtime. -use crate::mem::Pointer; -use std::convert::From; -use std::io; -use std::net::AddrParseError; - -const INVALID_INPUT: i64 = 11; -const TIMED_OUT: i64 = 13; - -/// An error that can be raised in the VM at runtime. -#[derive(Debug)] -pub(crate) enum RuntimeError { - /// An error to throw as-is. - Error(Pointer), - - /// A fatal error that should result in the VM terminating. - Panic(String), - - /// A non-blocking operation would block, and should be retried at a later - /// point in time. - WouldBlock, -} - -impl RuntimeError { - pub(crate) fn timed_out() -> Self { - RuntimeError::Error(Pointer::int(TIMED_OUT)) - } - - pub(crate) fn should_poll(&self) -> bool { - matches!(self, RuntimeError::WouldBlock) - } -} - -impl From for RuntimeError { - fn from(error: io::Error) -> Self { - if error.kind() == io::ErrorKind::WouldBlock { - RuntimeError::WouldBlock - } else { - let code = match error.kind() { - io::ErrorKind::NotFound => 1, - io::ErrorKind::PermissionDenied => 2, - io::ErrorKind::ConnectionRefused => 3, - io::ErrorKind::ConnectionReset => 4, - io::ErrorKind::ConnectionAborted => 5, - io::ErrorKind::NotConnected => 6, - io::ErrorKind::AddrInUse => 7, - io::ErrorKind::AddrNotAvailable => 8, - io::ErrorKind::BrokenPipe => 9, - io::ErrorKind::AlreadyExists => 10, - io::ErrorKind::InvalidInput => INVALID_INPUT, - io::ErrorKind::InvalidData => 12, - io::ErrorKind::TimedOut => TIMED_OUT, - io::ErrorKind::WriteZero => 14, - io::ErrorKind::Interrupted => 15, - io::ErrorKind::UnexpectedEof => 16, - _ => 0, - }; - - RuntimeError::Error(Pointer::int(code)) - } - } -} - -impl From for RuntimeError { - fn from(result: String) -> Self { - RuntimeError::Panic(result) - } -} - -impl From<&str> for RuntimeError { - fn from(result: &str) -> Self { - RuntimeError::Panic(result.to_string()) - } -} - -impl From for RuntimeError { - fn from(_: AddrParseError) -> Self { - RuntimeError::Error(Pointer::int(INVALID_INPUT)) - } -} diff --git a/vm/src/scheduler/mod.rs b/vm/src/scheduler/mod.rs deleted file mode 100644 index b89111e66..000000000 --- a/vm/src/scheduler/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod process; -pub mod timeout_worker; -pub mod timeouts; diff --git a/vm/src/state.rs b/vm/src/state.rs deleted file mode 100644 index 34757903d..000000000 --- a/vm/src/state.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::arc_without_weak::ArcWithoutWeak; -use crate::builtin_functions::BuiltinFunctions; -use crate::config::Config; -use crate::mem::Pointer; -use crate::network_poller::NetworkPoller; -use crate::permanent_space::PermanentSpace; -use crate::scheduler::process::Scheduler; -use crate::scheduler::timeout_worker::TimeoutWorker; -use ahash::RandomState; -use rand::{thread_rng, Rng}; -use std::collections::HashMap; -use std::env; -use std::panic::RefUnwindSafe; -use std::sync::Mutex; -use std::time; - -/// A reference counted State. -pub(crate) type RcState = ArcWithoutWeak; - -/// The state of a virtual machine. -pub(crate) struct State { - /// The virtual machine's configuration. - pub(crate) config: Config, - - /// The start time of the VM (more or less). - pub(crate) start_time: time::Instant, - - /// The commandline arguments passed to an Inko program. - pub(crate) arguments: Vec, - - /// The environment variables defined when the VM started. - /// - /// We cache environment variables because C functions used through the FFI - /// (or through libraries) may call `setenv()` concurrently with `getenv()` - /// calls, which is unsound. Caching the variables also means we can safely - /// use `localtime_r()` (which internally may call `setenv()`). - pub(crate) environment: HashMap, - - /// The exit status to use when the VM terminates. - pub(crate) exit_status: Mutex, - - /// The scheduler to use for executing Inko processes. - pub(crate) scheduler: Scheduler, - - /// A task used for handling timeouts, such as message and IO timeouts. - pub(crate) timeout_worker: TimeoutWorker, - - /// The network pollers to use for process threads. - pub(crate) network_pollers: Vec, - - /// All builtin functions that a compiler can use. - pub(crate) builtin_functions: BuiltinFunctions, - - /// A type for allocating and storing blocks and permanent objects. - pub(crate) permanent_space: PermanentSpace, - - /// The random state to use for building hashers. - /// - /// We use the same base state for all hashers, seeded with randomly - /// generated keys. This means all hashers start off with the same base - /// state, and thus produce the same hash codes. - /// - /// The alternative is to generate unique seeds for every hasher, in a way - /// that Rust does (= starting off with a thread-local randomly generated - /// number, then incrementing it). This however requires that we somehow - /// expose the means to generate such keys to the standard library, such - /// that it can reuse these where necessary (e.g. a `Map` needs to produce - /// the same results for the same values every time). This leads to - /// implementation details leaking into the standard library, and we want to - /// avoid that. - pub(crate) hash_state: RandomState, -} - -impl RefUnwindSafe for State {} - -impl State { - pub(crate) fn new( - config: Config, - permanent_space: PermanentSpace, - args: &[String], - ) -> RcState { - let arguments = args - .iter() - .map(|arg| permanent_space.allocate_string(arg.clone())) - .collect(); - - let mut rng = thread_rng(); - let hash_state = - RandomState::with_seeds(rng.gen(), rng.gen(), rng.gen(), rng.gen()); - - let environment = env::vars_os() - .into_iter() - .map(|(k, v)| { - ( - k.to_string_lossy().into_owned(), - permanent_space - .allocate_string(v.to_string_lossy().into_owned()), - ) - }) - .collect::>(); - - let scheduler = Scheduler::new( - config.process_threads as usize, - config.backup_threads as usize, - ); - - let network_pollers = - (0..config.netpoll_threads).map(|_| NetworkPoller::new()).collect(); - - let state = State { - scheduler, - environment, - config, - start_time: time::Instant::now(), - exit_status: Mutex::new(0), - timeout_worker: TimeoutWorker::new(), - arguments, - network_pollers, - builtin_functions: BuiltinFunctions::new(), - permanent_space, - hash_state, - }; - - ArcWithoutWeak::new(state) - } - - pub(crate) fn terminate(&self) { - self.scheduler.terminate(); - } - - pub(crate) fn set_exit_status(&self, new_status: i32) { - *self.exit_status.lock().unwrap() = new_status; - } - - pub(crate) fn current_exit_status(&self) -> i32 { - *self.exit_status.lock().unwrap() - } -}