From 54ca824d9afc2a2e21128ff0820485e8e5a891f3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 29 Mar 2023 00:12:44 +0200 Subject: [PATCH] Replace the interpreter with an LLVM compiler --- Cargo.lock | 420 +- Cargo.toml | 2 +- ast/src/nodes.rs | 44 +- ast/src/parser.rs | 152 +- bytecode/Cargo.toml | 9 - bytecode/src/lib.rs | 1312 --- compiler/Cargo.toml | 3 +- compiler/src/codegen.rs | 32 +- compiler/src/compiler.rs | 32 +- compiler/src/diagnostics.rs | 42 +- compiler/src/hir.rs | 247 +- compiler/src/lib.rs | 2 +- compiler/src/llvm.rs | 7458 +++++++++++++++++ compiler/src/mir/mod.rs | 525 +- compiler/src/mir/passes.rs | 769 +- compiler/src/mir/pattern_matching.rs | 43 +- compiler/src/mir/printer.rs | 2 +- compiler/src/type_check/define_types.rs | 159 +- compiler/src/type_check/expressions.rs | 1493 ++-- compiler/src/type_check/methods.rs | 450 +- compiler/src/type_check/mod.rs | 210 +- docs/source/guides/operators.md | 2 +- docs/source/internals/vm.md | 17 +- inko/Cargo.toml | 1 - inko/src/command/run.rs | 32 +- inko/src/command/test.rs | 16 +- libstd/src/std/array.inko | 103 +- libstd/src/std/bool.inko | 12 +- libstd/src/std/byte_array.inko | 22 +- libstd/src/std/channel.inko | 150 + libstd/src/std/clone.inko | 8 +- libstd/src/std/cmp.inko | 26 +- libstd/src/std/crypto/chacha.inko | 8 +- libstd/src/std/crypto/hash.inko | 12 +- libstd/src/std/crypto/md5.inko | 4 +- libstd/src/std/crypto/poly1305.inko | 4 +- libstd/src/std/crypto/sha1.inko | 4 +- libstd/src/std/crypto/sha2.inko | 8 +- libstd/src/std/debug.inko | 31 +- libstd/src/std/env.inko | 10 +- libstd/src/std/ffi.inko | 543 -- libstd/src/std/float.inko | 50 +- libstd/src/std/fmt.inko | 4 +- libstd/src/std/fs/dir.inko | 2 +- libstd/src/std/fs/file.inko | 36 +- libstd/src/std/fs/path.inko | 12 +- libstd/src/std/init.inko | 1 + libstd/src/std/int.inko | 96 +- libstd/src/std/io.inko | 6 +- libstd/src/std/iter.inko | 12 +- libstd/src/std/json.inko | 14 +- libstd/src/std/map.inko | 40 +- libstd/src/std/net/ip.inko | 48 +- libstd/src/std/net/socket.inko | 168 +- libstd/src/std/nil.inko | 8 +- libstd/src/std/ops.inko | 62 +- libstd/src/std/option.inko | 34 +- libstd/src/std/process.inko | 71 - libstd/src/std/rand.inko | 16 +- libstd/src/std/range.inko | 18 +- libstd/src/std/set.inko | 10 +- libstd/src/std/stdio.inko | 12 +- libstd/src/std/string.inko | 26 +- libstd/src/std/sys.inko | 20 +- libstd/src/std/test.inko | 76 +- libstd/src/std/time.inko | 88 +- libstd/src/std/tuple.inko | 106 +- libstd/test/helpers.inko | 12 +- libstd/test/main.inko | 2 - libstd/test/std/net/test_socket.inko | 62 +- libstd/test/std/test_array.inko | 8 +- libstd/test/std/test_cmp.inko | 8 +- libstd/test/std/test_ffi.inko | 285 - libstd/test/std/test_io.inko | 8 +- libstd/test/std/test_process.inko | 42 - libstd/test/std/test_test.inko | 4 +- types/Cargo.toml | 3 - types/src/check.rs | 1878 +++++ types/src/either.rs | 5 + types/src/format.rs | 922 ++ types/src/lib.rs | 7077 ++++------------ types/src/module_name.rs | 2 +- types/src/resolve.rs | 791 ++ types/src/test.rs | 155 + vm/Cargo.toml | 9 +- vm/src/arc_without_weak.rs | 1 + vm/src/builtin_functions.rs | 216 - vm/src/builtin_functions/array.rs | 30 - vm/src/builtin_functions/byte_array.rs | 101 - vm/src/builtin_functions/env.rs | 142 - vm/src/builtin_functions/ffi.rs | 172 - vm/src/builtin_functions/float.rs | 27 - vm/src/builtin_functions/fs.rs | 379 - vm/src/builtin_functions/hasher.rs | 57 - vm/src/builtin_functions/process.rs | 79 - vm/src/builtin_functions/random.rs | 109 - vm/src/builtin_functions/socket.rs | 670 -- vm/src/builtin_functions/stdio.rs | 90 - vm/src/builtin_functions/string.rs | 191 - vm/src/builtin_functions/sys.rs | 227 - vm/src/chunk.rs | 101 - vm/src/config.rs | 36 +- vm/src/context.rs | 98 + vm/src/context/unix.rs | 5 + vm/src/context/unix/aarch64.rs | 66 + vm/src/context/unix/x86_64.rs | 49 + vm/src/context/windows.rs | 5 + vm/src/context/windows/x86_64.rs | 115 + vm/src/execution_context.rs | 143 - vm/src/ffi.rs | 731 -- vm/src/hasher.rs | 3 +- vm/src/image.rs | 1076 --- vm/src/indexes.rs | 70 - vm/src/instructions/array.rs | 89 - vm/src/instructions/builtin_functions.rs | 23 - vm/src/instructions/byte_array.rs | 113 - vm/src/instructions/float.rs | 246 - vm/src/instructions/future.rs | 124 - vm/src/instructions/general.rs | 178 - vm/src/instructions/integer.rs | 326 - vm/src/instructions/mod.rs | 9 - vm/src/instructions/process.rs | 161 - vm/src/instructions/string.rs | 69 - vm/src/lib.rs | 19 +- vm/src/location_table.rs | 107 - vm/src/machine.rs | 14 +- vm/src/macros/mod.rs | 22 + vm/src/mem.rs | 1091 +-- vm/src/memory_map.rs | 151 + vm/src/numeric.rs | 26 - vm/src/page.rs | 42 + vm/src/permanent_space.rs | 350 - vm/src/process.rs | 1497 ++-- vm/src/registers.rs | 43 - vm/src/result.rs | 72 + vm/src/runtime.rs | 108 + vm/src/runtime/array.rs | 108 + vm/src/runtime/byte_array.rs | 189 + vm/src/runtime/class.rs | 31 + vm/src/runtime/env.rs | 126 + vm/src/runtime/float.rs | 117 + vm/src/runtime/fs.rs | 353 + vm/src/runtime/general.rs | 82 + vm/src/runtime/hasher.rs | 38 + vm/src/runtime/helpers.rs | 16 + vm/src/runtime/int.rs | 75 + vm/src/runtime/process.rs | 364 + vm/src/runtime/random.rs | 86 + vm/src/runtime/socket.rs | 480 ++ vm/src/runtime/stdio.rs | 106 + vm/src/runtime/string.rs | 278 + vm/src/runtime/sys.rs | 229 + vm/src/{builtin_functions => runtime}/time.rs | 43 +- vm/src/runtime_error.rs | 80 - vm/src/scheduler/mod.rs | 27 + vm/src/scheduler/process.rs | 385 +- vm/src/scheduler/timeout_worker.rs | 27 +- vm/src/scheduler/timeouts.rs | 11 +- vm/src/socket.rs | 215 +- vm/src/socket/socket_address.rs | 34 +- vm/src/stack.rs | 220 + vm/src/state.rs | 166 +- vm/src/test.rs | 80 +- 163 files changed, 21122 insertions(+), 19441 deletions(-) delete mode 100644 bytecode/Cargo.toml delete mode 100644 bytecode/src/lib.rs create mode 100644 compiler/src/llvm.rs create mode 100644 libstd/src/std/channel.inko delete mode 100644 libstd/src/std/ffi.inko delete mode 100644 libstd/test/std/test_ffi.inko create mode 100644 types/src/check.rs create mode 100644 types/src/either.rs create mode 100644 types/src/format.rs create mode 100644 types/src/resolve.rs create mode 100644 types/src/test.rs delete mode 100644 vm/src/builtin_functions.rs delete mode 100644 vm/src/builtin_functions/array.rs delete mode 100644 vm/src/builtin_functions/byte_array.rs delete mode 100644 vm/src/builtin_functions/env.rs delete mode 100644 vm/src/builtin_functions/ffi.rs delete mode 100644 vm/src/builtin_functions/float.rs delete mode 100644 vm/src/builtin_functions/fs.rs delete mode 100644 vm/src/builtin_functions/hasher.rs delete mode 100644 vm/src/builtin_functions/process.rs delete mode 100644 vm/src/builtin_functions/random.rs delete mode 100644 vm/src/builtin_functions/socket.rs delete mode 100644 vm/src/builtin_functions/stdio.rs delete mode 100644 vm/src/builtin_functions/string.rs delete mode 100644 vm/src/builtin_functions/sys.rs delete mode 100644 vm/src/chunk.rs create mode 100644 vm/src/context.rs create mode 100644 vm/src/context/unix.rs create mode 100644 vm/src/context/unix/aarch64.rs create mode 100644 vm/src/context/unix/x86_64.rs create mode 100644 vm/src/context/windows.rs create mode 100644 vm/src/context/windows/x86_64.rs delete mode 100644 vm/src/execution_context.rs delete mode 100644 vm/src/ffi.rs delete mode 100644 vm/src/image.rs delete mode 100644 vm/src/indexes.rs delete mode 100644 vm/src/instructions/array.rs delete mode 100644 vm/src/instructions/builtin_functions.rs delete mode 100644 vm/src/instructions/byte_array.rs delete mode 100644 vm/src/instructions/float.rs delete mode 100644 vm/src/instructions/future.rs delete mode 100644 vm/src/instructions/general.rs delete mode 100644 vm/src/instructions/integer.rs delete mode 100644 vm/src/instructions/mod.rs delete mode 100644 vm/src/instructions/process.rs delete mode 100644 vm/src/instructions/string.rs delete mode 100644 vm/src/location_table.rs create mode 100644 vm/src/memory_map.rs delete mode 100644 vm/src/numeric.rs create mode 100644 vm/src/page.rs delete mode 100644 vm/src/permanent_space.rs delete mode 100644 vm/src/registers.rs create mode 100644 vm/src/result.rs create mode 100644 vm/src/runtime.rs create mode 100644 vm/src/runtime/array.rs create mode 100644 vm/src/runtime/byte_array.rs create mode 100644 vm/src/runtime/class.rs create mode 100644 vm/src/runtime/env.rs create mode 100644 vm/src/runtime/float.rs create mode 100644 vm/src/runtime/fs.rs create mode 100644 vm/src/runtime/general.rs create mode 100644 vm/src/runtime/hasher.rs create mode 100644 vm/src/runtime/helpers.rs create mode 100644 vm/src/runtime/int.rs create mode 100644 vm/src/runtime/process.rs create mode 100644 vm/src/runtime/random.rs create mode 100644 vm/src/runtime/socket.rs create mode 100644 vm/src/runtime/stdio.rs create mode 100644 vm/src/runtime/string.rs create mode 100644 vm/src/runtime/sys.rs rename vm/src/{builtin_functions => runtime}/time.rs (81%) delete mode 100644 vm/src/runtime_error.rs create mode 100644 vm/src/stack.rs diff --git a/Cargo.lock b/Cargo.lock index ce95abcb8..19cd2c753 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,11 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "abort_on_panic" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116" - [[package]] name = "ahash" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "getrandom", @@ -20,6 +14,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "ast" version = "0.10.0" @@ -29,20 +32,32 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[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 +73,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 +90,40 @@ name = "compiler" version = "0.10.0" dependencies = [ "ast", - "bytecode", + "fnv", "getopts", + "inkwell", "similar-asserts", "types", "unicode-segmentation", ] +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +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 +131,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,26 +150,38 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fs_extra" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "generic-array" @@ -173,9 +204,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -192,6 +223,31 @@ dependencies = [ "vm", ] +[[package]] +name = "inkwell" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbac11e485159a525867fb7e6aa61981453e6a72f625fde6a4ab3047b0c6dec9" +dependencies = [ + "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 = "ipm" version = "0.10.0" @@ -229,38 +285,31 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[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]] @@ -280,28 +329,78 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "once_cell" -version = "1.13.1" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "polling" -version = "2.2.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" dependencies = [ + "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "winapi", + "pin-project-lite", + "windows-sys 0.45.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.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] [[package]] name = "rand" @@ -326,24 +425,62 @@ 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.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +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.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[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,11 +496,17 @@ 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.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -376,39 +519,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" @@ -421,17 +568,14 @@ name = "vm" version = "0.10.0" dependencies = [ "ahash", - "bytecode", "crossbeam-queue", "crossbeam-utils", "libc", - "libffi", - "libloading", "polling", "rand", "socket2", "unicode-segmentation", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -440,15 +584,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -477,39 +612,120 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "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", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "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", + "windows_x86_64_msvc 0.42.2", +] + +[[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_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[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_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/Cargo.toml b/Cargo.toml index 47ed6582f..d42f8387f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ast", "bytecode", "inko", "compiler", "vm", "ipm"] +members = ["ast", "inko", "compiler", "vm", "ipm"] [profile.release] panic = "abort" diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index ce2cc1657..1e21c7626 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, @@ -547,6 +535,7 @@ pub struct ReopenClass { pub class_name: Constant, pub body: ImplementationExpressions, pub location: SourceLocation, + pub bounds: Option, } impl Node for ReopenClass { @@ -555,10 +544,37 @@ impl Node for ReopenClass { } } +#[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, } @@ -651,7 +667,6 @@ pub enum Expression { True(Box), False(Box), Nil(Box), - Async(Box), ClassLiteral(Box), Scope(Box), Array(Box), @@ -684,7 +699,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(), diff --git a/ast/src/parser.rs b/ast/src/parser.rs index 8a2dcb59a..ac24ed482 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -1119,15 +1119,21 @@ impl Parser { Ok(TypeBound { name, requirements, location }) } - fn type_bound_requirements(&mut self) -> Result { + fn type_bound_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 +1152,7 @@ impl Parser { values.last().unwrap().location(), ); - Ok(TypeNames { values, location }) + Ok(Requirements { values, location }) } fn reopen_class( @@ -1155,14 +1161,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, }))) } @@ -1551,7 +1559,6 @@ impl Parser { // 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)?, @@ -2376,17 +2383,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 +2486,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 @@ -5006,7 +5001,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 +5019,7 @@ mod tests { }, body: ImplementationExpressions { values: Vec::new(), - location: cols(32, 33) + location: cols(38, 39) }, bounds: Some(TypeBounds { values: vec![ @@ -5034,9 +5029,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 +5039,8 @@ mod tests { }, arguments: None, location: cols(20, 20) - }, - TypeName { + }), + Requirement::Trait(TypeName { name: Constant { source: None, name: "B".to_string(), @@ -5053,7 +5048,7 @@ mod tests { }, arguments: None, location: cols(24, 24) - }, + }), ], location: cols(20, 24) }, @@ -5065,24 +5060,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) })) ); @@ -5144,6 +5142,7 @@ mod tests { values: Vec::new(), location: cols(8, 9) }, + bounds: None, location: cols(1, 9) })) ); @@ -5177,6 +5176,7 @@ mod tests { }], location: cols(8, 20) }, + bounds: None, location: cols(1, 20) })) ); @@ -5210,9 +5210,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] @@ -5246,6 +5278,7 @@ mod tests { }], location: cols(8, 27) }, + bounds: None, location: cols(1, 27) })) ); @@ -7151,47 +7184,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!( 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..39708dc26 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -12,8 +12,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/src/codegen.rs b/compiler/src/codegen.rs index c83b9bae5..359e68fbe 100644 --- a/compiler/src/codegen.rs +++ b/compiler/src/codegen.rs @@ -143,7 +143,7 @@ struct MethodInfo { } /// A compiler pass that lowers MIR into a bytecode file. -pub(crate) struct Lower<'a> { +pub(crate) struct LowerToBytecode<'a> { db: &'a Database, mir: &'a Mir, class_info: &'a HashMap, @@ -151,7 +151,7 @@ pub(crate) struct Lower<'a> { constant_indexes: HashMap, } -impl<'a> Lower<'a> { +impl<'a> LowerToBytecode<'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(); @@ -279,7 +279,7 @@ impl<'a> Lower<'a> { buffer.write_u16(main_method_idx); for index in 0..mir.modules.len() { - let mut chunk = Lower { + let mut chunk = LowerToBytecode { db, mir: &mir, constant_indexes: HashMap::new(), @@ -383,12 +383,13 @@ impl<'a> Lower<'a> { 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]) - // ); - // } + // TODO: remove + if method.id.name(self.db) == "main" { + 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 @@ -837,12 +838,21 @@ impl<'a> Lower<'a> { 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)); + buffer.push(Instruction::three(op, rec, idx, 1)); + } + mir::Instruction::SendForget(ins) => { + let op = Opcode::ProcessSend; + 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, rec, idx, 0)); } mir::Instruction::SendAsync(ins) => { let op = Opcode::ProcessSendAsync; diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index 2a40cd104..5e37766f6 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -1,6 +1,6 @@ -use crate::codegen; use crate::config::{Config, IMAGE_EXT, SOURCE, SOURCE_EXT}; use crate::hir; +use crate::llvm; use crate::mir::{passes as mir, Mir}; use crate::modules_parser::{ModulesParser, ParsedModule}; use crate::state::State; @@ -13,8 +13,8 @@ use crate::type_check::define_types::{ 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; @@ -63,20 +63,24 @@ impl Compiler { file: Option, ) -> Result { let input = self.main_module_path(file)?; - let code = self.compile_bytecode(input.clone())?; + let code = self.compile_to_machine_code(input.clone())?; let path = self.write_code(input, code); Ok(path) } + // TODO: remove + #[deprecated = "write to a temporary file instead"] 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 code = self.compile_to_machine_code(input)?; - Ok(code.bytes) + // TODO: replace + Ok(Vec::new()) + // Ok(code.bytes) } pub fn print_diagnostics(&self) { @@ -114,17 +118,21 @@ impl Compiler { Ok(path) } - fn compile_bytecode( + fn compile_to_machine_code( &mut self, file: PathBuf, - ) -> Result { + ) -> Result, CompileError> { 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) + + // TODO: decide what the return type should be + llvm::Lower::run_all(&self.state.db, &mir); + + Vec::new() }) } @@ -179,7 +187,6 @@ impl Compiler { && 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) } @@ -204,13 +211,14 @@ impl Compiler { 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, main_file: PathBuf, - code: codegen::Bytecode, + code: Vec, // TODO: replace ) -> PathBuf { let path = self.state.config.output.clone().unwrap_or_else(|| { let name = main_file @@ -230,7 +238,7 @@ impl Compiler { path }); - std::fs::write(&path, code.bytes).unwrap(); + std::fs::write(&path, code).unwrap(); path } diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index e07bb5079..660f739da 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -24,7 +24,6 @@ pub(crate) enum DiagnosticId { InvalidLoopKeyword, InvalidThrow, MissingField, - InvalidRef, InvalidPattern, InvalidField, MissingThrow, @@ -57,7 +56,6 @@ impl fmt::Display for DiagnosticId { 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", @@ -321,7 +319,7 @@ impl Diagnostics { self.error( DiagnosticId::InvalidType, format!( - "Incorrect type: expected '{}', found '{}'", + "Expected a value of type '{}', found '{}'", expected, given ), file, @@ -655,44 +653,6 @@ impl Diagnostics { ); } - pub(crate) fn unsendable_return_type( - &mut self, - name: &str, - type_name: String, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::InvalidCall, - format!( - "The method '{}' isn't available because its receiver is a \ - unique value, and the return type ('{}') isn't sendable", - name, type_name - ), - file, - location, - ); - } - - pub(crate) fn unsendable_throw_type( - &mut self, - name: &str, - type_name: String, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::InvalidCall, - format!( - "The method '{}' isn't available because its receiver is a \ - unique value, and the throw type ('{}') isn't sendable", - name, type_name - ), - file, - location, - ) - } - pub(crate) fn self_in_closure_in_recover( &mut self, file: PathBuf, diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 92e5efa00..af2eb92d0 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -119,15 +119,6 @@ pub(crate) struct Call { 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, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct BuiltinCall { pub(crate) info: Option, @@ -383,6 +374,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, } @@ -393,10 +385,16 @@ pub(crate) enum ReopenClassExpression { AsyncMethod(Box), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum Requirement { + Trait(TypeName), + Mutable(SourceLocation), +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct TypeBound { pub(crate) name: Constant, - pub(crate) requirements: Vec, + pub(crate) requirements: Vec, pub(crate) location: SourceLocation, } @@ -435,7 +433,6 @@ pub(crate) enum Expression { AssignSetter(Box), AssignVariable(Box), ReplaceVariable(Box), - AsyncCall(Box), Break(Box), BuiltinCall(Box), Call(Box), @@ -478,7 +475,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, @@ -552,6 +548,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)] @@ -1294,11 +1299,23 @@ impl<'a> LowerToHir<'a> { fn type_bound(&self, node: ast::TypeBound) -> TypeBound { TypeBound { name: self.constant(node.name), - requirements: self.type_names(node.requirements), + requirements: self.requirements(node.requirements), location: node.location, } } + fn requirements(&self, node: ast::Requirements) -> Vec { + node.values + .into_iter() + .map(|node| match node { + ast::Requirement::Trait(n) => { + Requirement::Trait(self.type_name(n)) + } + ast::Requirement::Mutable(loc) => Requirement::Mutable(loc), + }) + .collect() + } + fn define_trait(&mut self, node: ast::DefineTrait) -> TopLevelExpression { TopLevelExpression::Trait(Box::new(DefineTrait { public: node.public, @@ -1340,6 +1357,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, })) } @@ -1896,7 +1914,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)) } @@ -2145,46 +2162,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, @@ -2938,7 +2915,7 @@ impl<'a> LowerToHir<'a> { Expression::BuiltinCall(Box::new(BuiltinCall { info: None, name: Identifier { - name: types::CompilerMacro::PanicThrown.name().to_string(), + name: types::BuiltinFunction::PanicThrown.name().to_string(), location: location.clone(), }, arguments: vec![Expression::IdentifierRef(Box::new( @@ -4228,20 +4205,40 @@ mod tests { #[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![Requirement::Mutable(cols(14, 16))], + location: cols(11, 16), + }], + body: Vec::new(), + location: cols(1, 16) + })) + ); } #[test] @@ -4273,6 +4270,7 @@ mod tests { location: cols(10, 18) } ))], + bounds: Vec::new(), location: cols(1, 20) })) ); @@ -4306,6 +4304,7 @@ mod tests { location: cols(10, 25), } ))], + bounds: Vec::new(), location: cols(1, 27) })) ); @@ -4340,6 +4339,7 @@ mod tests { location: cols(10, 24) } ))], + bounds: Vec::new(), location: cols(1, 26) })) ); @@ -4416,7 +4416,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, @@ -4440,20 +4440,23 @@ mod tests { name: "T".to_string(), location: cols(17, 17) }, - requirements: vec![TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "X".to_string(), + requirements: vec![ + Requirement::Trait(TypeName { + source: None, + resolved_type: types::TypeRef::Unknown, + name: Constant { + name: "X".to_string(), + location: cols(20, 20) + }, + arguments: Vec::new(), location: cols(20, 20) - }, - arguments: Vec::new(), - location: cols(20, 20) - }], - location: cols(17, 20) + }), + Requirement::Mutable(cols(24, 26)) + ], + location: cols(17, 26) }], body: Vec::new(), - location: cols(1, 23), + location: cols(1, 29), trait_instance: None, class_instance: None, })) @@ -5087,108 +5090,6 @@ mod tests { ); } - #[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; @@ -5960,7 +5861,7 @@ mod tests { BuiltinCall { info: None, name: Identifier { - name: types::CompilerMacro::PanicThrown + name: types::BuiltinFunction::PanicThrown .name() .to_string(), location: cols(8, 14) diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index fb7e84a64..411233b5a 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,9 +1,9 @@ #![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 llvm; mod mir; mod modules_parser; mod presenters; diff --git a/compiler/src/llvm.rs b/compiler/src/llvm.rs new file mode 100644 index 000000000..e4fafefe0 --- /dev/null +++ b/compiler/src/llvm.rs @@ -0,0 +1,7458 @@ +//! Lowering of Inko MIR into LLVM IR. +use crate::mir::{ + CloneKind, Constant, Instruction, Method, Mir, RegisterId, RegisterKind, +}; +use fnv::{FnvHashMap, FnvHashSet}; +use inkwell::basic_block::BasicBlock; +use inkwell::intrinsics::Intrinsic; +use inkwell::passes::{PassManager, PassManagerBuilder}; +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; +use inkwell::types::{ + ArrayType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, + PointerType, StructType, +}; +use inkwell::values::{ + BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, + FunctionValue, GlobalValue, InstructionOpcode, IntValue, PointerValue, + StructValue, +}; +use inkwell::OptimizationLevel; +use inkwell::{ + builder, context, module, AddressSpace, AtomicOrdering, AtomicRMWBinOp, + FloatPredicate, IntPredicate, +}; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::mem::size_of; +use std::ops::Deref; +use std::path::Path; +use types::{ + Block, BuiltinFunction, ClassId, ConstantId, Database, MethodId, + MethodSource, ModuleId, ARRAY_ID, BOOLEAN_ID, BYTE_ARRAY_ID, CALL_METHOD, + CHANNEL_ID, DROPPER_METHOD, FLOAT_ID, INT_ID, NIL_ID, STRING_ID, +}; + +const NAME_MANGLING_VERSION: usize = 1; + +/// The size of an object header. +const HEADER_SIZE: u32 = 16; + +/// The size of a process, minus its fields. +const PROCESS_SIZE: u32 = 136; + +/// The size of the `State` type. +const STATE_SIZE: u32 = 384; + +/// The mask to use for tagged integers. +const INT_MASK: i64 = 0b001; + +/// 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. +const MIN_INT: i64 = i64::MIN >> INT_SHIFT; + +/// The maximum integer value that can be stored as a tagged signed integer. +const MAX_INT: i64 = i64::MAX >> INT_SHIFT; + +/// 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. +const FIELD_OFFSET: usize = 1; + +/// The mask to use to check if a value is a tagged integer or reference. +const TAG_MASK: i64 = 0b11; + +/// The mask to apply to get rid of the tagging bits. +const UNTAG_MASK: u64 = (!TAG_MASK) as u64; + +/// The mask to use for checking if a value is a reference. +const REF_MASK: i64 = 0b10; + +/// The field index of the `State` field that contains the `true` singleton. +const TRUE_INDEX: u32 = 0; + +/// The field index of the `State` field that contains the `false` singleton. +const FALSE_INDEX: u32 = 1; + +/// The field index of the `State` field that contains the `nil` singleton. +const NIL_INDEX: u32 = 2; + +const HEADER_CLASS_INDEX: u32 = 0; +const HEADER_KIND_INDEX: u32 = 1; +const HEADER_REFS_INDEX: u32 = 2; + +const BOXED_INT_VALUE_INDEX: u32 = 1; +const BOXED_FLOAT_VALUE_INDEX: u32 = 1; + +const CLASS_METHODS_COUNT_INDEX: u32 = 2; +const CLASS_METHODS_INDEX: u32 = 3; + +const METHOD_HASH_INDEX: u32 = 0; +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. +const OWNED_KIND: u8 = 0; +const REF_KIND: u8 = 1; +const ATOMIC_KIND: u8 = 2; +const PERMANENT_KIND: u8 = 3; +const INT_KIND: u8 = 4; +const FLOAT_KIND: u8 = 5; + +const RESULT_TAG_INDEX: u32 = 0; +const RESULT_VALUE_INDEX: u32 = 1; +const RESULT_OK_VALUE: u8 = 0; +const RESULT_ERROR_VALUE: u8 = 1; + +const LLVM_RESULT_VALUE_INDEX: u32 = 0; +const LLVM_RESULT_STATUS_INDEX: u32 = 1; + +const CONTEXT_STATE_INDEX: u32 = 0; +const CONTEXT_PROCESS_INDEX: u32 = 1; +const CONTEXT_ARGS_INDEX: u32 = 2; + +const MESSAGE_ARGUMENTS_INDEX: u32 = 2; + +const CLOSURE_CALL_INDEX: u32 = 0; +const CLOSURE_DROPPER_INDEX: u32 = 1; + +/// 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; + +/// 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 +} + +/// 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. +struct MethodHasher<'a> { + hashes: FnvHashMap<&'a str, u64>, + used: FnvHashSet, +} + +impl<'a> MethodHasher<'a> { + 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(), + ), + } + } + + 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) + } +} + +/// A cache of mangled symbol names. +struct SymbolNames { + classes: HashMap, + methods: HashMap, + constants: HashMap, + setup_functions: HashMap, +} + +impl SymbolNames { + 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 class_name = format!( + "_I{}T_{}::{}", + NAME_MANGLING_VERSION, + mod_name, + class.name(db) + ); + + classes.insert(class, class_name); + + for &method in &mir.classes[&class].methods { + let name = format!( + "_I{}M_{}::{}.{}", + NAME_MANGLING_VERSION, + 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!("_I{}C_{}::{}", NAME_MANGLING_VERSION, mod_name, name), + ); + } + + for &id in mir.modules.keys() { + let name = format!( + "_I{}M_{}::$setup", + NAME_MANGLING_VERSION, + id.name(db).as_str() + ); + + setup_functions.insert(id, name); + } + + Self { classes, methods, constants, setup_functions } + } +} + +#[derive(Copy, Clone)] +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, + ClassDrop, + ClassObject, + ClassProcess, + CpuCores, + DirectoryCreate, + DirectoryCreateRecursive, + DirectoryList, + DirectoryRemove, + DirectoryRemoveAll, + EnvArguments, + EnvExecutable, + EnvGet, + EnvGetWorkingDirectory, + EnvHomeDirectory, + EnvPlatform, + EnvSetWorkingDirectory, + EnvTempDirectory, + EnvVariables, + Exit, + FileCopy, + FileDrop, + FileFlush, + FileOpen, + FileRead, + FileRemove, + FileSeek, + FileSize, + FileWriteBytes, + FileWriteString, + FloatBoxed, + FloatBoxedPermanent, + FloatClone, + FloatEq, + FloatRound, + FloatToString, + Free, + HasherDrop, + HasherNew, + HasherToHash, + HasherWriteInt, + IntBoxed, + IntBoxedPermanent, + IntClone, + IntOverflow, + IntPow, + IntToString, + MessageNew, + MethodNew, + ObjectNew, + PathAccessedAt, + PathCreatedAt, + PathExists, + PathIsDirectory, + PathIsFile, + PathModifiedAt, + ProcessFinishMessage, + ProcessNew, + ProcessPanic, + ProcessPopStackFrame, + ProcessPushStackFrame, + ProcessSendMessage, + ProcessStackFrameLine, + ProcessStackFrameName, + ProcessStackFramePath, + 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, + StringNew, + StringNewPermanent, + StringSize, + StringSliceBytes, + StringToByteArray, + StringToCString, + StringToFloat, + StringToInt, + StringToLower, + StringToUpper, + TimeMonotonic, + TimeSystem, + TimeSystemOffset, +} + +impl RuntimeFunction { + 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::ClassDrop => "inko_class_drop", + 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::EnvPlatform => "inko_env_platform", + 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::HasherDrop => "inko_hasher_drop", + RuntimeFunction::HasherNew => "inko_hasher_new", + RuntimeFunction::HasherToHash => "inko_hasher_to_hash", + RuntimeFunction::HasherWriteInt => "inko_hasher_write_int", + 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::MethodNew => "inko_method_new", + RuntimeFunction::ObjectNew => "inko_object_new", + 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::ProcessPopStackFrame => { + "inko_process_pop_stack_frame" + } + RuntimeFunction::ProcessPushStackFrame => { + "inko_process_push_stack_frame" + } + RuntimeFunction::ProcessSendMessage => "inko_process_send_message", + 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::StringNew => "inko_string_new", + 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::StringToCString => "inko_string_to_cstring", + 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", + } + } + + fn build<'a, 'ctx>(self, module: &Module<'a, 'ctx>) -> FunctionValue<'ctx> { + let context = module.context; + let space = AddressSpace::default(); + let fn_type = match self { + RuntimeFunction::IntBoxedPermanent => { + let state = module.types.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.types.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.types.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.types.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.types.state.ptr_type(space).into(); + let val = context.pointer_type(); + + val.fn_type(&[state, val.into()], false) + } + RuntimeFunction::ArrayNewPermanent => { + let state = module.types.state.ptr_type(space).into(); + let len = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, len], false) + } + RuntimeFunction::ArrayNew => { + let state = module.types.state.ptr_type(space).into(); + let len = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, len], false) + } + RuntimeFunction::ArrayPush => { + let state = module.types.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::ObjectNew => { + let class = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[class], false) + } + RuntimeFunction::StringEquals => { + let state = module.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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 = context.pointer_type(); + + 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.types.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.types.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::ByteArrayPush => { + let state = module.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.result; + + ret.fn_type(&[proc, child], false) + } + RuntimeFunction::ChildProcessTryWait => { + let child = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[child], false) + } + RuntimeFunction::ChildProcessDrop => { + let state = module.types.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.types.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.types.result; + + ret.fn_type(&[state, proc, child, buffer, size], false) + } + RuntimeFunction::ChildProcessStderrRead => { + let state = module.types.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.types.result; + + ret.fn_type(&[state, proc, child, buffer, size], false) + } + RuntimeFunction::ChildProcessStderrClose => { + let state = module.types.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.types.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdinClose => { + let state = module.types.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.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, proc, child], false) + } + RuntimeFunction::ChildProcessStdinWriteBytes => { + let state = module.types.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.types.result; + + ret.fn_type(&[state, proc, child, input], false) + } + RuntimeFunction::ChildProcessStdinWriteString => { + let state = module.types.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.types.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.types.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::MethodNew => { + let hash = context.i64_type().into(); + let code = context.pointer_type().into(); + let ret = module.types.method; + + ret.fn_type(&[hash, code], false) + } + RuntimeFunction::MessageNew => { + let method = context.pointer_type().into(); + let length = context.i8_type().into(); + let ret = module.types.message.ptr_type(space); + + ret.fn_type(&[method, length], false) + } + RuntimeFunction::ProcessSendMessage => { + let state = module.types.state.ptr_type(space).into(); + let sender = context.pointer_type().into(); + let receiver = context.pointer_type().into(); + let message = module.types.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.types.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::IntToString => { + let state = module.types.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.types.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.types.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.types.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.types.result; + + ret.fn_type(&[state, proc, channel, time], false) + } + RuntimeFunction::ChannelSend => { + let state = module.types.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.types.result; + + ret.fn_type(&[proc, channel], false) + } + RuntimeFunction::ChannelWait => { + let proc = context.pointer_type().into(); + let channels = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc, channels], false) + } + RuntimeFunction::ClassDrop => { + let class = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[class], false) + } + RuntimeFunction::DirectoryCreate + | RuntimeFunction::DirectoryCreateRecursive + | RuntimeFunction::DirectoryList + | RuntimeFunction::DirectoryRemove + | RuntimeFunction::DirectoryRemoveAll => { + let state = module.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::EnvArguments => { + let state = module.types.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvExecutable => { + let state = module.types.state.ptr_type(space).into(); + let ret = module.types.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvGet => { + let state = module.types.state.ptr_type(space).into(); + let name = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, name], false) + } + RuntimeFunction::EnvGetWorkingDirectory => { + let state = module.types.state.ptr_type(space).into(); + let ret = module.types.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvHomeDirectory => { + let state = module.types.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvPlatform => { + context.pointer_type().fn_type(&[], false) + } + RuntimeFunction::EnvSetWorkingDirectory => { + let state = module.types.state.ptr_type(space).into(); + let path = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, path], false) + } + RuntimeFunction::EnvTempDirectory => { + let state = module.types.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvVariables => { + let state = module.types.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.types.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.types.result; + + ret.fn_type(&[state, proc, from, to], false) + } + RuntimeFunction::FileDrop => { + let state = module.types.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.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let ret = module.types.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.types.result; + + ret.fn_type(&[proc, path, mode], false) + } + RuntimeFunction::FileRead => { + let state = module.types.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.types.result; + + ret.fn_type(&[state, proc, file, buffer, size], false) + } + RuntimeFunction::FileRemove => { + let state = module.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::FileSeek => { + let state = module.types.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.types.result; + + ret.fn_type(&[state, proc, file, offset], false) + } + RuntimeFunction::FileSize => { + let state = module.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::FileWriteBytes + | RuntimeFunction::FileWriteString => { + let state = module.types.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.types.result; + + ret.fn_type(&[state, proc, file, input], false) + } + RuntimeFunction::HasherDrop => { + let state = module.types.state.ptr_type(space).into(); + let hasher = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, hasher], false) + } + RuntimeFunction::HasherNew => { + let state = module.types.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::HasherToHash => { + let state = module.types.state.ptr_type(space).into(); + let hasher = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, hasher], false) + } + RuntimeFunction::HasherWriteInt => { + let state = module.types.state.ptr_type(space).into(); + let hasher = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, hasher, value], false) + } + RuntimeFunction::PathAccessedAt + | RuntimeFunction::PathCreatedAt + | RuntimeFunction::PathModifiedAt => { + let state = module.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::PathExists + | RuntimeFunction::PathIsDirectory + | RuntimeFunction::PathIsFile => { + let state = module.types.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) + } + // TODO: what to do with these? + RuntimeFunction::ProcessPopStackFrame => todo!(), + RuntimeFunction::ProcessPushStackFrame => todo!(), + RuntimeFunction::ProcessStackFrameLine => todo!(), + RuntimeFunction::ProcessStackFrameName => todo!(), + RuntimeFunction::ProcessStackFramePath => todo!(), + RuntimeFunction::ProcessStacktraceLength => todo!(), + RuntimeFunction::ProcessSuspend => { + let state = module.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.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.types.result; + + ret.fn_type(&[state, socket, address, port], false) + } + RuntimeFunction::SocketConnect => { + let state = module.types.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.types.result; + + ret.fn_type( + &[state, proc, socket, address, port, deadline], + false, + ) + } + RuntimeFunction::SocketDrop => { + let state = module.types.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.types.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketLocalAddress + | RuntimeFunction::SocketPeerAddress => { + let state = module.types.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketRead + | RuntimeFunction::SocketReceiveFrom => { + let state = module.types.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.types.result; + + ret.fn_type( + &[state, process, socket, buffer, amount, deadline], + false, + ) + } + RuntimeFunction::SocketSendBytesTo + | RuntimeFunction::SocketSendStringTo => { + let state = module.types.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.types.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.types.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketSetLinger + | RuntimeFunction::SocketSetRecvSize + | RuntimeFunction::SocketSetSendSize + | RuntimeFunction::SocketSetTtl => { + let state = module.types.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketShutdownRead + | RuntimeFunction::SocketShutdownReadWrite + | RuntimeFunction::SocketShutdownWrite => { + let state = module.types.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketTryClone => { + let socket = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[socket], false) + } + RuntimeFunction::StderrFlush | RuntimeFunction::StdoutFlush => { + let state = module.types.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.types.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.types.result; + + ret.fn_type(&[state, proc, input], false) + } + RuntimeFunction::StdinRead => { + let state = module.types.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.types.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 + | RuntimeFunction::StringCharactersNext => { + let state = module.types.state.ptr_type(space).into(); + let input = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, input], false) + } + RuntimeFunction::StringConcat => { + let state = module.types.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.types.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.types.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringNew + | RuntimeFunction::StringNewPermanent => { + let state = module.types.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.types.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.types.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.types.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringToCString => { + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[string], false) + } + RuntimeFunction::StringToFloat => { + let state = module.types.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 = context.pointer_type(); + + ret.fn_type(&[state, string, start, end], false) + } + RuntimeFunction::StringToInt => { + let state = module.types.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 = context.pointer_type(); + + ret.fn_type(&[state, proc, string, radix, start, end], false) + } + RuntimeFunction::TimeSystem | RuntimeFunction::TimeSystemOffset => { + let state = module.types.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.types.result; + + ret.fn_type(&[proto, kind], false) + } + RuntimeFunction::SocketWriteBytes + | RuntimeFunction::SocketWriteString => { + let state = module.types.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.types.result; + + ret.fn_type(&[state, process, socket, buffer, deadline], false) + } + }; + + module.add_function(self.name(), fn_type, None) + } +} + +/// A wrapper around an LLVM Context that provides some additional methods. +struct Context { + inner: context::Context, +} + +impl Context { + fn new() -> Self { + Self { inner: context::Context::create() } + } + + fn pointer_type<'a>(&'a self) -> PointerType<'a> { + self.inner.i8_type().ptr_type(AddressSpace::default()) + } + + fn rust_string_type<'a>(&'a self) -> ArrayType<'a> { + self.inner.i8_type().array_type(size_of::() as u32) + } + + fn rust_vec_type<'a>(&'a self) -> ArrayType<'a> { + self.inner.i8_type().array_type(size_of::>() as u32) + } + + 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_type.into(), // Name + self.inner.i32_type().into(), // Instance size + self.inner.i16_type().into(), // Number of methods + method_type.array_type(methods as u32).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). + fn builtin_type<'a>( + &'a self, + name: &str, + header: StructType<'a>, + value: BasicTypeEnum, + ) -> StructType<'a> { + let typ = self.inner.opaque_struct_type(name); + + typ.set_body(&[header.into(), value], false); + typ + } +} + +impl Deref for Context { + type Target = context::Context; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A wrapper around an LLVM Builder that provides some additional methods. +struct Builder<'a, 'ctx> { + inner: builder::Builder<'ctx>, + context: &'ctx Context, + types: &'a Types<'ctx>, +} + +impl<'a, 'ctx> Builder<'a, 'ctx> { + fn new(context: &'ctx Context, types: &'a Types<'ctx>) -> Self { + Self { inner: context.create_builder(), context, types } + } + + fn extract_field( + &self, + receiver: StructValue<'ctx>, + index: u32, + ) -> BasicValueEnum<'ctx> { + self.inner.build_extract_value(receiver, index, "").unwrap() + } + + 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 + .inner + .build_struct_gep(receiver_type, receiver, index, "") + .unwrap(); + + self.inner.build_load(vtype, field_ptr, "") + } + + fn load_array_index( + &self, + array_type: ArrayType<'ctx>, + array: PointerValue<'ctx>, + index: usize, + ) -> Result, ()> { + 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), + ], + "", + ) + }; + + Ok(self.inner.build_load(array_type.get_element_type(), ptr, "")) + } + + 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); + } + + fn store_field>( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + value: V, + ) { + let field_ptr = self + .inner + .build_struct_gep(receiver_type, receiver, index, "") + .unwrap(); + + self.store(field_ptr, value); + } + + fn load_global_to_stack>( + &self, + typ: T, + variable: PointerValue<'ctx>, + global: GlobalValue<'ctx>, + ) { + self.store(variable, self.load(typ, global.as_pointer_value())); + } + + fn store>( + &self, + variable: PointerValue<'ctx>, + value: V, + ) { + self.inner.build_store(variable, value); + } + + fn load>( + &self, + typ: T, + variable: PointerValue<'ctx>, + ) -> BasicValueEnum<'ctx> { + self.inner.build_load(typ, variable, "") + } + + fn load_opaque_pointer( + &self, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load(self.context.i8_type(), variable).into_pointer_value() + } + + fn load_pointer>( + &self, + typ: T, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load(typ, variable).into_pointer_value() + } + + fn call( + &self, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum<'ctx>], + ) -> BasicValueEnum<'ctx> { + self.inner + .build_call(function, arguments, "") + .try_as_basic_value() + .left() + .unwrap() + } + + fn call_void( + &self, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum<'ctx>], + ) { + self.inner.build_call(function, arguments, ""); + } + + fn cast_pointer_to_int(&self, value: PointerValue<'ctx>) -> IntValue<'ctx> { + self.inner.build_ptr_to_int(value, self.context.i64_type(), "") + } + + fn cast_int_to_pointer(&self, value: IntValue<'ctx>) -> PointerValue<'ctx> { + self.inner.build_int_to_ptr(value, self.context.pointer_type(), "") + } + + fn u8_literal(&self, value: u8) -> IntValue<'ctx> { + self.context.i8_type().const_int(value as u64, false) + } + + fn i64_literal(&self, value: i64) -> IntValue<'ctx> { + self.u64_literal(value as u64) + } + + fn u16_literal(&self, value: u16) -> IntValue<'ctx> { + self.context.i16_type().const_int(value as u64, false) + } + + fn u32_literal(&self, value: u32) -> IntValue<'ctx> { + self.context.i32_type().const_int(value as u64, false) + } + + fn u64_literal(&self, value: u64) -> IntValue<'ctx> { + self.context.i64_type().const_int(value, false) + } + + fn f64_literal(&self, value: f64) -> FloatValue<'ctx> { + self.context.f64_type().const_float(value) + } + + fn string_literal( + &self, + value: &str, + ) -> (PointerValue<'ctx>, IntValue<'ctx>) { + let string = self.build_global_string_ptr(value, "").as_pointer_value(); + let len = self.u64_literal(value.len() as _); + + (string, len) + } + + fn int_eq( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_compare(IntPredicate::EQ, lhs, rhs, "") + } + + fn int_gt( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_compare(IntPredicate::SGT, lhs, rhs, "") + } + + fn int_ge( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_compare(IntPredicate::SGE, lhs, rhs, "") + } + + fn int_lt( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_compare(IntPredicate::SLT, lhs, rhs, "") + } + + fn int_le( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_compare(IntPredicate::SLE, lhs, rhs, "") + } + + fn int_sub( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_sub(lhs, rhs, "") + } + + fn int_add( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_add(lhs, rhs, "") + } + + fn int_mul( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_int_mul(lhs, rhs, "") + } + + fn bit_and( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_and(lhs, rhs, "") + } + + fn bit_or( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_or(lhs, rhs, "") + } + + fn left_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_left_shift(lhs, rhs, "") + } + + fn right_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_right_shift(lhs, rhs, false, "") + } + + fn signed_right_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_right_shift(lhs, rhs, true, "") + } + + fn float_eq( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.build_float_compare(FloatPredicate::OEQ, lhs, rhs, "") + } + + fn tagged_int(&self, value: i64) -> Option> { + if value >= MIN_INT && value <= MAX_INT { + let addr = (value << INT_SHIFT) | INT_MASK; + let int = self.i64_literal(addr); + + Some(int.const_to_pointer(self.context.pointer_type())) + } else { + None + } + } + + fn cast_to_untagged_pointer( + &self, + pointer: PointerValue<'ctx>, + layout: StructType<'ctx>, + ) -> PointerValue<'ctx> { + let tagged_addr = self.cast_pointer_to_int(pointer); + let mask = self.u64_literal(UNTAG_MASK); + let addr = self.bit_and(tagged_addr, mask); + + self.cast_int_to_pointer(addr) + } +} + +impl<'a, 'ctx> Deref for Builder<'a, 'ctx> { + type Target = builder::Builder<'ctx>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A wrapper around an LLVM Module that provides some additional methods. +struct Module<'a, 'ctx> { + inner: module::Module<'ctx>, + context: &'ctx Context, + + /// The name of the module. + name: String, + + /// The global types available to this module (i.e. all Inko class layouts). + types: &'a Types<'ctx>, + + /// The literals defined in this module, and their corresponding global + /// variables. + /// + /// This mapping only includes Int, Float and String literals. + literals: HashMap>, +} + +impl<'a, 'ctx> Module<'a, 'ctx> { + fn new( + context: &'ctx Context, + types: &'a Types<'ctx>, + name: String, + ) -> Self { + Self { + inner: context.create_module(&name), + context, + name, + types, + literals: HashMap::new(), + } + } + + 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) + } + + fn add_literal(&mut self, value: &Constant) -> GlobalValue<'ctx> { + if let Some(&global) = self.literals.get(value) { + global + } else { + let name = format!( + "_I{}L_{}_{}", + NAME_MANGLING_VERSION, + 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 + } + } + + fn add_constant(&mut self, name: &str) -> GlobalValue<'ctx> { + self.inner.get_global(name).unwrap_or_else(|| self.add_global(name)) + } + + 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.types.classes[&id].ptr_type(space); + + self.inner.add_global(typ, Some(space), name) + }) + } + + fn add_method(&self, name: &str, method: MethodId) -> FunctionValue<'ctx> { + self.inner.get_function(name).unwrap_or_else(|| { + self.inner.add_function( + name, + self.types.methods[&method].signature, + None, + ) + }) + } + + 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.types.state.ptr_type(space).into()]; + let typ = self.context.void_type().fn_type(&args, false); + + self.inner.add_function(name, typ, None) + } + } + + fn runtime_function( + &self, + function: RuntimeFunction, + ) -> FunctionValue<'ctx> { + self.inner + .get_function(function.name()) + .unwrap_or_else(|| function.build(&self)) + } +} + +impl<'a, 'ctx> Deref for Module<'a, 'ctx> { + type Target = module::Module<'ctx>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +struct MethodInfo<'ctx> { + index: u16, + hash: u64, + collision: bool, + signature: FunctionType<'ctx>, +} + +/// Types and layout information to expose to all modules. +struct Types<'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. + empty_class: StructType<'ctx>, + + /// The type to use for Inko methods (used for dynamic dispatch). + method: StructType<'ctx>, + + /// All MIR classes and their corresponding structure layouts. + classes: HashMap>, + + /// The structure layouts for all class instances. + instances: HashMap>, + + /// The structure layout of the runtime's `State` type. + state: StructType<'ctx>, + + /// The layout of object headers. + header: StructType<'ctx>, + + /// The layout of the runtime's result type. + result: StructType<'ctx>, + + /// The layout of the context type passed to async methods. + context: StructType<'ctx>, + + /// The layout to use for the type that stores the built-in type method + /// counts. + method_counts: StructType<'ctx>, + + /// Information about methods defined on classes, such as their signatures + /// and hash codes. + methods: HashMap>, + + /// The layout of messages sent to processes. + message: StructType<'ctx>, +} + +impl<'ctx> Types<'ctx> { + fn new(context: &'ctx Context, db: &Database, mir: &Mir) -> Self { + 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.inner.i8_type().into(), // Kind + context.inner.i32_type().into(), // References + ], + false, + ); + + let method = context.struct_type( + &[ + context.i64_type().into(), // Hash + context.pointer_type().into(), // Function pointer + ], + false, + ); + + let state_layout = context.struct_type( + &[ + context.i8_type().ptr_type(AddressSpace::default()).into(), + context.i8_type().ptr_type(AddressSpace::default()).into(), + context.i8_type().ptr_type(AddressSpace::default()).into(), + // We don't care about the rest of the State type, so we just + // pad it with the remaining bytes. + context.i8_type().array_type(STATE_SIZE - 24).into(), + ], + false, + ); + + let context_layout = context.struct_type( + &[ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + context.pointer_type().into(), // Arguments pointer + ], + false, + ); + + let result_layout = context.struct_type( + &[ + context.i8_type().into(), // Tag + context.i8_type().ptr_type(space).into(), // Value + ], + false, + ); + + 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 + ], + false, + ); + + let message_layout = context.struct_type( + &[ + context.pointer_type().into(), // Function + context.i8_type().into(), // Length + context.pointer_type().array_type(0).into(), // Arguments + ], + false, + ); + + 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 = if method.throw_type(db).is_never(db) { + context.pointer_type().fn_type(&args, false) + } else { + result_layout.fn_type(&args, false) + }; + + methods.insert( + method, + MethodInfo { index: 0, hash, signature, collision: false }, + ); + } + } + + 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 = + round_methods(mir_class.methods.len()) * METHOD_TABLE_FACTOR; + 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_type(&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_type(&name) + } + }; + + let mut buckets = vec![false; methods_len]; + let max_bucket = methods_len.saturating_sub(1); + + // 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() { + CALL_METHOD => CLOSURE_CALL_INDEX as _, + DROPPER_METHOD => CLOSURE_DROPPER_INDEX as _, + _ => unreachable!(), + } + } 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; + } + } + + 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()); + } + + if method.throw_type(db).is_never(db) { + context.pointer_type().fn_type(&args, false) + } else { + result_layout.fn_type(&args, false) + } + }; + + methods.insert( + method, + MethodInfo { + index: index as u16, + hash, + signature: typ, + collision, + }, + ); + } + + class_layouts.insert(*id, class); + instance_layouts.insert(*id, instance); + } + + 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, + } + } + + fn methods(&self, class: ClassId) -> u32 { + self.classes[&class] + .get_field_type_at_index(3) + .unwrap() + .into_array_type() + .len() + } +} + +/// A pass that lowers the MIR of a module into LLVM IR. +pub(crate) struct Lower<'a, 'b, 'ctx> { + db: &'a Database, + mir: &'a Mir, + module_index: usize, + types: &'a Types<'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> Lower<'a, 'b, 'ctx> { + pub(crate) fn run_all(db: &'a Database, mir: &'a Mir) { + let context = Context::new(); + let types = Types::new(&context, db, mir); + let names = SymbolNames::new(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(db).to_string(); + let mut module = Module::new(&context, &types, name); + + Lower { + db, + mir, + module_index, + names: &names, + context: &context, + module: &mut module, + types: &types, + functions: HashMap::new(), + } + .run(); + + modules.push(module); + } + + // TODO: move elsewhere + let main_module = Module::new(&context, &types, "$main".to_string()); + + GenerateMain { + db, + mir, + names: &names, + context: &context, + module: &main_module, + types: &types, + } + .run(); + + modules.push(main_module); + + Target::initialize_x86(&InitializationConfig::default()); + + let opt = OptimizationLevel::Default; + let reloc = RelocMode::PIC; + let model = CodeModel::Default; + let target = Target::from_name("x86-64").unwrap(); + let triple = TargetTriple::create("x86_64-pc-linux-gnu"); + let target_machine = target + .create_target_machine(&triple, "x86-64", "", opt, reloc, model) + .unwrap(); + let layout = target_machine.get_target_data().get_data_layout(); + + // TODO: remove + let cache_dir = Path::new("/tmp/inko"); + + if cache_dir.exists() { + std::fs::remove_dir_all(cache_dir).unwrap(); + } + + std::fs::create_dir(cache_dir).unwrap(); + + 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); + + let name = module.get_name().to_string_lossy(); + + pm.run_on(&module.inner); + + module + .print_to_file(&format!("/tmp/inko/{}.ll", name)) + .expect("Failed to print the LLVM IR"); + + target_machine + .write_to_file( + &module, + FileType::Object, + Path::new(&format!("/tmp/inko/{}.o", name)), + ) + .expect("Failed to write the object file"); + } + } + + 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.types, + self.context, + &mut self.module, + self.names, + self.module_index, + class_id, + &self.mir.methods[method_id], + ) + .run(); + + self.functions + .entry(class_id) + .or_insert_with(Vec::new) + .push(func); + } + } + + self.generate_setup_function(); + + if let Err(err) = self.module.verify() { + println!( + "WARNING: the LLVM module {} is invalid:\n\n{}\n", + self.mir.modules[self.module_index].id.name(self.db), + err.to_string() + ); + } + } + + 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, self.types); + let entry_block = self.context.append_basic_block(fn_val, ""); + + builder.position_at_end(entry_block); + + let state_var = + builder.build_alloca(self.types.state.ptr_type(space), ""); + + builder.store(state_var, fn_val.get_nth_param(0).unwrap()); + + let body = self.context.append_basic_block(fn_val, ""); + + builder.build_unconditional_branch(body); + builder.position_at_end(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.types.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.types.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 ptr = builder + .call(class_new, &[name_ptr, fields_len, methods_len]) + .into_pointer_value(); + let method_new = + self.module.runtime_function(RuntimeFunction::MethodNew); + + for method in &self.mir.classes[&class_id].methods { + let info = &self.types.methods[method]; + let name = &self.names.methods[method]; + + // TODO: remove once all modules are processed, as then this is + // actually an indicator of a compiler bug. + if self.module.get_function(name).is_none() { + continue; + } + + let func = self + .module + .get_function(name) + .unwrap() + .as_global_value() + .as_pointer_value(); + + let write_to = unsafe { + builder.build_gep( + self.types.empty_class, + ptr, + &[ + builder.u32_literal(0), + builder.u32_literal(CLASS_METHODS_INDEX as _), + builder.u16_literal(info.index), + ], + "", + ) + }; + + let hash = builder.u64_literal(info.hash); + let method = builder + .call(method_new, &[hash.into(), func.into()]) + .into_struct_value(); + + builder.store(write_to, method); + } + + builder.store(global.as_pointer_value(), 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.build_return(None); + } + + fn set_constant_global( + &self, + builder: &Builder<'a, '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<'a, 'ctx>, + state_var: PointerValue<'ctx>, + constant: &Constant, + ) -> BasicValueEnum<'ctx> { + let state = builder.load(self.types.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 ptr = builder + .build_global_string_ptr(val, "") + .as_pointer_value() + .into(); + let len = builder.u64_literal(val.len() as u64).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StringNewPermanent); + + builder.call(func, &[state, ptr, 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 { + 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, + types: &'a Types<'ctx>, + + /// The index of the MIR module our method is defined in. + /// TODO: do we need this? + module_index: usize, + + /// The class the method belongs to that we're lowering. + class_id: ClassId, + + /// 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 LLVM context to use for generating instructions. + context: &'ctx Context, + + /// 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 function value of the method we're compiling. + function: FunctionValue<'ctx>, + + /// A flag indicating that this method may throw a value. + throws: bool, +} + +impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { + fn new( + db: &'a Database, + mir: &'a Mir, + types: &'a Types<'ctx>, + context: &'ctx Context, + module: &'b mut Module<'a, 'ctx>, + names: &'a SymbolNames, + module_index: usize, + class_id: ClassId, + method: &'b Method, + ) -> Self { + let throws = !method.id.throw_type(db).is_never(db); + let function = module.add_method(&names.methods[&method.id], method.id); + + LowerMethod { + db, + mir, + types, + module_index, + class_id, + method, + names, + context, + module, + variables: HashMap::new(), + function, + throws, + } + } + + fn run(&mut self) -> FunctionValue<'ctx> { + if self.method.id.is_async(self.db) { + self.async_method(); + } else { + self.regular_method(); + } + + self.function + } + + fn regular_method(&mut self) { + let builder = Builder::new(self.context, self.types); + let entry_block = self.add_basic_block(); + + builder.position_at_end(entry_block); + + let space = AddressSpace::default(); + let state_var = self.new_stack_slot(self.types.state.ptr_type(space)); + let proc_var = self.new_stack_slot(self.context.pointer_type()); + + // Build the stores for all the arguments, including the generated ones. + builder.store(state_var, self.function.get_nth_param(0).unwrap()); + builder.store(proc_var, self.function.get_nth_param(1).unwrap()); + + self.define_register_variables(&builder); + + for (arg, reg) in self + .function + .get_param_iter() + .skip(2) + .zip(self.method.arguments.iter()) + { + builder.store(self.variables[reg], arg); + } + + self.method_body(builder, state_var, proc_var); + } + + fn async_method(&mut self) { + let builder = Builder::new(self.context, self.types); + let entry_block = self.add_basic_block(); + + builder.position_at_end(entry_block); + + let space = AddressSpace::default(); + let state_typ = self.types.state.ptr_type(space); + let state_var = self.new_stack_slot(state_typ); + let proc_var = self.new_stack_slot(self.context.pointer_type()); + let num_args = self.method.arguments.len() as u32; + let args_type = self.context.pointer_type().array_type(num_args); + let args_var = self.new_stack_slot(args_type.ptr_type(space)); + let ctx_var = self.new_stack_slot(self.types.context.ptr_type(space)); + + self.define_register_variables(&builder); + + // Destructure the context into its components. This is necessary as the + // context only lives until the first yield. + builder.store(ctx_var, self.function.get_nth_param(0).unwrap()); + + let ctx = builder.load_pointer(self.types.context, ctx_var); + + builder.store( + state_var, + builder.load_field(self.types.context, ctx, CONTEXT_STATE_INDEX), + ); + builder.store( + proc_var, + builder.load_field(self.types.context, ctx, CONTEXT_PROCESS_INDEX), + ); + + let args = builder + .load_field(self.types.context, ctx, CONTEXT_ARGS_INDEX) + .into_pointer_value(); + + 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]]; + + builder.store( + self_var, + builder.load(self.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 = builder.load_pointer(args_type, args_var); + let val = builder + .load_array_index(args_type, args, index) + .unwrap() + .into_pointer_value(); + + builder.store(var, val); + } + + self.method_body(builder, state_var, proc_var); + } + + fn method_body( + &mut self, + builder: Builder<'a, 'ctx>, + 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.add_basic_block()); + } + + builder.build_unconditional_branch( + 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]; + + builder.position_at_end(llvm_block); + + for ins in &mir_block.instructions { + // TODO: move some of this into `self`? + self.instruction( + ins, + &llvm_blocks, + &builder, + state_var, + proc_var, + ); + } + + for &child in &mir_block.successors { + if visited.insert(child) { + queue.push_back(child); + } + } + } + } + + fn instruction( + &mut self, + ins: &Instruction, + llvm_blocks: &[BasicBlock], + builder: &Builder<'a, 'ctx>, + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + ) { + match ins { + Instruction::CallBuiltin(ins) => match ins.name { + BuiltinFunction::IntAdd => { + self.checked_int_operation( + "llvm.sadd.with.overflow", + builder, + 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", + builder, + 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", + builder, + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + + self.check_division_overflow(builder, proc_var, lhs, rhs); + + let raw = builder.build_int_signed_div(lhs, rhs, ""); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + + self.check_division_overflow(builder, proc_var, lhs, rhs); + + let raw = builder.build_int_signed_rem(lhs, rhs, ""); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.bit_and(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.bit_or(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, val_var); + let raw = builder.build_not(val, ""); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.build_xor(lhs, rhs, ""); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_eq(lhs, rhs); + let res = self.new_bool(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_gt(lhs, rhs); + let res = self.new_bool(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_ge(lhs, rhs); + let res = self.new_bool(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_le(lhs, rhs); + let res = self.new_bool(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_lt(lhs, rhs); + let res = self.new_bool(builder, state_var, raw); + + 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 = builder.load_opaque_pointer(proc_var).into(); + let lhs = self.read_int(builder, lhs_var).into(); + let rhs = self.read_int(builder, rhs_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::IntPow); + let raw = + builder.call(func, &[proc, lhs, rhs]).into_int_value(); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_add(lhs, rhs, ""); + let res = self.new_float(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_sub(lhs, rhs, ""); + let res = self.new_float(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_div(lhs, rhs, ""); + let res = self.new_float(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_mul(lhs, rhs, ""); + let res = self.new_float(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_rem( + builder.build_float_add( + builder.build_float_rem(lhs, rhs, ""), + rhs, + "", + ), + rhs, + "", + ); + let res = self.new_float(builder, state_var, raw); + + 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(builder, val_var); + let func = Intrinsic::find("llvm.ceil") + .and_then(|intr| { + intr.get_declaration( + &self.module.inner, + &[self.context.f64_type().into()], + ) + }) + .unwrap(); + let raw = + builder.call(func, &[val.into()]).into_float_value(); + let res = self.new_float(builder, state_var, raw); + + 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(builder, val_var); + let func = Intrinsic::find("llvm.floor") + .and_then(|intr| { + intr.get_declaration( + &self.module.inner, + &[self.context.f64_type().into()], + ) + }) + .unwrap(); + let raw = + builder.call(func, &[val.into()]).into_float_value(); + let res = self.new_float(builder, state_var, raw); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let lhs = self.read_float(builder, lhs_var).into(); + let rhs = self.read_int(builder, rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatRound); + let res = builder.call(func, &[state, lhs, rhs]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let lhs = self.read_float(builder, lhs_var).into(); + let rhs = self.read_float(builder, rhs_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::FloatEq); + let res = builder.call(func, &[state, lhs, rhs]); + + 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(builder, val_var); + let bits = builder + .build_bitcast(val, self.context.i64_type(), "") + .into_int_value(); + let res = self.new_int(builder, state_var, bits); + + 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(builder, val_var); + let bits = builder + .build_bitcast(val, self.context.f64_type(), "") + .into_float_value(); + let res = self.new_float(builder, state_var, bits); + + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_compare( + FloatPredicate::OGT, + lhs, + rhs, + "", + ); + + builder + .store(reg_var, self.new_bool(builder, state_var, raw)); + } + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_compare( + FloatPredicate::OGE, + lhs, + rhs, + "", + ); + + builder + .store(reg_var, self.new_bool(builder, state_var, raw)); + } + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_compare( + FloatPredicate::OLT, + lhs, + rhs, + "", + ); + + builder + .store(reg_var, self.new_bool(builder, state_var, raw)); + } + 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(builder, lhs_var); + let rhs = self.read_float(builder, rhs_var); + let raw = builder.build_float_compare( + FloatPredicate::OLE, + lhs, + rhs, + "", + ); + + builder + .store(reg_var, self.new_bool(builder, state_var, raw)); + } + BuiltinFunction::FloatIsInf => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(builder, val_var); + let pos_inf = builder.f64_literal(f64::INFINITY); + let neg_inf = builder.f64_literal(f64::NEG_INFINITY); + let cond1 = builder.float_eq(val, pos_inf); + let cond2 = builder.float_eq(val, neg_inf); + let raw = builder.bit_and(cond1, cond2); + + builder + .store(reg_var, self.new_bool(builder, state_var, raw)); + } + BuiltinFunction::FloatIsNan => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(builder, val_var); + let raw = builder.build_float_compare( + FloatPredicate::ONE, + val, + val, + "", + ); + + builder + .store(reg_var, self.new_bool(builder, state_var, raw)); + } + BuiltinFunction::FloatToInt => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(builder, val_var); + let raw = builder.build_float_to_signed_int( + val, + self.context.i64_type(), + "", + ); + let res = self.new_int(builder, state_var, raw); + + builder.store(reg_var, res); + } + BuiltinFunction::FloatToString => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let val = self.read_float(builder, val_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatToString); + let res = builder.call(func, &[state, val]); + + builder.store(reg_var, res); + } + BuiltinFunction::ArrayCapacity => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::ArrayCapacity; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, val]); + + builder.store(reg_var, res); + } + BuiltinFunction::ArrayClear => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::ArrayClear; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, val]); + + builder.store(reg_var, res); + } + BuiltinFunction::ArrayDrop => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::ArrayDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, val]); + + builder.store(reg_var, res); + } + BuiltinFunction::ArrayGet => { + 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 = builder.load_opaque_pointer(array_var).into(); + let index = self.read_int(builder, index_var).into(); + let func_name = RuntimeFunction::ArrayGet; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[array, index]); + + builder.store(reg_var, res); + } + BuiltinFunction::ArrayLength => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func_name = RuntimeFunction::ArrayLength; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, array]); + + builder.store(reg_var, res); + } + BuiltinFunction::ArrayPop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let array = builder.load_opaque_pointer(array_var).into(); + let func_name = RuntimeFunction::ArrayPop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[array]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let value = builder.load_opaque_pointer(value_var).into(); + let func_name = RuntimeFunction::ArrayPush; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, array, value]); + + 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 array = builder.load_opaque_pointer(array_var).into(); + let idx = self.read_int(builder, idx_var).into(); + let func_name = RuntimeFunction::ArrayRemove; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[array, idx]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let amount = self.read_int(builder, amount_var).into(); + let func_name = RuntimeFunction::ArrayReserve; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, array, amount]); + + 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 array = builder.load_opaque_pointer(array_var).into(); + let index = self.read_int(builder, index_var).into(); + let value = builder.load_opaque_pointer(value_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::ArraySet); + let res = builder.call(func, &[array, index, value]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayNew => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayNew); + let res = builder.call(func, &[state]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let target = builder.load_opaque_pointer(target_var).into(); + let source = builder.load_opaque_pointer(source_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayAppend); + let res = builder.call(func, &[state, target, source]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayClear => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayClear); + let res = builder.call(func, &[state, array]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayClone => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayClone); + let res = builder.call(func, &[state, array]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let target = builder.load_opaque_pointer(target_var).into(); + let source = builder.load_opaque_pointer(source_var).into(); + let start = self.read_int(builder, start_var).into(); + let length = self.read_int(builder, length_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayCopyFrom); + let res = builder + .call(func, &[state, target, source, start, length]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayDrainToString => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayDrainToString, + ); + let res = builder.call(func, &[state, array]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayToString => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayToString); + let res = builder.call(func, &[state, array]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayDrop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayDrop); + let res = builder.call(func, &[state, array]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let lhs = builder.load_opaque_pointer(lhs_var).into(); + let rhs = builder.load_opaque_pointer(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayEq); + let res = builder.call(func, &[state, lhs, rhs]); + + 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 = builder.load_opaque_pointer(array_var).into(); + let index = self.read_int(builder, index_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayGet); + let res = builder.call(func, &[array, index]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayLength => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayLength); + let res = builder.call(func, &[state, array]); + + builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayPop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let array = builder.load_opaque_pointer(array_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayPop); + let res = builder.call(func, &[array]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let value = builder.load_opaque_pointer(value_var).into(); + let func_name = RuntimeFunction::ByteArrayPush; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, array, value]); + + 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 = builder.load_opaque_pointer(array_var).into(); + let index = self.read_int(builder, index_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayRemove); + let res = builder.call(func, &[array, index]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let size = self.read_int(builder, size_var).into(); + let fill = self.read_int(builder, fill_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayResize); + let res = builder.call(func, &[state, array, size, fill]); + + 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 = builder.load_opaque_pointer(array_var).into(); + let index = self.read_int(builder, index_var).into(); + let value = builder.load_opaque_pointer(value_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArraySet); + let res = builder.call(func, &[array, index, value]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let array = builder.load_opaque_pointer(array_var).into(); + let start = self.read_int(builder, start_var).into(); + let length = self.read_int(builder, length_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArraySlice); + let res = + builder.call(func, &[state, array, start, length]); + + 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 = builder.load_opaque_pointer(proc_var).into(); + let program = + builder.load_opaque_pointer(program_var).into(); + let args = builder.load_opaque_pointer(args_var).into(); + let env = builder.load_opaque_pointer(env_var).into(); + let stdin = self.read_int(builder, stdin_var).into(); + let stdout = self.read_int(builder, stdout_var).into(); + let stderr = self.read_int(builder, stderr_var).into(); + let dir = builder.load_opaque_pointer(dir_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ChildProcessSpawn); + let res = builder.call( + func, + &[proc, program, args, env, stdin, stdout, stderr, dir], + ); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessDrop => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let child = builder.load_opaque_pointer(child_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ChildProcessDrop); + let res = builder.call(func, &[state, child]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStderrClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let child = builder.load_opaque_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStderrClose, + ); + let res = builder.call(func, &[state, child]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStderrRead => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buffer_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let child = builder.load_opaque_pointer(child_var).into(); + let buffer = builder.load_opaque_pointer(buffer_var).into(); + let size = self.read_int(builder, size_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStderrRead, + ); + let res = + builder.call(func, &[state, proc, child, buffer, size]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let child = builder.load_opaque_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinClose, + ); + let res = builder.call(func, &[state, child]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinFlush => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let child = builder.load_opaque_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinFlush, + ); + let res = builder.call(func, &[state, proc, child]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinWriteBytes => { + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let child = builder.load_opaque_pointer(child_var).into(); + let input = builder.load_opaque_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinWriteBytes, + ); + let res = builder.call(func, &[state, proc, child, input]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let child = builder.load_opaque_pointer(child_var).into(); + let input = builder.load_opaque_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinWriteString, + ); + let res = builder.call(func, &[state, proc, child, input]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdoutClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let child = builder.load_opaque_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdoutClose, + ); + let res = builder.call(func, &[state, child]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdoutRead => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buffer_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let child = builder.load_opaque_pointer(child_var).into(); + let buffer = builder.load_opaque_pointer(buffer_var).into(); + let size = self.read_int(builder, size_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdoutRead, + ); + let res = + builder.call(func, &[state, proc, child, buffer, size]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessTryWait => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let child = builder.load_opaque_pointer(child_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ChildProcessTryWait); + let res = builder.call(func, &[child]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessWait => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let child = builder.load_opaque_pointer(child_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ChildProcessWait); + let res = builder.call(func, &[proc, child]); + + builder.store(reg_var, res); + } + BuiltinFunction::CpuCores => { + let reg_var = self.variables[&ins.register]; + let func = + self.module.runtime_function(RuntimeFunction::CpuCores); + let res = builder.call(func, &[]); + + builder.store(reg_var, res); + } + BuiltinFunction::DirectoryCreate => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryCreate; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::DirectoryCreateRecursive => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryCreateRecursive; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::DirectoryList => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryList; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::DirectoryRemove => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryRemove; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::DirectoryRemoveRecursive => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryRemoveAll; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvArguments => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvArguments; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvExecutable => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvExecutable; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvGet => { + let reg_var = self.variables[&ins.register]; + let name_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let name = builder.load_opaque_pointer(name_var).into(); + let func_name = RuntimeFunction::EnvGet; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, name]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvGetWorkingDirectory => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvGetWorkingDirectory; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvHomeDirectory => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvHomeDirectory; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvPlatform => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::EnvPlatform; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvSetWorkingDirectory => { + let reg_var = self.variables[&ins.register]; + let dir_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let dir = builder.load_opaque_pointer(dir_var).into(); + let func_name = RuntimeFunction::EnvSetWorkingDirectory; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, dir]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvTempDirectory => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvTempDirectory; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::EnvVariables => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvVariables; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::Exit => { + let status_var = self.variables[&ins.arguments[0]]; + let status = self.read_int(builder, status_var).into(); + let func_name = RuntimeFunction::Exit; + let func = self.module.runtime_function(func_name); + + builder.call_void(func, &[status]); + builder.build_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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let from = builder.load_opaque_pointer(from_var).into(); + let to = builder.load_opaque_pointer(to_var).into(); + let func_name = RuntimeFunction::FileCopy; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, from, to]); + + builder.store(reg_var, res); + } + BuiltinFunction::FileDrop => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let file = builder.load_opaque_pointer(file_var).into(); + let func_name = RuntimeFunction::FileDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, file]); + + builder.store(reg_var, res); + } + BuiltinFunction::FileFlush => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let file = builder.load_opaque_pointer(file_var).into(); + let func_name = RuntimeFunction::FileFlush; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, file]); + + 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 = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let mode = self.read_int(builder, mode_var).into(); + let func_name = RuntimeFunction::FileOpen; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[proc, path, mode]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let file = builder.load_opaque_pointer(file_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let size = self.read_int(builder, size_var).into(); + let func_name = RuntimeFunction::FileRead; + let func = self.module.runtime_function(func_name); + let res = + builder.call(func, &[state, proc, file, buf, size]); + + builder.store(reg_var, res); + } + BuiltinFunction::FileRemove => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::FileRemove; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let off = self.read_int(builder, off_var).into(); + let func_name = RuntimeFunction::FileSeek; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path, off]); + + builder.store(reg_var, res); + } + BuiltinFunction::FileSize => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::FileSize; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let file = builder.load_opaque_pointer(file_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let func_name = RuntimeFunction::FileWriteBytes; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, file, buf]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let file = builder.load_opaque_pointer(file_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let func_name = RuntimeFunction::FileWriteString; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, file, buf]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChannelReceive => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let chan = builder.load_opaque_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelReceive; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[proc, chan]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let chan = builder.load_opaque_pointer(chan_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::ChannelReceiveUntil; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, chan, time]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChannelDrop => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let chan = builder.load_opaque_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, chan]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChannelWait => { + let reg_var = self.variables[&ins.register]; + let chans_var = self.variables[&ins.arguments[0]]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let chans = builder.load_opaque_pointer(chans_var).into(); + let func_name = RuntimeFunction::ChannelWait; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[proc, chans]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChannelNew => { + let reg_var = self.variables[&ins.register]; + let cap_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let cap = self.read_int(builder, cap_var).into(); + let func_name = RuntimeFunction::ChannelNew; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, cap]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let chan = builder.load_opaque_pointer(chan_var).into(); + let msg = self.read_int(builder, msg_var).into(); + let func_name = RuntimeFunction::ChannelSend; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, chan, msg]); + + builder.store(reg_var, res); + } + BuiltinFunction::ChannelTryReceive => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let chan = builder.load_opaque_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelTryReceive; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[proc, chan]); + + builder.store(reg_var, res); + } + BuiltinFunction::HasherDrop => { + let reg_var = self.variables[&ins.register]; + let hash_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let hasher = builder.load_opaque_pointer(hash_var).into(); + let func_name = RuntimeFunction::HasherDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, hasher]); + + builder.store(reg_var, res); + } + BuiltinFunction::HasherNew => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::HasherNew; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::HasherToHash => { + let reg_var = self.variables[&ins.register]; + let hash_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let hasher = builder.load_opaque_pointer(hash_var).into(); + let func_name = RuntimeFunction::HasherToHash; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, hasher]); + + builder.store(reg_var, res); + } + BuiltinFunction::HasherWriteInt => { + let reg_var = self.variables[&ins.register]; + let hash_var = self.variables[&ins.arguments[0]]; + let int_var = self.variables[&ins.arguments[1]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let hasher = builder.load_opaque_pointer(hash_var).into(); + let value = self.read_int(builder, int_var).into(); + let func_name = RuntimeFunction::HasherWriteInt; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, hasher, value]); + + 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(builder, lhs_var).into(); + let rhs = self.read_int(builder, rhs_var).into(); + let func = Intrinsic::find("llvm.fshl") + .and_then(|intr| { + intr.get_declaration( + &self.module.inner, + &[self.context.i64_type().into()], + ) + }) + .unwrap(); + let raw = + builder.call(func, &[lhs, lhs, rhs]).into_int_value(); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var).into(); + let rhs = self.read_int(builder, rhs_var).into(); + let func = Intrinsic::find("llvm.fshr") + .and_then(|intr| { + intr.get_declaration( + &self.module.inner, + &[self.context.i64_type().into()], + ) + }) + .unwrap(); + let raw = + builder.call(func, &[lhs, lhs, rhs]).into_int_value(); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + + self.check_shift_bits(proc_var, builder, lhs, rhs); + + let raw = builder.left_shift(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + + self.check_shift_bits(proc_var, builder, lhs, rhs); + + let raw = builder.signed_right_shift(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + + self.check_shift_bits(proc_var, builder, lhs, rhs); + + let raw = builder.right_shift(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, val_var); + let raw = builder + .build_cast( + InstructionOpcode::SIToFP, + val, + self.context.f64_type(), + "", + ) + .into_float_value(); + + let res = self.new_float(builder, state_var, raw); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let val = self.read_int(builder, val_var).into(); + let ret = builder.call(func, &[state, val]); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_add(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_mul(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + 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(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let raw = builder.int_sub(lhs, rhs); + let res = self.new_int(builder, state_var, raw); + + builder.store(reg_var, res); + } + BuiltinFunction::IsNull => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = builder.load_opaque_pointer(val_var); + let raw = builder.build_is_null(val, ""); + let res = self.new_bool(builder, state_var, raw); + + 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 = builder.load_opaque_pointer(lhs_var); + let rhs = builder.load_opaque_pointer(rhs_var); + let raw = builder.int_eq( + builder.cast_pointer_to_int(lhs), + builder.cast_pointer_to_int(rhs), + ); + let res = self.new_bool(builder, state_var, raw); + + builder.store(reg_var, res); + } + BuiltinFunction::Panic => { + let val_var = self.variables[&ins.arguments[0]]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::ProcessPanic; + let func = self.module.runtime_function(func_name); + + builder.call_void(func, &[proc, val]); + builder.build_unreachable(); + } + BuiltinFunction::PathAccessedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::PathAccessedAt; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::PathCreatedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::PathCreatedAt; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::PathModifiedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::PathModifiedAt; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::PathExists => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::PathExists; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::PathIsDirectory => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::PathIsDirectory; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::PathIsFile => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let path = builder.load_opaque_pointer(path_var).into(); + let func_name = RuntimeFunction::PathIsFile; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, path]); + + builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFrameLine => { + // TODO + } + BuiltinFunction::ProcessStackFrameName => { + // TODO + } + BuiltinFunction::ProcessStackFramePath => { + // TODO + } + BuiltinFunction::ProcessStacktrace => { + // TODO + } + BuiltinFunction::ProcessStacktraceDrop => { + // TODO + } + BuiltinFunction::ProcessStacktraceLength => { + // TODO + } + BuiltinFunction::ProcessSuspend => { + let reg_var = self.variables[&ins.register]; + let time_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::ProcessSuspend; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, time]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let rng = builder.load_opaque_pointer(rng_var).into(); + let size = self.read_int(builder, size_var).into(); + let func_name = RuntimeFunction::RandomBytes; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, rng, size]); + + builder.store(reg_var, res); + } + BuiltinFunction::RandomDrop => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let rng = builder.load_opaque_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, rng]); + + builder.store(reg_var, res); + } + BuiltinFunction::RandomFloat => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let rng = builder.load_opaque_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomFloat; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, rng]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let rng = builder.load_opaque_pointer(rng_var).into(); + let min = self.read_int(builder, min_var).into(); + let max = self.read_int(builder, max_var).into(); + let func_name = RuntimeFunction::RandomFloatRange; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, rng, min, max]); + + 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(builder, seed_var).into(); + let func_name = RuntimeFunction::RandomFromInt; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[seed]); + + builder.store(reg_var, res); + } + BuiltinFunction::RandomInt => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let rng = builder.load_opaque_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomInt; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, rng]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let rng = builder.load_opaque_pointer(rng_var).into(); + let min = self.read_int(builder, min_var).into(); + let max = self.read_int(builder, max_var).into(); + let func_name = RuntimeFunction::RandomIntRange; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, rng, min, max]); + + builder.store(reg_var, res); + } + BuiltinFunction::RandomNew => { + let reg_var = self.variables[&ins.register]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let func_name = RuntimeFunction::RandomNew; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[proc]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketAccept; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, sock, time]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairAddress => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[1]]; + let pair = builder.load_opaque_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairAddress; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[pair]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairDrop => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[1]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let pair = builder.load_opaque_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, pair]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairPort => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[1]]; + let pair = builder.load_opaque_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairPort; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[pair]); + + 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(builder, proto_var).into(); + let kind = self.read_int(builder, kind_var).into(); + let func_name = RuntimeFunction::SocketNew; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[proto, kind]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let addr = builder.load_opaque_pointer(addr_var).into(); + let port = self.read_int(builder, port_var).into(); + let func_name = RuntimeFunction::SocketBind; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, addr, port]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let addr = builder.load_opaque_pointer(addr_var).into(); + let port = self.read_int(builder, port_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketConnect; + let func = self.module.runtime_function(func_name); + let res = builder + .call(func, &[state, proc, sock, addr, port, time]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketDrop => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = self.read_int(builder, val_var).into(); + let func_name = RuntimeFunction::SocketListen; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketLocalAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketLocalAddress; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketPeerAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketPeerAddress; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let size = self.read_int(builder, size_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketRead; + let func = self.module.runtime_function(func_name); + let res = builder + .call(func, &[state, proc, sock, buf, size, time]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let size = self.read_int(builder, size_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketReceiveFrom; + let func = self.module.runtime_function(func_name); + let res = builder + .call(func, &[state, proc, sock, buf, size, time]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let addr = self.read_int(builder, addr_var).into(); + let port = self.read_int(builder, port_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketSendBytesTo; + let func = self.module.runtime_function(func_name); + let res = builder.call( + func, + &[state, proc, sock, buf, addr, port, time], + ); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let addr = self.read_int(builder, addr_var).into(); + let port = self.read_int(builder, port_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketSendStringTo; + let func = self.module.runtime_function(func_name); + let res = builder.call( + func, + &[state, proc, sock, buf, addr, port, time], + ); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetBroadcast; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetKeepalive; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = self.read_int(builder, val_var).into(); + let func_name = RuntimeFunction::SocketSetLinger; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetNodelay; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetOnlyV6; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = self.read_int(builder, val_var).into(); + let func_name = RuntimeFunction::SocketSetRecvSize; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetReuseAddress; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = builder.load_opaque_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetReusePort; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = self.read_int(builder, val_var).into(); + let func_name = RuntimeFunction::SocketSetSendSize; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let val = self.read_int(builder, val_var).into(); + let func_name = RuntimeFunction::SocketSetTtl; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock, val]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownRead => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownRead; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownReadWrite => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownReadWrite; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownWrite => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownWrite; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, sock]); + + builder.store(reg_var, res); + } + BuiltinFunction::SocketTryClone => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let sock = builder.load_opaque_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketTryClone; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[sock]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketWriteBytes; + let func = self.module.runtime_function(func_name); + let res = + builder.call(func, &[state, proc, sock, buf, time]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let sock = builder.load_opaque_pointer(sock_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let time = self.read_int(builder, time_var).into(); + let func_name = RuntimeFunction::SocketWriteString; + let func = self.module.runtime_function(func_name); + let res = + builder.call(func, &[state, proc, sock, buf, time]); + + builder.store(reg_var, res); + } + BuiltinFunction::StderrFlush => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let func_name = RuntimeFunction::StderrFlush; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc]); + + builder.store(reg_var, res); + } + BuiltinFunction::StderrWriteBytes => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let input = builder.load_opaque_pointer(input_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StderrWriteBytes); + + let ret = builder.call(func, &[state, proc, input]); + + builder.store(reg_var, ret); + } + BuiltinFunction::StderrWriteString => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let input = builder.load_opaque_pointer(input_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StderrWriteString); + + let ret = builder.call(func, &[state, proc, input]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let buf = builder.load_opaque_pointer(buf_var).into(); + let size = self.read_int(builder, size_var).into(); + let func_name = RuntimeFunction::StdinRead; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, buf, size]); + + builder.store(reg_var, res); + } + BuiltinFunction::StdoutFlush => { + let reg_var = self.variables[&ins.register]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let func_name = RuntimeFunction::StdoutFlush; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc]); + + builder.store(reg_var, res); + } + BuiltinFunction::StdoutWriteBytes => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let input = builder.load_opaque_pointer(input_var).into(); + let func_name = RuntimeFunction::StdoutWriteBytes; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, input]); + + builder.store(reg_var, res); + } + BuiltinFunction::StdoutWriteString => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let input = builder.load_opaque_pointer(input_var).into(); + let func_name = RuntimeFunction::StdoutWriteString; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, proc, input]); + + 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 = builder.load_opaque_pointer(string_var).into(); + let index = self.read_int(builder, index_var).into(); + let func_name = RuntimeFunction::StringByte; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[string, index]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringCharacters => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let string = builder.load_opaque_pointer(string_var).into(); + let func_name = RuntimeFunction::StringCharacters; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[string]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringCharactersDrop => { + let reg_var = self.variables[&ins.register]; + let iter_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let iter = builder.load_opaque_pointer(iter_var).into(); + let func_name = RuntimeFunction::StringCharactersDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, iter]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringCharactersNext => { + let reg_var = self.variables[&ins.register]; + let iter_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let iter = builder.load_opaque_pointer(iter_var).into(); + let func_name = RuntimeFunction::StringCharactersNext; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, iter]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringConcat => { + let reg_var = self.variables[&ins.register]; + let len = builder.i64_literal(ins.arguments.len() as _); + let temp_type = self + .context + .pointer_type() + .array_type(ins.arguments.len() as _); + let temp_var = self.new_stack_slot(temp_type); + + for (idx, reg) in ins.arguments.iter().enumerate() { + let val = + builder.load_opaque_pointer(self.variables[reg]); + + builder.store_array_field( + temp_type, temp_var, idx as _, val, + ); + } + + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let func_name = RuntimeFunction::StringConcat; + let func = self.module.runtime_function(func_name); + let res = builder + .call(func, &[state, temp_var.into(), len.into()]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringConcatArray => { + let reg_var = self.variables[&ins.register]; + let ary_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let ary = builder.load_opaque_pointer(ary_var).into(); + let func_name = RuntimeFunction::StringConcatArray; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, ary]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringDrop => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let func_name = RuntimeFunction::StringDrop; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let lhs = builder.load_opaque_pointer(lhs_var).into(); + let rhs = builder.load_opaque_pointer(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StringEquals); + let ret = builder.call(func, &[state, lhs, rhs]); + + builder.store(reg_var, ret); + } + BuiltinFunction::StringSize => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let func_name = RuntimeFunction::StringSize; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let start = self.read_int(builder, start_var).into(); + let len = self.read_int(builder, len_var).into(); + let func_name = RuntimeFunction::StringSliceBytes; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string, start, len]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringToByteArray => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let func_name = RuntimeFunction::StringToByteArray; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let start = builder.load_opaque_pointer(start_var).into(); + let end = builder.load_opaque_pointer(end_var).into(); + let func_name = RuntimeFunction::StringToFloat; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string, start, end]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let radix = builder.load_opaque_pointer(radix_var).into(); + let start = builder.load_opaque_pointer(start_var).into(); + let end = builder.load_opaque_pointer(end_var).into(); + let func_name = RuntimeFunction::StringToInt; + let func = self.module.runtime_function(func_name); + let res = + builder.call(func, &[state, string, radix, start, end]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringToLower => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let func_name = RuntimeFunction::StringToLower; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string]); + + builder.store(reg_var, res); + } + BuiltinFunction::StringToUpper => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = builder + .load_pointer(self.types.state, state_var) + .into(); + let string = builder.load_opaque_pointer(string_var).into(); + let func_name = RuntimeFunction::StringToUpper; + let func = self.module.runtime_function(func_name); + let res = builder.call(func, &[state, string]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let res = builder.call(func, &[state]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let res = builder.call(func, &[state]); + + 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 = builder + .load_pointer(self.types.state, state_var) + .into(); + let res = builder.call(func, &[state]); + + builder.store(reg_var, res); + } + BuiltinFunction::GetNil => unreachable!(), + BuiltinFunction::Moved => unreachable!(), + BuiltinFunction::PanicThrown => unreachable!(), + }, + Instruction::Goto(ins) => { + builder.build_unconditional_branch(llvm_blocks[ins.block.0]); + } + Instruction::Return(ins) => { + let var = self.variables[&ins.register]; + let val = builder.load(self.context.pointer_type(), var); + let ret = if self.throws { + self.new_result_value(builder, val, false) + } else { + val + }; + + builder.build_return(Some(&ret)); + } + Instruction::Throw(ins) => { + let var = self.variables[&ins.register]; + let val = builder.load(self.context.pointer_type(), var); + let ret = if self.throws { + self.new_result_value(builder, val, true) + } else { + val + }; + + builder.build_return(Some(&ret)); + } + Instruction::AllocateArray(ins) => { + let state = + builder.load_pointer(self.types.state, state_var).into(); + let len = builder.u64_literal(ins.values.len() as u64).into(); + let new_func = + self.module.runtime_function(RuntimeFunction::ArrayNew); + let set_func = + self.module.runtime_function(RuntimeFunction::ArraySet); + let array = builder.call(new_func, &[state, len]).into(); + + for (idx, reg) in ins.values.iter().enumerate() { + let var = self.variables[reg]; + let idx = builder.i64_literal(idx as _).into(); + let val = builder + .load(self.context.pointer_type(), var) + .into_pointer_value() + .into(); + + builder.call(set_func, &[array, idx, val]); + } + } + Instruction::Branch(ins) => { + let cond_var = self.variables[&ins.condition]; + let cond_ptr = builder.load_opaque_pointer(cond_var); + + // Load the `true` singleton from `State`. + let state = + builder.load_pointer(self.types.state, state_var).into(); + let bool_ptr = builder + .load_field(self.types.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 = builder.cast_pointer_to_int(cond_ptr); + let bool_int = builder.cast_pointer_to_int(bool_ptr); + let cond = builder.int_eq(cond_int, bool_int); + + builder.build_conditional_branch( + cond, + llvm_blocks[ins.if_true.0], + llvm_blocks[ins.if_false.0], + ); + } + Instruction::BranchResult(ins) => { + let res_var = self.variables[&ins.result]; + let ok_block = llvm_blocks[ins.ok.0]; + let err_block = llvm_blocks[ins.error.0]; + let tag_val = builder + .load_field(self.types.result, res_var, RESULT_TAG_INDEX) + .into_int_value(); + let ok_val = builder.u8_literal(RESULT_OK_VALUE); + let condition = builder.int_eq(tag_val, ok_val); + + builder + .build_conditional_branch(condition, ok_block, err_block); + } + Instruction::Switch(ins) => { + let reg_var = self.variables[&ins.register]; + let val = builder.load_opaque_pointer(reg_var); + let addr = builder.cast_pointer_to_int(val); + let shift = builder.i64_literal(INT_SHIFT as i64); + let untagged = builder.right_shift(addr, shift); + let mut cases = Vec::with_capacity(ins.blocks.len()); + + for (index, block) in ins.blocks.iter().enumerate() { + cases.push(( + builder.u64_literal(index as u64), + llvm_blocks[block.0], + )); + } + + // Technically it doesn't matter which block we pick here as a + // switch() is always exhaustive. + let fallback = cases.last().unwrap().1; + + builder.build_switch(untagged, fallback, &cases); + } + Instruction::SwitchKind(ins) => { + let val_var = self.variables[&ins.register]; + let kind_var = self.kind_of(&builder, val_var); + let kind = builder + .load(self.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 = llvm_blocks[ins.blocks[0].0]; + let ref_block = llvm_blocks[ins.blocks[1].0]; + let atomic_block = llvm_blocks[ins.blocks[2].0]; + let perm_block = llvm_blocks[ins.blocks[3].0]; + let int_block = llvm_blocks[ins.blocks[4].0]; + let float_block = llvm_blocks[ins.blocks[5].0]; + let cases = [ + (builder.u8_literal(OWNED_KIND), owned_block), + (builder.u8_literal(REF_KIND), ref_block), + (builder.u8_literal(ATOMIC_KIND), atomic_block), + (builder.u8_literal(PERMANENT_KIND), perm_block), + (builder.u8_literal(INT_KIND), int_block), + (builder.u8_literal(FLOAT_KIND), float_block), + ]; + + builder.build_switch(kind, owned_block, &cases); + } + Instruction::Nil(ins) => { + let result = self.variables[&ins.register]; + let state = builder.load_pointer(self.types.state, state_var); + let val = + builder.load_field(self.types.state, state, NIL_INDEX); + + builder.store(result, val); + } + Instruction::True(ins) => { + let result = self.variables[&ins.register]; + let state = builder.load_pointer(self.types.state, state_var); + let val = + builder.load_field(self.types.state, state, TRUE_INDEX); + + builder.store(result, val); + } + Instruction::False(ins) => { + let result = self.variables[&ins.register]; + let state = builder.load_pointer(self.types.state, state_var); + let val = + builder.load_field(self.types.state, state, FALSE_INDEX); + + builder.store(result, val); + } + Instruction::Int(ins) => { + let result = self.variables[&ins.register]; + + if let Some(ptr) = builder.tagged_int(ins.value) { + builder.store(result, ptr); + } else { + let global = + self.module.add_literal(&Constant::Int(ins.value)); + + builder.load_global_to_stack( + self.context.pointer_type(), + result, + global, + ); + } + } + Instruction::Float(ins) => { + let var = self.variables[&ins.register]; + let global = + self.module.add_literal(&Constant::Float(ins.value)); + + builder.load_global_to_stack( + self.context.pointer_type(), + var, + global, + ); + } + Instruction::String(ins) => { + let var = self.variables[&ins.register]; + let global = self + .module + .add_literal(&Constant::String(ins.value.clone())); + + builder.load_global_to_stack( + self.context.pointer_type(), + var, + global, + ); + } + Instruction::MoveRegister(ins) => { + let source = self.variables[&ins.source]; + let target = self.variables[&ins.target]; + + builder.store( + target, + builder.load(self.context.pointer_type(), source), + ); + } + Instruction::MoveResult(ins) => { + let reg_var = self.variables[&ins.register]; + let res_var = self.variables[&ins.result]; + let val = builder.load_field( + self.types.result, + res_var, + RESULT_VALUE_INDEX as _, + ); + + builder.store(reg_var, val); + } + Instruction::CallStatic(ins) => { + let reg_var = self.variables[&ins.register]; + let func_name = &self.names.methods[&ins.method]; + let func = self.module.add_method(func_name, ins.method); + let mut args: Vec = vec![ + builder.load_pointer(self.types.state, state_var).into(), + builder.load_opaque_pointer(proc_var).into(), + ]; + + for reg in &ins.arguments { + args.push( + builder.load_opaque_pointer(self.variables[reg]).into(), + ); + } + + builder.store(reg_var, builder.call(func, &args)); + } + Instruction::CallInstance(ins) => { + let reg_var = self.variables[&ins.register]; + 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![ + builder.load_pointer(self.types.state, state_var).into(), + builder.load_opaque_pointer(proc_var).into(), + builder.load_opaque_pointer(rec_var).into(), + ]; + + for reg in &ins.arguments { + args.push( + builder.load_opaque_pointer(self.variables[reg]).into(), + ); + } + + builder.store(reg_var, builder.call(func, &args)); + } + Instruction::CallDynamic(ins) => { + // 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.add_basic_block(); + let after_loop = self.add_basic_block(); + + let index_type = self.context.i64_type(); + let index_var = self.new_stack_slot(index_type); + let reg_var = self.variables[&ins.register]; + let rec_var = self.variables[&ins.receiver]; + + let rec = builder.load_opaque_pointer(rec_var); + let info = &self.types.methods[&ins.method]; + let rec_class = self.class_of(builder, rec); + let rec_type = self.types.empty_class; + + // (class.method_slots - 1) as u64 + let len = builder.build_int_cast( + builder.int_sub( + builder + .load_field( + rec_type, + rec_class, + CLASS_METHODS_COUNT_INDEX, + ) + .into_int_value(), + builder.u16_literal(1), + ), + self.context.i64_type(), + "", + ); + + let hash = builder.u64_literal(info.hash); + + builder.store(index_var, hash); + + let space = AddressSpace::default(); + let func_type = self.types.methods[&ins.method].signature; + let func_var = self.new_stack_slot(func_type.ptr_type(space)); + + builder.build_unconditional_branch(loop_start); + + // The start of the probing loop (probing is necessary). + builder.position_at_end(loop_start); + + // slot = index & len + let index = + builder.load(index_type, index_var).into_int_value(); + let slot = builder.bit_and(index, len); + let method_addr = unsafe { + builder.build_gep( + rec_type, + rec_class, + &[ + builder.u32_literal(0), + builder.u32_literal(CLASS_METHODS_INDEX as _), + slot, + ], + "", + ) + }; + + let method = builder + .load(self.types.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 eq_block = self.add_basic_block(); + let ne_block = self.add_basic_block(); + + // method.hash == hash + let hash_eq = builder.int_eq( + builder + .extract_field(method, METHOD_HASH_INDEX) + .into_int_value(), + hash, + ); + + builder + .build_conditional_branch(hash_eq, eq_block, ne_block); + + // The block to jump to when the hash codes didn't match. + builder.position_at_end(ne_block); + builder.store( + index_var, + builder.int_add(index, builder.u64_literal(1)), + ); + builder.build_unconditional_branch(loop_start); + + // The block to jump to when the hash codes matched + builder.position_at_end(eq_block); + } + + builder.store( + func_var, + builder.extract_field(method, METHOD_FUNCTION_INDEX), + ); + + builder.build_unconditional_branch(after_loop); + + // The block to jump to at the end of the loop, used for + // calling the native function. + builder.position_at_end(after_loop); + + let mut args: Vec = vec![ + builder.load_pointer(self.types.state, state_var).into(), + builder.load_opaque_pointer(proc_var).into(), + rec.into(), + ]; + + for reg in &ins.arguments { + args.push( + builder.load_opaque_pointer(self.variables[reg]).into(), + ); + } + + let func_val = + builder.load_pointer(func_type.ptr_type(space), func_var); + let result = builder + .build_indirect_call(func_type, func_val, &args, "") + .try_as_basic_value() + .left() + .unwrap(); + + builder.store(reg_var, result); + } + Instruction::CallClosure(ins) => { + let reg_var = self.variables[&ins.register]; + 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.types.state.ptr_type(space).into(), // State + self.context.pointer_type().into(), // Process + self.context.pointer_type().into(), // Closure + ]; + + for _ in &ins.arguments { + sig_args.push(self.context.pointer_type().into()); + } + + // Load the method from the method table. + let rec = builder.load_opaque_pointer(rec_var); + let header = + builder.cast_to_untagged_pointer(rec, self.types.header); + let class = builder + .load_field(self.types.header, header, HEADER_CLASS_INDEX) + .into_pointer_value(); + + let mut args: Vec = vec![ + builder.load_pointer(self.types.state, state_var).into(), + builder.load_opaque_pointer(proc_var).into(), + rec.into(), + ]; + + for reg in &ins.arguments { + args.push( + builder.load_opaque_pointer(self.variables[reg]).into(), + ); + } + + let method_addr = unsafe { + builder.build_gep( + self.types.empty_class, + class, + &[ + builder.u32_literal(0), + builder.u32_literal(CLASS_METHODS_INDEX as _), + builder.u32_literal(CLOSURE_CALL_INDEX as _), + builder.u32_literal(METHOD_FUNCTION_INDEX), + ], + "", + ) + }; + + let func_type = if ins.throws { + self.types.result.fn_type(&sig_args, false) + } else { + self.context.pointer_type().fn_type(&sig_args, false) + }; + + let func_val = builder + .load_pointer(func_type.ptr_type(space), method_addr); + let result = builder + .build_indirect_call(func_type, func_val, &args, "") + .try_as_basic_value() + .left() + .unwrap(); + + builder.store(reg_var, result); + } + Instruction::CallDropper(_) => { + // TODO: implement + } + 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 = 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 = 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 addr = unsafe { + builder.build_gep( + self.types.message, + message, + &[ + builder.u32_literal(0), + builder.u32_literal(MESSAGE_ARGUMENTS_INDEX), + builder.u32_literal(index as _), + ], + "", + ) + }; + + builder.store( + addr, + builder.load_opaque_pointer(self.variables[reg]), + ); + } + + let state = + builder.load_pointer(self.types.state, state_var).into(); + let sender = builder.load_opaque_pointer(proc_var).into(); + let receiver = builder.load_opaque_pointer(rec_var).into(); + + builder.call_void( + send_message, + &[state, sender, receiver, message.into()], + ); + } + Instruction::GetField(ins) => { + let reg_var = self.variables[&ins.register]; + let rec_var = self.variables[&ins.receiver]; + let index = (ins.field.index(self.db) + FIELD_OFFSET) as u32; + let rec = builder.load_opaque_pointer(rec_var); + + // TODO: the problem is this: if the receiver is typed as + // `Self`, and we are in a closure, then here we'll resolve the + // type's class to that of the closure, not of the actual + // receiver. + let class = self + .register_type(ins.receiver) + .class_id(self.db, self.class_id) + .unwrap(); + let layout = self.types.instances[&class]; + + // TODO: remove + if index as usize >= layout.get_field_types().len() { + println!( + "{} index {} out of bounds, length: {}, location: {}:{}", + class.name(self.db), + index, + layout.get_field_types().len(), + self.mir.location(ins.location).module.file(self.db).display(), + self.mir.location(ins.location).range.line_range.start(), + ); + } + + let source = builder.cast_to_untagged_pointer(rec, layout); + let field = builder.load_field(layout, source, index); + + builder.store(reg_var, field); + } + Instruction::SetField(ins) => { + let rec_var = self.variables[&ins.receiver]; + let val_var = self.variables[&ins.value]; + let index = (ins.field.index(self.db) + FIELD_OFFSET) as u32; + let rec = builder.load_opaque_pointer(rec_var); + let val = builder.load_opaque_pointer(val_var); + let class = self + .register_type(ins.receiver) + .class_id(self.db, self.class_id) + .unwrap(); + let layout = self.types.instances[&class]; + let source = builder.cast_to_untagged_pointer(rec, layout); + + builder.store_field(layout, source, index, val); + } + Instruction::CheckRefs(ins) => { + let var = self.variables[&ins.register]; + let proc = builder.load_opaque_pointer(proc_var).into(); + let check = builder.load_opaque_pointer(var).into(); + let func = + self.module.runtime_function(RuntimeFunction::CheckRefs); + + builder.call_void(func, &[proc, check]); + } + Instruction::Free(ins) => { + let var = self.variables[&ins.register]; + let free = builder.load_opaque_pointer(var).into(); + let func = self.module.runtime_function(RuntimeFunction::Free); + + builder.call_void(func, &[free]); + } + Instruction::Clone(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.source]; + let val = builder.load_opaque_pointer(val_var); + + match ins.kind { + CloneKind::Float => { + let state = + builder.load_pointer(self.types.state, state_var); + let func = self + .module + .runtime_function(RuntimeFunction::FloatClone); + let result = builder + .call(func, &[state.into(), val.into()]) + .into_pointer_value(); + + builder.store(reg_var, result); + } + CloneKind::Int => { + let addr = builder.cast_pointer_to_int(val); + let mask = builder.i64_literal(INT_MASK); + let bits = builder.bit_and(addr, mask); + let cond = builder.int_eq(bits, mask); + let after_block = self.add_basic_block(); + let tagged_block = self.add_basic_block(); + let heap_block = self.add_basic_block(); + + builder.build_conditional_branch( + cond, + tagged_block, + heap_block, + ); + + // The block to jump to when the Int is a tagged Int. + builder.position_at_end(tagged_block); + builder.store(reg_var, val); + builder.build_unconditional_branch(after_block); + + // The block to jump to when the Int is a boxed Int. + builder.position_at_end(heap_block); + + let func = self + .module + .runtime_function(RuntimeFunction::IntClone); + let state = + builder.load_pointer(self.types.state, state_var); + let result = builder + .call(func, &[state.into(), val.into()]) + .into_pointer_value(); + + builder.store(reg_var, result); + builder.build_unconditional_branch(after_block); + + builder.position_at_end(after_block); + } + } + } + Instruction::Increment(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.value]; + let val = builder.load_opaque_pointer(val_var); + let header = + builder.cast_to_untagged_pointer(val, self.types.header); + let one = builder.u32_literal(1); + let old = builder + .load_field(self.types.header, header, HEADER_REFS_INDEX) + .into_int_value(); + let new = builder.build_int_add(old, one, ""); + + builder.store_field( + self.types.header, + header, + HEADER_REFS_INDEX, + new, + ); + + let old_addr = builder.cast_pointer_to_int(val); + let mask = builder.i64_literal(REF_MASK); + let new_addr = builder.bit_or(old_addr, mask); + let ref_ptr = builder.build_int_to_ptr( + new_addr, + self.context.pointer_type(), + "", + ); + + builder.store(reg_var, ref_ptr); + } + Instruction::Decrement(ins) => { + let var = self.variables[&ins.register]; + let header = builder.cast_to_untagged_pointer( + builder.load_opaque_pointer(var), + self.types.header, + ); + + let old_refs = builder + .load_field(self.types.header, header, HEADER_REFS_INDEX) + .into_int_value(); + let one = builder.u32_literal(1); + let new_refs = builder.build_int_sub(old_refs, one, ""); + + builder.store_field( + self.types.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 = builder.load_opaque_pointer(val_var); + let one = builder.u32_literal(1); + let field = builder + .build_struct_gep( + self.types.header, + val, + HEADER_REFS_INDEX as u32, + "", + ) + .unwrap(); + let op = AtomicRMWBinOp::Add; + let order = AtomicOrdering::AcquireRelease; + + builder.build_atomicrmw(op, field, one, order).unwrap(); + builder.store(reg_var, val); + } + Instruction::DecrementAtomic(ins) => { + let var = self.variables[&ins.register]; + let header = builder.load_pointer(self.types.header, var); + let one = builder.u32_literal(1); + let field = builder + .build_struct_gep( + self.types.header, + header, + HEADER_REFS_INDEX as u32, + "", + ) + .unwrap(); + let op = AtomicRMWBinOp::Sub; + let order = AtomicOrdering::AcquireRelease; + let old_refs = + builder.build_atomicrmw(op, field, one, order).unwrap(); + let is_zero = builder.int_eq(old_refs, one); + + builder.build_conditional_branch( + is_zero, + llvm_blocks[ins.if_true.0], + llvm_blocks[ins.if_false.0], + ); + } + Instruction::Allocate(ins) => { + let reg_var = self.variables[&ins.register]; + let name = &self.names.classes[&ins.class]; + let class = self + .module + .add_class(ins.class, name) + .as_pointer_value() + .into(); + let func = + self.module.runtime_function(RuntimeFunction::ObjectNew); + let ptr = builder.call(func, &[class]); + + builder.store(reg_var, ptr); + } + Instruction::Spawn(ins) => { + let reg_var = self.variables[&ins.register]; + let name = &self.names.classes[&ins.class]; + let class = self + .module + .add_class(ins.class, name) + .as_pointer_value() + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::ProcessNew); + let ptr = builder.call(func, &[proc, class]); + + 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); + + builder.load_global_to_stack( + self.context.pointer_type(), + var, + global, + ); + } + Instruction::Reduce(ins) => { + let amount = self + .context + .i16_type() + .const_int(ins.amount as u64, false) + .into(); + let proc = builder.load_opaque_pointer(proc_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::Reduce); + + builder.call_void(func, &[proc, amount]); + } + Instruction::Finish(ins) => { + let proc = builder.load_opaque_pointer(proc_var).into(); + let terminate = self + .context + .bool_type() + .const_int(ins.terminate as _, false) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ProcessFinishMessage); + + builder.call_void(func, &[proc, terminate]); + builder.build_unreachable(); + } + Instruction::Reference(_) => unreachable!(), + Instruction::Drop(_) => unreachable!(), + } + } + + fn kind_of( + &mut self, + builder: &Builder<'a, 'ctx>, + 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.new_stack_slot(self.context.i8_type()); + let perm_block = self.add_basic_block(); + let ref_block = self.add_basic_block(); + let header_block = self.add_basic_block(); + let after_block = self.add_basic_block(); + let pointer = builder.load_opaque_pointer(pointer_variable); + let addr = builder.cast_pointer_to_int(pointer); + let mask = builder.i64_literal(TAG_MASK); + let bits = builder.bit_and(addr, mask); + + // This generates the equivalent of the following: + // + // match ptr as usize & MASK { + // INT_MASK => ... + // REF_MASK => ... + // _ => ... + // } + builder.build_switch( + bits, + header_block, + &[ + (builder.i64_literal(INT_MASK), perm_block), + (builder.i64_literal(REF_MASK), ref_block), + ], + ); + + // The case for when the value is a tagged integer. + builder.position_at_end(perm_block); + builder.store(result, builder.u8_literal(PERMANENT_KIND)); + builder.build_unconditional_branch(after_block); + + // The case for when the value is a reference. + builder.position_at_end(ref_block); + builder.store(result, builder.u8_literal(REF_KIND)); + builder.build_unconditional_branch(after_block); + + // The fallback case where we read the kind from the object header. This + // generates the equivalent of `(*(ptr as *mut Header)).kind`. + builder.position_at_end(header_block); + + let header_val = builder + .load_field(self.types.header, pointer, HEADER_KIND_INDEX) + .into_int_value(); + + builder.store(result, header_val); + builder.build_unconditional_branch(after_block); + builder.position_at_end(after_block); + result + } + + fn class_of( + &mut self, + builder: &Builder<'a, 'ctx>, + receiver: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + let tagged_block = self.add_basic_block(); + let heap_block = self.add_basic_block(); + let after_block = self.add_basic_block(); + let class_var = self.new_stack_slot(self.context.pointer_type()); + let int_class = self + .module + .add_class(ClassId::int(), &self.names.classes[&ClassId::int()]); + + let addr = builder.cast_pointer_to_int(receiver); + let mask = builder.i64_literal(INT_MASK); + let bits = builder.bit_and(addr, mask); + let is_tagged = builder.int_eq(bits, mask); + + builder.build_conditional_branch(is_tagged, tagged_block, heap_block); + + // The block to jump to when the receiver is a tagged integer. + builder.position_at_end(tagged_block); + builder.store( + class_var, + builder.load_opaque_pointer(int_class.as_pointer_value()), + ); + builder.build_unconditional_branch(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. + builder.position_at_end(heap_block); + + let header = + builder.cast_to_untagged_pointer(receiver, self.types.header); + let class = builder + .load_field(self.types.header, header, HEADER_CLASS_INDEX) + .into_pointer_value(); + + builder.store(class_var, class); + builder.build_unconditional_branch(after_block); + + // The block to jump to to load the method pointer. + builder.position_at_end(after_block); + builder.load_pointer(self.types.empty_class, class_var) + } + + fn read_int( + &mut self, + builder: &Builder<'a, 'ctx>, + variable: PointerValue<'ctx>, + ) -> IntValue<'ctx> { + let pointer = builder.load_opaque_pointer(variable); + let res_type = self.context.i64_type(); + let res_var = self.new_stack_slot(res_type); + let tagged_block = self.add_basic_block(); + let heap_block = self.add_basic_block(); + let after_block = self.add_basic_block(); + + let addr = builder.cast_pointer_to_int(pointer); + let mask = builder.i64_literal(INT_MASK); + let bits = builder.bit_and(addr, mask); + let cond = builder.int_eq(bits, mask); + + builder.build_conditional_branch(cond, tagged_block, heap_block); + + // The block to jump to when the Int is a tagged Int. + builder.position_at_end(tagged_block); + + let shift = builder.i64_literal(INT_SHIFT as i64); + let untagged = builder.signed_right_shift(addr, shift); + + builder.store(res_var, untagged); + builder.build_unconditional_branch(after_block); + + // The block to jump to when the Int is a heap Int. + builder.position_at_end(heap_block); + + let layout = self.types.instances[&ClassId::int()]; + + builder.store( + res_var, + builder.load_field(layout, pointer, BOXED_INT_VALUE_INDEX), + ); + builder.build_unconditional_branch(after_block); + + builder.position_at_end(after_block); + builder.load(res_type, res_var).into_int_value() + } + + fn read_float( + &mut self, + builder: &Builder<'a, 'ctx>, + variable: PointerValue<'ctx>, + ) -> FloatValue<'ctx> { + let layout = self.types.instances[&ClassId::float()]; + let ptr = builder.load_pointer(layout, variable); + + builder + .load_field(layout, ptr, BOXED_FLOAT_VALUE_INDEX) + .into_float_value() + } + + fn new_float( + &mut self, + builder: &Builder<'a, 'ctx>, + state_var: PointerValue<'ctx>, + value: FloatValue<'ctx>, + ) -> PointerValue<'ctx> { + let func = self.module.runtime_function(RuntimeFunction::FloatBoxed); + let state = builder.load_pointer(self.types.state, state_var); + + builder.call(func, &[state.into(), value.into()]).into_pointer_value() + } + + fn checked_int_operation( + &mut self, + name: &str, + builder: &Builder<'a, 'ctx>, + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + reg_var: PointerValue<'ctx>, + lhs_var: PointerValue<'ctx>, + rhs_var: PointerValue<'ctx>, + ) { + let ok_block = self.add_basic_block(); + let err_block = self.add_basic_block(); + let after_block = self.add_basic_block(); + let lhs = self.read_int(builder, lhs_var); + let rhs = self.read_int(builder, rhs_var); + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let add = Intrinsic::find(name) + .and_then(|intr| { + intr.get_declaration( + &self.module.inner, + &[self.context.i64_type().into()], + ) + }) + .unwrap(); + + let res = + builder.call(add, &[lhs.into(), rhs.into()]).into_struct_value(); + + // Check if we overflowed the operation. + let new_val = builder + .extract_field(res, LLVM_RESULT_VALUE_INDEX) + .into_int_value(); + let overflow = builder + .extract_field(res, LLVM_RESULT_STATUS_INDEX) + .into_int_value(); + + builder.build_conditional_branch(overflow, err_block, ok_block); + + // The block to jump to if the operation didn't overflow. + builder.position_at_end(ok_block); + builder.store(reg_var, self.new_int(builder, state_var, new_val)); + builder.build_unconditional_branch(after_block); + + // The block to jump to if the operation overflowed. + builder.position_at_end(err_block); + + let proc = builder.load_opaque_pointer(proc_var); + + builder.call_void(func, &[proc.into(), lhs.into(), rhs.into()]); + builder.build_unreachable(); + builder.position_at_end(after_block); + } + + fn new_int( + &mut self, + builder: &Builder<'a, 'ctx>, + state_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + let res_var = self.new_stack_slot(self.context.pointer_type()); + let tagged_block = self.add_basic_block(); + let heap_block = self.add_basic_block(); + let after_block = self.add_basic_block(); + let and_block = self.add_basic_block(); + + let min = builder.i64_literal(MIN_INT); + let max = builder.i64_literal(MAX_INT); + + builder.build_conditional_branch( + 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. + builder.position_at_end(and_block); + builder.build_conditional_branch( + builder.int_le(value, max), + tagged_block, + heap_block, + ); + + // The block to jump to when the Int fits in a tagged pointer. + builder.position_at_end(tagged_block); + + let shift = builder.i64_literal(INT_SHIFT as i64); + let mask = builder.i64_literal(INT_MASK); + let addr = builder.bit_or(builder.left_shift(value, shift), mask); + + builder.store(res_var, builder.cast_int_to_pointer(addr)); + builder.build_unconditional_branch(after_block); + + // The block to jump to when the Int must be boxed. + builder.position_at_end(heap_block); + + let func = self.module.runtime_function(RuntimeFunction::IntBoxed); + let state = builder.load_pointer(self.types.state, state_var); + let res = builder.call(func, &[state.into(), value.into()]); + + builder.store(res_var, res); + builder.build_unconditional_branch(after_block); + + builder.position_at_end(after_block); + builder.load_opaque_pointer(res_var) + } + + fn new_bool( + &mut self, + builder: &Builder<'a, 'ctx>, + state_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + let result = self.new_stack_slot(self.context.pointer_type()); + let state = builder.load_pointer(self.types.state, state_var); + let true_block = self.add_basic_block(); + let false_block = self.add_basic_block(); + let after_block = self.add_basic_block(); + + builder.build_conditional_branch(value, true_block, false_block); + + // The block to jump to when the condition is true. + builder.position_at_end(true_block); + builder.store( + result, + builder.load_field(self.types.state, state, TRUE_INDEX), + ); + builder.build_unconditional_branch(after_block); + + // The block to jump to when the condition is false. + builder.position_at_end(false_block); + builder.store( + result, + builder.load_field(self.types.state, state, FALSE_INDEX), + ); + builder.build_unconditional_branch(after_block); + + builder.position_at_end(after_block); + builder.load_opaque_pointer(result) + } + + fn new_stack_slot>( + &mut self, + value_type: T, + ) -> PointerValue<'ctx> { + let builder = Builder::new(self.context, self.types); + let block = self.function.get_first_basic_block().unwrap(); + + if let Some(ins) = block.get_first_instruction() { + builder.position_before(&ins); + } else { + builder.position_at_end(block); + } + + builder.build_alloca(value_type, "") + } + + fn check_division_overflow( + &self, + builder: &Builder<'a, 'ctx>, + process_var: PointerValue<'ctx>, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) { + let min = builder.i64_literal(i64::MIN); + let minus_one = builder.i64_literal(-1); + let zero = builder.i64_literal(0); + let and_block = self.add_basic_block(); + let or_block = self.add_basic_block(); + let overflow_block = self.add_basic_block(); + let ok_block = self.add_basic_block(); + + // lhs == MIN AND rhs == -1 + builder.build_conditional_branch( + builder.int_eq(lhs, min), + and_block, + or_block, + ); + + builder.position_at_end(and_block); + builder.build_conditional_branch( + builder.int_eq(rhs, minus_one), + overflow_block, + or_block, + ); + + // OR rhs == 0 + builder.position_at_end(or_block); + builder.build_conditional_branch( + builder.int_eq(rhs, zero), + overflow_block, + ok_block, + ); + + // The block to jump to if an overflow would occur. + builder.position_at_end(overflow_block); + + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let proc = builder.load_opaque_pointer(process_var); + + builder.call_void(func, &[proc.into(), lhs.into(), rhs.into()]); + builder.build_unreachable(); + + // The block to jump to when it's safe to perform the + // operation. + builder.position_at_end(ok_block); + } + + fn check_shift_bits( + &self, + process_var: PointerValue<'ctx>, + builder: &Builder<'a, 'ctx>, + value: IntValue<'ctx>, + bits: IntValue<'ctx>, + ) { + let ok_block = self.add_basic_block(); + let err_block = self.add_basic_block(); + let cond = + builder.int_gt(bits, builder.i64_literal((i64::BITS - 1) as _)); + + builder.build_conditional_branch(cond, err_block, ok_block); + + // The block to jump to when the operation would overflow. + builder.position_at_end(err_block); + + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let proc = builder.load_opaque_pointer(process_var); + + builder.call_void(func, &[proc.into(), value.into(), bits.into()]); + builder.build_unreachable(); + + // The block to jump to when all is well. + builder.position_at_end(ok_block); + } + + fn define_register_variables(&mut self, builder: &Builder<'a, 'ctx>) { + for (index, reg) in self.method.registers.iter().enumerate() { + let id = RegisterId(index); + + let typ = if let RegisterKind::Result = reg.kind { + self.types.result.as_basic_type_enum() + } else { + self.context.pointer_type().as_basic_type_enum() + }; + + self.variables.insert(id, builder.build_alloca(typ, "")); + } + } + + fn register_type(&self, register: RegisterId) -> types::TypeRef { + self.method.registers.value_type(register) + } + + fn add_basic_block(&self) -> BasicBlock<'ctx> { + self.context.append_basic_block(self.function, "") + } + + fn new_result_value( + &mut self, + builder: &Builder<'a, 'ctx>, + value: BasicValueEnum<'ctx>, + error: bool, + ) -> BasicValueEnum<'ctx> { + let var_type = self.types.result; + let var = self.new_stack_slot(var_type); + let tag = if error { RESULT_ERROR_VALUE } else { RESULT_OK_VALUE }; + let tag_lit = builder.u8_literal(tag); + + builder.store_field(var_type, var, RESULT_TAG_INDEX, tag_lit); + builder.store_field(var_type, var, RESULT_VALUE_INDEX, value); + builder.load(var_type, var) + } +} + +/// A pass for generating the entry module and method (i.e. `main()`). +pub(crate) struct GenerateMain<'a, 'ctx> { + db: &'a Database, + mir: &'a Mir, + types: &'a Types<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'a Module<'a, 'ctx>, +} + +impl<'a, 'ctx> GenerateMain<'a, 'ctx> { + fn run(mut self) { + let space = AddressSpace::default(); + let builder = Builder::new(self.context, self.types); + let fn_type = self.context.i32_type().fn_type(&[], false); + let fn_val = self.module.add_function("main", fn_type, None); + let entry_block = self.context.append_basic_block(fn_val, ""); + + // TODO: move to dedicated type/function + builder.position_at_end(entry_block); + + let layout = self.types.method_counts; + let counts = builder.build_alloca(layout, ""); + + builder.store_field(layout, counts, 0, self.methods(ClassId::int())); + builder.store_field(layout, counts, 1, self.methods(ClassId::float())); + builder.store_field(layout, counts, 2, self.methods(ClassId::string())); + builder.store_field(layout, counts, 3, self.methods(ClassId::array())); + builder.store_field( + layout, + counts, + 4, + self.methods(ClassId::boolean()), + ); + builder.store_field(layout, counts, 5, self.methods(ClassId::nil())); + builder.store_field( + layout, + counts, + 6, + self.methods(ClassId::byte_array()), + ); + builder.store_field( + layout, + counts, + 7, + self.methods(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 runtime = + builder.call(rt_new, &[counts.into()]).into_pointer_value(); + let state = + 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); + + 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.types.context.ptr_type(space).into()], + false, + ), + None, + ) + .as_global_value() + .as_pointer_value(); + + let main_class = builder.load(self.types.empty_class, main_class_ptr); + + builder.call_void( + rt_start, + &[runtime.into(), main_class.into(), main_method.into()], + ); + + builder + .build_return(Some(&self.context.i32_type().const_int(0, false))); + + if let Err(err) = self.module.verify() { + panic!("The LLVM main module is invalid:\n\n{}", err.to_string()); + } + + self.module + .print_to_file("/tmp/main.ll") + .expect("Failed to print the main LLVM IR"); + } + + fn methods(&self, id: ClassId) -> IntValue<'ctx> { + self.context.i16_type().const_int(self.types.methods(id) as _, false) + } +} + +#[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/mir/mod.rs b/compiler/src/mir/mod.rs index d4a7d71cb..cdf5151b5 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -3,11 +3,11 @@ //! 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 types::collections::IndexMap; +use types::BuiltinFunction; /// The number of reductions to perform after calling a method. const CALL_COST: u16 = 1; @@ -33,7 +33,7 @@ impl Registers { pub(crate) fn alloc(&mut self, value_type: types::TypeRef) -> RegisterId { let id = self.values.len(); - self.values.push(Register { value_type }); + self.values.push(Register { value_type, kind: RegisterKind::Regular }); RegisterId(id) } @@ -45,8 +45,12 @@ impl Registers { self.get(register).value_type } - pub(crate) fn len(&self) -> usize { - self.values.len() + pub(crate) fn mark_as_result(&mut self, register: RegisterId) { + self.values[register.0].kind = RegisterKind::Result; + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.values.iter() } } @@ -169,68 +173,58 @@ impl Block { pub(crate) fn branch_result( &mut self, + result: RegisterId, ok: BlockId, error: BlockId, location: LocationId, ) { self.instructions.push(Instruction::BranchResult(Box::new( - BranchResult { ok, error, location }, + BranchResult { result, ok, error, location }, ))); } - pub(crate) fn jump_table( + pub(crate) fn switch( &mut self, register: RegisterId, blocks: Vec, location: LocationId, ) { - self.instructions.push(Instruction::JumpTable(Box::new(JumpTable { + self.instructions.push(Instruction::Switch(Box::new(Switch { register, blocks, location, }))); } - pub(crate) fn return_value( + pub(crate) fn switch_kind( &mut self, register: RegisterId, + blocks: Vec, location: LocationId, ) { - self.instructions - .push(Instruction::Return(Box::new(Return { register, location }))); + self.instructions.push(Instruction::SwitchKind(Box::new(SwitchKind { + register, + blocks, + location, + }))); } - pub(crate) fn throw_value( + pub(crate) fn return_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 }, - ))); + .push(Instruction::Return(Box::new(Return { register, location }))); } - pub(crate) fn throw_async_value( + pub(crate) fn throw_value( &mut self, register: RegisterId, - value: RegisterId, location: LocationId, ) { - self.instructions.push(Instruction::ThrowAsync(Box::new(ThrowAsync { - register, - value, - location, - }))); + self.instructions + .push(Instruction::Throw(Box::new(Throw { register, location }))); } pub(crate) fn finish(&mut self, terminate: bool, location: LocationId) { @@ -323,19 +317,6 @@ impl Block { ))); } - pub(crate) fn strings( - &mut self, - register: RegisterId, - values: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::Strings(Box::new(Strings { - register, - values, - location, - }))); - } - pub(crate) fn move_register( &mut self, target: RegisterId, @@ -347,6 +328,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 +365,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 +439,29 @@ 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, + result: RegisterId, location: LocationId, ) { self.instructions.push(Instruction::MoveResult(Box::new(MoveResult { register, + result, 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,38 +469,42 @@ 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, + throws: bool, location: LocationId, ) { self.instructions.push(Instruction::CallClosure(Box::new( - CallClosure { receiver, arguments, location }, + CallClosure { register, receiver, arguments, throws, location }, ))); } @@ -519,43 +520,17 @@ impl Block { 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 +542,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, }))); } @@ -631,45 +588,28 @@ impl Block { }))); } - pub(crate) fn get_constant( + pub(crate) fn spawn( &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( - &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,7 +626,9 @@ impl Block { pub(crate) enum Constant { Int(i64), Float(f64), + // TODO: use Rc to make cloning cheaper String(String), + // TODO: use Rc to make cloning cheaper Array(Vec), } @@ -735,6 +677,17 @@ impl fmt::Display for Constant { } } +/// A flag signalling what kind of register a register is. +#[derive(Clone, Copy)] +pub(crate) enum RegisterKind { + /// A regular register holding an Inko value. + Regular, + + /// A register used for storing a runtime Result value, used for error + /// handling. + Result, +} + /// A MIR register/temporary variable. /// /// Registers may be introduced through user-defined local variables, @@ -743,6 +696,7 @@ impl fmt::Display for Constant { #[derive(Clone)] pub(crate) struct Register { pub(crate) value_type: types::TypeRef, + pub(crate) kind: RegisterKind, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -756,13 +710,15 @@ 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 SwitchKind { pub(crate) register: RegisterId, pub(crate) blocks: Vec, pub(crate) location: LocationId, @@ -770,6 +726,7 @@ pub(crate) struct JumpTable { #[derive(Clone)] pub(crate) struct BranchResult { + pub(crate) result: RegisterId, pub(crate) ok: BlockId, pub(crate) error: BlockId, pub(crate) location: LocationId, @@ -781,7 +738,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 +745,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)] @@ -867,9 +778,6 @@ pub(crate) struct Free { pub(crate) enum CloneKind { Float, Int, - Process, - String, - Other, } /// Clones a value type. @@ -884,20 +792,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 +813,24 @@ 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 DecrementAtomic { + pub(crate) register: RegisterId, + pub(crate) if_true: BlockId, + pub(crate) if_false: BlockId, + pub(crate) location: LocationId, +} + #[derive(Clone)] pub(crate) struct MoveResult { pub(crate) register: RegisterId, + pub(crate) result: RegisterId, pub(crate) location: LocationId, } @@ -962,20 +871,6 @@ pub(crate) struct Throw { 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 +892,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 +902,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 +912,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,21 +921,17 @@ pub(crate) struct CallDynamic { #[derive(Clone)] pub(crate) struct CallClosure { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) arguments: Vec, + pub(crate) throws: bool, pub(crate) location: LocationId, } #[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,16 +942,6 @@ 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)] @@ -1098,18 +975,9 @@ pub(crate) struct Allocate { } #[derive(Clone)] -pub(crate) struct IntEq { - pub(crate) register: RegisterId, - pub(crate) left: RegisterId, - pub(crate) right: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct StringEq { +pub(crate) struct Spawn { pub(crate) register: RegisterId, - pub(crate) left: RegisterId, - pub(crate) right: RegisterId, + pub(crate) class: types::ClassId, pub(crate) location: LocationId, } @@ -1134,7 +1002,8 @@ pub(crate) enum Instruction { AllocateArray(Box), Branch(Box), BranchResult(Box), - JumpTable(Box), + Switch(Box), + SwitchKind(Box), False(Box), Float(Box), Goto(Box), @@ -1144,34 +1013,29 @@ pub(crate) enum Instruction { 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), } @@ -1181,7 +1045,8 @@ impl Instruction { 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, @@ -1189,37 +1054,32 @@ impl Instruction { 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::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, } @@ -1235,13 +1095,25 @@ impl Instruction { } Instruction::BranchResult(ref v) => { format!( - "branch_result ok = b{}, error = b{}", - v.ok.0, v.error.0 + "branch_result r{}, ok = b{}, error = b{}", + v.result.0, v.ok.0, v.error.0 + ) + } + Instruction::Switch(ref v) => { + format!( + "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,11 +1162,8 @@ 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) + format!("r{} = move_result r{}", v.register.0, v.result.0) } Instruction::Return(ref v) => { format!("return r{}", v.register.0) @@ -1302,32 +1171,28 @@ impl Instruction { 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::Spawn(ref v) => { + format!("r{} = spawn {}", v.register.0, v.class.name(db)) + } Instruction::AllocateArray(ref v) => { format!("r{} = array [{}]", v.register.0, join(&v.values)) } - Instruction::Strings(ref v) => { - format!("r{} = strings [{}]", 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 +1200,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,7 +1209,8 @@ impl Instruction { } Instruction::CallClosure(ref v) => { format!( - "call_closure r{}({})", + "r{} = call_closure r{}({})", + v.register.0, v.receiver.0, join(&v.arguments) ) @@ -1352,24 +1219,16 @@ impl Instruction { format!("call_dropper r{}", 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 +1250,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 +1276,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,11 +1336,17 @@ pub(crate) struct Method { pub(crate) id: types::MethodId, pub(crate) registers: Registers, pub(crate) body: Graph, + pub(crate) arguments: Vec, } impl Method { pub(crate) fn new(id: types::MethodId) -> Self { - Self { id, body: Graph::new(), registers: Registers::new() } + Self { + id, + body: Graph::new(), + registers: Registers::new(), + arguments: Vec::new(), + } } } @@ -1512,6 +1374,7 @@ pub(crate) struct Mir { pub(crate) classes: HashMap, pub(crate) traits: HashMap, pub(crate) methods: HashMap, + // TODO: do we really need this? pub(crate) closure_classes: HashMap, locations: Vec, } @@ -1558,6 +1421,10 @@ impl Mir { pub(crate) fn location(&self, index: LocationId) -> &Location { &self.locations[index.0] } + + pub(crate) fn number_of_methods(&self, class: types::ClassId) -> usize { + self.classes.get(&class).unwrap().methods.len() + } } #[cfg(test)] diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 5c64b6629..2755eccb6 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -8,12 +8,12 @@ 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 types::format::format_type; +use types::{self, Block as _, TypeBounds, FIELDS_LIMIT}; const SELF_NAME: &str = "self"; const SELF_ID: usize = 0; @@ -393,22 +393,27 @@ impl<'a> GenerateDropper<'a> { 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 loc = self.mir.add_location( + self.module.id, + async_dropper, + self.location.clone(), + ); 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(), @@ -417,6 +422,9 @@ impl<'a> GenerateDropper<'a> { lower.current_block_mut().reduce_call(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); } @@ -429,25 +437,31 @@ impl<'a> GenerateDropper<'a> { 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 method_type = self.method_type(name, types::MethodKind::Mutable); let mut method = Method::new(method_type); + let loc = self.mir.add_location( + self.module.id, + method_type, + self.location.clone(), + ); 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()); - 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); } let variants = self.class.id.variants(lower.db()); @@ -485,7 +499,7 @@ 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); + lower.block_mut(before_block).switch(tag_reg, blocks, loc); lower.current_block = after_block; @@ -507,24 +521,31 @@ impl<'a> GenerateDropper<'a> { ) -> 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 method_type = self.method_type(name, kind); let mut method = Method::new(method_type); + let loc = self.mir.add_location( + self.module.id, + method_type, + self.location.clone(), + ); 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()); - 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); } // We check the ref count _after_ running the destructor, as otherwise a @@ -565,7 +586,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 +595,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) { @@ -1061,8 +1081,9 @@ impl<'a> LowerToMir<'a> { let mut method = Method::new(id); let loc = self.mir.add_location(self.module.id, id, node.location); 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); @@ -1258,7 +1279,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 +1292,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 +1327,11 @@ 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; - self.current_block_mut() - .get_field(outer_self, inner_self, field, location); - - self.self_register = outer_self; + self.current_block_mut().get_field(captured, closure, field, location); + self.self_register = captured; } fn warn_unreachable(&mut self) { @@ -1362,7 +1385,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), @@ -1559,7 +1581,11 @@ impl<'a> LowerMethod<'a> { let id = node.class_id.unwrap(); let loc = self.add_location(node.location); - self.current_block_mut().allocate(ins, id, loc); + if id.kind(self.db()).is_async() { + self.current_block_mut().spawn(ins, id, loc); + } else { + self.current_block_mut().allocate(ins, id, loc); + } for field in node.fields { let id = field.field_id.unwrap(); @@ -1596,7 +1622,7 @@ impl<'a> LowerMethod<'a> { 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), + format_type(self.db(), node.resolved_type), self.file(), node.location.clone(), ); @@ -1701,7 +1727,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 } } @@ -1735,10 +1766,16 @@ 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.handle_result( + result, + node.else_block, + returns, + throws, + loc, + ) } types::CallKind::GetField(info) => { let rec = self.expression(node.receiver.unwrap()); @@ -1747,7 +1784,7 @@ impl<'a> LowerMethod<'a> { self.current_block_mut().get_field(reg, rec, info.id, loc); reg } - types::CallKind::ClosureCall(info) => { + types::CallKind::CallClosure(info) => { self.verify_inferred_type(info.returns, &node.location); if !info.throws.is_inferred(self.db()) { @@ -1772,9 +1809,18 @@ impl<'a> LowerMethod<'a> { } } - self.current_block_mut().call_closure(rec, args, loc); + let res = self.new_register(returns); + let throwing = if info.throws.is_never(self.db()) { + false + } else { + self.mark_register_as_result(res); + true + }; + + self.current_block_mut() + .call_closure(res, rec, args, throwing, loc); self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + self.handle_result(res, node.else_block, returns, throws, loc) } _ => { unreachable!() @@ -1791,7 +1837,13 @@ impl<'a> LowerMethod<'a> { receiver: Option, arguments: Vec, location: LocationId, - ) { + ) -> RegisterId { + let result = self.new_register(info.returns); + + if !info.throws.is_never(self.db()) { + self.mark_register_as_result(result); + } + let mut rec = match info.receiver { types::Receiver::Explicit => receiver.unwrap(), types::Receiver::Implicit => { @@ -1813,9 +1865,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 +1876,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( @@ -1885,7 +1939,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,34 +1965,21 @@ 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, + result: RegisterId, 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 { + self.mark_register_as_moved(result); + + // 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); let before_else = self.current_block; let ok_block = self.add_block(); let else_block = self.add_block(); @@ -1950,12 +1991,11 @@ impl<'a> LowerMethod<'a> { 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.current_block_mut().move_result(else_var, result, else_loc); self.else_argument(node.argument, else_var, else_loc); let else_res = self.body(node.body, else_loc); @@ -1971,24 +2011,20 @@ impl<'a> LowerMethod<'a> { } self.current_block = after_else; - self.block_mut(before_else) - .branch_result(ok_block, else_block, location); + .branch_result(result, ok_block, else_block, location); - self.block_mut(ok_block).move_result(ret_reg, location); + self.block_mut(ok_block).move_result(ret_reg, result, 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 + result } fn assign_setter(&mut self, node: hir::AssignSetter) -> RegisterId { @@ -2008,10 +2044,16 @@ 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.handle_call(info, rec, args, loc); self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + self.handle_result( + result, + node.else_block, + returns, + throws, + loc, + ) } types::CallKind::SetField(info) => { let rec = self.expression(node.receiver); @@ -2150,14 +2192,12 @@ impl<'a> LowerMethod<'a> { let block = self.current_block_mut(); if info.dynamic { - block.call_dynamic(rec, info.id, args, loc); + block.call_dynamic(reg, rec, info.id, args, loc); } else { - block.call_virtual(rec, info.id, args, loc); + block.call_instance(reg, rec, info.id, args, loc); } block.reduce_call(loc); - block.move_result(reg, loc); - self.exit_call_scope(entered, reg); reg } @@ -2171,104 +2211,60 @@ impl<'a> LowerMethod<'a> { // 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; - - 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.mark_register_as_moved(reg); - reg - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::PanicThrown, - ) => { + types::BuiltinFunction::PanicThrown => { + let ret = self.get_nil(loc); let reg = self.new_register(types::TypeRef::string()); let arg = args[0]; - let mid = self + let method = self .register_type(arg) - .type_id(self.db(), self.method.id.self_type(self.db())) + .type_id(self.db()) .ok() .and_then(|id| { id.method(self.db(), types::TO_STRING_METHOD) }) .unwrap(); - self.current_block_mut().call_virtual( + self.current_block_mut().call_dynamic( + reg, arg, - mid, + method, 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, + self.current_block_mut().call_builtin( + ret, + types::BuiltinFunction::Panic, vec![reg], loc, ); - self.get_nil(loc) + ret } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::Strings, - ) => { - let reg = self.new_register(types::TypeRef::string()); + types::BuiltinFunction::GetNil => { + let reg = self.new_register(returns); - self.current_block_mut().strings(reg, args, loc); + self.current_block_mut().nil_literal(reg, loc); reg } + name => { + let reg = self.new_register(returns); + + if !throws.is_never(self.db()) { + self.mark_register_as_result(reg); + } + + self.current_block_mut().call_builtin(reg, name, args, loc); + self.current_block_mut().reduce_call(loc); + self.handle_result(reg, node.else_block, returns, throws, loc) + } } } @@ -2305,36 +2301,6 @@ impl<'a> LowerMethod<'a> { 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; - - if throw { - self.current_block_mut() - .throw_async_value(write_reg, register, location); - } else { - self.current_block_mut() - .return_async_value(write_reg, register, location); - } - - if !self.register_type(register).is_permanent(self.db()) { - let drop_id = self.add_block(); - let after_id = self.add_block(); - - self.add_edge(before_id, drop_id); - self.add_edge(before_id, after_id); - self.add_edge(drop_id, after_id); - - self.current_block_mut() - .branch(write_reg, after_id, drop_id, location); - - self.current_block = drop_id; - - self.drop_register(register, location); - self.current_block_mut().goto(after_id, location); - - self.current_block = after_id; - } - let terminate = self.method.id.is_main(self.db()); self.current_block_mut().finish(terminate, location); @@ -2380,7 +2346,7 @@ impl<'a> LowerMethod<'a> { } else { let reg = self.new_register(value_type); - self.current_block_mut().increment(reg, val, loc); + self.current_block_mut().reference(reg, val, loc); reg } } @@ -2478,8 +2444,7 @@ 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 compiler = pmatch::Compiler::new(self.state, vars); let result = compiler.compile(rows); if result.missing { @@ -2701,7 +2666,7 @@ impl<'a> LowerMethod<'a> { self.add_drop_flag(target, loc); if state.increment.contains(&source) { - self.current_block_mut().increment(target, source, loc); + self.current_block_mut().reference(target, source, loc); } else { self.current_block_mut() .move_register(target, source, loc); @@ -2879,8 +2844,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 +2901,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 } @@ -2999,7 +2972,7 @@ 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_with_self_type(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(); @@ -3040,7 +3013,7 @@ 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 } @@ -3084,11 +3057,9 @@ impl<'a> LowerMethod<'a> { } types::IdentifierKind::Method(info) => { let entered = self.enter_call_scope(); - let reg = self.new_register(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.exit_call_scope(entered, reg); reg } @@ -3153,11 +3124,9 @@ 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 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.exit_call_scope(entered, reg); reg } @@ -3179,7 +3148,7 @@ impl<'a> LowerMethod<'a> { let class_id = types::Class::alloc( self.db_mut(), format!("Closure{}", closure_id.0), - types::ClassKind::Regular, + types::ClassKind::Closure, types::Visibility::Private, module, ); @@ -3199,7 +3168,6 @@ impl<'a> LowerMethod<'a> { 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); @@ -3451,11 +3419,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; } @@ -3528,11 +3496,7 @@ impl<'a> LowerMethod<'a> { 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 +3553,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 +3607,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 +3615,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 +3652,22 @@ 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_with_self_type(self.db()).unwrap(); + let block = self.current_block_mut(); + + match class.0 { + types::INT_ID => { + block.clone(CloneKind::Int, reg, source, location); } - }; + types::FLOAT_ID => { + block.clone(CloneKind::Float, reg, source, location); + } + _ => { + block.increment_atomic(reg, source, location); + } + } self.mark_register_as_moved(reg); - self.current_block_mut().clone(kind, reg, source, location); reg } @@ -4189,6 +4152,10 @@ impl<'a> LowerMethod<'a> { final_state } + fn mark_register_as_result(&mut self, register: RegisterId) { + self.method.registers.mark_as_result(register); + } + fn mark_register_as_moved(&mut self, register: RegisterId) { self.update_register_state(register, RegisterState::Moved); } @@ -4228,7 +4195,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 { @@ -4288,6 +4255,16 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { vec![ok, err] } + 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.if_true = ok; + ins.if_false = err; + + 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]; @@ -4297,7 +4274,17 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { 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 +4352,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 +4405,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 +4453,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 +4491,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_with_self_type(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 +4526,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 +4559,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 +4589,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 +4602,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,17 +4618,16 @@ 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); + + if let Some(class) = typ.class_id_with_self_type(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(); + let reg = self.method.registers.alloc(types::TypeRef::nil()); - self.block_mut(block).call_virtual( + self.block_mut(block).call_instance( + reg, value, method, Vec::new(), @@ -4666,6 +4649,148 @@ 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(_))) + { + // TODO: reuse with ExpandDrop + 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_with_self_type(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(); + + self.block_mut(block_id).switch_kind( + value, + vec![normal_id, normal_id, atomic_id, after_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.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); + } + + 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..b75bece79 100644 --- a/compiler/src/mir/pattern_matching.rs +++ b/compiler/src/mir/pattern_matching.rs @@ -22,9 +22,10 @@ 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, }; /// A binding to define as part of a pattern. @@ -485,9 +486,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. @@ -501,18 +499,8 @@ pub(crate) struct Compiler<'a> { } impl<'a> Compiler<'a> { - pub(crate) fn new( - state: &'a mut State, - self_type: TypeId, - variables: Variables, - ) -> Self { - Self { - state, - reachable: HashSet::new(), - missing: false, - variables, - self_type, - } + pub(crate) fn new(state: &'a mut State, variables: Variables) -> Self { + Self { state, reachable: HashSet::new(), missing: false, variables } } pub(crate) fn compile(mut self, rows: Vec) -> Match { @@ -799,16 +787,14 @@ 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); + let bounds = TypeBounds::new(); // TODO: what bounds to use? types .into_iter() .map(|raw_type| { - let inferred = raw_type - .inferred(self.db_mut(), &mut ctx, false) + let inferred = TypeResolver::new(self.db_mut(), &args, &bounds) + .resolve(raw_type) .cast_according_to(source_variable_type, self.db()); self.new_variable(inferred) @@ -818,7 +804,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 +837,7 @@ impl<'a> Compiler<'a> { Type::Finite(cons) } - ClassKind::Regular => { + ClassKind::Regular | ClassKind::Closure => { let fields = class_id.fields(self.db()); let args = fields .iter() @@ -900,7 +886,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 +916,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()) } fn success(block: BlockId) -> Decision { diff --git a/compiler/src/mir/printer.rs b/compiler/src/mir/printer.rs index 040cf5968..f4395b24e 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), diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index 3c7723acf..d47eea1a8 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::{Environment, 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, TypeArguments, TypeId, + TypeRef, Visibility, ENUM_TAG_FIELD, ENUM_TAG_INDEX, FIELDS_LIMIT, + MAIN_CLASS, VARIANTS_LIMIT, }; /// The maximum number of members a single variant can store. We subtract one as @@ -243,68 +245,13 @@ 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, - ); - - 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 +259,7 @@ impl<'a> ImplementTraits<'a> { &bounds, ); + let rules = Rules::default(); let mut definer = DefineTypeSignature::new(self.state, self.module, &scope, rules); @@ -451,31 +399,22 @@ 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); for bound in &node.bounds { for req in &bound.requirements { - checker.check_type_name(req); + if let hir::Requirement::Trait(req) = req { + checker.check_type_name(req); + } } } - 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 +436,6 @@ 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 in a class. @@ -621,22 +556,6 @@ 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, @@ -854,41 +773,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,6 +819,7 @@ 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("std::option", "Option"); self.import_class("std::map", "Map"); @@ -1140,7 +1037,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) { @@ -1753,7 +1650,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..556351ab5 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -7,15 +7,16 @@ 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, BuiltinFunction, CallInfo, CallKind, ClassId, + ClassInstance, Closure, ClosureCallInfo, ClosureId, ConstantKind, + ConstantPatternKind, Database, FieldId, FieldInfo, IdentifierKind, + MethodId, MethodKind, MethodLookup, MethodSource, ModuleId, Receiver, + Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, TypeRef, + Variable, VariableId, CALL_METHOD, STRING_MODULE, TO_STRING_TRAIT, }; const IGNORE_VARIABLE: &str = "_"; @@ -233,8 +234,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, @@ -251,28 +258,41 @@ struct MethodCall { 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,63 +321,60 @@ 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, } } - 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).is_err() { 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(), ); + + return; } } @@ -395,7 +412,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 +432,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(), @@ -434,137 +459,50 @@ impl MethodCall { format!( "The receiver ('{}') of this call requires sendable \ arguments, but '{}' isn't sendable", - format_type_with_context( + format_type_with_arguments( &state.db, - &self.context, + &self.type_arguments, self.receiver ), - format_type_with_context(&state.db, &self.context, given), + format_type_with_arguments( + &state.db, + &self.type_arguments, + given + ), ), self.module.file(&state.db), location.clone(), ); } - if given.type_check(&mut state.db, expected, &mut self.context, true) { - 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(), - ); - } - - 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() => - { - return; - } - 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, + let mut scope = Environment::new( + given.type_arguments(&state.db), + self.type_arguments.clone(), ); - if !self.output_type_is_sendable(state, typ) { - let name = self.method.name(&state.db); - - state.diagnostics.unsendable_throw_type( - name, - format_type_with_context(&state.db, &self.context, typ), + if TypeChecker::new(&state.db).run(given, expected, &mut scope).is_err() + { + 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(), ); } - - 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); - - state.diagnostics.unsendable_return_type( - name, - format_type_with_context(&state.db, &self.context, typ), - self.module.file(&state.db), - location.clone(), - ); - } - - typ - } + fn throw_type(&mut self, state: &mut State) -> TypeRef { + let typ = self.method.throw_type(&state.db); - fn receiver_id(&self) -> TypeId { - self.context.self_type + TypeResolver::new(&mut state.db, &self.type_arguments, &self.bounds) + .resolve(typ) } - fn output_type_is_sendable(&self, state: &State, typ: TypeRef) -> bool { - if !self.require_sendable { - return true; - } + fn return_type(&mut self, state: &mut State) -> TypeRef { + let typ = self.method.return_type(&state.db); - // 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) - } + TypeResolver::new(&mut state.db, &self.type_arguments, &self.bounds) + .resolve(typ) } } @@ -579,17 +517,34 @@ impl<'a> DefineConstants<'a> { state: &'a mut State, modules: &mut Vec, ) -> 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,7 +604,6 @@ 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()); @@ -674,7 +628,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 +639,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 +655,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,27 +663,20 @@ 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 = @@ -766,18 +709,15 @@ impl<'a> Expressions<'a> { 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); + 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); + method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); let mut scope = LexicalScope::method(receiver, returns, throws); self.verify_type_parameter_requirements(&node.type_parameters); @@ -786,14 +726,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( @@ -808,13 +748,11 @@ impl<'a> Expressions<'a> { 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 returns = TypeRef::nil(); + let throws = TypeRef::Never; let mut scope = LexicalScope::method(receiver, returns, throws); self.verify_type_parameter_requirements(&node.type_parameters); @@ -845,7 +783,7 @@ impl<'a> Expressions<'a> { 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 = @@ -886,9 +824,13 @@ 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 mut resolver = TypeResolver::new(self.db_mut(), &args, bounds); + + resolver.rigid = true; + + let typ = resolver.resolve(raw_type); method.set_field_type(self.db_mut(), name, field, typ); } @@ -969,14 +911,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 +967,21 @@ 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); - node.resolved_type = call.return_type(self.state, &node.location); + node.resolved_type = call.return_type(self.state); node.resolved_type } @@ -1097,15 +1040,16 @@ 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) { + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = + 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_with_arguments(self.db(), &lhs, typ), + format_type_with_arguments(self.db(), &rhs, first), self.file(), node.location().clone(), ); @@ -1128,11 +1072,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 +1085,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 +1112,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 +1120,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 +1128,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,7 +1177,7 @@ 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. @@ -1310,21 +1249,22 @@ 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) { + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = 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_with_arguments(self.db(), &lhs, typ), + format_type_with_arguments(self.db(), &rhs, returns), self.file(), loc.clone(), ); @@ -1354,7 +1294,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) @@ -1417,11 +1356,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 +1374,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 +1430,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 +1469,15 @@ 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) { + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = + 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_with_arguments(self.db(), &lhs, typ), + format_type_with_arguments(self.db(), &rhs, first), self.file(), node.location().clone(), ); @@ -1552,11 +1496,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 +1523,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 +1570,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 { @@ -1673,11 +1603,13 @@ impl<'a> CheckMethodBody<'a> { let value = self.expression(&mut field.value, scope); let value_casted = value.cast_according_to(expected, self.db()); - if !value_casted.type_check(self.db_mut(), expected, &mut ctx, true) + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = + TypeChecker::check(self.db_mut(), value_casted, expected) { 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(), &lhs, value), + format_type_with_arguments(self.db(), &rhs, expected), self.file(), field.value.location().clone(), ); @@ -1686,7 +1618,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 +1652,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 +1713,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 +1721,18 @@ 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; - + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = + 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_with_arguments(self.db(), &lhs, value_type), + format_type_with_arguments(self.db(), &rhs, exp_type), self.file(), - node.location.clone(), + node.value.location().clone(), ); } @@ -1887,19 +1812,13 @@ 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 let Err((_, _, _)) = + 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 +1844,19 @@ 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) { + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = + 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_with_arguments(self.db(), &lhs, ex_type), + format_type_with_arguments(self.db(), &rhs, var_type), ), self.file(), node.location.clone(), @@ -1977,20 +1898,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 +1935,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 +1980,12 @@ 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 let Err((_, _, _)) = + 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 +2018,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(), @@ -2184,11 +2088,7 @@ impl<'a> CheckMethodBody<'a> { format!( "This pattern expects a regular class instance, \ 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(), @@ -2207,11 +2107,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 +2124,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 +2135,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,9 +2146,14 @@ impl<'a> CheckMethodBody<'a> { continue; }; - let field_type = field - .value_type(self.db()) - .inferred(self.db_mut(), &mut ctx, immutable) + let bounds = TypeBounds::new(); // TODO: which bounds to use? + let raw_type = field.value_type(self.db()); + let mut resolver = TypeResolver::new(self.db_mut(), &args, &bounds); + + resolver.immutable = immutable; + + let field_type = resolver + .resolve(raw_type) .cast_according_to(value_type, self.db()); node.field_id = Some(field); @@ -2302,7 +2198,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 +2206,16 @@ impl<'a> CheckMethodBody<'a> { input_type.as_owned(self.db()) }; - if !compare.type_check(self.db_mut(), pattern_type, &mut typ_ctx, true) + if let Err((_, _, _)) = + TypeChecker::check(self.db(), compare, pattern_type) { - let self_type = self.self_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 +2234,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 +2242,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 +2260,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 +2284,21 @@ 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 = TypeBounds::new(); // TODO: which bounds to use? for (patt, member) in node.values.iter_mut().zip(members.into_iter()) { - let typ = member - .inferred(self.db_mut(), &mut ctx, immutable) + let mut resolver = TypeResolver::new(self.db_mut(), &args, &bounds); + + resolver.immutable = immutable; + + let typ = resolver + .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 +2438,20 @@ 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 let Err((_, _, _)) = + 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 +2465,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,6 +2475,7 @@ impl<'a> CheckMethodBody<'a> { .map_or(false, |(id, _, _)| id.is_moving(self.db())); let closure = Closure::alloc(self.db_mut(), moving); + let bounds = TypeBounds::new(); // TODO: which bounds to use? let throw_type = if let Some(n) = node.throw_type.as_mut() { self.type_signature(n, self_type) } else { @@ -2592,10 +2483,13 @@ impl<'a> CheckMethodBody<'a> { expected .as_mut() - .map(|(id, _, context)| { - id.throw_type(db).inferred(db, *context, false) + .map(|(id, _, type_arguments)| { + let raw_type = id.throw_type(db); + + TypeResolver::new(db, type_arguments, &bounds) + .resolve(raw_type) }) - .unwrap_or_else(|| TypeRef::placeholder(self.db_mut())) + .unwrap_or_else(|| TypeRef::placeholder(self.db_mut(), None)) }; let return_type = if let Some(n) = node.return_type.as_mut() { @@ -2605,10 +2499,13 @@ impl<'a> CheckMethodBody<'a> { expected .as_mut() - .map(|(id, _, context)| { - id.return_type(db).inferred(db, *context, false) + .map(|(id, _, type_arguments)| { + let raw_type = id.return_type(db); + + TypeResolver::new(db, type_arguments, &bounds) + .resolve(raw_type) }) - .unwrap_or_else(|| TypeRef::placeholder(self.db_mut())) + .unwrap_or_else(|| TypeRef::placeholder(self.db_mut(), None)) }; closure.set_throw_type(self.db_mut(), throw_type); @@ -2641,11 +2538,13 @@ impl<'a> CheckMethodBody<'a> { expected .as_mut() - .and_then(|(id, _, context)| { - id.positional_argument_input_type(db, index) - .map(|t| t.inferred(db, context, false)) + .and_then(|(id, _, type_arguments)| { + id.positional_argument_input_type(db, index).map(|t| { + TypeResolver::new(db, type_arguments, &bounds) + .resolve(t) + }) }) - .unwrap_or_else(|| TypeRef::placeholder(db)) + .unwrap_or_else(|| TypeRef::placeholder(db, None)) }; let var = @@ -2701,7 +2600,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,14 +2677,21 @@ 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_type_bounds(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); + let returns = call.return_type(self.state); + let throws = call.throw_type(self.state); self.check_missing_try(name, throws, &node.location); @@ -2819,9 +2725,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 +2757,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,14 +2797,21 @@ 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_type_bounds(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); + let returns = call.return_type(self.state); + let throws = call.throw_type(self.state); self.check_missing_try(name, throws, &node.location); @@ -3004,7 +2901,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 +2919,12 @@ 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 let Err((_, _, _)) = + 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 +3032,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 +3047,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 +3070,20 @@ 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) { + // TODO: do we need the type arguments here for formatting? + if let Err((_, lhs, rhs)) = + 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_with_arguments(self.db(), &lhs, returned), + format_type_with_arguments(self.db(), &rhs, expected), self.file(), node.location.clone(), ); } node.resolved_type = returned; - TypeRef::Never } @@ -3197,7 +3094,6 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let mut thrown = self.expression(&mut node.value, scope); let expected = scope.throw_type; - let mut ctx = TypeContext::new(self.self_type); if scope.in_recover() && thrown.is_owned(self.db()) { thrown = thrown.as_uni(self.db()); @@ -3207,10 +3103,12 @@ impl<'a> CheckMethodBody<'a> { self.state .diagnostics .throw_not_allowed(self.file(), node.location.clone()); - } else if !thrown.type_check(self.db_mut(), expected, &mut ctx, true) { + } else if let Err((_, lhs, rhs)) = + TypeChecker::check(self.db(), thrown, expected) + { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, thrown), - format_type_with_context(self.db(), &ctx, expected), + format_type_with_arguments(self.db(), &lhs, thrown), + format_type_with_arguments(self.db(), &rhs, expected), self.file(), node.location.clone(), ); @@ -3243,7 +3141,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 +3176,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 +3186,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 +3206,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 +3250,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,142 +3271,81 @@ 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 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(), - &setter, - module, - allow_type_private, - ) { - MethodLookup::Ok(id) => id, - MethodLookup::Private => { - self.private_method_call(&setter, 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::InstanceOnStatic => { - self.invalid_instance_call(&setter, receiver, loc); + return TypeRef::Error; + } + MethodLookup::InstanceOnStatic => { + self.invalid_instance_call(&setter, receiver, &node.location); - return TypeRef::Error; - } - MethodLookup::StaticOnInstance => { - self.invalid_static_call(&setter, receiver, loc); + return TypeRef::Error; + } + MethodLookup::StaticOnInstance => { + self.invalid_static_call(&setter, receiver, &node.location); - return TypeRef::Error; + return TypeRef::Error; + } + MethodLookup::None => { + if node.else_block.is_some() { + self.state + .diagnostics + .never_throws(self.file(), node.location.clone()); } - MethodLookup::None => { - let field_name = &node.name.name; - - 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 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); + let returns = call.return_type(self.state); + let throws = call.throw_type(self.state); if let Some(block) = node.else_block.as_mut() { if throws.is_never(self.db()) { @@ -3531,6 +3370,103 @@ impl<'a> CheckMethodBody<'a> { 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 { + 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 = TypeBounds::new(); // TODO: which bounds to use? + let var_type = + TypeResolver::new(self.db_mut(), &targs, &bounds).resolve(raw_type); + + let value = value.cast_according_to(var_type, self.db()); + + if let Err((_, _, _)) = 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 { + id: field, + variable_type: var_type, + }); + + true + } + fn call( &mut self, node: &mut hir::Call, @@ -3539,7 +3475,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) @@ -3581,7 +3517,8 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } - let mut ctx = TypeContext::new(self.self_type); + // TODO: do we actually need this? + let targs = TypeArguments::new(); let mut exp_args = Vec::new(); for (index, arg_node) in node.arguments.iter_mut().enumerate() { @@ -3603,13 +3540,15 @@ 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 let Err((_, lhs, rhs)) = + 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_with_arguments(self.db(), &lhs, given), + format_type_with_arguments(self.db(), &rhs, exp), self.file(), arg_expr_node.location().clone(), ); @@ -3618,15 +3557,30 @@ 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 throws = { + let raw_type = closure.throw_type(self.db()); + let mut resolver = + TypeResolver::new(&mut self.state.db, &targs, self.bounds); + + // TODO: do we actually need rigid types here instead of + // placeholders? + resolver.rigid = true; + + resolver.resolve(raw_type) + }; + + let returns = { + let raw_type = closure.return_type(self.db()); + + let mut resolver = + TypeResolver::new(&mut self.state.db, &targs, self.bounds); + + // TODO: do we actually need rigid types here instead of + // placeholders? + resolver.rigid = true; - let returns = closure - .return_type(self.db()) - .as_rigid_type(&mut self.state.db, self.bounds) - .inferred(self.db_mut(), &mut ctx, false); + resolver.resolve(raw_type) + }; if let Some(block) = node.else_block.as_mut() { if throws.is_never(self.db()) { @@ -3640,7 +3594,7 @@ impl<'a> CheckMethodBody<'a> { self.check_missing_try(CALL_METHOD, throws, &node.location); } - node.kind = CallKind::ClosureCall(ClosureCallInfo { + node.kind = CallKind::CallClosure(ClosureCallInfo { id: closure, expected_arguments: exp_args, returns, @@ -3657,129 +3611,93 @@ 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, - }); + if node.else_block.is_some() { + self.state + .diagnostics + .never_throws(self.file(), (&node.location).clone()); + } - 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); + let returns = call.return_type(self.state); + let throws = call.throw_type(self.state); if let Some(block) = node.else_block.as_mut() { if throws.is_never(self.db()) { @@ -3790,7 +3708,7 @@ impl<'a> CheckMethodBody<'a> { self.try_else_block(block, returns, throws, scope); } else { - self.check_missing_try(name, throws, &node.location); + self.check_missing_try(&node.name.name, throws, &node.location); } let rec_info = Receiver::class_or_explicit(self.db(), receiver); @@ -3812,10 +3730,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,17 +3793,22 @@ 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); + let returns = call.return_type(self.state); + let throws = call.throw_type(self.state); if let Some(block) = node.else_block.as_mut() { if throws.is_never(self.db()) { @@ -3911,86 +3833,83 @@ impl<'a> CheckMethodBody<'a> { 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 { + 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); + let db = &mut self.state.db; + let raw_type = field.value_type(db); + let immutable = receiver.is_ref(db); + let args = ins.type_arguments(db).clone(); + let bounds = TypeBounds::new(); // TODO: what bounds to use? + let mut resolver = TypeResolver::new(db, &args, &bounds); - 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); + resolver.immutable = immutable; - 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()); + let mut returns = resolver.resolve(raw_type); - fut_args.assign(fut_params[0], returns); - fut_args.assign(fut_params[1], throws); + if returns.is_value_type(db) { + returns = returns.as_owned(db); + } else if !immutable && raw_type.is_owned_or_uni(db) { + returns = returns.as_mut(db); + } - 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, variable_type: returns }); - returns + Some(returns) } fn try_else_block( @@ -4004,20 +3923,14 @@ impl<'a> CheckMethodBody<'a> { 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; - + if let Err((_, _, _)) = + TypeChecker::check(self.db(), throws, exp_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), + format_type(self.db(), throws), + format_type(self.db(), exp_type), self.file(), node.location.clone(), ); @@ -4075,20 +3988,18 @@ impl<'a> CheckMethodBody<'a> { // 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()) - { + if let BuiltinFunction::PanicThrown = id { let trait_id = self.db().trait_in_module(STRING_MODULE, TO_STRING_TRAIT); let arg = args[0]; - if !arg.implements_trait_id(self.db(), trait_id, self.self_type) { + if !arg.implements_trait_id(self.db(), trait_id) { 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), + format_type(self.db(), arg), STRING_MODULE, TO_STRING_TRAIT ), @@ -4098,8 +4009,8 @@ impl<'a> CheckMethodBody<'a> { } } - let returns = id.return_type(self.db()); - let throws = id.throw_type(self.db()); + let returns = id.return_type(); + let throws = id.throw_type(); if let Some(block) = node.else_block.as_mut() { self.try_else_block(block, returns, throws, scope); @@ -4118,22 +4029,47 @@ 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) { - 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) - ), - self.file(), - node.location.clone(), - ); + let rules = Rules { + type_parameters_as_rigid: true, + type_parameters_as_owned: true, + allow_mutable_self_parameters: expr_type.is_any(self.db()), + ..Default::default() + }; + let type_scope = TypeScope::with_bounds( + self.module, + self.self_type, + Some(self.method), + self.bounds, + ); - return TypeRef::Error; + 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()) { + if let Err((_, lhs, rhs)) = + 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_arguments(self.db(), &lhs, expr_type), + format_type_with_arguments(self.db(), &rhs, cast_type) + ), + self.file(), + node.location.clone(), + ); + + return TypeRef::Error; + } } node.resolved_type = cast_type; @@ -4149,14 +4085,13 @@ impl<'a> CheckMethodBody<'a> { 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) + && rec.implements_trait_id(self.db(), index_mut) { INDEX_MUT_METHOD - } else if rec.implements_trait_id(self.db(), index, stype) { + } else if rec.implements_trait_id(self.db(), index) { INDEX_METHOD } else { self.state.diagnostics.error( @@ -4164,7 +4099,7 @@ impl<'a> CheckMethodBody<'a> { format!( "The type '{typ}' must implement either \ {module}::{index} or {module}::{index_mut}", - typ = format_type_with_self(self.db(), stype, rec), + typ = format_type(self.db(), rec), module = INDEX_MODULE, index = INDEX_TRAIT, index_mut = INDEX_MUT_TRAIT @@ -4184,16 +4119,21 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; }; - 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.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 returns = call.return_type(self.state, &node.location); + let returns = call.return_type(self.state); node.info = Some(CallInfo { id: method, @@ -4249,7 +4189,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 +4293,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 +4321,7 @@ impl<'a> CheckMethodBody<'a> { expected, &mut node.value, scope, - &mut call.context, + &call.type_arguments, ); if call.named_arguments.contains(name) { @@ -4433,7 +4373,7 @@ impl<'a> CheckMethodBody<'a> { "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), + format_type(self.db(), throws), ), self.file(), location.clone(), @@ -4453,13 +4393,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 +4403,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(), @@ -4565,7 +4500,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/methods.rs b/compiler/src/type_check/methods.rs index 8511aaf57..740a1acf5 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, }; @@ -381,7 +383,11 @@ impl<'a> DefineMethods<'a> { 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,10 +467,17 @@ impl<'a> DefineMethods<'a> { } }; + 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); @@ -484,11 +497,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() }; @@ -519,9 +530,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 +546,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 { @@ -573,6 +586,7 @@ impl<'a> DefineMethods<'a> { &mut self, class_id: ClassId, node: &mut hir::DefineInstanceMethod, + bounds: TypeBounds, ) { let async_class = class_id.kind(self.db()).is_async(); @@ -623,17 +637,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, @@ -662,6 +673,7 @@ impl<'a> DefineMethods<'a> { &node.location, ); + method.set_bounds(self.db_mut(), bounds); node.method_id = Some(method); } @@ -699,18 +711,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 +728,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 +743,25 @@ 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.throw_type.is_some() { + self.state.diagnostics.error( + DiagnosticId::InvalidMethod, + "Async methods can't throw values", + self.file(), + node.location.clone(), + ); + } + + 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, @@ -774,7 +796,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 +804,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, @@ -843,7 +864,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 +872,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, @@ -922,10 +942,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 +1003,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 +1024,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 +1033,6 @@ impl<'a> CheckMainMethod<'a> { return None; }; - let stype = TypeId::ClassInstance(ClassInstance::new(class)); - if !class.kind(self.db()).is_async() { return None; } @@ -1009,9 +1042,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 +1121,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); } for req in trait_id.required_methods(self.db()) { @@ -1126,7 +1158,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,7 +1172,6 @@ impl<'a> ImplementTraitMethods<'a> { node: &mut hir::DefineInstanceMethod, class_instance: ClassInstance, trait_instance: TraitInstance, - bounded: bool, bounds: &TypeBounds, ) { let name = &node.name.name; @@ -1191,11 +1222,10 @@ 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( @@ -1220,24 +1250,22 @@ impl<'a> ImplementTraitMethods<'a> { &scope, ); - let mut check_ctx = TypeContext::new(self_type); - - if trait_instance.instance_of().is_generic(self.db()) { - trait_instance - .type_arguments(self.db()) - .copy_into(&mut check_ctx.type_arguments); - } + let targs = TypeArguments::for_trait(self.db(), trait_instance); + let mut scope = Environment::new(targs.clone(), targs); - if !method.type_check(self.db_mut(), original, &mut check_ctx) { + if TypeChecker::new(self.db()) + .check_method(method, original, &mut scope) + .is_err() + { let file = self.file(); - let expected = format_type(self.db(), original); + let lhs = + format_type_with_arguments(self.db(), &scope.left, method); + let rhs = + format_type_with_arguments(self.db(), &scope.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 +1279,8 @@ impl<'a> ImplementTraitMethods<'a> { method.mark_as_destructor(self.db_mut()); } + method.set_bounds(self.db_mut(), bounds.clone()); + self.add_method_to_class( method, class_instance.instance_of(), @@ -1275,287 +1305,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..e91895e76 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, @@ -88,6 +103,12 @@ pub(crate) struct Rules { /// When set to `true`, type parameters are defined as rigid parameters. pub(crate) type_parameters_as_rigid: bool, + /// If we allow the creation of mutable references to type parameters + /// defined on `self`. + /// + /// TODO: do we really need this? + pub(crate) allow_mutable_self_parameters: bool, + /// If private types are allowed. pub(crate) allow_private_types: bool, } @@ -95,9 +116,9 @@ 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_mutable_self_parameters: false, allow_private_types: true, } } @@ -268,12 +289,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 +335,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 +410,24 @@ 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 !self.rules.allow_mutable_self_parameters + && !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) + } } } @@ -497,18 +529,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 +561,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 +650,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 +663,11 @@ impl<'a> CheckTypeSignature<'a> { ); } - if !arg.is_compatible_with_type_parameter( - self.db_mut(), - param, - &mut context, - ) { + // TODO: the old code would relax ownership here, but I'm not sure + // if that was actually correct, and if we should mimic that here. + let exp = TypeRef::Infer(TypeId::TypeParameter(param)); + + if TypeChecker::check(self.db(), arg, exp).is_err() { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( @@ -698,10 +711,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 +740,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 +758,92 @@ 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::InvalidBound, + 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 { + match req { + hir::Requirement::Trait(req) => { + 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); + } + } + hir::Requirement::Mutable(loc) => { + if new_param.is_mutable(&state.db) { + state.diagnostics.error( + DiagnosticId::InvalidBound, + format!( + "The parameter '{}' is already mutable", + name + ), + module.file(&state.db), + loc.clone(), + ); + } + + 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::*; @@ -1176,8 +1252,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 +1328,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/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/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..46d66da60 100644 --- a/inko/Cargo.toml +++ b/inko/Cargo.toml @@ -8,7 +8,6 @@ license = "MPL-2.0" [features] default = [] jemalloc = ["jemallocator"] -libffi-system = ["vm/libffi-system"] [dependencies] getopts = "^0.2" diff --git a/inko/src/command/run.rs b/inko/src/command/run.rs index 3a38455e9..5178c3db3 100644 --- a/inko/src/command/run.rs +++ b/inko/src/command/run.rs @@ -6,8 +6,6 @@ use compiler::config::{Config as CompilerConfig, IMAGE_EXT}; use getopts::{Options, ParsingStyle}; use std::path::PathBuf; use vm::config::Config; -use vm::image::Image; -use vm::machine::Machine; const USAGE: &str = "Usage: inko run [OPTIONS] [FILE] @@ -60,22 +58,6 @@ pub fn run(arguments: &[String]) -> Result { let input = matches.free.get(0); 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") { @@ -97,12 +79,14 @@ pub fn run(arguments: &[String]) -> Result { 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) + todo!("make 'inko run' work again") + // TODO: compile to object file and run that + // 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) } 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..0d8eae67e 100644 --- a/inko/src/command/test.rs +++ b/inko/src/command/test.rs @@ -5,8 +5,6 @@ use compiler::compiler::{CompileError, Compiler}; use compiler::config::Config as CompilerConfig; use getopts::Options; use vm::config::Config; -use vm::image::Image; -use vm::machine::Machine; const USAGE: &str = "Usage: inko test [OPTIONS] @@ -53,12 +51,14 @@ pub fn run(arguments: &[String]) -> Result { 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) + // TODO: compile to object file and run + todo!("make 'inko test' work again") + // 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) } Err(CompileError::Invalid) => Ok(1), Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), diff --git a/libstd/src/std/array.inko b/libstd/src/std/array.inko index d869d2b1a..d87853059 100644 --- a/libstd/src/std/array.inko +++ b/libstd/src/std/array.inko @@ -43,12 +43,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 } @@ -108,7 +109,7 @@ class builtin Array[T] { 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) } + if _INKO.is_null(val) { Option.None } else { Option.Some(val) } } # Removes the value at the given index, returning the removed value. @@ -149,36 +150,7 @@ class builtin Array[T] { fn pub get(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) - } - - # Returns a mutable reference to the value at the given index. - # - # # Examples - # - # Retrieving an existing value: - # - # let numbers = [10, 20] - # - # numbers.get_mut(0) # => Option.Some(mut 10) - # - # Retrieving a value from a non-existing index: - # - # let numbers = [10, 20] - # - # 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) + Option.Some((ref _INKO.array_get(self, index)) as ref T) } # Inserts the value at the given index, returning the old value. @@ -208,11 +180,6 @@ class builtin Array[T] { 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] } - } - # Returns an `Iter` that iterates over all values in `self`, returning them # by value. # @@ -266,7 +233,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,6 +308,34 @@ class builtin Array[T] { } } +impl Array if T: mut { + # Returns a mutable reference to the value at the given index. + # + # # Examples + # + # Retrieving an existing value: + # + # let numbers = [10, 20] + # + # numbers.get_mut(0) # => Option.Some(mut 10) + # + # Retrieving a value from a non-existing index: + # + # let numbers = [10, 20] + # + # 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 } + + Option.Some((mut _INKO.array_get(self, index)) as mut T) + } + + # 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] } + } +} + impl Drop for Array { fn mut drop { let mut index = 0 @@ -351,7 +346,7 @@ impl Drop for Array { } } -impl Contains[T] for Array if T: Equal { +impl Contains[T] for Array if T: Equal[T] { # Returns `true` if `self` contains the given value. # # # Examples @@ -367,24 +362,14 @@ impl Contains[T] for Array if T: Equal { impl Index[Int, ref T] for Array { fn pub index(index: Int) -> ref T { bounds_check(index, length) - - let raw = _INKO.array_get(self, index) as T - let res = ref raw - - _INKO.moved(raw) - res + ref(_INKO.array_get(self, index)) as ref T } } -impl IndexMut[Int, mut T] for Array { +impl IndexMut[Int, mut T] for Array if T: mut { fn pub mut index_mut(index: Int) -> mut T { bounds_check(index, length) - - let raw = _INKO.array_get(self, index) as T - let res = mut raw - - _INKO.moved(raw) - res + (mut _INKO.array_get(self, index)) as mut T } } @@ -413,8 +398,8 @@ impl SetIndex[Int, T] for Array { } } -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 @@ -425,7 +410,7 @@ impl Clone for Array if T: Clone { } } -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,7 +426,7 @@ 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 diff --git a/libstd/src/std/bool.inko b/libstd/src/std/bool.inko index bdf0ce7c4..80442bd52 100644 --- a/libstd/src/std/bool.inko +++ b/libstd/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/libstd/src/std/byte_array.inko index 800ac4159..ce89e564f 100644 --- a/libstd/src/std/byte_array.inko +++ b/libstd/src/std/byte_array.inko @@ -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) } @@ -127,7 +127,7 @@ class builtin ByteArray { fn pub mut pop -> Option[Int] { let val = _INKO.byte_array_pop(self) - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } + if _INKO.is_null(val) { Option.None } else { Option.Some(val) } } # Removes the value at the given index, returning the removed value. @@ -369,13 +369,13 @@ impl SetIndex[Int, Int] for ByteArray { } 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 +404,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 +417,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) } } diff --git a/libstd/src/std/channel.inko b/libstd/src/std/channel.inko new file mode 100644 index 000000000..15f52f4b2 --- /dev/null +++ b/libstd/src/std/channel.inko @@ -0,0 +1,150 @@ +# 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] { + let val = try _INKO.channel_try_receive(self) else return Option.None + + Option.Some(val as uni T) + } + + # 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] { + let time = deadline.to_int + let val = + try _INKO.channel_receive_until(self, time) else return Option.None + + Option.Some(val as uni T) + } +} + +impl Clone[Channel[T]] for Channel { + fn pub clone -> Channel[T] { + self + } +} + +impl Drop for Channel { + fn mut drop { + loop { + # The value is dropped implicitly at the end of this scope. + (try _INKO.channel_try_receive(self) else return) as uni T + } + } +} diff --git a/libstd/src/std/clone.inko b/libstd/src/std/clone.inko index 387b6a587..dffaf13de 100644 --- a/libstd/src/std/clone.inko +++ b/libstd/src/std/clone.inko @@ -1,7 +1,7 @@ # 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 +# 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/libstd/src/std/cmp.inko index 80bfe516c..c81619c00 100644 --- a/libstd/src/std/cmp.inko +++ b/libstd/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/libstd/src/std/crypto/chacha.inko index 169216073..814721365 100644 --- a/libstd/src/std/crypto/chacha.inko +++ b/libstd/src/std/crypto/chacha.inko @@ -237,7 +237,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 +246,7 @@ class pub ChaCha20 { panic("ChaCha20 nonce sizes must be exactly {CHACHA_NONCE_SIZE} bytes") } - Self { + ChaCha20 { @matrix = Matrix { @words = [ 0x61707865, @@ -378,7 +378,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 +389,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/hash.inko b/libstd/src/std/crypto/hash.inko index c937b8517..d8825d0ad 100644 --- a/libstd/src/std/crypto/hash.inko +++ b/libstd/src/std/crypto/hash.inko @@ -17,8 +17,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. @@ -117,8 +117,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} } } @@ -140,8 +140,8 @@ impl ToString for Hash { } } -impl Equal for Hash { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Hash] for Hash { + fn pub ==(other: ref Hash) -> Bool { @bytes == other.bytes } } diff --git a/libstd/src/std/crypto/md5.inko b/libstd/src/std/crypto/md5.inko index 50ed930ef..f8e105be2 100644 --- a/libstd/src/std/crypto/md5.inko +++ b/libstd/src/std/crypto/md5.inko @@ -108,8 +108,8 @@ class pub Md5 { } # 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, diff --git a/libstd/src/std/crypto/poly1305.inko b/libstd/src/std/crypto/poly1305.inko index 160ca302d..6fb84f8f8 100644 --- a/libstd/src/std/crypto/poly1305.inko +++ b/libstd/src/std/crypto/poly1305.inko @@ -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, diff --git a/libstd/src/std/crypto/sha1.inko b/libstd/src/std/crypto/sha1.inko index 37593600e..76cfd8328 100644 --- a/libstd/src/std/crypto/sha1.inko +++ b/libstd/src/std/crypto/sha1.inko @@ -54,8 +54,8 @@ class pub Sha1 { } # 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, diff --git a/libstd/src/std/crypto/sha2.inko b/libstd/src/std/crypto/sha2.inko index 0bfd84a48..1cd4e2b6b 100644 --- a/libstd/src/std/crypto/sha2.inko +++ b/libstd/src/std/crypto/sha2.inko @@ -106,8 +106,8 @@ class pub Sha256 { } # 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, @@ -260,8 +260,8 @@ class pub Sha512 { } # 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, diff --git a/libstd/src/std/debug.inko b/libstd/src/std/debug.inko index f19af9809..e3bb6e6d0 100644 --- a/libstd/src/std/debug.inko +++ b/libstd/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 } } } @@ -52,21 +52,22 @@ impl Clone for StackFrame { # frame.name # => 'second' # } fn pub stacktrace(skip: Int) -> Array[StackFrame] { - let trace = _INKO.process_stacktrace(skip) - let length = _INKO.process_stacktrace_length(trace) + let len = _INKO.process_stacktrace_length + 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(index)) + let name = _INKO.process_stack_frame_name(index) + let line = _INKO.process_stack_frame_line(index) + + frames.push(StackFrame.new(path, name, line)) + index += 1 } - _INKO.process_stacktrace_drop(trace) frames } diff --git a/libstd/src/std/env.inko b/libstd/src/std/env.inko index f65029b3f..4b9d889ab 100644 --- a/libstd/src/std/env.inko +++ b/libstd/src/std/env.inko @@ -33,7 +33,7 @@ import std::io::Error fn pub get(name: String) -> Option[String] { let val = _INKO.env_get(name) - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } + if _INKO.is_null(val) { Option.None } else { Option.Some(val) } } # Returns all defined environment variables and their values. @@ -46,7 +46,9 @@ fn pub get(name: String) -> Option[String] { # # env.variables # => Map { 'HOME': '/home/alice', ... } fn pub variables -> Map[String, String] { - _INKO.env_variables.into_iter.reduce(Map.new) fn (map, name) { + let vars = _INKO.env_variables as Array[String] + + vars.into_iter.reduce(Map.new) fn (map, name) { match get(name) { case Some(v) -> { map[name] = v @@ -69,7 +71,7 @@ fn pub variables -> Map[String, String] { 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) } + if _INKO.is_null(dir) { Option.None } else { Option.Some(dir.into_path) } } # Returns the path to the temporary directory. @@ -136,7 +138,7 @@ 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. 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/float.inko b/libstd/src/std/float.inko index 982dbec43..a68d9ee75 100644 --- a/libstd/src/std/float.inko +++ b/libstd/src/std/float.inko @@ -60,10 +60,10 @@ 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] { + fn pub static parse(string: String) -> Option[Float] { let res = _INKO.string_to_float(string, -1, -1) - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + if _INKO.is_null(res) { Option.None } else { Option.Some(res) } } # Returns the absolute value of `self`. @@ -72,7 +72,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 +82,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) } @@ -216,44 +216,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 +263,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/libstd/src/std/fmt.inko index 88f588b2d..603a1b2fe 100644 --- a/libstd/src/std/fmt.inko +++ b/libstd/src/std/fmt.inko @@ -26,8 +26,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/libstd/src/std/fs/dir.inko b/libstd/src/std/fs/dir.inko index 1d5520f13..a3ead2266 100644 --- a/libstd/src/std/fs/dir.inko +++ b/libstd/src/std/fs/dir.inko @@ -113,5 +113,5 @@ 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 + (paths as Array[String]).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 index 2d7d33cbf..496f3bf82 100644 --- a/libstd/src/std/fs/file.inko +++ b/libstd/src/std/fs/file.inko @@ -16,6 +16,12 @@ 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 @@ -70,15 +76,15 @@ class pub ReadOnlyFile { # import std::fs::file::ReadOnlyFile # # let handle = try! ReadOnlyFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { + fn pub static new(path: IntoPath) !! Error -> ReadOnlyFile { let path_obj = path.into_path let fd = try { - _INKO.file_open_read_only(path_obj.to_string) + _INKO.file_open(path_obj.to_string, FILE_READ_ONLY) } else (e) { throw Error.from_int(e) } - Self { @path = path_obj, @fd = fd } + ReadOnlyFile { @path = path_obj, @fd = fd } } } @@ -121,15 +127,15 @@ class pub WriteOnlyFile { # import std::fs::file::WriteOnlyFile # # let file = try! WriteOnlyFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { + fn pub static new(path: IntoPath) !! Error -> WriteOnlyFile { let path_obj = path.into_path let fd = try { - _INKO.file_open_write_only(path_obj.to_string) + _INKO.file_open(path_obj.to_string, FILE_WRITE_ONLY) } else (e) { throw Error.from_int(e) } - Self { @path = path_obj, @fd = fd } + WriteOnlyFile { @path = path_obj, @fd = fd } } # Opens a file in append-only mode. @@ -139,15 +145,15 @@ class pub WriteOnlyFile { # import std::fs::file::WriteOnlyFile # # let file = try! WriteOnlyFile.append('/dev/null') - fn pub static append(path: IntoPath) !! Error -> Self { + fn pub static append(path: IntoPath) !! Error -> WriteOnlyFile { let path_obj = path.into_path let fd = try { - _INKO.file_open_append_only(path_obj.to_string) + _INKO.file_open(path_obj.to_string, FILE_APPEND_ONLY) } else (e) { throw Error.from_int(e) } - Self { @path = path_obj, @fd = fd } + WriteOnlyFile { @path = path_obj, @fd = fd } } } @@ -192,15 +198,15 @@ class pub ReadWriteFile { # import std::fs::file::ReadWriteFile # # let handle = try! ReadWriteFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { + fn pub static new(path: IntoPath) !! Error -> ReadWriteFile { let path_obj = path.into_path let fd = try { - _INKO.file_open_read_write(path_obj.to_string) + _INKO.file_open(path_obj.to_string, FILE_READ_WRITE) } else (e) { throw Error.from_int(e) } - Self { @path = path_obj, @fd = fd } + ReadWriteFile { @path = path_obj, @fd = fd } } # Opens a file for both reading and appending: @@ -210,15 +216,15 @@ class pub ReadWriteFile { # import std::fs::file::ReadWriteFile # # let handle = try! ReadWriteFile.append('/dev/null') - fn pub static append(path: IntoPath) !! Error -> Self { + fn pub static append(path: IntoPath) !! Error -> ReadWriteFile { let path_obj = path.into_path let fd = try { - _INKO.file_open_read_append(path_obj.to_string) + _INKO.file_open(path_obj.to_string, FILE_READ_APPEND) } else (e) { throw Error.from_int(e) } - Self { @path = path_obj, @fd = fd } + ReadWriteFile { @path = path_obj, @fd = fd } } } diff --git a/libstd/src/std/fs/path.inko b/libstd/src/std/fs/path.inko index ecdd361c5..352eab74f 100644 --- a/libstd/src/std/fs/path.inko +++ b/libstd/src/std/fs/path.inko @@ -105,8 +105,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. @@ -277,7 +277,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 +290,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 } } @@ -335,8 +335,8 @@ impl Size for Path { } } -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/init.inko b/libstd/src/std/init.inko index af2bdcbc5..aed2ed38b 100644 --- a/libstd/src/std/init.inko +++ b/libstd/src/std/init.inko @@ -13,3 +13,4 @@ import std::option import std::process import std::string import std::tuple +import std::channel diff --git a/libstd/src/std/int.inko b/libstd/src/std/int.inko index bee013918..57237de3b 100644 --- a/libstd/src/std/int.inko +++ b/libstd/src/std/int.inko @@ -41,10 +41,10 @@ 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] { + fn pub static from_base2(string: String) -> Option[Int] { let res = _INKO.string_to_int(string, 2, -1, -1) - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + if _INKO.is_null(res) { Option.None } else { Option.Some(res) } } # Parses an `Int` from a `String`, using base 10. @@ -56,10 +56,10 @@ 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] { + fn pub static from_base10(string: String) -> Option[Int] { let res = _INKO.string_to_int(string, 10, -1, -1) - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + if _INKO.is_null(res) { Option.None } else { Option.Some(res) } } # Parses an `Int` from a `String`, using base 16. @@ -75,10 +75,10 @@ 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] { + fn pub static from_base16(string: String) -> Option[Int] { let res = _INKO.string_to_int(string, 16, -1, -1) - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + if _INKO.is_null(res) { Option.None } else { Option.Some(res) } } # Formats `self` as a `String` using base 2. @@ -124,7 +124,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 +134,7 @@ class builtin Int { # # -42.opposite # => 42 # 42.opposite # => -42 - fn pub opposite -> Self { + fn pub opposite -> Int { 0 - self } @@ -300,8 +300,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 +311,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 +346,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/libstd/src/std/io.inko index 122b969a3..f714ea7ce 100644 --- a/libstd/src/std/io.inko +++ b/libstd/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 diff --git a/libstd/src/std/iter.inko b/libstd/src/std/iter.inko index d967e3d66..b2ec0f255 100644 --- a/libstd/src/std/iter.inko +++ b/libstd/src/std/iter.inko @@ -401,8 +401,8 @@ class pub Enum[T, E] { let @block: fn !! E -> 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 !! E -> Option[T]) -> Enum[T, E] { + Enum { @block = block } } # Returns an iterator that produces the given value once. @@ -415,10 +415,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, Never] { 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,7 +441,7 @@ 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) !! E -> T) -> Enum[T, E] { let mut index = 0 let wrapper = fn move { if index < limit { @@ -451,7 +451,7 @@ class pub Enum[T, E] { } } - Self { @block = wrapper } + Enum { @block = wrapper } } } diff --git a/libstd/src/std/json.inko b/libstd/src/std/json.inko index 6daefc85b..b9db8207a 100644 --- a/libstd/src/std/json.inko +++ b/libstd/src/std/json.inko @@ -251,7 +251,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 +322,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, @@ -503,7 +503,7 @@ class pub Parser { let codepoint = _INKO.string_to_int(@string, 16, start, @index) - if _INKO.is_undefined(codepoint) { + if _INKO.is_null(codepoint) { throw error("'{slice_string(start)}' is an invalid Unicode codepoint") } @@ -607,7 +607,7 @@ class pub Parser { # 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) } + if _INKO.is_null(res).false? { return Json.Int(res) } } } @@ -725,8 +725,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, diff --git a/libstd/src/std/map.inko b/libstd/src/std/map.inko index c4276d406..98d66be7c 100644 --- a/libstd/src/std/map.inko +++ b/libstd/src/std/map.inko @@ -18,7 +18,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 @@ -59,8 +59,8 @@ class pub DefaultHasher { let @hasher: Any # Returns a new `Hasher`. - fn pub static new -> Self { - Self { @hasher = _INKO.hasher_new } + fn pub static new -> DefaultHasher { + DefaultHasher { @hasher = _INKO.hasher_new } } } @@ -107,7 +107,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 +123,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) } @@ -136,13 +136,13 @@ class pub Map[K: Hash + Equal, V] { # let map = Map.with_capacity(32) # # map['name'] = 'Alice' - fn pub static with_capacity(amount: Int) -> Self { + 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. @@ -331,14 +331,6 @@ class pub Map[K: Hash + Equal, V] { } } - # 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) - } - } - # Merges two `Map` objects together. # # # Examples @@ -353,7 +345,7 @@ class pub Map[K: Hash + Equal, V] { # # 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) @@ -537,7 +529,17 @@ class pub Map[K: Hash + Equal, V] { } } -impl Equal for Map if V: Equal { +impl Map if V: mut { + # 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) + } + } +} + +impl Equal[Map[K, V]] for Map if V: Equal[V] { # Returns `true` if `self` and the given `Map` are identical to each # other. # @@ -552,7 +554,7 @@ impl Equal for Map if V: Equal { # map2['name'] = 'Alice' # # map1 == map2 # => true - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Map[K, V]) -> Bool { if length != other.length { return false } iter.all? fn (ours) { @@ -588,7 +590,7 @@ impl Index[ref K, ref V] for Map { } } -impl IndexMut[ref K, mut V] for Map { +impl IndexMut[ref K, mut V] for Map if V: mut { # Returns a mutable reference to the value of the given key. # # # Panics diff --git a/libstd/src/std/net/ip.inko b/libstd/src/std/net/ip.inko index bb0512fe1..129b582e2 100644 --- a/libstd/src/std/net/ip.inko +++ b/libstd/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 @@ -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 @@ -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 } @@ -662,8 +664,8 @@ class pub Ipv4Address { } # 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/libstd/src/std/net/socket.inko index e6c175250..6de78dddf 100644 --- a/libstd/src/std/net/socket.inko +++ b/libstd/src/std/net/socket.inko @@ -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 } } @@ -156,11 +160,11 @@ class pub Socket { # import std::net::socket::(Type, Socket) # # try! Socket.ipv4(Type.DGRAM) - fn pub static ipv4(type: Type) !! Error -> Self { + fn pub static ipv4(type: Type) !! Error -> Socket { let id = type.into_int - let fd = try _INKO.socket_allocate_ipv4(id) else (e) throw Error.from_int(e) + let fd = try _INKO.socket_new(IPV4, id) else (e) throw Error.from_int(e) - Self { @fd = fd, @deadline = NO_DEADLINE } + Socket { @fd = fd, @deadline = NO_DEADLINE } } # Creates a new IPv6 socket. @@ -170,11 +174,11 @@ class pub Socket { # import std::net::socket::(Type, Socket) # # try! Socket.ipv6(Type.DGRAM) - fn pub static ipv6(type: Type) !! Error -> Self { + fn pub static ipv6(type: Type) !! Error -> Socket { let id = type.into_int - let fd = try _INKO.socket_allocate_ipv6(id) else (e) throw Error.from_int(e) + let fd = try _INKO.socket_new(IPV6, id) else (e) throw Error.from_int(e) - Self { @fd = fd, @deadline = NO_DEADLINE } + Socket { @fd = fd, @deadline = NO_DEADLINE } } # Sets the point in time after which socket operations must time out, known as @@ -306,7 +310,7 @@ class pub Socket { # buffer.to_string # => 'ping' fn pub accept !! Error -> Socket { let fd = try { - _INKO.socket_accept_ip(@fd, @deadline) + _INKO.socket_accept(@fd, @deadline) } else (err) { throw Error.from_int(err) } @@ -439,54 +443,26 @@ class pub Socket { 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) - } - # 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) - } - # 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) - } - # 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) - } - # 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) - } - # Sets the value of the `SO_LINGER` option. fn pub mut linger=(value: ref Duration) !! Error { let nanos = value.to_nanos @@ -494,41 +470,21 @@ class pub Socket { 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) - } - # 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) - } - # 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) - } - # 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) - } - # Sets the value of the `SO_REUSEADDR` option. fn pub mut reuse_address=(value: Bool) !! Error { try { @@ -538,14 +494,6 @@ class pub Socket { } } - # 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 @@ -573,10 +521,10 @@ class pub Socket { # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { + fn pub try_clone !! Error -> Socket { let fd = try _INKO.socket_try_clone(@fd) else (e) throw Error.from_int(e) - Self { @fd = fd, @deadline = NO_DEADLINE } + Socket { @fd = fd, @deadline = NO_DEADLINE } } } @@ -638,7 +586,7 @@ class pub UdpSocket { # let ip = try! IpAddress.parse('0.0.0.0') # # try! UdpSocket.new(ip, port: 0) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + fn pub static new(ip: IpAddress, port: Int) !! Error -> UdpSocket { let socket = if ip.v6? { try Socket.ipv6(Type.DGRAM) } else { @@ -647,7 +595,7 @@ class pub UdpSocket { try socket.bind(ip, port) - Self { @socket = socket } + UdpSocket { @socket = socket } } # Connects `self` to the remote address. @@ -746,8 +694,8 @@ class pub UdpSocket { # # 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 !! Error -> UdpSocket { + UdpSocket { @socket = try @socket.try_clone } } } @@ -791,7 +739,7 @@ class pub TcpClient { # let ip = try! IpAddress.parse('127.0.0.1') # # try! TcpClient.new(ip, port: 40_000) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + fn pub static new(ip: IpAddress, port: Int) !! Error -> TcpClient { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -800,7 +748,7 @@ class pub TcpClient { try socket.connect(ip, port) - Self { @socket = socket } + TcpClient { @socket = socket } } # Creates a new `TcpClient` but limits the amount of time spent waiting for @@ -826,7 +774,7 @@ class pub TcpClient { ip: IpAddress, port: Int, timeout_after: ToInstant - ) !! Error -> Self { + ) !! Error -> TcpClient { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -837,7 +785,7 @@ class pub TcpClient { try socket.connect(ip, port) - Self { @socket = socket } + TcpClient { @socket = socket } } # Returns the local address of this socket. @@ -873,8 +821,8 @@ class pub TcpClient { # # 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 !! Error -> TcpClient { + TcpClient { @socket = try @socket.try_clone } } } @@ -920,7 +868,7 @@ class pub TcpServer { # let ip = try! IpAddress.parse('0.0.0.0') # # try! TcpServer.new(ip, port: 40_000) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + fn pub static new(ip: IpAddress, port: Int) !! Error -> TcpServer { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -933,7 +881,7 @@ class pub TcpServer { try socket.bind(ip, port) try socket.listen - Self { @socket = socket } + TcpServer { @socket = socket } } # Accepts a new incoming connection from `self`. @@ -975,8 +923,8 @@ class pub TcpServer { # # 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 !! Error -> TcpServer { + TcpServer { @socket = try @socket.try_clone } } } @@ -1009,8 +957,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 +1015,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 +1026,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 } } @@ -1130,7 +1078,7 @@ class pub 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) + let fd = try _INKO.socket_new(UNIX, id) else (e) throw Error.from_int(e) UnixSocket { @fd = fd, @deadline = NO_DEADLINE } } @@ -1261,7 +1209,7 @@ class pub UnixSocket { # buffer.to_string # => 'ping' fn pub accept !! Error -> UnixSocket { let fd = try { - _INKO.socket_accept_unix(@fd, @deadline) + _INKO.socket_accept(@fd, @deadline) } else (err) { throw Error.from_int(err) } @@ -1373,21 +1321,11 @@ class pub UnixSocket { 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) - } - # 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) - } - # 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) @@ -1412,10 +1350,10 @@ class pub UnixSocket { # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { + fn pub try_clone !! Error -> UnixSocket { let fd = try _INKO.socket_try_clone(@fd) else (e) throw Error.from_int(e) - Self { @fd = fd, @deadline = NO_DEADLINE } + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } } } @@ -1469,12 +1407,12 @@ class pub UnixDatagram { # import std::net::socket::UnixDatagram # # try! UnixDatagram.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + fn pub static new(address: ref ToString) !! Error -> UnixDatagram { let socket = try UnixSocket.new(Type.DGRAM) try socket.bind(address) - Self { @socket = socket } + UnixDatagram { @socket = socket } } # Connects `self` to the remote addres.s @@ -1556,8 +1494,8 @@ class pub UnixDatagram { # # 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 !! Error -> UnixDatagram { + UnixDatagram { @socket = try @socket.try_clone } } } @@ -1599,12 +1537,12 @@ class pub UnixClient { # let listener = try! UnixServer.new('/tmp/test.sock') # # try! UnixClient.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + fn pub static new(address: ref ToString) !! Error -> UnixClient { let socket = try UnixSocket.new(Type.STREAM) try socket.connect(address) - Self { @socket = socket } + UnixClient { @socket = socket } } # Creates a new `UnixClient` but limits the amount of time spent waiting for @@ -1627,14 +1565,14 @@ class pub UnixClient { fn pub static with_timeout( address: ref ToString, timeout_after: ToInstant - ) !! Error -> Self { + ) !! Error -> UnixClient { let socket = try UnixSocket.new(Type.STREAM) let _ = socket.timeout_after = timeout_after try socket.connect(address) - Self { @socket = socket } + UnixClient { @socket = socket } } # Returns the local address of this socket. @@ -1670,8 +1608,8 @@ class pub UnixClient { # # 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 !! Error -> UnixClient { + UnixClient { @socket = try @socket.try_clone } } } @@ -1709,13 +1647,13 @@ class pub UnixServer { # import std::net::socket::UnixServer # # try! UnixServer.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + fn pub static new(address: ref ToString) !! Error -> UnixServer { let socket = try UnixSocket.new(Type.STREAM) try socket.bind(address) try socket.listen - Self { @socket = socket } + UnixServer { @socket = socket } } # Accepts a new incoming connection from `self`. @@ -1754,7 +1692,7 @@ class pub UnixServer { # # 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 !! Error -> UnixServer { + UnixServer { @socket = try @socket.try_clone } } } diff --git a/libstd/src/std/nil.inko b/libstd/src/std/nil.inko index 8bd98e684..2255260e6 100644 --- a/libstd/src/std/nil.inko +++ b/libstd/src/std/nil.inko @@ -19,14 +19,14 @@ class builtin 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 { +impl Clone[Nil] for Nil { + fn pub clone -> Nil { _INKO.get_nil } } diff --git a/libstd/src/std/ops.inko b/libstd/src/std/ops.inko index 5d3a26fac..3fc8bd5ce 100644 --- a/libstd/src/std/ops.inko +++ b/libstd/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/libstd/src/std/option.inko index b80c08105..b8eca508d 100644 --- a/libstd/src/std/option.inko +++ b/libstd/src/std/option.inko @@ -33,18 +33,6 @@ class pub enum Option[T] { } } - # 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 - } - } - # Returns the wrapped value. # # This method panics when used with a `None`. @@ -174,7 +162,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 +198,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 +212,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/libstd/src/std/process.inko b/libstd/src/std/process.inko index f0f8e309d..9e0ddfe7e 100644 --- a/libstd/src/std/process.inko +++ b/libstd/src/std/process.inko @@ -1,10 +1,4 @@ # 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. @@ -25,68 +19,3 @@ fn pub panic(message: String) -> Never { 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/rand.inko b/libstd/src/std/rand.inko index 1fc2ebfef..ad95497e6 100644 --- a/libstd/src/std/rand.inko +++ b/libstd/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/libstd/src/std/range.inko index e26c6d5c0..c1e99afd9 100644 --- a/libstd/src/std/range.inko +++ b/libstd/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 @@ -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 } } } @@ -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 } } } @@ -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/libstd/src/std/set.inko b/libstd/src/std/set.inko index 16c179312..b6ef8da7e 100644 --- a/libstd/src/std/set.inko +++ b/libstd/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`. @@ -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/libstd/src/std/stdio.inko b/libstd/src/std/stdio.inko index 50b174070..7357082bf 100644 --- a/libstd/src/std/stdio.inko +++ b/libstd/src/std/stdio.inko @@ -4,8 +4,8 @@ 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 {} + fn pub static new -> STDIN { + STDIN {} } } @@ -21,8 +21,8 @@ impl Read for STDIN { # ignored. class pub STDOUT { # Returns a new handle to the output stream. - fn pub static new -> Self { - Self {} + fn pub static new -> STDOUT { + STDOUT {} } } @@ -50,8 +50,8 @@ impl Write for STDOUT { # ignored. class pub STDERR { # Returns a new handle to the error stream. - fn pub static new -> Self { - Self {} + fn pub static new -> STDERR { + STDERR {} } } diff --git a/libstd/src/std/string.inko b/libstd/src/std/string.inko index df3dafff2..72f983839 100644 --- a/libstd/src/std/string.inko +++ b/libstd/src/std/string.inko @@ -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) } } @@ -594,7 +594,7 @@ impl Iter[String, Never] for Characters { fn pub mut next -> Option[String] { let val = _INKO.string_characters_next(@iter) - if _INKO.is_undefined(val) { + if _INKO.is_null(val) { Option.None } else { Option.Some(val as String) @@ -696,8 +696,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 +711,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/libstd/src/std/sys.inko index 58c4972f5..ea1d47637 100644 --- a/libstd/src/std/sys.inko +++ b/libstd/src/std/sys.inko @@ -173,8 +173,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, @@ -321,8 +321,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 +349,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 } } } @@ -391,8 +391,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 } } } @@ -417,8 +417,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 } } } diff --git a/libstd/src/std/test.inko b/libstd/src/std/test.inko index f99a83786..afd739cbf 100644 --- a/libstd/src/std/test.inko +++ b/libstd/src/std/test.inko @@ -57,12 +57,13 @@ # # For more information about the available assertions, refer to the # documentation of the `Test` type. +import std::channel::(wait) import std::cmp::(Compare, Equal) import std::debug import std::fmt import std::fs::path::Path import std::io::Write -import std::process::(sleep, poll) +import std::process::(sleep) import std::rand::(Random, Shuffle) import std::stdio::STDOUT import std::sys @@ -106,8 +107,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 +119,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] + fmt::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] + fmt::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] + fmt::Format](got: ref T, minimum: ref T) { if got > minimum { return } @failures.push(Failure.new(format(got), "> {format(minimum)}")) @@ -167,7 +168,7 @@ class pub Test { } # Asserts that an expression (which may throw) equals the given value. - fn pub mut try_equal[T: Equal + fmt::Format, E: fmt::Format]( + fn pub mut try_equal[T: Equal[T] + fmt::Format, E: fmt::Format]( block: fn !! E -> T, expected: ref T ) { @@ -208,8 +209,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 { @@ -279,12 +280,24 @@ impl Reporter for Plain { } 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 +319,11 @@ 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 { + case { @path = path, @line = line } -> Failure { @got = got, @expected = expected, @path = path, @@ -354,8 +367,8 @@ class pub Tests { let @tests: Array[uni Test] # 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?), @@ -405,24 +418,23 @@ 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) { diff --git a/libstd/src/std/time.inko b/libstd/src/std/time.inko index f0fe3c820..2687afb65 100644 --- a/libstd/src/std/time.inko +++ b/libstd/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, @@ -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/libstd/src/std/tuple.inko index a2ac887de..01eaf2e30 100644 --- a/libstd/src/std/tuple.inko +++ b/libstd/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/test/helpers.inko b/libstd/test/helpers.inko index 772950b6c..94fd49c54 100644 --- a/libstd/test/helpers.inko +++ b/libstd/test/helpers.inko @@ -29,7 +29,7 @@ class pub Script { let @stdin: Option[String] let @imports: Array[String] - fn pub static new(id: Int, code: String) -> Self { + fn pub static new(id: Int, code: String) -> Script { let path = env.temporary_directory.join("inko_test_script_{id}.inko") let exe = try! env.executable let cmd = Command.new(exe) @@ -42,7 +42,7 @@ class pub Script { cmd.stderr(Stream.Piped) cmd.stdout(Stream.Piped) - Self { + Script { @id = id, @path = path, @code = code, @@ -52,24 +52,24 @@ class pub Script { } } - fn pub move import(module: String) -> Self { + fn pub move import(module: String) -> Script { @imports.push(module) self } - fn pub move stdin(input: String) -> Self { + fn pub move stdin(input: String) -> Script { @stdin = Option.Some(input) @cmd.stdin(Stream.Piped) self } - fn pub move argument(value: String) -> Self { + fn pub move argument(value: String) -> Script { @cmd.argument(value) self } - fn pub move variable(name: String, value: String) -> Self { + fn pub move variable(name: String, value: String) -> Script { @cmd.variable(name, value) self } diff --git a/libstd/test/main.inko b/libstd/test/main.inko index dd45d2f00..fd8b0c278 100644 --- a/libstd/test/main.inko +++ b/libstd/test/main.inko @@ -21,7 +21,6 @@ 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 @@ -56,7 +55,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) diff --git a/libstd/test/std/net/test_socket.inko b/libstd/test/std/net/test_socket.inko index f6d3f317a..3f2c6dd05 100644 --- a/libstd/test/std/net/test_socket.inko +++ b/libstd/test/std/net/test_socket.inko @@ -16,22 +16,22 @@ import std::time::(Duration, Instant) class SocketPath { let @path: Path - fn static pair(id: Int) -> (Self, Self) { + 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") try file.remove(p1) else nil try file.remove(p2) else nil - (Self { @path = p1 }, Self { @path = p2 }) + (SocketPath { @path = p1 }, SocketPath { @path = p2 }) } - fn static new(id: Int) -> Self { + fn static new(id: Int) -> SocketPath { let path = env.temporary_directory.join("inko-test-{id}.sock") try file.remove(path) else nil - Self { @path = path } + SocketPath { @path = path } } } @@ -249,58 +249,44 @@ fn pub tests(t: mut Tests) { t.test('Socket.ttl') fn (t) { let socket = try! Socket.ipv4(Type.STREAM) - try! socket.ttl = 10 - - t.equal(try! socket.ttl, 10) + t.no_throw fn { 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.no_throw fn { try socket.only_ipv6 = true } } 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.no_throw fn { try socket.no_delay = true } } t.test('Socket.broadcast?') fn (t) { let socket = try! Socket.ipv4(Type.DGRAM) - try! socket.broadcast = true - - t.true(try! socket.broadcast?) + t.no_throw fn { try socket.broadcast = true } } 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.no_throw fn { 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.no_throw fn { 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) + t.no_throw fn { try socket.send_buffer_size = 256 } } # Obtaining the TCP keepalive setting fails on Windows. See @@ -309,32 +295,20 @@ fn pub tests(t: mut Tests) { t.test('Socket.keepalive') fn (t) { let socket = try! Socket.ipv4(Type.STREAM) - try! socket.keepalive = true - - t.true(try! socket.keepalive) + t.no_throw fn { try socket.keepalive = true } } } 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.no_throw fn { try socket.reuse_address = true } } 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.no_throw fn { try socket.reuse_port = true } } t.test('Socket.shutdown_read') fn (t) { @@ -1077,17 +1051,13 @@ fn unix_tests(t: mut Tests) { 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.no_throw fn { 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.no_throw fn { try socket.send_buffer_size = 256 } } t.test('UnixSocket.shutdown_read') fn (t) { diff --git a/libstd/test/std/test_array.inko b/libstd/test/std/test_array.inko index 3376a06a3..eb6002bae 100644 --- a/libstd/test/std/test_array.inko +++ b/libstd/test/std/test_array.inko @@ -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 } } } diff --git a/libstd/test/std/test_cmp.inko b/libstd/test/std/test_cmp.inko index 0380e02d8..b445207b9 100644 --- a/libstd/test/std/test_cmp.inko +++ b/libstd/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_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_io.inko b/libstd/test/std/test_io.inko index 1853b9b63..aafb8c0a0 100644 --- a/libstd/test/std/test_io.inko +++ b/libstd/test/std/test_io.inko @@ -6,8 +6,8 @@ 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]) } } } @@ -31,8 +31,8 @@ impl Read for Reader { class Writer { let @buffer: ByteArray - fn static new -> Self { - Self { @buffer = ByteArray.new } + fn static new -> Writer { + Writer { @buffer = ByteArray.new } } } diff --git a/libstd/test/std/test_process.inko b/libstd/test/std/test_process.inko index 5fe53382e..df3157a06 100644 --- a/libstd/test/std/test_process.inko +++ b/libstd/test/std/test_process.inko @@ -2,20 +2,6 @@ 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 @@ -23,32 +9,4 @@ fn pub tests(t: mut Tests) { 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_test.inko b/libstd/test/std/test_test.inko index ad3589bba..0ff910bbd 100644 --- a/libstd/test/std/test_test.inko +++ b/libstd/test/std/test_test.inko @@ -7,8 +7,8 @@ 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} } } 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..e3aa0a657 --- /dev/null +++ b/types/src/check.rs @@ -0,0 +1,1878 @@ +//! 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, +} + +impl Rules { + fn new() -> Rules { + Rules { subtyping: true, relaxed_ownership: false } + } + + fn no_subtyping(self) -> Rules { + Rules { subtyping: false, relaxed_ownership: self.relaxed_ownership } + } + + fn relaxed(self) -> Rules { + Rules { subtyping: self.subtyping, relaxed_ownership: true } + } + + fn strict(self) -> Rules { + Rules { subtyping: self.subtyping, relaxed_ownership: false } + } +} + +/// 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 describing why a type-check failed. +/// +/// TODO: expand with more useful info +/// TODO: how would we consume this to generate error messages? Is this even +/// useful without also including the full "path" the type-checker took? +#[derive(Debug)] +pub enum TypeError { + Invalid, + InvalidSubtype, + InvalidTypeArgument(Box), + InvalidBound, + NotImplemented(ClassInstance, TraitInstance), + NotRequired(TypeParameterId, TraitInstance), + InvalidMethodKind, + InvalidVisibility, + InvalidTypeParameter, + InvalidArgumentName, + InvalidArgument(Box), + InvalidThrowType(Box), + InvalidReturnType(Box), +} + +/// 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> { + // TODO: do we actually need the type arguments when formatting every time? + // I think we only need that if the RHS is a collection of arguments for a + // method call + pub fn check( + db: &'a Database, + left: TypeRef, + right: TypeRef, + ) -> Result<(), (TypeError, TypeArguments, TypeArguments)> { + let mut env = + Environment::new(left.type_arguments(db), right.type_arguments(db)); + + TypeChecker::new(db) + .run(left, right, &mut env) + .map_err(|err| (err, env.left, env.right)) + } + + 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, + ) -> Result<(), TypeError> { + self.check_type_ref(left, right, env, Rules::new()) + } + + pub fn check_method( + mut self, + left: MethodId, + right: MethodId, + env: &mut Environment, + ) -> Result<(), TypeError> { + let rules = Rules::new(); + let lhs = left.get(self.db); + let rhs = right.get(self.db); + + if lhs.kind != rhs.kind { + return Err(TypeError::InvalidMethodKind); + } + + if lhs.visibility != rhs.visibility { + return Err(TypeError::InvalidVisibility); + } + + if lhs.name != rhs.name { + return Err(TypeError::Invalid); + } + + if lhs.type_parameters.len() != rhs.type_parameters.len() { + return Err(TypeError::Invalid); + } + + let param_rules = rules.no_subtyping(); + + lhs.type_parameters + .values() + .iter() + .zip(rhs.type_parameters.values().iter()) + .try_for_each(|(&lhs, &rhs)| { + self.check_parameters(lhs, rhs, env, param_rules) + .map_err(|_| TypeError::InvalidTypeParameter) + })?; + + self.check_arguments(&lhs.arguments, &rhs.arguments, env, rules, true)?; + self.check_type_ref(lhs.throw_type, rhs.throw_type, env, rules) + .map_err(|e| TypeError::InvalidThrowType(Box::new(e)))?; + + self.check_type_ref(lhs.return_type, rhs.return_type, env, rules) + .map_err(|e| TypeError::InvalidReturnType(Box::new(e))) + } + + pub fn check_bounds( + &mut self, + bounds: &TypeBounds, + env: &mut Environment, + ) -> Result<(), TypeError> { + 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). + for (¶m, &bound) in bounds.iter() { + 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 Err(TypeError::InvalidBound); + } + + bound + .requirements(self.db) + .into_iter() + .try_for_each(|r| { + self.check_type_ref_with_trait(val, r, &mut env, rules) + }) + .map_err(|_| TypeError::InvalidBound)?; + } + + Ok(()) + } + + fn check_type_ref( + &mut self, + left: TypeRef, + right: TypeRef, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + if !self.checked.insert((left, right)) { + return Ok(()); + } + + // 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); + let right = self.resolve(right, &env.right); + + // 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 => Ok(()), + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + id.required(self.db) + .map_or(true, |p| p.requirements(self.db).is_empty()) + .then(|| ()) + .ok_or(TypeError::Invalid) + } + _ => Err(TypeError::Invalid), + }, + // 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); + Ok(()) + } + _ => Ok(()), + }, + // 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); + Ok(()) + } + _ => Ok(()), + }, + // 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)) => { + self.check_rigids(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 => Ok(()), + _ => Err(TypeError::Invalid), + } + } + 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 | TypeRef::Error => Ok(()), + _ => Err(TypeError::Invalid), + }, + 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 | TypeRef::Error => Ok(()), + _ => Err(TypeError::Invalid), + }, + 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 => Ok(()), + _ => Err(TypeError::Invalid), + }, + TypeRef::Ref(left_id) => match right { + 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) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => Ok(()), + _ => Err(TypeError::Invalid), + }, + 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 => Ok(()), + _ => Err(TypeError::Invalid), + }, + TypeRef::RefUni(left_id) => match right { + TypeRef::RefUni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Error => Ok(()), + _ => Err(TypeError::Invalid), + }, + TypeRef::MutUni(left_id) => match right { + TypeRef::MutUni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Error => Ok(()), + _ => Err(TypeError::Invalid), + }, + TypeRef::Placeholder(left_id) => { + // If we reach this point it means the placeholder isn't + // assigned a value. + left_id.assign(self.db, right); + Ok(()) + } + _ => Err(TypeError::Invalid), + } + } + + fn check_type_id( + &mut self, + left_id: TypeId, + right_id: TypeId, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + 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. + Err(TypeError::Invalid) + } + TypeId::ClassInstance(lhs) => match right_id { + TypeId::ClassInstance(rhs) => { + if lhs.instance_of != rhs.instance_of { + return Err(TypeError::InvalidSubtype); + } + + if !lhs.instance_of.is_generic(self.db) { + return Ok(()); + } + + 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() + .try_for_each(|param| { + let lhs = lhs_args.get(param).unwrap(); + let rhs = rhs_args.get(param).unwrap(); + + self.check_type_ref(lhs, rhs, env, rules).map_err( + |e| TypeError::InvalidTypeArgument(Box::new(e)), + ) + }) + } + TypeId::TraitInstance(rhs) => { + self.check_class_with_trait(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => { + rhs.requirements(self.db).into_iter().try_for_each(|req| { + self.check_class_with_trait(lhs, req, env, rules) + }) + } + _ => Err(TypeError::Invalid), + }, + 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().try_for_each(|req| { + self.check_traits(lhs, req, env, rules) + }) + } + _ => Err(TypeError::Invalid), + }, + 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) + } + _ => Err(TypeError::Invalid), + }, + 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.throw_type, + rhs_obj.throw_type, + env, + rules, + )?; + 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. + Ok(()) + } + _ => Err(TypeError::Invalid), + }, + } + } + + fn check_rigid_with_type_id( + &mut self, + left: TypeParameterId, + right: TypeId, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + match right { + TypeId::RigidTypeParameter(rhs) => self.check_rigids(left, rhs), + TypeId::TraitInstance(rhs) => { + self.check_parameter_with_trait(left, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => { + if left == rhs { + return Ok(()); + } + + rhs.requirements(self.db).into_iter().try_for_each(|req| { + self.check_parameter_with_trait(left, req, env, rules) + }) + } + _ => Err(TypeError::Invalid), + } + } + + fn check_type_id_with_placeholder( + &mut self, + left: TypeRef, + left_id: TypeId, + placeholder: TypePlaceholderId, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + // 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 Ok(()); + }; + + let mut reqs = req.requirements(self.db).into_iter(); + + match left_id { + TypeId::ClassInstance(lhs) => reqs.try_for_each(|req| { + self.check_class_with_trait(lhs, req, env, rules) + }), + TypeId::TraitInstance(lhs) => { + reqs.try_for_each(|req| self.check_traits(lhs, req, env, rules)) + } + TypeId::TypeParameter(lhs) | TypeId::RigidTypeParameter(lhs) => { + reqs.try_for_each(|req| { + self.check_parameter_with_trait(lhs, req, env, rules) + }) + } + _ => Err(TypeError::Invalid), + } + } + + 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()).is_ok() + } + + fn check_class_with_trait( + &mut self, + left: ClassInstance, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + // `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 Err(TypeError::InvalidSubtype); + } + + let imp = if let Some(found) = + left.instance_of.trait_implementation(self.db, right.instance_of) + { + found + } else { + return Err(TypeError::NotImplemented(left, right)); + }; + + // 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, + ) -> Result<(), TypeError> { + 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) + } + _ => Err(TypeError::Invalid), + }, + 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. + _ => Ok(()), + }, + _ => Err(TypeError::Invalid), + } + } + + fn check_parameter_with_trait( + &mut self, + left: TypeParameterId, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + left.requirements(self.db) + .into_iter() + .any(|req| self.check_traits(req, right, env, rules).is_ok()) + .then(|| ()) + .ok_or_else(|| TypeError::NotRequired(left, right)) + } + + fn check_parameters( + &mut self, + left: TypeParameterId, + right: TypeParameterId, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + if left == right { + return Ok(()); + } + + right.requirements(self.db).into_iter().try_for_each(|req| { + self.check_parameter_with_trait(left, req, env, rules) + }) + } + + fn check_rigids( + &mut self, + left: TypeParameterId, + right: TypeParameterId, + ) -> Result<(), TypeError> { + if left == right { + Ok(()) + } else { + Err(TypeError::Invalid) + } + } + + fn check_traits( + &mut self, + left: TraitInstance, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> Result<(), TypeError> { + if left == right { + return Ok(()); + } + + 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).is_ok() + }) + .then(|| ()) + .ok_or_else(|| TypeError::InvalidSubtype) + } else { + Err(TypeError::InvalidSubtype) + }; + } + + if !left.instance_of.is_generic(self.db) { + return Ok(()); + } + + 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().try_for_each( + |param| { + let lhs = lhs_args.get(param).unwrap(); + let rhs = rhs_args.get(param).unwrap(); + + self.check_type_ref(lhs, rhs, env, rules) + .map_err(|e| TypeError::InvalidTypeArgument(Box::new(e))) + }, + ) + } + + fn check_arguments( + &mut self, + left: &Arguments, + right: &Arguments, + env: &mut Environment, + rules: Rules, + same_name: bool, + ) -> Result<(), TypeError> { + if left.len() != right.len() { + return Err(TypeError::Invalid); + } + + left.mapping + .values() + .iter() + .zip(right.mapping.values().iter()) + .try_for_each(|(ours, theirs)| { + if same_name && ours.name != theirs.name { + return Err(TypeError::InvalidArgumentName); + } + + self.check_type_ref( + ours.value_type, + theirs.value_type, + env, + rules, + ) + .map_err(|e| TypeError::InvalidArgument(Box::new(e))) + }) + } + + fn resolve(&self, typ: TypeRef, arguments: &TypeArguments) -> TypeRef { + match typ { + TypeRef::Owned(TypeId::TypeParameter(id)) + | TypeRef::Uni(TypeId::TypeParameter(id)) + | TypeRef::Infer(TypeId::TypeParameter(id)) => { + self.resolve_type_parameter(typ, id, arguments) + } + TypeRef::Ref(TypeId::TypeParameter(id)) => { + self.resolve_type_parameter(typ, id, arguments).as_ref(self.db) + } + TypeRef::Mut(TypeId::TypeParameter(id)) => { + self.resolve_type_parameter(typ, id, arguments).as_mut(self.db) + } + TypeRef::Placeholder(id) => { + id.value(self.db).map_or(typ, |v| self.resolve(v, arguments)) + } + _ => typ, + } + } + + fn resolve_type_parameter( + &self, + typ: TypeRef, + id: TypeParameterId, + arguments: &TypeArguments, + ) -> TypeRef { + match arguments.get(id) { + Some(arg @ TypeRef::Placeholder(id)) => id + .value(self.db) + .map(|v| self.resolve(v, arguments)) + .unwrap_or(arg), + Some(arg) => arg, + _ => typ, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::format::{format_type, format_type_with_arguments}; + use crate::test::{ + closure, generic_instance_id, generic_trait_instance, + generic_trait_instance_id, immutable, implement, infer, instance, + mutable, new_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 let Err((_, lhs, rhs)) = TypeChecker::check(db, left, right) { + panic!( + "Expected {} to be compatible with {}", + format_type_with_arguments(db, &lhs, left), + format_type_with_arguments(db, &rhs, right) + ); + } + } + + #[track_caller] + fn check_err(db: &Database, left: TypeRef, right: TypeRef) { + assert!( + TypeChecker::check(db, left, right).is_err(), + "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. The placeholder + // is still assigned to handle recursive types/checks. + check_err(&db, owned(instance(foo)), placeholder(var2)); + assert_eq!(var2.value(&db), Some(owned(instance(foo)))); + + // 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_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. The placeholder + // is still assigned to handle recursive types/checks. + check_err(&db, uni(instance(foo)), placeholder(var2)); + assert_eq!(var2.value(&db), Some(uni(instance(foo)))); + + // 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.resolve(&db), 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); + + 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.resolve(&db), immutable(instance(thing))); + + check_ok(&db, immutable(instance(thing)), TypeRef::Any); + check_ok(&db, immutable(instance(thing)), TypeRef::Error); + + check_err(&db, immutable(instance(thing)), mutable(instance(thing))); + check_err(&db, immutable(instance(thing)), owned(instance(thing))); + } + + #[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.resolve(&db), 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.resolve(&db), 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.is_ok()); + } + + #[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.resolve(&db), 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_closures_with_throw_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_throw_type(&mut db, int); + fun1.set_return_type(&mut db, TypeRef::Any); + fun2.set_throw_type(&mut db, int); + fun2.set_return_type(&mut db, TypeRef::Any); + fun3.set_throw_type(&mut db, float); + fun3.set_return_type(&mut db, TypeRef::Any); + + 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_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.is_err()); + } +} 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..9be387181 --- /dev/null +++ b/types/src/format.rs @@ -0,0 +1,922 @@ +//! 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_verbose(db: &Database, typ: T) -> String { + TypeFormatter::verbose(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, + pub verbose: bool, +} + +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, + verbose: false, + } + } + + pub fn verbose( + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + ) -> Self { + Self { + db, + type_arguments, + buffer: String::new(), + depth: 0, + verbose: true, + } + } + + 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 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); + } + } + } + + 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"); + } + + if buffer.verbose { + if param.requirements.is_empty() { + return; + } + + buffer.write(if param.mutable { " + " } else { ": " }); + + 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); + }; + } +} + +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.throw_type(block.throw_type); + 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.throw_type(fun.throw_type); + 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::test::{new_parameter, new_trait, parameter, trait_instance}; + 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_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_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_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_verbose_with_type_parameter() { + 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 to_string = new_trait(&mut db, "ToString"); + let to_foo = new_trait(&mut db, "ToFoo"); + + param1.set_mutable(&mut db); + param2.set_mutable(&mut db); + param1.add_requirements( + &mut db, + vec![trait_instance(to_string), trait_instance(to_foo)], + ); + + assert_eq!( + format_type_verbose(&db, parameter(param1)), + "A: mut + ToString + ToFoo" + ); + assert_eq!(format_type_verbose(&db, parameter(param2)), "B: mut"); + assert_eq!(format_type_verbose(&db, parameter(param3)), "C"); + } + + #[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_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 = 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); + + assert_eq!(format_type(&db, block_ins), "fn move (A, B) !! C -> 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..956fbe4ec 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -2,12 +2,18 @@ #![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 std::cell::Cell; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; @@ -15,11 +21,11 @@ use std::path::PathBuf; 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; @@ -44,7 +50,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"; @@ -83,205 +89,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 +120,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,7 +140,7 @@ 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 { @@ -327,13 +150,27 @@ impl TypePlaceholderId { } } - fn add_depending(self, db: &mut Database, placeholder: TypePlaceholderId) { - self.get_mut(db).depending.push(placeholder); + // TODO: rename to value() + fn resolve(self, db: &Database) -> TypeRef { + // 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.resolve(db), + _ => typ, + } + } + + 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,89 +178,11 @@ 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. @@ -434,6 +193,13 @@ 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 { @@ -446,12 +212,12 @@ impl TypeParameter { } 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,62 +248,28 @@ 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) - }) + fn get(self, db: &Database) -> &TypeParameter { + &db.type_parameters[self.0] } - 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_mut(self, db: &mut Database) -> &mut TypeParameter { + &mut db.type_parameters[self.0] } fn as_rigid_type(self, bounds: &TypeBounds) -> TypeId { @@ -547,61 +279,10 @@ impl TypeParameterId { 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,6 +290,23 @@ pub struct TypeArguments { } impl TypeArguments { + pub fn for_class(db: &Database, instance: ClassInstance) -> TypeArguments { + if instance.instance_of().is_generic(db) { + instance.type_arguments(db).clone() + } else { + TypeArguments::new() + } + } + + pub fn for_trait(db: &Database, instance: TraitInstance) -> TypeArguments { + if instance.instance_of().is_generic(db) { + instance.type_arguments(db).clone() + } else { + TypeArguments::new() + } + } + + // TODO: remove? fn rigid(db: &mut Database, index: u32, bounds: &TypeBounds) -> Self { let mut new_args = Self::new(); @@ -623,6 +321,7 @@ impl TypeArguments { Self { mapping: HashMap::default() } } + // TODO: if `parameter` has `original` set, map to that instead. pub fn assign(&mut self, parameter: TypeParameterId, value: TypeRef) { self.mapping.insert(parameter, value); } @@ -658,24 +357,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. @@ -894,12 +575,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 +591,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 +616,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,115 +668,10 @@ 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) @@ -1121,44 +689,6 @@ impl TraitInstance { 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. @@ -1228,17 +758,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, } @@ -1248,6 +774,7 @@ impl TypeBounds { Self { mapping: HashMap::default() } } + // TODO: handle bounded parameters pub fn set(&mut self, parameter: TypeParameterId, bounds: TypeParameterId) { self.mapping.insert(parameter, bounds); } @@ -1255,6 +782,26 @@ 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 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 + } } /// An implementation of a trait, with (optionally) additional bounds for the @@ -1324,6 +871,7 @@ pub enum ClassKind { Enum, Regular, Tuple, + Closure, } impl ClassKind { @@ -1342,12 +890,17 @@ impl ClassKind { pub fn is_tuple(self) -> bool { matches!(self, ClassKind::Tuple) } + + pub fn is_closure(self) -> bool { + matches!(self, ClassKind::Closure) + } } /// An Inko class as declared using the `class` keyword. pub struct Class { kind: ClassKind, name: String, + atomic: 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 +943,7 @@ impl Class { kind, visibility, destructor: false, + atomic: kind.is_async(), fields: IndexMap::new(), type_parameters: IndexMap::new(), methods: HashMap::new(), @@ -1408,6 +962,18 @@ impl 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 + } + fn tuple(name: String) -> Self { Self::new( name, @@ -1446,8 +1012,8 @@ impl ClassId { ClassId(NIL_ID) } - pub fn future() -> ClassId { - ClassId(FUTURE_ID) + pub fn channel() -> ClassId { + ClassId(CHANNEL_ID) } pub fn array() -> ClassId { @@ -1657,6 +1223,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 +1262,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 +1275,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 +1291,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 +1316,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 +1329,37 @@ 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(TypePlaceholder::alloc(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 +1405,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,63 +1419,6 @@ 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() } @@ -2012,52 +1436,6 @@ impl ClassInstance { 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 +1472,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. @@ -2177,59 +1524,1095 @@ 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, + ByteArrayNew, + ByteArrayAppend, + ByteArrayClear, + ByteArrayClone, + ByteArrayCopyFrom, + ByteArrayDrainToString, + ByteArrayDrop, + ByteArrayEq, + ByteArrayGet, + ByteArrayLength, + ByteArrayPop, + ByteArrayPush, + ByteArrayRemove, + ByteArrayResize, + ByteArraySet, + ByteArraySlice, + ByteArrayToString, + ChildProcessDrop, + ChildProcessSpawn, + ChildProcessStderrClose, + ChildProcessStderrRead, + ChildProcessStdinClose, + ChildProcessStdinFlush, + ChildProcessStdinWriteBytes, + ChildProcessStdinWriteString, + ChildProcessStdoutClose, + ChildProcessStdoutRead, + ChildProcessTryWait, + ChildProcessWait, + CpuCores, + DirectoryCreate, + DirectoryCreateRecursive, + DirectoryList, + DirectoryRemove, + DirectoryRemoveRecursive, + EnvArguments, + EnvExecutable, + EnvGet, + EnvGetWorkingDirectory, + EnvHomeDirectory, + EnvPlatform, + 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, + ChannelDrop, + ChannelNew, + ChannelReceive, + ChannelReceiveUntil, + ChannelSend, + ChannelTryReceive, + ChannelWait, + GetNil, + HasherDrop, + HasherNew, + HasherToHash, + HasherWriteInt, + IntAdd, + IntBitAnd, + IntBitNot, + IntBitOr, + IntBitXor, + IntDiv, + IntEq, + IntGe, + IntGt, + IntLe, + IntLt, + IntRem, + IntMul, + IntPow, + IntRotateLeft, + IntRotateRight, + IntShl, + IntShr, + IntSub, + IntToFloat, + IntToString, + IntUnsignedShr, + IntWrappingAdd, + IntWrappingMul, + IntWrappingSub, + IsNull, + Moved, + ObjectEq, + Panic, + PanicThrown, + PathAccessedAt, + PathCreatedAt, + PathExists, + PathIsDirectory, + PathIsFile, + PathModifiedAt, + ProcessStackFrameLine, + ProcessStackFrameName, + ProcessStackFramePath, + ProcessStacktrace, + ProcessStacktraceDrop, + ProcessStacktraceLength, + ProcessSuspend, + RandomBytes, + RandomDrop, + RandomFloat, + RandomFloatRange, + RandomFromInt, + RandomInt, + RandomIntRange, + RandomNew, + SocketAccept, + SocketAddressPairAddress, + SocketAddressPairDrop, + SocketAddressPairPort, + SocketNew, + SocketBind, + SocketConnect, + SocketDrop, + 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, + StringByte, + StringCharacters, + StringCharactersDrop, + StringCharactersNext, + StringConcat, + StringConcatArray, + StringDrop, + StringEq, + StringSize, + StringSliceBytes, + StringToByteArray, + StringToFloat, + StringToInt, + StringToLower, + StringToUpper, + TimeMonotonic, + TimeSystem, + TimeSystemOffset, } 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) - } - - fn add(db: &mut Database, name: String, func: Self) -> BuiltinFunctionId { - let id = db.builtin_functions.len(); - - db.builtin_functions.insert(name, func); - BuiltinFunctionId(id) - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct BuiltinFunctionId(usize); - -impl BuiltinFunctionId { - pub fn kind(self, db: &Database) -> BuiltinFunctionKind { - self.get(db).kind - } - - 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 mapping() -> HashMap { + let mut map = HashMap::new(); + let funcs = 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::EnvPlatform, + 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::GetNil, + BuiltinFunction::HasherDrop, + BuiltinFunction::HasherNew, + BuiltinFunction::HasherToHash, + BuiltinFunction::HasherWriteInt, + 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::IsNull, + BuiltinFunction::Moved, + BuiltinFunction::ObjectEq, + BuiltinFunction::Panic, + BuiltinFunction::PanicThrown, + BuiltinFunction::PathAccessedAt, + BuiltinFunction::PathCreatedAt, + BuiltinFunction::PathExists, + BuiltinFunction::PathIsDirectory, + BuiltinFunction::PathIsFile, + BuiltinFunction::PathModifiedAt, + 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, + ]; + + for func in funcs { + map.insert(func.name().to_string(), func); + } + + map } - fn get(self, db: &Database) -> &BuiltinFunction { - &db.builtin_functions[self.0] + 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::EnvPlatform => "env_platform", + 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::GetNil => "get_nil", + BuiltinFunction::HasherDrop => "hasher_drop", + BuiltinFunction::HasherNew => "hasher_new", + BuiltinFunction::HasherToHash => "hasher_to_hash", + BuiltinFunction::HasherWriteInt => "hasher_write_int", + 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::IsNull => "is_null", + BuiltinFunction::Moved => "moved", + BuiltinFunction::ObjectEq => "object_eq", + BuiltinFunction::Panic => "panic", + BuiltinFunction::PanicThrown => "panic_thrown", + 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::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", + } + } + + pub fn return_type(self) -> TypeRef { + match self { + BuiltinFunction::ArrayCapacity => TypeRef::int(), + BuiltinFunction::ArrayClear => TypeRef::nil(), + BuiltinFunction::ArrayDrop => TypeRef::nil(), + BuiltinFunction::ArrayGet => TypeRef::Any, + BuiltinFunction::ArrayLength => TypeRef::int(), + BuiltinFunction::ArrayPop => TypeRef::Any, + 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::Any, + BuiltinFunction::ChildProcessSpawn => TypeRef::Any, + BuiltinFunction::ChildProcessStderrClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStderrRead => TypeRef::int(), + BuiltinFunction::ChildProcessStdinClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStdinFlush => TypeRef::nil(), + BuiltinFunction::ChildProcessStdinWriteBytes => TypeRef::int(), + BuiltinFunction::ChildProcessStdinWriteString => TypeRef::int(), + BuiltinFunction::ChildProcessStdoutClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStdoutRead => TypeRef::int(), + BuiltinFunction::ChildProcessTryWait => TypeRef::int(), + BuiltinFunction::ChildProcessWait => TypeRef::int(), + BuiltinFunction::CpuCores => TypeRef::int(), + BuiltinFunction::DirectoryCreate => TypeRef::nil(), + BuiltinFunction::DirectoryCreateRecursive => TypeRef::nil(), + BuiltinFunction::DirectoryList => TypeRef::Any, + BuiltinFunction::DirectoryRemove => TypeRef::nil(), + BuiltinFunction::DirectoryRemoveRecursive => TypeRef::nil(), + BuiltinFunction::EnvArguments => TypeRef::Any, + BuiltinFunction::EnvExecutable => TypeRef::string(), + BuiltinFunction::EnvGet => TypeRef::string(), + BuiltinFunction::EnvGetWorkingDirectory => TypeRef::string(), + BuiltinFunction::EnvHomeDirectory => TypeRef::string(), + BuiltinFunction::EnvPlatform => TypeRef::int(), + BuiltinFunction::EnvSetWorkingDirectory => TypeRef::nil(), + BuiltinFunction::EnvTempDirectory => TypeRef::string(), + BuiltinFunction::EnvVariables => TypeRef::Any, + BuiltinFunction::Exit => TypeRef::Never, + BuiltinFunction::FileCopy => TypeRef::int(), + BuiltinFunction::FileDrop => TypeRef::nil(), + BuiltinFunction::FileFlush => TypeRef::nil(), + BuiltinFunction::FileOpen => TypeRef::Any, + BuiltinFunction::FileRead => TypeRef::int(), + BuiltinFunction::FileRemove => TypeRef::nil(), + BuiltinFunction::FileSeek => TypeRef::int(), + BuiltinFunction::FileSize => TypeRef::int(), + BuiltinFunction::FileWriteBytes => TypeRef::int(), + BuiltinFunction::FileWriteString => TypeRef::int(), + 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 => TypeRef::Any, + BuiltinFunction::ChannelDrop => TypeRef::nil(), + BuiltinFunction::ChannelWait => TypeRef::nil(), + BuiltinFunction::ChannelNew => TypeRef::Any, + BuiltinFunction::ChannelSend => TypeRef::nil(), + BuiltinFunction::ChannelTryReceive => TypeRef::Any, + BuiltinFunction::GetNil => TypeRef::nil(), + BuiltinFunction::HasherDrop => TypeRef::nil(), + BuiltinFunction::HasherNew => TypeRef::Any, + BuiltinFunction::HasherToHash => TypeRef::int(), + BuiltinFunction::HasherWriteInt => TypeRef::nil(), + 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::IsNull => TypeRef::boolean(), + BuiltinFunction::Moved => TypeRef::nil(), + BuiltinFunction::ObjectEq => TypeRef::boolean(), + BuiltinFunction::Panic => TypeRef::Never, + BuiltinFunction::PathAccessedAt => TypeRef::float(), + BuiltinFunction::PathCreatedAt => TypeRef::float(), + BuiltinFunction::PathExists => TypeRef::boolean(), + BuiltinFunction::PathIsDirectory => TypeRef::boolean(), + BuiltinFunction::PathIsFile => TypeRef::boolean(), + BuiltinFunction::PathModifiedAt => TypeRef::float(), + 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 => TypeRef::Any, + BuiltinFunction::SocketAddressPairAddress => TypeRef::string(), + BuiltinFunction::SocketAddressPairDrop => TypeRef::nil(), + BuiltinFunction::SocketAddressPairPort => TypeRef::int(), + BuiltinFunction::SocketBind => TypeRef::nil(), + BuiltinFunction::SocketConnect => TypeRef::nil(), + BuiltinFunction::SocketDrop => TypeRef::nil(), + BuiltinFunction::SocketListen => TypeRef::nil(), + BuiltinFunction::SocketLocalAddress => TypeRef::Any, + BuiltinFunction::SocketNew => TypeRef::Any, + BuiltinFunction::SocketPeerAddress => TypeRef::Any, + BuiltinFunction::SocketRead => TypeRef::int(), + BuiltinFunction::SocketReceiveFrom => TypeRef::Any, + BuiltinFunction::SocketSendBytesTo => TypeRef::int(), + BuiltinFunction::SocketSendStringTo => TypeRef::int(), + BuiltinFunction::SocketSetBroadcast => TypeRef::nil(), + BuiltinFunction::SocketSetKeepalive => TypeRef::nil(), + BuiltinFunction::SocketSetLinger => TypeRef::nil(), + BuiltinFunction::SocketSetNodelay => TypeRef::nil(), + BuiltinFunction::SocketSetOnlyV6 => TypeRef::nil(), + BuiltinFunction::SocketSetRecvSize => TypeRef::nil(), + BuiltinFunction::SocketSetReuseAddress => TypeRef::nil(), + BuiltinFunction::SocketSetReusePort => TypeRef::nil(), + BuiltinFunction::SocketSetSendSize => TypeRef::nil(), + BuiltinFunction::SocketSetTtl => TypeRef::nil(), + BuiltinFunction::SocketShutdownRead => TypeRef::nil(), + BuiltinFunction::SocketShutdownReadWrite => TypeRef::nil(), + BuiltinFunction::SocketShutdownWrite => TypeRef::nil(), + BuiltinFunction::SocketTryClone => TypeRef::Any, + BuiltinFunction::SocketWriteBytes => TypeRef::int(), + BuiltinFunction::SocketWriteString => TypeRef::int(), + BuiltinFunction::StderrFlush => TypeRef::nil(), + BuiltinFunction::StderrWriteBytes => TypeRef::int(), + BuiltinFunction::StderrWriteString => TypeRef::int(), + BuiltinFunction::StdinRead => TypeRef::int(), + BuiltinFunction::StdoutFlush => TypeRef::nil(), + BuiltinFunction::StdoutWriteBytes => TypeRef::int(), + BuiltinFunction::StdoutWriteString => TypeRef::int(), + BuiltinFunction::StringByte => TypeRef::int(), + BuiltinFunction::StringCharacters => TypeRef::Any, + BuiltinFunction::StringCharactersDrop => TypeRef::nil(), + BuiltinFunction::StringCharactersNext => TypeRef::Any, + 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 => TypeRef::float(), + BuiltinFunction::StringToInt => TypeRef::int(), + BuiltinFunction::StringToLower => TypeRef::string(), + BuiltinFunction::StringToUpper => TypeRef::string(), + BuiltinFunction::TimeMonotonic => TypeRef::int(), + BuiltinFunction::TimeSystem => TypeRef::float(), + BuiltinFunction::TimeSystemOffset => TypeRef::int(), + BuiltinFunction::PanicThrown => TypeRef::Never, + } + } + + pub fn throw_type(self) -> TypeRef { + match self { + BuiltinFunction::ArrayCapacity => TypeRef::Never, + BuiltinFunction::ArrayClear => TypeRef::Never, + BuiltinFunction::ArrayDrop => TypeRef::Never, + BuiltinFunction::ArrayGet => TypeRef::Never, + BuiltinFunction::ArrayLength => TypeRef::Never, + BuiltinFunction::ArrayPop => TypeRef::Never, + BuiltinFunction::ArrayPush => TypeRef::Never, + BuiltinFunction::ArrayRemove => TypeRef::Never, + BuiltinFunction::ArrayReserve => TypeRef::Never, + BuiltinFunction::ArraySet => TypeRef::Never, + BuiltinFunction::ByteArrayAppend => TypeRef::Never, + BuiltinFunction::ByteArrayClear => TypeRef::Never, + BuiltinFunction::ByteArrayClone => TypeRef::Never, + BuiltinFunction::ByteArrayCopyFrom => TypeRef::Never, + BuiltinFunction::ByteArrayDrainToString => TypeRef::Never, + BuiltinFunction::ByteArrayDrop => TypeRef::Never, + BuiltinFunction::ByteArrayEq => TypeRef::Never, + BuiltinFunction::ByteArrayGet => TypeRef::Never, + BuiltinFunction::ByteArrayLength => TypeRef::Never, + BuiltinFunction::ByteArrayNew => TypeRef::Never, + BuiltinFunction::ByteArrayPop => TypeRef::Never, + BuiltinFunction::ByteArrayPush => TypeRef::Never, + BuiltinFunction::ByteArrayRemove => TypeRef::Never, + BuiltinFunction::ByteArrayResize => TypeRef::Never, + BuiltinFunction::ByteArraySet => TypeRef::Never, + BuiltinFunction::ByteArraySlice => TypeRef::Never, + BuiltinFunction::ByteArrayToString => TypeRef::Never, + BuiltinFunction::ChannelDrop => TypeRef::Never, + BuiltinFunction::ChannelNew => TypeRef::Never, + BuiltinFunction::ChannelReceive => TypeRef::Never, + BuiltinFunction::ChannelReceiveUntil => TypeRef::Any, + BuiltinFunction::ChannelSend => TypeRef::Never, + BuiltinFunction::ChannelTryReceive => TypeRef::Any, + BuiltinFunction::ChannelWait => TypeRef::Never, + BuiltinFunction::ChildProcessDrop => TypeRef::Never, + BuiltinFunction::ChildProcessSpawn => TypeRef::int(), + BuiltinFunction::ChildProcessStderrClose => TypeRef::Never, + BuiltinFunction::ChildProcessStderrRead => TypeRef::int(), + BuiltinFunction::ChildProcessStdinClose => TypeRef::Never, + BuiltinFunction::ChildProcessStdinFlush => TypeRef::int(), + BuiltinFunction::ChildProcessStdinWriteBytes => TypeRef::int(), + BuiltinFunction::ChildProcessStdinWriteString => TypeRef::int(), + BuiltinFunction::ChildProcessStdoutClose => TypeRef::Never, + BuiltinFunction::ChildProcessStdoutRead => TypeRef::int(), + BuiltinFunction::ChildProcessTryWait => TypeRef::int(), + BuiltinFunction::ChildProcessWait => TypeRef::int(), + BuiltinFunction::CpuCores => TypeRef::Never, + BuiltinFunction::DirectoryCreate => TypeRef::int(), + BuiltinFunction::DirectoryCreateRecursive => TypeRef::int(), + BuiltinFunction::DirectoryList => TypeRef::int(), + BuiltinFunction::DirectoryRemove => TypeRef::int(), + BuiltinFunction::DirectoryRemoveRecursive => TypeRef::int(), + BuiltinFunction::EnvArguments => TypeRef::Never, + BuiltinFunction::EnvExecutable => TypeRef::int(), + BuiltinFunction::EnvGet => TypeRef::Never, + BuiltinFunction::EnvGetWorkingDirectory => TypeRef::int(), + BuiltinFunction::EnvHomeDirectory => TypeRef::Never, + BuiltinFunction::EnvPlatform => TypeRef::Never, + BuiltinFunction::EnvSetWorkingDirectory => TypeRef::int(), + BuiltinFunction::EnvTempDirectory => TypeRef::Never, + BuiltinFunction::EnvVariables => TypeRef::Never, + BuiltinFunction::Exit => TypeRef::Never, + BuiltinFunction::FileCopy => TypeRef::int(), + BuiltinFunction::FileDrop => TypeRef::Never, + BuiltinFunction::FileFlush => TypeRef::int(), + BuiltinFunction::FileOpen => TypeRef::int(), + BuiltinFunction::FileRead => TypeRef::int(), + BuiltinFunction::FileRemove => TypeRef::int(), + BuiltinFunction::FileSeek => TypeRef::int(), + BuiltinFunction::FileSize => TypeRef::int(), + BuiltinFunction::FileWriteBytes => TypeRef::int(), + BuiltinFunction::FileWriteString => TypeRef::int(), + BuiltinFunction::FloatAdd => TypeRef::Never, + BuiltinFunction::FloatCeil => TypeRef::Never, + BuiltinFunction::FloatDiv => TypeRef::Never, + BuiltinFunction::FloatEq => TypeRef::Never, + BuiltinFunction::FloatFloor => TypeRef::Never, + BuiltinFunction::FloatFromBits => TypeRef::Never, + BuiltinFunction::FloatGe => TypeRef::Never, + BuiltinFunction::FloatGt => TypeRef::Never, + BuiltinFunction::FloatIsInf => TypeRef::Never, + BuiltinFunction::FloatIsNan => TypeRef::Never, + BuiltinFunction::FloatLe => TypeRef::Never, + BuiltinFunction::FloatLt => TypeRef::Never, + BuiltinFunction::FloatMod => TypeRef::Never, + BuiltinFunction::FloatMul => TypeRef::Never, + BuiltinFunction::FloatRound => TypeRef::Never, + BuiltinFunction::FloatSub => TypeRef::Never, + BuiltinFunction::FloatToBits => TypeRef::Never, + BuiltinFunction::FloatToInt => TypeRef::Never, + BuiltinFunction::FloatToString => TypeRef::Never, + BuiltinFunction::GetNil => TypeRef::Never, + BuiltinFunction::HasherDrop => TypeRef::Never, + BuiltinFunction::HasherNew => TypeRef::Never, + BuiltinFunction::HasherToHash => TypeRef::Never, + BuiltinFunction::HasherWriteInt => TypeRef::Never, + BuiltinFunction::IntAdd => TypeRef::Never, + BuiltinFunction::IntBitAnd => TypeRef::Never, + BuiltinFunction::IntBitNot => TypeRef::Never, + BuiltinFunction::IntBitOr => TypeRef::Never, + BuiltinFunction::IntBitXor => TypeRef::Never, + BuiltinFunction::IntDiv => TypeRef::Never, + BuiltinFunction::IntEq => TypeRef::Never, + BuiltinFunction::IntGe => TypeRef::Never, + BuiltinFunction::IntGt => TypeRef::Never, + BuiltinFunction::IntLe => TypeRef::Never, + BuiltinFunction::IntLt => TypeRef::Never, + BuiltinFunction::IntMul => TypeRef::Never, + BuiltinFunction::IntPow => TypeRef::Never, + BuiltinFunction::IntRem => TypeRef::Never, + BuiltinFunction::IntRotateLeft => TypeRef::Never, + BuiltinFunction::IntRotateRight => TypeRef::Never, + BuiltinFunction::IntShl => TypeRef::Never, + BuiltinFunction::IntShr => TypeRef::Never, + BuiltinFunction::IntSub => TypeRef::Never, + BuiltinFunction::IntToFloat => TypeRef::Never, + BuiltinFunction::IntToString => TypeRef::Never, + BuiltinFunction::IntUnsignedShr => TypeRef::Never, + BuiltinFunction::IntWrappingAdd => TypeRef::Never, + BuiltinFunction::IntWrappingMul => TypeRef::Never, + BuiltinFunction::IntWrappingSub => TypeRef::Never, + BuiltinFunction::IsNull => TypeRef::Never, + BuiltinFunction::Moved => TypeRef::Never, + BuiltinFunction::ObjectEq => TypeRef::Never, + BuiltinFunction::Panic => TypeRef::Never, + BuiltinFunction::PanicThrown => TypeRef::Never, + BuiltinFunction::PathAccessedAt => TypeRef::int(), + BuiltinFunction::PathCreatedAt => TypeRef::int(), + BuiltinFunction::PathExists => TypeRef::Never, + BuiltinFunction::PathIsDirectory => TypeRef::Never, + BuiltinFunction::PathIsFile => TypeRef::Never, + BuiltinFunction::PathModifiedAt => TypeRef::int(), + BuiltinFunction::ProcessStackFrameLine => TypeRef::Never, + BuiltinFunction::ProcessStackFrameName => TypeRef::Never, + BuiltinFunction::ProcessStackFramePath => TypeRef::Never, + BuiltinFunction::ProcessStacktrace => TypeRef::Never, + BuiltinFunction::ProcessStacktraceDrop => TypeRef::Never, + BuiltinFunction::ProcessStacktraceLength => TypeRef::Never, + BuiltinFunction::ProcessSuspend => TypeRef::Never, + BuiltinFunction::RandomBytes => TypeRef::Never, + BuiltinFunction::RandomDrop => TypeRef::Never, + BuiltinFunction::RandomFloat => TypeRef::Never, + BuiltinFunction::RandomFloatRange => TypeRef::Never, + BuiltinFunction::RandomFromInt => TypeRef::Never, + BuiltinFunction::RandomInt => TypeRef::Never, + BuiltinFunction::RandomIntRange => TypeRef::Never, + BuiltinFunction::RandomNew => TypeRef::Never, + BuiltinFunction::SocketAccept => TypeRef::int(), + BuiltinFunction::SocketAddressPairAddress => TypeRef::Never, + BuiltinFunction::SocketAddressPairDrop => TypeRef::Never, + BuiltinFunction::SocketAddressPairPort => TypeRef::Never, + BuiltinFunction::SocketBind => TypeRef::int(), + BuiltinFunction::SocketConnect => TypeRef::int(), + BuiltinFunction::SocketDrop => TypeRef::Never, + BuiltinFunction::SocketListen => TypeRef::int(), + BuiltinFunction::SocketLocalAddress => TypeRef::int(), + BuiltinFunction::SocketNew => TypeRef::int(), + BuiltinFunction::SocketPeerAddress => TypeRef::int(), + BuiltinFunction::SocketRead => TypeRef::int(), + BuiltinFunction::SocketReceiveFrom => TypeRef::int(), + BuiltinFunction::SocketSendBytesTo => TypeRef::int(), + BuiltinFunction::SocketSendStringTo => TypeRef::int(), + BuiltinFunction::SocketSetBroadcast => TypeRef::int(), + BuiltinFunction::SocketSetKeepalive => TypeRef::int(), + BuiltinFunction::SocketSetLinger => TypeRef::int(), + BuiltinFunction::SocketSetNodelay => TypeRef::int(), + BuiltinFunction::SocketSetOnlyV6 => TypeRef::int(), + BuiltinFunction::SocketSetRecvSize => TypeRef::int(), + BuiltinFunction::SocketSetReuseAddress => TypeRef::int(), + BuiltinFunction::SocketSetReusePort => TypeRef::int(), + BuiltinFunction::SocketSetSendSize => TypeRef::int(), + BuiltinFunction::SocketSetTtl => TypeRef::int(), + BuiltinFunction::SocketShutdownRead => TypeRef::int(), + BuiltinFunction::SocketShutdownReadWrite => TypeRef::int(), + BuiltinFunction::SocketShutdownWrite => TypeRef::int(), + BuiltinFunction::SocketTryClone => TypeRef::int(), + BuiltinFunction::SocketWriteBytes => TypeRef::int(), + BuiltinFunction::SocketWriteString => TypeRef::int(), + BuiltinFunction::StderrFlush => TypeRef::int(), + BuiltinFunction::StderrWriteBytes => TypeRef::int(), + BuiltinFunction::StderrWriteString => TypeRef::int(), + BuiltinFunction::StdinRead => TypeRef::int(), + BuiltinFunction::StdoutFlush => TypeRef::int(), + BuiltinFunction::StdoutWriteBytes => TypeRef::int(), + BuiltinFunction::StdoutWriteString => TypeRef::int(), + BuiltinFunction::StringByte => TypeRef::Never, + BuiltinFunction::StringCharacters => TypeRef::Never, + BuiltinFunction::StringCharactersDrop => TypeRef::Never, + BuiltinFunction::StringCharactersNext => TypeRef::Never, + BuiltinFunction::StringConcat => TypeRef::Never, + BuiltinFunction::StringConcatArray => TypeRef::Never, + BuiltinFunction::StringDrop => TypeRef::Never, + BuiltinFunction::StringEq => TypeRef::Never, + BuiltinFunction::StringSize => TypeRef::Never, + BuiltinFunction::StringSliceBytes => TypeRef::Never, + BuiltinFunction::StringToByteArray => TypeRef::Never, + BuiltinFunction::StringToFloat => TypeRef::Never, + BuiltinFunction::StringToInt => TypeRef::Never, + BuiltinFunction::StringToLower => TypeRef::Never, + BuiltinFunction::StringToUpper => TypeRef::Never, + BuiltinFunction::TimeMonotonic => TypeRef::Never, + BuiltinFunction::TimeSystem => TypeRef::Never, + BuiltinFunction::TimeSystemOffset => TypeRef::Never, + } } } @@ -2262,21 +2645,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,6 +2677,7 @@ pub struct Method { visibility: Visibility, type_parameters: IndexMap, arguments: Arguments, + bounds: TypeBounds, throw_type: TypeRef, return_type: TypeRef, source: MethodSource, @@ -2316,13 +2687,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 +2718,12 @@ 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 +2731,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 +2741,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 +2760,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,118 +2776,23 @@ 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) + pub fn name(self, db: &Database) -> &String { + &self.get(db).name } - 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) - }) + pub fn is_private(self, db: &Database) -> bool { + !self.is_public(db) } - 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(); + pub fn is_public(self, db: &Database) -> bool { + self.get(db).visibility == Visibility::Public + } - 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 - } - - pub fn is_private(self, db: &Database) -> bool { - !self.is_public(db) - } - - pub fn is_public(self, db: &Database) -> bool { - self.get(db).visibility == Visibility::Public - } - - pub fn is_mutable(self, db: &Database) -> bool { - matches!( - self.get(db).kind, - MethodKind::Mutable | MethodKind::AsyncMutable - ) - } - - pub fn is_immutable(self, db: &Database) -> bool { - matches!( - self.get(db).kind, - MethodKind::Async | MethodKind::Static | MethodKind::Instance - ) + pub fn is_mutable(self, db: &Database) -> bool { + matches!( + self.get(db).kind, + MethodKind::Mutable | MethodKind::AsyncMutable + ) } pub fn is_async(self, db: &Database) -> bool { @@ -2613,7 +2880,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 +2888,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] } @@ -2669,51 +2944,6 @@ impl Block for MethodId { } } -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()`) @@ -2767,7 +2997,7 @@ pub struct ClosureCallInfo { #[derive(Clone, Debug, PartialEq, Eq)] pub struct BuiltinCallInfo { - pub id: BuiltinFunctionId, + pub id: BuiltinFunction, pub returns: TypeRef, pub throws: TypeRef, } @@ -2782,7 +3012,7 @@ pub struct FieldInfo { pub enum CallKind { Unknown, Call(CallInfo), - ClosureCall(ClosureCallInfo), + CallClosure(ClosureCallInfo), GetField(FieldInfo), SetField(FieldInfo), } @@ -2945,12 +3175,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 +3227,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, @@ -3102,7 +3325,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); @@ -3195,71 +3418,7 @@ 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] } @@ -3279,26 +3438,6 @@ impl ClosureId { 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 { @@ -3332,24 +3471,6 @@ impl Block for ClosureId { } } -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 { @@ -3399,18 +3520,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 +3536,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, @@ -3513,15 +3590,14 @@ impl 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 +3606,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 +3676,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 +3686,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 +3732,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 +3785,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 +3795,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 +3805,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 +3813,23 @@ impl TypeRef { } } + pub fn is_mutable(self, db: &Database) -> bool { + match self { + TypeRef::Owned(_) + | TypeRef::Uni(_) + | TypeRef::Mut(_) + | TypeRef::Infer(_) + | TypeRef::Error + | TypeRef::Unknown => true, + TypeRef::Placeholder(id) => id.resolve(db).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 +3839,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_with_self_type(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 +3889,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)) } @@ -3802,46 +3897,6 @@ impl TypeRef { } } - pub fn is_sendable_output(self, db: &Database) -> bool { - if self.is_value_type(db) { - return true; - } - - match self { - TypeRef::Uni(_) - | TypeRef::UniSelf - | TypeRef::Never - | TypeRef::Any - | TypeRef::Error => true, - TypeRef::Owned(TypeId::ClassInstance(id)) => { - let class = id.instance_of; - - if class.is_generic(db) - && !id - .type_arguments(db) - .mapping - .iter() - .all(|(_, v)| v.is_sendable_output(db)) - { - return false; - } - - class - .fields(db) - .into_iter() - .all(|f| f.value_type(db).is_sendable_output(db)) - } - TypeRef::Placeholder(id) => { - id.value(db).map_or(true, |v| v.is_sendable_output(db)) - } - _ => false, - } - } - - 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 +3928,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,11 +3935,40 @@ impl TypeRef { } } + pub fn allow_as_ref(self, db: &Database) -> bool { + match self { + TypeRef::Any => true, + TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Ref(_) => true, + TypeRef::Placeholder(id) => { + id.value(db).map_or(false, |v| v.allow_as_ref(db)) + } + _ => false, + } + } + + pub fn allow_as_mut(self, db: &Database) -> bool { + match self { + 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(false, |v| v.allow_as_mut(db)) + } + _ => false, + } + } + pub fn as_mut(self, db: &Database) -> Self { match self { - TypeRef::Owned(id) | TypeRef::Infer(id) => TypeRef::Mut(id), + 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::OwnedSelf | TypeRef::UniSelf => TypeRef::MutSelf, TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_mut(db)) } @@ -3922,7 +4003,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 +4017,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 +4024,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 +4034,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,46 +4079,7 @@ 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) - } - + // TODO: can we get rid of this and use the new resolver? 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)), @@ -4249,37 +4091,7 @@ impl TypeRef { } } - 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, - } - } - - pub fn is_value_type(self, db: &Database) -> bool { + pub fn is_value_type(self, db: &Database) -> bool { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) if ins.instance_of.kind(db).is_async() => @@ -4292,7 +4104,12 @@ impl TypeRef { | TypeRef::Uni(TypeId::ClassInstance(ins)) => { matches!( ins.instance_of.0, - INT_ID | FLOAT_ID | STRING_ID | BOOLEAN_ID | NIL_ID + INT_ID + | FLOAT_ID + | STRING_ID + | BOOLEAN_ID + | NIL_ID + | CHANNEL_ID ) } TypeRef::Placeholder(id) => { @@ -4322,6 +4139,7 @@ impl TypeRef { } } + // TODO: remove this method? pub fn is_inferred(self, db: &Database) -> bool { match self { TypeRef::Owned(id) @@ -4352,20 +4170,11 @@ impl TypeRef { } } - pub fn implements_trait_id( - self, - db: &Database, - trait_id: TraitId, - self_type: TypeId, - ) -> bool { + pub fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { 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) @@ -4375,490 +4184,38 @@ impl TypeRef { } } - pub fn class_id(self, db: &Database, self_type: TypeId) -> Option { + pub fn class_id_with_self_type(self, db: &Database) -> Option { 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::Placeholder(p) => { - p.value(db).and_then(|v| v.class_id(db, self_type)) + p.value(db).and_then(|v| v.class_id_with_self_type(db)) } _ => None, } } - 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 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); - - let compat = - self.type_check_directly(db, rhs, context, subtyping); - - if compat && update { - placeholder.assign(db, self); - } - - return compat; - } - - return self.type_check_directly( - db, - assigned.cast_according_to(with, db), - context, - subtyping, - ); - } - - if self.type_check_directly(db, with, context, subtyping) { - context.type_arguments.assign(param, self); - - return true; - } - - false - } - - 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) - } else { - with.assign(db, self); - true - } - } - - fn type_check_directly( + pub fn class_id( self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - subtyping: 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); - } - + db: &Database, + self_class: ClassId, + ) -> Option { 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::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, self_class)) } + _ => None, } - - 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), - }; + fn is_instance_of(self, db: &Database, id: ClassId) -> bool { + self.class_id_with_self_type(db) == Some(id) } } @@ -4929,26 +4286,6 @@ impl TypeId { } } - 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, @@ -5014,53 +4351,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 +4366,7 @@ pub struct Database { closures: Vec, variables: Vec, constants: Vec, - builtin_functions: IndexMap, + builtin_functions: HashMap, type_placeholders: Vec, variants: Vec, @@ -5085,6 +4375,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 { @@ -5096,12 +4388,12 @@ impl Database { classes: vec![ Class::regular(INT_NAME.to_string()), Class::regular(FLOAT_NAME.to_string()), - Class::regular(STRING_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::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()), @@ -5118,10 +4410,12 @@ impl Database { closures: Vec::new(), variables: Vec::new(), constants: Vec::new(), - builtin_functions: IndexMap::new(), + builtin_functions: BuiltinFunction::mapping(), type_placeholders: Vec::new(), variants: Vec::new(), main_module: None, + main_method: None, + main_class: None, } } @@ -5134,7 +4428,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 +4441,8 @@ 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 module(&self, name: &str) -> ModuleId { @@ -5200,16 +4494,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 +4569,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,350 +4656,52 @@ mod tests { } #[test] - fn test_trait_instance_type_check_with_generic_trait_instance() { + fn test_trait_instance_as_rigid_type_with_regular_trait() { let mut db = Database::new(); - let trait_a = Trait::alloc( + let to_s = Trait::alloc( &mut db, - "A".to_string(), + "ToString".to_string(), ModuleId(0), Visibility::Private, ); - let trait_b = Trait::alloc( + 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, - "B".to_string(), + "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(); - 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(); + args.assign(param1, TypeRef::Owned(TypeId::TypeParameter(param2))); - ins1_args.assign(param1, TypeRef::Any); - ins2_args.assign(param1, TypeRef::Any); - ins3_args.assign(param1, TypeRef::Never); + 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(); - 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()); + assert_ne!(old_arg, new_arg); + assert_eq!(new_arg, TypeParameterId(1).as_owned_rigid()); + } - let int = Class::alloc( + #[test] + fn test_class_alloc() { + let mut db = Database::new(); + let id = 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( - &mut db, - "A".to_string(), + "A".to_string(), ClassKind::Regular, Visibility::Private, ModuleId(0), @@ -5865,2904 +4819,330 @@ mod tests { } #[test] - fn test_class_instance_type_check_with_class_instance() { + fn test_class_instance_as_rigid_type_with_regular_trait() { 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( + let string = Class::alloc( &mut db, - "Int".to_string(), + "String".to_string(), ClassKind::Regular, Visibility::Private, ModuleId(0), ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); + let string_ins = ClassInstance::new(string); + let bounds = TypeBounds::new(); + let rigid = string_ins.as_rigid_type(&mut db, &bounds); - 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 - )); + assert_eq!(rigid, string_ins); } #[test] - fn test_class_instance_type_check_with_generic_class_instance() { + fn test_class_instance_as_rigid_type_with_generic_trait() { 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( + let array = Class::alloc( &mut db, - "B".to_string(), + "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(); - 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(); + args.assign(param1, TypeRef::Owned(TypeId::TypeParameter(param2))); - ins1_args.assign(param1, TypeRef::Any); - ins2_args.assign(param1, TypeRef::Any); - ins3_args.assign(param1, TypeRef::Never); + 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(); - 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)) - ); - } + assert_ne!(old_arg, new_arg); + assert_eq!(new_arg, TypeParameterId(1).as_owned_rigid()); + } #[test] - fn test_type_id_named_type_with_module() { + fn test_method_alloc() { let mut db = Database::new(); - let string = Class::alloc( + let id = Method::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, + "foo".to_string(), Visibility::Private, - ModuleId(0), + MethodKind::Moving, ); - 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()); + 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_named_type_with_trait_instance() { + fn test_method_id_named_type() { let mut db = Database::new(); - let to_array = Trait::alloc( + let method = Method::alloc( &mut db, - "ToArray".to_string(), ModuleId(0), + "foo".to_string(), Visibility::Private, + MethodKind::Instance, ); - let param = to_array.new_type_parameter(&mut db, "T".to_string()); - let ins = TypeId::TraitInstance(TraitInstance::generic( - &mut db, - to_array, - TypeArguments::new(), - )); + let param = method.new_type_parameter(&mut db, "A".to_string()); assert_eq!( - ins.named_type(&db, "T"), + method.named_type(&db, "A"), 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( - &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!(closure1_type.type_check( - &mut db, - closure2_type, - &mut ctx, - false - )); } #[test] - fn test_type_id_format_type_with_class() { + fn test_module_alloc() { let mut db = Database::new(); - let id = TypeId::Class(Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - )); + let name = ModuleName::new("foo"); + let id = Module::alloc(&mut db, name.clone(), "foo.inko".into()); - assert_eq!(format_type(&db, id), "String"); + 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_trait() { + fn test_module_id_file() { let mut db = Database::new(); - let id = TypeId::Trait(Trait::alloc( + let id = Module::alloc( &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - )); + ModuleName::new("foo"), + PathBuf::from("test.inko"), + ); - assert_eq!(format_type(&db, id), "ToString"); + assert_eq!(id.file(&db), PathBuf::from("test.inko")); } #[test] - fn test_type_id_format_type_with_module() { + fn test_module_id_symbol() { let mut db = Database::new(); - let id = TypeId::Module(Module::alloc( + let id = Module::alloc( &mut db, - ModuleName::new("foo::bar"), - "foo/bar.inko".into(), - )); + ModuleName::new("foo"), + PathBuf::from("test.inko"), + ); + + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - assert_eq!(format_type(&db, id), "foo::bar"); + assert_eq!(id.symbol(&db, "A"), Some(Symbol::Module(id))); } #[test] - fn test_type_id_format_type_with_class_instance() { + fn test_module_id_symbols() { 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"); + 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_tuple_instance() { + fn test_module_id_symbol_exists() { 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); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); + + assert!(id.symbol_exists(&db, "A")); + assert!(!id.symbol_exists(&db, "B")); + } - let ins = - TypeId::ClassInstance(ClassInstance::generic(&mut db, id, args)); + #[test] + fn test_function_closure() { + let mut db = Database::new(); + let id = Closure::alloc(&mut db, false); - assert_eq!(format_type(&db, ins), "(Any, Never)"); + assert_eq!(id.0, 0); } #[test] - fn test_type_id_format_type_with_trait_instance() { + fn test_closure_id_as_rigid_type_with_regular_function() { let mut db = Database::new(); - let id = Trait::alloc( + let closure = Closure::alloc(&mut db, false); + let to_s = Trait::alloc( &mut db, "ToString".to_string(), ModuleId(0), Visibility::Private, ); - let ins = TypeId::TraitInstance(TraitInstance::new(id)); + 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); - assert_eq!(format_type(&db, ins), "ToString"); + let bounds = TypeBounds::new(); + let new_closure = closure.as_rigid_type(&mut db, &bounds); + + assert_eq!(new_closure, ClosureId(1)); } #[test] - fn test_type_id_format_type_with_generic_class_instance() { + fn test_closure_id_as_rigid_type_with_generic_function() { let mut db = Database::new(); - let id = Class::alloc( - &mut db, - "Future".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param1 = id.new_type_parameter(&mut db, "T".to_string()); + let closure = Closure::alloc(&mut db, false); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let param_type = TypeRef::Owned(TypeId::TypeParameter(param)); - id.new_type_parameter(&mut db, "E".to_string()); + 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 mut targs = TypeArguments::new(); + let bounds = TypeBounds::new(); + let new_closure = closure.as_rigid_type(&mut db, &bounds); - targs.assign(param1, TypeRef::Any); + assert_ne!(closure, new_closure); - let ins = - TypeId::ClassInstance(ClassInstance::generic(&mut db, id, targs)); + let new_arg = new_closure.get(&db).arguments.get("a").unwrap(); - assert_eq!(format_type(&db, ins), "Future[Any, E]"); + 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_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 +5156,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 +5176,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 +5196,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.resolve(&db), TypeRef::Any); + assert_eq!(var2.resolve(&db), TypeRef::Any); + assert_eq!(var3.resolve(&db), 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..e091479af 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)] diff --git a/types/src/resolve.rs b/types/src/resolve.rs new file mode 100644 index 000000000..0c092680c --- /dev/null +++ b/types/src/resolve.rs @@ -0,0 +1,791 @@ +//! Resolving abstract types into concrete types. +use crate::either::Either; +use crate::{ + ClassInstance, Closure, Database, TraitId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameterId, TypePlaceholder, 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. + pub immutable: bool, + + /// When set to true, non-rigid type parameters are turned into rigid + /// parameters instead of placeholders. + pub 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 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) => match id.resolve(self.db) { + TypeRef::Unknown => value, + typ => self.resolve(typ), + }, + _ => 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( + TypePlaceholder::alloc(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(); + + for arg in new.arguments.mapping.values_mut() { + arg.value_type = self.resolve(arg.value_type); + } + + new.throw_type = self.resolve(new.throw_type); + new.return_type = self.resolve(new.return_type); + 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 { + let mut resolver = TypeResolver::new(db, type_arguments, bounds); + + resolver.immutable = true; + resolver.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.set_throw_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.throw_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..4a310f51f --- /dev/null +++ b/types/src/test.rs @@ -0,0 +1,155 @@ +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_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 index 82dc4f1fc..677bfea90 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -8,13 +8,9 @@ license = "MPL-2.0" [lib] doctest = false - -[features] -libffi-system = ["libffi/system"] +crate-type = ["staticlib", "lib"] [dependencies] -libloading = "^0.7" -libffi = "^3.0" crossbeam-utils = "^0.8" crossbeam-queue = "^0.3" libc = "^0.2" @@ -22,7 +18,6 @@ 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" @@ -36,4 +31,6 @@ features = [ "Win32_System_Time", "Win32_System_SystemInformation", "Win32_System_SystemServices", + "Win32_System_Memory", + "Win32_System_Diagnostics_Debug", ] diff --git a/vm/src/arc_without_weak.rs b/vm/src/arc_without_weak.rs index a0794ce9e..86154f4a0 100644 --- a/vm/src/arc_without_weak.rs +++ b/vm/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/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/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/config.rs b/vm/src/config.rs index aa8595ae9..b63ee5ac9 100644 --- a/vm/src/config.rs +++ b/vm/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 = 1 * 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/vm/src/context.rs b/vm/src/context.rs new file mode 100644 index 000000000..f6ba522f1 --- /dev/null +++ b/vm/src/context.rs @@ -0,0 +1,98 @@ +//! 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; + +#[cfg(unix)] +mod unix; + +#[cfg(windows)] +mod windows; + +/// 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" { + #[cfg(windows)] + fn inko_context_init( + low: *mut u8, + high: *mut *mut u8, + func: NativeAsyncMethod, + ctx: *mut u8, + ); + + #[cfg(not(windows))] + fn inko_context_init( + high: *mut *mut u8, + func: NativeAsyncMethod, + ctx: *mut u8, + ); + + fn inko_context_switch(stack: *mut *mut u8); +} + +#[inline(never)] +#[cfg(not(windows))] +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)] +#[cfg(windows)] +pub(crate) unsafe fn start( + state: &State, + mut process: ProcessPointer, + func: NativeAsyncMethod, + args: Vec<*mut u8>, +) { + let ctx = + Context { state: state as _, process, arguments: args.as_mut_ptr() }; + let low = process.stack.as_ref().unwrap().ptr(); + let high = &mut process.stack_pointer; + + inko_context_init(low, high, 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/vm/src/context/unix.rs b/vm/src/context/unix.rs new file mode 100644 index 000000000..5db896330 --- /dev/null +++ b/vm/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/vm/src/context/unix/aarch64.rs b/vm/src/context/unix/aarch64.rs new file mode 100644 index 000000000..03478d536 --- /dev/null +++ b/vm/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/vm/src/context/unix/x86_64.rs b/vm/src/context/unix/x86_64.rs new file mode 100644 index 000000000..694201461 --- /dev/null +++ b/vm/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/context/windows.rs b/vm/src/context/windows.rs new file mode 100644 index 000000000..b99cb7890 --- /dev/null +++ b/vm/src/context/windows.rs @@ -0,0 +1,5 @@ +#[cfg(target_arch = "x86_64")] +mod x86_64; + +#[cfg(not(target_arch = "x86_64"))] +std::compile_error!("Only x86-64 is supported for Windows at this time"); diff --git a/vm/src/context/windows/x86_64.rs b/vm/src/context/windows/x86_64.rs new file mode 100644 index 000000000..0f369bda1 --- /dev/null +++ b/vm/src/context/windows/x86_64.rs @@ -0,0 +1,115 @@ +// See the following resources for more details: +// +// - https://probablydance.com/2013/02/20/handmade-coroutines-for-windows +// - https://github.com/slembcke/Tina/blob/c0aad745d43b5265df952e89602e1a687a78ee65/extras/win-asm/win64-swap.S + +// RCX: a pointer pointer to the low address of the new stack +// RDX: a pointer pointer to the high address of the new stack +// R8: a pointer to a function to call +// R9: an extra data argument to pass to the function. +asm_func!( + "inko_context_init", + " + push qword ptr gs:[0x8] // Stack high address + push qword ptr gs:[0x10] // Stack low address + push qword ptr gs:[0x1478] // Deallocation stack + + push rbp + push rbx + push rdi + push rsi + push r12 + push r13 + push r14 + push r15 + + sub rsp, 160 + movaps [rsp + 0x90], xmm6 + movaps [rsp + 0x80], xmm7 + movaps [rsp + 0x70], xmm8 + movaps [rsp + 0x60], xmm9 + movaps [rsp + 0x50], xmm10 + movaps [rsp + 0x40], xmm11 + movaps [rsp + 0x30], xmm12 + movaps [rsp + 0x20], xmm13 + movaps [rsp + 0x10], xmm14 + movaps [rsp + 0x00], xmm15 + + // Swap the stack pointers + mov r10, [rdx] + mov [rdx], rsp + mov rsp, r10 + + mov qword ptr gs:[0x8], rdx + mov qword ptr gs:[0x10], rcx + mov qword ptr gs:[0x1478], rcx + + // Reserve shadow space + sub rsp, 0x20 + + mov rcx, r9 + call r8 + " +); + +// RCX: a pointer pointer to a stack to restore. +asm_func!( + "inko_context_switch", + " + push qword ptr gs:[0x8] + push qword ptr gs:[0x10] + push qword ptr gs:[0x1478] + push rbp + push rbx + push rdi + push rsi + push r12 + push r13 + push r14 + push r15 + + sub rsp, 160 + movaps [rsp + 0x90], xmm6 + movaps [rsp + 0x80], xmm7 + movaps [rsp + 0x70], xmm8 + movaps [rsp + 0x60], xmm9 + movaps [rsp + 0x50], xmm10 + movaps [rsp + 0x40], xmm11 + movaps [rsp + 0x30], xmm12 + movaps [rsp + 0x20], xmm13 + movaps [rsp + 0x10], xmm14 + movaps [rsp + 0x00], xmm15 + + // Swap the stack pointers + mov r8, [rcx] + mov [rcx], rsp + mov rsp, r8 + + movaps xmm6, [rsp + 0x90] + movaps xmm7, [rsp + 0x80] + movaps xmm8, [rsp + 0x70] + movaps xmm9, [rsp + 0x60] + movaps xmm10, [rsp + 0x50] + movaps xmm11, [rsp + 0x40] + movaps xmm12, [rsp + 0x30] + movaps xmm13, [rsp + 0x20] + movaps xmm14, [rsp + 0x10] + movaps xmm15, [rsp + 0x00] + add rsp, 160 + + pop r15 + pop r14 + pop r13 + pop r12 + pop rsi + pop rdi + pop rbx + pop rbp + + pop qword ptr gs:[0x1478] + pop qword ptr gs:[0x10] + pop qword ptr gs:[0x8] + + ret + " +); 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 index e11ddce49..50157aaa2 100644 --- a/vm/src/hasher.rs +++ b/vm/src/hasher.rs @@ -4,7 +4,8 @@ use std::hash::{Hash, Hasher as _}; use std::i64; #[derive(Clone)] -pub(crate) struct Hasher { +#[repr(C)] +pub struct Hasher { hasher: AHasher, } 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/lib.rs b/vm/src/lib.rs index 01eb4a253..42427bb13 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -6,28 +6,21 @@ 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 context; pub mod hasher; -pub mod image; 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 page; pub mod platform; 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/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 index 0ce28ef1c..a41ac8a31 100644 --- a/vm/src/machine.rs +++ b/vm/src/machine.rs @@ -113,7 +113,9 @@ impl<'a> Machine<'a> { } } - state.scheduler.run(&*state, entry_class, entry_method); + let proc = Process::main(entry_class, entry_method); + + state.scheduler.run(&*state, proc); Ok(state.current_exit_status()) } @@ -539,7 +541,7 @@ impl<'a> Machine<'a> { Opcode::ProcessAllocate => { let reg = ins.arg(0); let idx = ins.u32_arg(1, 2); - let res = process::allocate(self.state, idx); + let res = process::allocate(self.state, thread, idx); state.context.set_register(reg, res); } @@ -846,10 +848,6 @@ impl<'a> Machine<'a> { 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); } @@ -1057,6 +1055,10 @@ impl<'a> Machine<'a> { self.state.terminate(); } + if let Some(stack) = process.stack.take() { + thread.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 diff --git a/vm/src/macros/mod.rs b/vm/src/macros/mod.rs index a07db0a14..349d8f008 100644 --- a/vm/src/macros/mod.rs +++ b/vm/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/vm/src/mem.rs b/vm/src/mem.rs index fbbb1b81c..a107ed971 100644 --- a/vm/src/mem.rs +++ b/vm/src/mem.rs @@ -1,65 +1,53 @@ 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::mem::{align_of, size_of, swap}; use std::ops::Deref; +use std::os::raw::c_char; 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; +const INT_SHIFT: usize = 1; /// The minimum integer value that can be stored as a tagged signed integer. -pub(crate) const MIN_INTEGER: i64 = i64::MIN >> INT_SHIFT_BITS; +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_INTEGER: i64 = i64::MAX >> INT_SHIFT_BITS; - -/// The bit set for all immediate values. -const IMMEDIATE_BIT: usize = 0b1; +pub(crate) const MAX_INT: i64 = i64::MAX >> INT_SHIFT; /// 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; +const INT_MASK: usize = 0b01; -/// The address of the `Nil` singleton. -const NIL_ADDRESS: usize = 0b00_0001; +/// 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 address of the `undefined` singleton. -const UNDEFINED_ADDRESS: usize = 0b00_1001; + /// The value is a reference to a heap allocated value. + Ref = 1, -/// The mask to apply for permanent objects. -const PERMANENT_MASK: usize = 0b00_0010; + /// The value is an owned value that uses atomic reference counting. + Atomic = 2, -/// The mask to apply for references. -const REF_MASK: usize = 0b00_0100; + /// The value musn't be dropped until the program stops. + Permanent = 3, -/// The mask to use for detecting values that are not immediate or permanent -/// values. -const LOCAL_OWNED_MASK: usize = 0b00_0011; + /// The value is a boxed Int. + Int = 4, -/// The mask to use for untagging a pointer. -const UNTAG_MASK: usize = (!0b111) as usize; + /// The value is a boxed Float. + Float = 5, +} pub(crate) fn allocate(layout: Layout) -> *mut u8 { unsafe { @@ -73,124 +61,34 @@ pub(crate) fn allocate(layout: Layout) -> *mut u8 { } } -/// 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) - } +fn with_mask(ptr: *const T, mask: usize) -> *const T { + (ptr as usize | mask) as _ +} - pub(crate) unsafe fn get_mut<'a, T>(self) -> &'a mut T { - &mut *(self.untagged_ptr() as *mut T) - } +fn mask_is_set(ptr: *const T, mask: usize) -> bool { + (ptr as usize & mask) == mask +} - /// 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) unsafe fn header_of<'a, T>(ptr: *const T) -> &'a mut Header { + &mut *(ptr as *mut Header) +} - pub(crate) fn untagged_ptr(self) -> *mut u8 { - (self.as_ptr() as usize & UNTAG_MASK) as _ - } +pub(crate) fn is_tagged_int(ptr: *const T) -> bool { + mask_is_set(ptr, INT_MASK) +} - pub(crate) unsafe fn as_int(self) -> i64 { - self.as_ptr() as i64 >> INT_SHIFT_BITS - } +fn fits_in_tagged_int(value: i64) -> bool { + (MIN_INT..=MAX_INT).contains(&value) +} - pub(crate) unsafe fn free(self) { - let header = self.get::
(); - let layout = header.class.instance_layout(); +pub(crate) fn tagged_int(value: i64) -> *const Int { + with_mask((value << INT_SHIFT) as *const Int, INT_MASK) +} - dealloc(self.untagged_ptr(), layout); - } +pub(crate) unsafe fn free(ptr: *mut T) { + let layout = header_of(ptr).class.instance_layout(); - pub(crate) fn mask_is_set(self, mask: usize) -> bool { - (self.as_ptr() as usize & mask) == mask - } + dealloc(ptr as *mut u8, layout); } /// The header used by heap allocated objects. @@ -199,33 +97,35 @@ impl Pointer { /// certain offsets in an object, even when not knowing what type of object /// we're dealing with. #[repr(C)] -pub(crate) struct Header { +pub struct Header { /// The class of the object. - pub(crate) class: ClassPointer, + pub class: ClassPointer, - /// A flag indicating the object uses atomic reference counting. - atomic: bool, + /// 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. /// - /// A 16-bits integer should be enough for every program. + /// If this count overflows the program terminates. In practise this should + /// never happen, as one needs _a lot_ of references to achieve this. /// - /// 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, + /// 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.atomic = false; + self.kind = Kind::Owned; self.references = 0; } pub(crate) fn init_atomic(&mut self, class: ClassPointer) { self.class = class; - self.atomic = true; + 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 @@ -233,171 +133,77 @@ impl Header { self.references = 1; } - pub(crate) fn is_atomic(&self) -> bool { - self.atomic + pub(crate) fn set_permanent(&mut self) { + self.kind = Kind::Permanent; } - 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 is_permanent(&self) -> bool { + matches!(self.kind, Kind::Permanent) } - 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) } + pub(crate) fn references(&self) -> u32 { + self.references } } -/// A method bound to an object. +/// A function 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. +/// 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(crate) struct Method { +pub 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>, -} + pub hash: u64, -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 - } + /// A pointer to the native function that backs this method. + pub code: extern "system" fn(), } -impl Deref for MethodPointer { - type Target = Method; - - fn deref(&self) -> &Method { - unsafe { &*(self.0 as *const Method) } +impl Method { + pub(crate) fn new(hash: u64, code: extern "system" fn()) -> Method { + Method { hash, code } } } /// 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, - +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: usize, + 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, - /// All the methods of this class. + /// 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 table is always a power of two, which means some - /// slots are NULL. - methods: [MethodPointer; 0], + /// The length of this array _must_ be a power of two. + pub methods: [Method; 0], } impl Class { - pub(crate) fn drop(ptr: ClassPointer) { - unsafe { - let layout = Self::layout(ptr.method_slots); - let raw_ptr = ptr.as_ptr(); + 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); - } + drop_in_place(raw_ptr); + dealloc(raw_ptr as *mut u8, layout); } pub(crate) fn alloc( name: RustString, methods: u16, - size: usize, + size: u32, ) -> ClassPointer { - let mut class_ptr = unsafe { + let ptr = unsafe { let layout = Self::layout(methods); // For classes we zero memory out, so unused method slots are set to @@ -408,173 +214,61 @@ impl Class { handle_alloc_error(layout); } - ClassPointer::new(ptr) + ptr }; - let class_ptr_copy = class_ptr; - let class = unsafe { class_ptr.get_mut() }; + let obj = unsafe { &mut *ptr }; - class.header.init(class_ptr_copy); + init!(obj.name => name); + init!(obj.instance_size => size); + init!(obj.method_slots => methods); - init!(class.name => name); - init!(class.instance_size => size); - init!(class.method_slots => methods); - - class_ptr + ClassPointer(ptr) } /// Returns a new class for a regular object. pub(crate) fn object( name: RustString, - fields: usize, + fields: u8, methods: u16, ) -> ClassPointer { - let size = size_of::() + (fields * size_of::()); + let size = + size_of::
() + (fields as usize * size_of::<*mut u8>()); - Self::alloc(name, methods, size) + Self::alloc(name, methods, size as u32) } /// Returns a new class for a process. pub(crate) fn process( name: RustString, - fields: usize, + fields: u8, methods: u16, ) -> ClassPointer { - let size = size_of::() + (fields * size_of::()); + let size = + size_of::() + (fields as usize * size_of::<*mut u8>()); - 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() - } + 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::()); + 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); - } + Layout::from_size_align_unchecked( + self.instance_size as usize, + ALIGNMENT, + ) } } /// 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) - } -} +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct ClassPointer(*mut Class); impl Deref for ClassPointer { type Target = Class; @@ -586,71 +280,57 @@ impl Deref for ClassPointer { /// A resizable array. #[repr(C)] -pub(crate) struct Array { - header: Header, - value: Vec, +pub struct Array { + pub(crate) header: Header, + pub(crate) value: Vec<*mut u8>, } 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) unsafe fn drop(ptr: *mut Self) { + drop_in_place(ptr); } - pub(crate) fn alloc(class: ClassPointer, value: Vec) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; + 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 value(&self) -> &Vec { - &self.value - } + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: Vec<*mut u8>, + ) -> *mut Self { + let ptr = Self::alloc(class, value); - pub(crate) fn value_mut(&mut self) -> &mut Vec { - &mut self.value + unsafe { header_of(ptr) }.set_permanent(); + ptr } } /// A resizable array of bytes. #[repr(C)] -pub(crate) struct ByteArray { - header: Header, - value: Vec, +pub struct ByteArray { + pub(crate) header: Header, + pub(crate) 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) unsafe fn drop(ptr: *mut Self) { + drop_in_place(ptr); } - pub(crate) fn alloc(class: ClassPointer, value: Vec) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; + 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 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(); @@ -661,228 +341,208 @@ impl ByteArray { /// A signed 64-bits integer. #[repr(C)] -pub(crate) struct Int { - header: Header, - value: i64, +pub struct Int { + pub(crate) header: Header, + pub(crate) value: i64, } impl Int { - pub(crate) fn alloc(class: ClassPointer, value: i64) -> Pointer { - if (MIN_INTEGER..=MAX_INTEGER).contains(&value) { - return Pointer::int(value); + pub(crate) fn new(class: ClassPointer, value: i64) -> *const Self { + if fits_in_tagged_int(value) { + tagged_int(value) + } else { + Self::boxed(class, value) } + } - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; + 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 + ptr as _ } - pub(crate) unsafe fn read(ptr: Pointer) -> i64 { - if ptr.is_tagged_int() { - ptr.as_int() - } else { - ptr.get::().value - } - } + pub(crate) fn boxed_permanent( + class: ClassPointer, + value: i64, + ) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; - pub(crate) unsafe fn read_u64(ptr: Pointer) -> u64 { - let val = Self::read(ptr); + obj.header.init(class); + obj.header.set_permanent(); + init!(obj.value => value); + ptr as _ + } - if val < 0 { - 0 + pub(crate) unsafe fn read(ptr: *const Int) -> i64 { + if is_tagged_int(ptr) { + ptr as i64 >> INT_SHIFT } else { - val as u64 + (*ptr).value } } } -/// A heap allocated float. #[repr(C)] -pub(crate) struct Float { - header: Header, - value: f64, +pub struct Bool { + pub(crate) header: Header, } -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 +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::()); + } } - /// 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 + 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); + ptr as _ } } -/// 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, +pub struct Nil { + pub(crate) header: Header, } -impl String { - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); +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) unsafe fn drop_and_deallocate(ptr: Pointer) { - Self::drop(ptr); - ptr.free(); - } + pub(crate) fn alloc(class: ClassPointer) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; - pub(crate) unsafe fn read(ptr: &Pointer) -> &str { - ptr.get::().value().as_slice() + obj.header.init(class); + ptr as _ } +} - pub(crate) fn alloc(class: ClassPointer, value: RustString) -> Pointer { - Self::from_immutable_string(class, ImmutableString::from(value)) - } +/// A heap allocated float. +#[repr(C)] +pub struct Float { + pub(crate) header: Header, + pub(crate) value: f64, +} - pub(crate) fn from_immutable_string( - class: ClassPointer, - value: ImmutableString, - ) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; +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_atomic(class); + obj.header.init(class); + obj.header.kind = Kind::Float; init!(obj.value => value); - ptr + ptr as _ } - pub(crate) fn value(&self) -> &ImmutableString { - &self.value + 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 module containing classes, methods, and code to run. +/// 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 Module { - header: Header, +pub struct String { + pub(crate) header: Header, + pub(crate) value: ImmutableString, } -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(); - } +impl String { + pub(crate) unsafe fn drop(ptr: *const Self) { + drop_in_place(ptr as *mut Self); } - 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) unsafe fn drop_and_deallocate(ptr: *const Self) { + Self::drop(ptr); + free(ptr as *mut u8); } - pub(crate) fn name(&self) -> &RustString { - &self.header.class.name + pub(crate) unsafe fn read<'a>(ptr: *const String) -> &'a str { + (*ptr).value.as_slice() } -} - -/// 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() + pub(crate) unsafe fn read_as_c_char<'a>( + ptr: *const String, + ) -> *const c_char { + (*ptr).value.as_c_char_pointer() } -} - -impl Deref for ModulePointer { - type Target = Module; - fn deref(&self) -> &Module { - unsafe { &*(self.0 as *const Module) } + pub(crate) fn alloc( + class: ClassPointer, + value: RustString, + ) -> *const String { + Self::from_immutable_string(class, ImmutableString::from(value)) } -} - -/// 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::() }; + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: RustString, + ) -> *const String { + let ptr = + Self::from_immutable_string(class, ImmutableString::from(value)); - obj.header.init(class); + unsafe { header_of(ptr) }.set_permanent(); 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) fn from_immutable_string( + class: ClassPointer, + value: ImmutableString, + ) -> *const String { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; - pub(crate) unsafe fn get_field(&self, index: FieldIndex) -> Pointer { - *self.fields.as_ptr().add(index.into()) + obj.header.init_atomic(class); + init!(obj.value => value); + ptr as _ } } #[cfg(test)] mod tests { use super::*; - use crate::test::{empty_method, empty_module, OwnedClass}; + use crate::test::setup; 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); + 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); @@ -893,160 +553,22 @@ mod tests { } #[test] - fn test_pointer_with_mask() { - let ptr = Pointer::with_mask(0x4 as _, 0b10); + fn test_with_mask() { + let ptr = with_mask(0x4 as *const u8, 0b10); - assert_eq!(ptr.as_ptr() as usize, 0x6); + assert_eq!(ptr as usize, 0x6); } #[test] - fn test_pointer_integer_tagging() { + fn test_tagged_int() { 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()); + assert_eq!(Int::read(tagged_int(3)), 3); + assert_eq!(Int::read(tagged_int(0)), 0); + assert_eq!(Int::read(tagged_int(-3)), -3); + assert!(is_tagged_int(tagged_int(3))); } } - #[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); @@ -1054,7 +576,7 @@ mod tests { assert_eq!(class.method_slots, 0); assert_eq!(class.instance_size, 24); - Class::drop(class); + unsafe { Class::drop(class) }; } #[test] @@ -1064,7 +586,7 @@ mod tests { assert_eq!(class.method_slots, 0); assert_eq!(class.instance_size, 24); - Class::drop(class); + unsafe { Class::drop(class) }; } #[test] @@ -1073,92 +595,23 @@ mod tests { 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() - ); - } + unsafe { Class::drop(class) }; } #[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); + let state = setup(); + let tagged = Int::new(state.int_class, 42); + let max = Int::new(state.int_class, i64::MAX); + let min = Int::new(state.int_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(); + free(min as *mut Int); + free(max as *mut Int); } } } diff --git a/vm/src/memory_map.rs b/vm/src/memory_map.rs new file mode 100644 index 000000000..4bc154c6a --- /dev/null +++ b/vm/src/memory_map.rs @@ -0,0 +1,151 @@ +use crate::page::{multiple_of_page_size, page_size}; +use std::io::{Error, Result as IoResult}; +use std::ptr::null_mut; + +#[cfg(not(windows))] +use libc::{ + mmap, mprotect, munmap, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE, + PROT_READ, PROT_WRITE, +}; + +#[cfg(windows)] +use windows_sys::Win32::System::Memory::{ + VirtualAlloc, VirtualFree, VirtualProtect, MEM_COMMIT, MEM_RELEASE, + MEM_RESERVE, PAGE_NOACCESS, PAGE_READWRITE, +}; + +#[cfg(any(target_os = "linux", target_os = "bsd"))] +fn mmap_options(stack: bool) -> std::os::raw::c_int { + let opts = MAP_PRIVATE | MAP_ANON; + + if stack { + opts | libc::MAP_STACK + } else { + opts + } +} + +#[cfg(not(any(target_os = "linux", target_os = "bsd")))] +fn mmap_options(_stack: bool) -> std::os::raw::c_int { + MAP_PRIVATE | MAP_ANON +} + +/// A chunk of memory created using `mmap` and similar functions. +pub(crate) struct MemoryMap { + pub(crate) ptr: *mut u8, + pub(crate) len: usize, +} + +impl MemoryMap { + #[cfg(not(windows))] + pub(crate) fn new(size: usize, stack: bool) -> Self { + let size = multiple_of_page_size(size); + let ptr = unsafe { + mmap( + null_mut(), + size, + PROT_READ | PROT_WRITE, + mmap_options(stack), + -1, + 0, + ) + }; + + if ptr == MAP_FAILED { + panic!("mmap(2) failed: {}", Error::last_os_error()); + } + + Self { ptr: ptr as *mut u8, len: size } + } + + #[cfg(windows)] + pub(crate) fn new(size: usize, _stack: bool) -> Self { + let size = multiple_of_page_size(size); + let ptr = unsafe { + VirtualAlloc( + null_mut(), + size, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + ) + }; + + if ptr.is_null() { + panic!("VirtualAlloc() failed: {}", Error::last_os_error()); + } + + Stack { ptr: ptr as *mut u8, len: map_size } + } + + #[cfg(not(windows))] + 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()) + } + } + + #[cfg(windows)] + pub(crate) fn protect(&mut self, start: usize) -> IoResult<()> { + let res = unsafe { + let mut old = 0; + + VirtualProtect( + self.ptr.add(start), + page_size(), + PAGE_NOACCESS, + &mut old, + ) + }; + + if res != 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } + } +} + +#[cfg(not(windows))] +impl Drop for MemoryMap { + fn drop(&mut self) { + unsafe { + munmap(self.ptr as _, self.len); + } + } +} + +#[cfg(windows)] +impl Drop for MemoryMap { + fn drop(&mut self) { + unsafe { + VirtualFree(self.ptr as _, self.len, MEM_RELEASE); + } + } +} + +#[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/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/page.rs b/vm/src/page.rs new file mode 100644 index 000000000..a1e5d5e01 --- /dev/null +++ b/vm/src/page.rs @@ -0,0 +1,42 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(windows)] +use windows_sys::Win32::System::SystemInformation::GetSystemInfo; + +#[cfg(not(windows))] +use libc::{sysconf, _SC_PAGESIZE}; + +static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); + +#[cfg(windows)] +fn page_size_raw() -> usize { + unsafe { + let mut info = MaybeUninit::uninit(); + + GetSystemInfo(info.as_mut_ptr()); + info.assume_init_ref().dwPageSize as usize + } +} + +#[cfg(not(windows))] +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/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/process.rs b/vm/src/process.rs index 40d67d3c5..70e90328d 100644 --- a/vm/src/process.rs +++ b/vm/src/process.rs @@ -1,150 +1,65 @@ 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::mem::{ + allocate, free, ClassPointer, Header, Int, String as InkoString, +}; +use crate::scheduler::process::Thread; use crate::scheduler::timeouts::Timeout; +use crate::stack::Stack; use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; +use std::cell::UnsafeCell; use std::collections::VecDeque; -use std::mem::{align_of, size_of, swap}; +use std::mem::{align_of, forget, size_of, ManuallyDrop}; use std::ops::Drop; use std::ops::{Deref, DerefMut}; -use std::ptr::{copy_nonoverlapping, drop_in_place, NonNull}; +use std::ptr::{drop_in_place, null_mut, 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); +/// 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); - state.waiting_for_future(timeout); - FutureResult::None - } +/// A single stack frame in a process' call stack. +/// +/// Frames are statically allocated and managed by the generated code, hence +/// this type doesn't implement `Drop`. +#[repr(C)] +pub struct StackFrame { + pub name: *const InkoString, + pub path: *const InkoString, + pub line: *const Int, } -/// 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. +/// A message sent between two processes. #[repr(C)] -struct ScheduledMethodInner { - /// The method to run. - method: MethodIndex, +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: [Pointer; 0], + arguments: [*mut u8; 0], } -/// An owned pointer to a ScheduledMethodInner. -struct ScheduledMethod(NonNull); - -impl ScheduledMethod { - fn new(method: MethodIndex, arguments: Vec) -> Self { +impl Message { + pub(crate) fn new(method: NativeAsyncMethod, length: u8) -> OwnedMessage { unsafe { - let layout = Self::layout(arguments.len() as u8); - let raw_ptr = alloc(layout) as *mut ScheduledMethodInner; + let layout = Self::layout(length); + let raw_ptr = alloc(layout) as *mut Self; if raw_ptr.is_null() { handle_alloc_error(layout); @@ -153,21 +68,14 @@ impl ScheduledMethod { let msg = &mut *raw_ptr; init!(msg.method => method); - init!(msg.length => arguments.len() as u8); + init!(msg.length => length); - copy_nonoverlapping( - arguments.as_ptr(), - msg.arguments.as_mut_ptr(), - arguments.len(), - ); - - Self(NonNull::new_unchecked(raw_ptr)) + OwnedMessage(NonNull::new_unchecked(raw_ptr)) } } unsafe fn layout(length: u8) -> Layout { - let size = size_of::() - + (length as usize * size_of::()); + 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. @@ -175,18 +83,40 @@ impl ScheduledMethod { } } -impl Deref for ScheduledMethod { - type Target = ScheduledMethodInner; +#[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) -> &ScheduledMethodInner { + fn deref(&self) -> &Message { unsafe { self.0.as_ref() } } } -impl Drop for ScheduledMethod { +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 = Self::layout(self.0.as_ref().length); + 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); @@ -194,27 +124,9 @@ impl Drop for ScheduledMethod { } } -/// 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, + messages: VecDeque, } impl Mailbox { @@ -222,134 +134,19 @@ impl Mailbox { Mailbox { messages: VecDeque::new() } } - fn send(&mut self, message: Message) { + fn send(&mut self, message: OwnedMessage) { self.messages.push_back(message); } - fn receive(&mut self) -> Option { + 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) } - } +pub(crate) enum Task { + Resume, + Start(NativeAsyncMethod, Vec<*mut u8>), + Wait, } /// The status of a process, represented as a set of bits. @@ -370,8 +167,8 @@ impl ProcessStatus { /// 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 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; @@ -382,9 +179,12 @@ impl ProcessStatus { /// 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_FUTURE | Self::SLEEPING | Self::WAITING_FOR_IO; + Self::WAITING_FOR_CHANNEL | Self::SLEEPING | Self::WAITING_FOR_IO; pub(crate) fn new() -> Self { Self { bits: Self::NORMAL } @@ -394,6 +194,14 @@ impl ProcessStatus { 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) } @@ -406,8 +214,8 @@ impl ProcessStatus { 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_channel(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_CHANNEL, enable); } fn set_waiting_for_io(&mut self, enable: bool) { @@ -418,8 +226,8 @@ impl ProcessStatus { 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_for_channel(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_CHANNEL) } fn is_waiting(&self) -> bool { @@ -513,7 +321,7 @@ impl ProcessState { return RescheduleRights::Failed; } - if self.status.is_waiting_for_future() + if self.status.is_waiting_for_channel() || self.status.is_waiting_for_io() { // We may be suspended for some time without actually waiting for @@ -531,13 +339,13 @@ impl ProcessState { } } - pub(crate) fn waiting_for_future( + pub(crate) fn waiting_for_channel( &mut self, timeout: Option>, ) { self.timeout = timeout; - self.status.set_waiting_for_future(true); + self.status.set_waiting_for_channel(true); } pub(crate) fn waiting_for_io( @@ -558,12 +366,12 @@ impl ProcessState { RescheduleRights::Acquired } - fn try_reschedule_for_future(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_future() { + fn try_reschedule_for_channel(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_channel() { return RescheduleRights::Failed; } - self.status.set_waiting_for_future(false); + self.status.set_waiting_for_channel(false); if self.timeout.take().is_some() { RescheduleRights::AcquiredWithTimeout @@ -589,39 +397,88 @@ impl ProcessState { /// A lightweight process. #[repr(C)] -pub(crate) struct Process { - pub(crate) header: Header, +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, - /// A boolean indicating that the result was thrown rather than returned. - thrown: bool, + /// 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, - /// The currently running task, if any. - task: Option, + /// The call stack. + /// + /// The most recent entry comes last. The values are pointers to statically + /// allocated stack frames, managed by the generated/native code. + pub(crate) call_stack: Vec<*const StackFrame>, - /// The last value returned or thrown. - result: Pointer, + /// A pointer to the thread running this process. + thread: Option>, - /// The internal shared state of the process. + /// 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. - fields: [Pointer; 0], + /// 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.as_pointer().as_ptr() as *mut Self); - ptr.as_pointer().free(); + drop_in_place(ptr.0.as_ptr()); + free(ptr.0.as_ptr()); } } - pub(crate) fn alloc(class: ClassPointer) -> ProcessPointer { - let ptr = Pointer::new(allocate(unsafe { class.instance_layout() })); - let obj = unsafe { ptr.get_mut::() }; + 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 @@ -630,12 +487,14 @@ impl Process { obj.header.init_atomic(class); - init!(obj.thrown => false); - init!(obj.result => Pointer::undefined_singleton()); + init!(obj.run_lock => UnsafeCell::new(Mutex::new(()))); + init!(obj.stack_pointer => stack.stack_pointer()); + init!(obj.stack => ManuallyDrop::new(stack)); + init!(obj.call_stack => Vec::new()); + init!(obj.thread => None); init!(obj.state => Mutex::new(state)); - init!(obj.task => None); - unsafe { ProcessPointer::from_pointer(ptr) } + unsafe { ProcessPointer::new(ptr) } } /// Returns a new Process acting as the main process. @@ -643,19 +502,14 @@ impl Process { /// This process always runs on the main thread. pub(crate) fn main( class: ClassPointer, - method: MethodPointer, + method: NativeAsyncMethod, + stack: Stack, ) -> 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); + let mut process = Self::alloc(class, stack); + let message = Message::new(method, 0); - // 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.send_message(message); process } @@ -668,111 +522,77 @@ impl Process { } /// 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, + message: OwnedMessage, ) -> 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); + pub(crate) fn next_task(&mut self) -> Task { 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)); + if state.status.is_running() { + return Task::Resume; } - let message = if let Some(message) = proc_state.mailbox.receive() { - message - } else { - proc_state.status.set_waiting_for_message(true); - - return None; + let message = { + if let Some(message) = state.mailbox.receive() { + message + } else { + state.status.set_waiting_for_message(true); + return Task::Wait; + } }; - 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 func = message.method; + let len = message.length as usize; + let args = unsafe { + slice::from_raw_parts(message.arguments.as_ptr(), len).to_vec() }; - let mut task = Task::new(ctx, message.write); - - task.stack.extend_from_slice(values); + self.stack_pointer = self.stack.stack_pointer(); + state.status.set_running(true); + Task::Start(func, args) + } - self.task = Some(task); + pub(crate) fn take_stack(&mut self) -> Option { + if self.stack_pointer.is_null() { + None + } else { + self.stack_pointer = null_mut(); - self.task.as_ref().map(TaskPointer::new) + Some(unsafe { ManuallyDrop::take(&mut self.stack) }) + } } - /// Finishes the exection of a task, and decides what to do next with this - /// process. + /// 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_task(&mut self) -> bool { + pub(crate) fn finish_message(&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); + state.status.set_running(false); 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(); @@ -788,71 +608,50 @@ impl Process { 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 + /// 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_return_value(&mut self, value: Pointer) { - self.result = value; + pub(crate) fn set_thread(&mut self, thread: &mut Thread) { + self.thread = Some(NonNull::from(thread)); } - pub(crate) fn set_throw_value(&mut self, value: Pointer) { - self.thrown = true; - self.result = value; + pub(crate) fn unset_thread(&mut self) { + self.thread = None; } - pub(crate) fn move_result(&mut self) -> Pointer { - let result = self.result; - - self.result = Pointer::undefined_singleton(); - self.thrown = false; - - result + /// 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() } } /// A pointer to a process. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct ProcessPointer(NonNull); +pub 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)) } @@ -861,8 +660,19 @@ impl ProcessPointer { self.0.as_ptr() as usize } - pub(crate) fn as_pointer(self) -> Pointer { - Pointer::new(self.0.as_ptr() as *mut u8) + 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); + } + } } } @@ -880,133 +690,240 @@ impl DerefMut for ProcessPointer { } } -/// 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. +#[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 value was returned. - Returned(Pointer), + /// The index into the ring buffer to use for receiving a value. + receive_index: usize, - /// The value was thrown and should be thrown again. - Thrown(Pointer), + /// 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, } -/// 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, +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(), + } + } - /// A value was written, but no further action is needed. - Continue, + pub(crate) fn has_messages(&self) -> bool { + self.messages.len() > 0 + } - /// A value was written, and the given process needs to be rescheduled. - Reschedule(ProcessPointer), + pub(crate) fn add_waiting_for_message(&mut self, process: ProcessPointer) { + self.waiting_for_message.push(process); + } - /// A value was written, the given process needs to be rescheduled, and a - /// timeout needs to be invalidated. - RescheduleWithTimeout(ProcessPointer), + 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 + } + } } -/// Storage for a value to be computed some time in the future. +/// 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. /// -/// 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. +/// 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(crate) struct Future { - header: Header, - state: RcFutureState, +pub struct Channel { + pub(crate) header: Header, + pub(crate) state: Mutex, } -impl Future { - pub(crate) fn alloc(class: ClassPointer, state: RcFutureState) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; +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(class); - init!(obj.state => state); - ptr + init!(obj.state => Mutex::new(ChannelState::new(capacity))); + ptr as _ } - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); + pub(crate) unsafe fn drop(ptr: *mut Channel) { + drop_in_place(ptr); } - pub(crate) fn get( + pub(crate) fn send( &self, - consumer: ProcessPointer, - timeout: Option>, - ) -> FutureResult { - self.state.get(consumer, timeout) - } + sender: ProcessPointer, + message: *mut u8, + ) -> SendResult { + let mut state = self.state.lock().unwrap(); - pub(crate) fn lock(&self) -> MutexGuard { - self.state.lock().unwrap() - } + if !state.send(message) { + state.waiting_for_space.push(sender); + return SendResult::Full; + } - pub(crate) fn disconnect(&self) -> Pointer { - let mut future = self.state.lock().unwrap(); - let result = future.result; + 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 + } + } - future.disconnected = true; - future.result = Pointer::undefined_singleton(); + pub(crate) fn receive( + &self, + receiver: ProcessPointer, + timeout: Option>, + ) -> ReceiveResult { + let mut state = self.state.lock().unwrap(); - result + 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::location_table::Location; - use crate::mem::{Class, Method}; - use crate::test::{ - empty_class, empty_method, empty_process_class, new_process, - OwnedClass, OwnedProcess, - }; + use crate::mem::tagged_int; + use crate::test::{empty_class, empty_process_class, 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); + 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 { - assert_eq!( - *message.scheduled.arguments.as_ptr().add(0), - Pointer::int(1) - ); - assert_eq!( - *message.scheduled.arguments.as_ptr().add(1), - Pointer::int(2) - ); - } + unsafe extern "system" fn method(_ctx: *mut u8) { + // This function is used for testing the sending/receiving of messages. } #[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(); + fn test_type_sizes() { + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>>(), 8); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::(), 136); + assert_eq!(size_of::(), 48); + assert_eq!(size_of::>(), 56); + assert_eq!(size_of::>>(), 8); - mailbox.send(message); + assert_eq!(size_of::(), 104); + assert_eq!(size_of::(), 80); + } - let message = mailbox.receive().unwrap(); + #[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!(message.scheduled.length, 2); + assert_eq!(offset_of!(proc, header), 0); + assert_eq!(offset_of!(proc, fields), 136); } #[test] @@ -1037,14 +954,14 @@ mod tests { } #[test] - fn test_process_status_set_waiting_for_future() { + fn test_process_status_set_waiting_for_channel() { let mut status = ProcessStatus::new(); - status.set_waiting_for_future(true); - assert!(status.is_waiting_for_future()); + status.set_waiting_for_channel(true); + assert!(status.is_waiting_for_channel()); - status.set_waiting_for_future(false); - assert!(!status.is_waiting_for_future()); + status.set_waiting_for_channel(false); + assert!(!status.is_waiting_for_channel()); } #[test] @@ -1055,12 +972,12 @@ mod tests { assert!(status.is_waiting()); status.set_sleeping(false); - status.set_waiting_for_future(true); + status.set_waiting_for_channel(true); assert!(status.is_waiting()); status.no_longer_waiting(); - assert!(!status.is_waiting_for_future()); + assert!(!status.is_waiting_for_channel()); assert!(!status.is_waiting()); } @@ -1103,42 +1020,42 @@ mod tests { RescheduleRights::Failed ); - state.waiting_for_future(None); + state.waiting_for_channel(None); assert_eq!( state.try_reschedule_after_timeout(), RescheduleRights::Acquired ); - assert!(!state.status.is_waiting_for_future()); + assert!(!state.status.is_waiting_for_channel()); assert!(!state.status.is_waiting()); let timeout = Timeout::with_rc(Duration::from_secs(0)); - state.waiting_for_future(Some(timeout)); + state.waiting_for_channel(Some(timeout)); assert_eq!( state.try_reschedule_after_timeout(), RescheduleRights::AcquiredWithTimeout ); - assert!(!state.status.is_waiting_for_future()); + assert!(!state.status.is_waiting_for_channel()); assert!(!state.status.is_waiting()); } #[test] - fn test_process_state_waiting_for_future() { + fn test_process_state_waiting_for_channel() { let mut state = ProcessState::new(); let timeout = Timeout::with_rc(Duration::from_secs(0)); - state.waiting_for_future(None); + state.waiting_for_channel(None); - assert!(state.status.is_waiting_for_future()); + assert!(state.status.is_waiting_for_channel()); assert!(state.timeout.is_none()); - state.waiting_for_future(Some(timeout)); + state.waiting_for_channel(Some(timeout)); - assert!(state.status.is_waiting_for_future()); + assert!(state.status.is_waiting_for_channel()); assert!(state.timeout.is_some()); } @@ -1161,52 +1078,54 @@ mod tests { } #[test] - fn test_process_state_try_reschedule_for_future() { + fn test_process_state_try_reschedule_for_channel() { let mut state = ProcessState::new(); - assert_eq!(state.try_reschedule_for_future(), RescheduleRights::Failed); + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::Failed + ); - state.status.set_waiting_for_future(true); + state.status.set_waiting_for_channel(true); assert_eq!( - state.try_reschedule_for_future(), + state.try_reschedule_for_channel(), RescheduleRights::Acquired ); - assert!(!state.status.is_waiting_for_future()); + assert!(!state.status.is_waiting_for_channel()); - state.status.set_waiting_for_future(true); + state.status.set_waiting_for_channel(true); state.timeout = Some(Timeout::with_rc(Duration::from_secs(0))); assert_eq!( - state.try_reschedule_for_future(), + state.try_reschedule_for_channel(), RescheduleRights::AcquiredWithTimeout ); - assert!(!state.status.is_waiting_for_future()); + 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)); + let process = OwnedProcess::new(Process::alloc(*class, Stack::new(32))); - assert_eq!(process.header.class.as_ptr(), class.as_ptr()); - assert!(process.task.is_none()); + assert_eq!(process.header.class, class.0); } #[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)); + let stack = Stack::new(32); + let process = + OwnedProcess::new(Process::main(*proc_class, method, stack)); 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)); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); assert!(!process.is_main()); @@ -1217,7 +1136,8 @@ mod tests { #[test] fn test_process_suspend() { let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); + 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); @@ -1227,375 +1147,306 @@ mod tests { } #[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(); + 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_eq!(state.mailbox.messages.len(), 1); - assert_eq!(state.mailbox.receive().unwrap().scheduled.length, 1); - } + assert!(!process.timeout_expired()); - #[test] - fn test_process_task_to_run_without_a_task() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); + process.suspend(timeout); - assert!(process.task_to_run().is_none()); + assert!(!process.timeout_expired()); + assert!(!process.state().status.timeout_expired()); } #[test] - fn test_process_task_to_run_waiting_server() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); + fn test_process_pointer_identifier() { + let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; - assert!(process.task_to_run().is_none()); - assert!(process.state().status.is_waiting_for_message()); - assert!(!process.state().status.is_waiting()); + assert_eq!(ptr.identifier(), 0x4); } #[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) }; + fn test_channel_alloc() { + let class = empty_class("Channel"); + let chan = Channel::alloc(*class, 4); - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, vec![Pointer::int(42)]); + unsafe { + let chan = &(*chan); + let state = chan.state.lock().unwrap(); - let mut task = process.task_to_run().unwrap(); + assert_eq!(chan.header.class, *class); + assert_eq!(state.messages.len(), 4); + } - assert!(process.task.is_some()); - assert!(task.stack.pop() == Some(Pointer::int(42))); + unsafe { + Channel::drop(chan); + free(chan); + } } #[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); + 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!( - process.task_to_run(), - process.task.as_ref().map(TaskPointer::new) - ); + assert_eq!(chan.send(*sender, msg as _), SendResult::Sent); - Method::drop_and_deallocate(method); + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } } #[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()); + 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); - Method::drop_and_deallocate(method); + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } } #[test] - fn test_process_finish_task_without_pending_work() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); + 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); - process.set_main(); - - assert!(!process.finish_task()); - } + chan.receive(*process, None); - #[test] - fn test_process_finish_task_with_clients() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); + assert_eq!( + chan.send(*process, msg as _), + SendResult::Reschedule(*process) + ); - assert!(!process.finish_task()); - assert!(process.state().status.is_waiting_for_message()); + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } } #[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) }; + 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); - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); + chan.receive(*process, Some(Timeout::with_rc(Duration::from_secs(0)))); - 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); + assert_eq!( + chan.send(*process, msg as _), + SendResult::RescheduleWithTimeout(*process) + ); unsafe { - process.set_field(idx, Pointer::int(4)); - - assert!(process.get_field(idx) == Pointer::int(4)); + Channel::drop(chan_ptr); + free(chan_ptr); } } #[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)); + 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!(!process.timeout_expired()); + assert_eq!(chan.receive(*process, None), ReceiveResult::None); + assert!(process.state().status.is_waiting_for_channel()); - process.suspend(timeout); - - assert!(!process.timeout_expired()); - assert!(!process.state().status.timeout_expired()); + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } } #[test] - fn test_process_pointer_identifier() { - let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; + 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); - assert_eq!(ptr.identifier(), 0x4); - } + chan.send(*process, msg as _); - #[test] - fn test_process_pointer_as_pointer() { - let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; + assert_eq!(chan.receive(*process, None), ReceiveResult::Some(msg as _)); - assert_eq!(ptr.as_pointer(), Pointer::new(0x4 as *mut _)); + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } } #[test] - fn test_future_new() { - let fut_class = empty_class("Future"); - let state = FutureState::new(); - let future = Future::alloc(*fut_class, state); + 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 _); - unsafe { - assert_eq!( - future.get::
().class.as_ptr(), - fut_class.as_ptr() - ); - } + assert_eq!( + chan.receive(*process, None), + ReceiveResult::Reschedule(msg as _, *process) + ); unsafe { - Future::drop(future); - future.free(); + Channel::drop(chan_ptr); + free(chan_ptr); } } #[test] - fn test_future_write_without_consumer() { - let state = FutureState::new(); - let result = state.write(Pointer::int(42), false); + fn test_message_new() { + let message = Message::new(method, 2); - assert_eq!(result, WriteResult::Continue); - assert!(!state.lock().unwrap().thrown); + assert_eq!(message.length, 2); } #[test] - fn test_future_write_thrown() { - let state = FutureState::new(); - let result = state.write(Pointer::int(42), true); + fn test_mailbox_send() { + let mut mail = Mailbox::new(); + let msg = Message::new(method, 0); - assert_eq!(result, WriteResult::Continue); - assert!(state.lock().unwrap().thrown); + mail.send(msg); + assert!(mail.receive().is_some()); } #[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() { + fn test_process_send_message() { 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 stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg = Message::new(method, 0); - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Continue); + assert_eq!(process.send_message(msg), RescheduleRights::Acquired); + assert_eq!(process.state().mailbox.messages.len(), 1); } #[test] - fn test_future_write_with_waiting_consumer() { + fn test_process_next_task_without_messages() { 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 stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Reschedule(*process)); + assert!(matches!(process.next_task(), Task::Wait)); } #[test] - fn test_future_write_with_waiting_consumer_with_timeout() { + fn test_process_next_task_with_new_message() { 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 stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg = Message::new(method, 0); - let result = state.write(Pointer::int(42), false); + process.send_message(msg); - assert_eq!(result, WriteResult::RescheduleWithTimeout(*process)); - assert!(!process.state().status.is_waiting_for_future()); - assert!(process.state().timeout.is_none()); + assert!(matches!(process.next_task(), Task::Start(_, _))); } #[test] - fn test_future_get_without_result() { + fn test_process_next_task_with_existing_message() { let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg1 = Message::new(method, 0); + let msg2 = Message::new(method, 0); - assert_eq!(state.get(*process, None), FutureResult::None); - assert_eq!(state.lock().unwrap().consumer, Some(*process)); - assert!(process.state().status.is_waiting_for_future()); - } + process.send_message(msg1); + process.next_task(); + process.send_message(msg2); - #[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()); + assert!(matches!(process.next_task(), Task::Resume)); } #[test] - fn test_future_get_with_result() { + fn test_process_take_stack() { let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let value = Pointer::int(42); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); - 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()); + assert!(process.take_stack().is_some()); + assert!(process.stack_pointer.is_null()); } #[test] - fn test_future_get_with_thrown_result() { + fn test_process_finish_message() { 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; + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); - assert_eq!(state.get(*process, None), FutureResult::Thrown(value)); - assert!(state.lock().unwrap().consumer.is_none()); - assert!(!process.state().status.is_waiting_for_future()); + assert!(!process.finish_message()); + assert!(process.state().status.is_waiting_for_message()); } #[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(); + fn test_channel_state_send() { + let mut state = ChannelState::new(2); - assert!(state.lock().unwrap().disconnected); - assert!(result == Pointer::undefined_singleton()); + assert!(!state.is_full()); + assert_eq!(state.capacity(), 2); - unsafe { - Future::drop(fut); - fut.free(); - } + 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_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); + fn test_channel_state_receive() { + let mut state = ChannelState::new(2); - task.push_context(ctx2); + assert!(state.receive().is_none()); - proc.task = Some(task); + state.send(0x1 as _); + state.send(0x2 as _); - 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) - }) - ); + assert!(state.is_full()); + assert_eq!(state.receive(), Some(0x1 as _)); + assert!(!state.is_full()); - Method::drop_and_deallocate(method1); - Method::drop_and_deallocate(method2); + assert_eq!(state.receive(), Some(0x2 as _)); + assert_eq!(state.receive(), None); + assert!(!state.is_full()); } } 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/result.rs b/vm/src/result.rs new file mode 100644 index 000000000..501ad48f4 --- /dev/null +++ b/vm/src/result.rs @@ -0,0 +1,72 @@ +use crate::mem::tagged_int; +use std::io; + +const INVALID_INPUT: i64 = 11; +const TIMED_OUT: i64 = 13; + +/// 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). +#[repr(C, u8)] +#[derive(Eq, PartialEq, Debug)] +pub enum Result { + /// The operation succeeded. + Ok(*mut u8), + + /// The operation failed. + Error(*mut u8), + + /// No result is produced just yet. + None, +} + +impl Result { + 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; + + #[test] + fn test_type_sizes() { + assert_eq!(size_of::(), 16); + } +} diff --git a/vm/src/runtime.rs b/vm/src/runtime.rs new file mode 100644 index 000000000..a745d440c --- /dev/null +++ b/vm/src/runtime.rs @@ -0,0 +1,108 @@ +mod array; +mod byte_array; +mod class; +mod env; +mod float; +mod fs; +mod general; +mod hasher; +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::thread; + +#[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, +) { + (*runtime).start(class, method); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_state( + runtime: *mut Runtime, +) -> *const State { + (*runtime).state.as_ptr() as _ +} + +/// 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::new(); + + Self { state: State::new(config, counts, &[]) } + } + + /// 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/vm/src/runtime/array.rs b/vm/src/runtime/array.rs new file mode 100644 index 000000000..6afbe10ca --- /dev/null +++ b/vm/src/runtime/array.rs @@ -0,0 +1,108 @@ +use crate::mem::{Array, Int, Nil}; +use crate::state::State; +use std::ptr::null_mut; + +#[no_mangle] +pub unsafe extern "system" fn inko_array_new( + state: *const State, + length: usize, +) -> *mut Array { + Array::alloc((*state).array_class, Vec::with_capacity(length)) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_new_permanent( + state: *const State, + length: usize, +) -> *mut Array { + Array::alloc_permanent((*state).array_class, Vec::with_capacity(length)) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_reserve( + state: *const State, + array: *mut Array, + length: usize, +) -> *const Nil { + (*array).value.reserve_exact(length); + (*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) -> *mut u8 { + (*array).value.pop().unwrap_or_else(null_mut) +} + +#[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/vm/src/runtime/byte_array.rs b/vm/src/runtime/byte_array.rs new file mode 100644 index 000000000..f049aa674 --- /dev/null +++ b/vm/src/runtime/byte_array.rs @@ -0,0 +1,189 @@ +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; +use std::ptr::null; + +#[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 { + null() + } +} + +#[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: usize, + length: usize, +) -> *mut ByteArray { + let bytes = &*bytes; + let end = min((start + length) as usize, bytes.value.len()); + + ByteArray::alloc( + (*state).byte_array_class, + bytes.value[start..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: usize, + length: usize, +) -> *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..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/vm/src/runtime/class.rs b/vm/src/runtime/class.rs new file mode 100644 index 000000000..228a3cda0 --- /dev/null +++ b/vm/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/vm/src/runtime/env.rs b/vm/src/runtime/env.rs new file mode 100644 index 000000000..53340c40f --- /dev/null +++ b/vm/src/runtime/env.rs @@ -0,0 +1,126 @@ +use crate::mem::{tagged_int, Array, Int, String as InkoString}; +use crate::platform; +use crate::result::Result as InkoResult; +use crate::state::State; +use std::env; +use std::path::PathBuf; +use std::ptr::null_mut; + +#[no_mangle] +pub unsafe extern "system" fn inko_env_get( + state: *const State, + name: *const InkoString, +) -> *const InkoString { + let state = &(*state); + let name = InkoString::read(name); + + state.environment.get(name).cloned().unwrap_or_else(|| null_mut() as _) +} + +#[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, +) -> *const InkoString { + let state = &*state; + + // 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. + var.cloned() + .filter(|&p| !InkoString::read(p).is_empty()) + .unwrap_or_else(|| null_mut() as _) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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_platform() -> *const Int { + tagged_int(platform::operating_system()) +} + +#[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(|err| InkoResult::io_error(err)) +} + +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/runtime/float.rs b/vm/src/runtime/float.rs new file mode 100644 index 000000000..5606fa0d1 --- /dev/null +++ b/vm/src/runtime/float.rs @@ -0,0 +1,117 @@ +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 + } +} + +// TODO: do in LLVM? +#[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/vm/src/runtime/fs.rs b/vm/src/runtime/fs.rs new file mode 100644 index 000000000..a15a7851d --- /dev/null +++ b/vm/src/runtime/fs.rs @@ -0,0 +1,353 @@ +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::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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|time| system_time_to_timestamp(time)) + .map(|time| { + InkoResult::Ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(|err| InkoResult::io_error(err)) +} + +#[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(|time| system_time_to_timestamp(time)) + .map(|time| { + InkoResult::Ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(|err| InkoResult::io_error(err)) +} + +#[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(|time| system_time_to_timestamp(time)) + .map(|time| { + InkoResult::Ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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/vm/src/runtime/general.rs b/vm/src/runtime/general.rs new file mode 100644 index 000000000..6be9221b5 --- /dev/null +++ b/vm/src/runtime/general.rs @@ -0,0 +1,82 @@ +use crate::context; +use crate::mem::{free, header_of, is_tagged_int, ClassPointer, Method}; +use crate::process::ProcessPointer; +use crate::runtime::process::panic; +use std::alloc::alloc; +use std::process::exit; + +#[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 {} references", + &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_method_new( + hash: u64, + code: extern "system" fn(), +) -> Method { + Method::new(hash, code) +} + +#[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_object_new(class: ClassPointer) -> *mut u8 { + let ptr = alloc(class.instance_layout()); + + header_of(ptr).init(class); + ptr +} diff --git a/vm/src/runtime/hasher.rs b/vm/src/runtime/hasher.rs new file mode 100644 index 000000000..c43017a23 --- /dev/null +++ b/vm/src/runtime/hasher.rs @@ -0,0 +1,38 @@ +use crate::hasher::Hasher; +use crate::mem::{Int, Nil}; +use crate::state::State; +use std::hash::BuildHasher as _; + +#[no_mangle] +pub unsafe extern "system" fn inko_hasher_new(state: &State) -> *mut Hasher { + let hasher = (*state).hash_state.build_hasher(); + + Box::into_raw(Box::new(Hasher::new(hasher))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_hasher_write_int( + state: *const State, + hasher: *mut Hasher, + value: *const Int, +) -> *const Nil { + (*hasher).write_int(Int::read(value)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_hasher_to_hash( + state: *const State, + hasher: *mut Hasher, +) -> *const Int { + Int::new((*state).int_class, (*hasher).finish()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_hasher_drop( + state: *const State, + hasher: *mut Hasher, +) -> *const Nil { + drop(Box::from_raw(hasher)); + (*state).nil_singleton +} diff --git a/vm/src/runtime/helpers.rs b/vm/src/runtime/helpers.rs new file mode 100644 index 000000000..178fcbce5 --- /dev/null +++ b/vm/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/vm/src/runtime/int.rs b/vm/src/runtime/int.rs new file mode 100644 index 000000000..44922178b --- /dev/null +++ b/vm/src/runtime/int.rs @@ -0,0 +1,75 @@ +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) + } +} + +// TODO: do this in LLVM +#[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/vm/src/runtime/process.rs b/vm/src/runtime/process.rs new file mode 100644 index 000000000..07a065e26 --- /dev/null +++ b/vm/src/runtime/process.rs @@ -0,0 +1,364 @@ +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::scheduler::process::Action; +use crate::scheduler::timeouts::Timeout; +use crate::state::State; +use std::cmp::max; +use std::fmt::Write as _; +use std::process::exit; +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.call_stack { + unsafe { + let frame = &*frame; + let _ = write!( + buffer, + "\n {} line {}, in '{}'", + InkoString::read(frame.path), + Int::read(frame.line), + InkoString::read(frame.name), + ); + } + } + + let _ = write!( + buffer, + "\nProcess {:#x} panicked: {}", + process.identifier(), + message + ); + + eprintln!("{}", buffer); + exit(1); +} + +#[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::new(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_push_stack_frame( + mut process: ProcessPointer, + frame: *const StackFrame, +) { + process.call_stack.push(frame); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_pop_stack_frame( + mut process: ProcessPointer, +) { + process.call_stack.pop(); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_name( + process: ProcessPointer, + index: usize, +) -> *const InkoString { + (*process.call_stack[index]).name +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_path( + process: ProcessPointer, + index: usize, +) -> *const InkoString { + (*process.call_stack[index]).path +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_line( + process: ProcessPointer, + index: usize, +) -> *const Int { + (*process.call_stack[index]).line +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace_length( + state: *const State, + process: ProcessPointer, +) -> *const Int { + Int::new((*state).int_class, process.call_stack.len() as i64) +} + +#[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), + 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) + } + } +} + +#[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), + 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); + } + } + } +} + +#[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/vm/src/runtime/random.rs b/vm/src/runtime/random.rs new file mode 100644 index 000000000..bc7d5774c --- /dev/null +++ b/vm/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/vm/src/runtime/socket.rs b/vm/src/runtime/socket.rs new file mode 100644 index 000000000..24b1c7606 --- /dev/null +++ b/vm/src/runtime/socket.rs @@ -0,0 +1,480 @@ +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(|v| Result::ok_boxed(v)) + .unwrap_or_else(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|sock| Result::ok_boxed(sock)) + .unwrap_or_else(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[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(|err| Result::io_error(err)) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_try_clone( + socket: *mut Socket, +) -> Result { + (*socket) + .try_clone() + .map(|sock| Result::ok_boxed(sock)) + .unwrap_or_else(|err| Result::io_error(err)) +} + +#[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/vm/src/runtime/stdio.rs b/vm/src/runtime/stdio.rs new file mode 100644 index 000000000..b91a3b5f5 --- /dev/null +++ b/vm/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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} diff --git a/vm/src/runtime/string.rs b/vm/src/runtime/string.rs new file mode 100644 index 000000000..146a341bf --- /dev/null +++ b/vm/src/runtime/string.rs @@ -0,0 +1,278 @@ +use crate::mem::{ + header_of, tagged_int, Array, Bool, ByteArray, Float, Int, Nil, + String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::runtime::process::panic; +use crate::state::State; +use std::cmp::min; +use std::os::raw::c_char; +use std::ptr::{null, null_mut}; +use std::slice; +use unicode_segmentation::{Graphemes, UnicodeSegmentation}; + +#[no_mangle] +pub unsafe extern "system" fn inko_string_new( + state: *const State, + bytes: *const u8, + length: i64, +) -> *const InkoString { + let bytes = slice::from_raw_parts(bytes, length as usize).to_vec(); + let string = String::from_utf8_unchecked(bytes); + + InkoString::alloc((*state).string_class, string) +} + +#[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 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 { + if !header_of(pointer).is_permanent() { + 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, +) -> *const Float { + 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(|val| Float::alloc((*state).float_class, val)) + .unwrap_or_else(|_| null_mut()) +} + +#[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, +) -> *const Int { + 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(|val| Int::new((*state).int_class, val)) + .unwrap_or_else(|_| null_mut()) +} + +#[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, +) -> *const InkoString { + let iter = &mut *(iter as *mut Graphemes); + + iter.next() + .map(|v| InkoString::alloc((*state).string_class, v.to_string())) + .unwrap_or_else(null) +} + +#[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 as usize], + ) + .into_owned() + }; + + InkoString::alloc((*state).string_class, new_string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_c_string( + string: *const InkoString, +) -> *const c_char { + InkoString::read_as_c_char(string) +} diff --git a/vm/src/runtime/sys.rs b/vm/src/runtime/sys.rs new file mode 100644 index 000000000..4b88651eb --- /dev/null +++ b/vm/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(|child| InkoResult::ok_boxed(child)) + .unwrap_or_else(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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(|err| InkoResult::io_error(err)) +} + +#[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/vm/src/builtin_functions/time.rs b/vm/src/runtime/time.rs similarity index 81% rename from vm/src/builtin_functions/time.rs rename to vm/src/runtime/time.rs index a52b803a4..b1acdb442 100644 --- a/vm/src/builtin_functions/time.rs +++ b/vm/src/runtime/time.rs @@ -1,8 +1,4 @@ -//! 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::mem::{Float, Int}; use crate::state::State; use std::mem::MaybeUninit; @@ -106,36 +102,31 @@ fn offset() -> i64 { } } -pub(crate) fn time_monotonic( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { +#[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; - Ok(Int::alloc(state.permanent_space.int_class(), nanos)) + Int::new(state.int_class, nanos) } -pub(crate) fn time_system( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Float::alloc(state.permanent_space.float_class(), utc())) +#[no_mangle] +pub unsafe extern "system" fn inko_time_system( + state: *const State, +) -> *const Float { + Float::alloc((*state).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())) +#[no_mangle] +pub unsafe extern "system" fn inko_time_system_offset( + state: *const State, +) -> *const Int { + Int::new((*state).int_class, offset()) } #[cfg(test)] 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 index b89111e66..a625f22cd 100644 --- a/vm/src/scheduler/mod.rs +++ b/vm/src/scheduler/mod.rs @@ -1,3 +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/vm/src/scheduler/process.rs index 8843e4922..839b915b4 100644 --- a/vm/src/scheduler/process.rs +++ b/vm/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/vm/src/scheduler/timeout_worker.rs index fb19a9b2d..2bcc6fa13 100644 --- a/vm/src/scheduler/timeout_worker.rs +++ b/vm/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/vm/src/scheduler/timeouts.rs index c3a6c2afe..4bdf2224d 100644 --- a/vm/src/scheduler/timeouts.rs +++ b/vm/src/scheduler/timeouts.rs @@ -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/vm/src/socket.rs index 6236e0b27..e6fb2846f 100644 --- a/vm/src/socket.rs +++ b/vm/src/socket.rs @@ -2,12 +2,10 @@ 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 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}; @@ -29,47 +27,16 @@ use windows_sys::Win32::Networking::WinSock::{ 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 +45,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 +58,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 +74,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 +112,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 +150,7 @@ impl Socket { domain: Domain, kind: Type, unix: bool, - ) -> Result { + ) -> io::Result { let socket = RawSocket::new(domain, kind, None)?; socket.set_nonblocking(true)?; @@ -199,53 +162,42 @@ 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) => @@ -255,24 +207,21 @@ impl Socket { // 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()); + 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) => { // 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 +230,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 @@ -306,7 +255,7 @@ impl Socket { // *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()) + result } pub(crate) fn deregister(&mut self, state: &State) { @@ -314,7 +263,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 +281,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 +304,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 +312,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 +320,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)?) } - 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 +364,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), diff --git a/vm/src/socket/socket_address.rs b/vm/src/socket/socket_address.rs index 44aa9e6b8..037c7881d 100644 --- a/vm/src/socket/socket_address.rs +++ b/vm/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/vm/src/stack.rs b/vm/src/stack.rs new file mode 100644 index 000000000..c94ef48a1 --- /dev/null +++ b/vm/src/stack.rs @@ -0,0 +1,220 @@ +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. +/// For example, on Unix we use mmap() but for Windows we use VirtualAlloc(). +#[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/vm/src/state.rs b/vm/src/state.rs index 34757903d..f5a7721e7 100644 --- a/vm/src/state.rs +++ b/vm/src/state.rs @@ -1,24 +1,72 @@ use crate::arc_without_weak::ArcWithoutWeak; -use crate::builtin_functions::BuiltinFunctions; use crate::config::Config; -use crate::mem::Pointer; +use crate::mem::{ + Array, Bool, ByteArray, Class, ClassPointer, Float, Int, Nil, + String as InkoString, +}; use crate::network_poller::NetworkPoller; -use crate::permanent_space::PermanentSpace; +use crate::process::Channel; 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::mem::size_of; use std::panic::RefUnwindSafe; -use std::sync::Mutex; 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 a virtual machine. -pub(crate) struct State { +#[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(crate) int_class: ClassPointer, + pub(crate) float_class: ClassPointer, + pub(crate) string_class: ClassPointer, + pub(crate) array_class: ClassPointer, + pub(crate) bool_class: ClassPointer, + pub(crate) nil_class: ClassPointer, + pub(crate) byte_array_class: ClassPointer, + pub(crate) channel_class: ClassPointer, + /// The virtual machine's configuration. pub(crate) config: Config, @@ -26,7 +74,7 @@ pub(crate) struct State { pub(crate) start_time: time::Instant, /// The commandline arguments passed to an Inko program. - pub(crate) arguments: Vec, + pub(crate) arguments: Vec<*const InkoString>, /// The environment variables defined when the VM started. /// @@ -34,10 +82,7 @@ pub(crate) struct State { /// (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, + pub(crate) environment: HashMap, /// The scheduler to use for executing Inko processes. pub(crate) scheduler: Scheduler, @@ -48,12 +93,6 @@ pub(crate) struct State { /// 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 @@ -71,17 +110,32 @@ pub(crate) struct State { pub(crate) hash_state: RandomState, } +unsafe impl Sync for State {} impl RefUnwindSafe for State {} impl State { pub(crate) fn new( config: Config, - permanent_space: PermanentSpace, + 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| permanent_space.allocate_string(arg.clone())) + .map(|arg| InkoString::alloc_permanent(string_class, arg.clone())) .collect(); let mut rng = thread_rng(); @@ -93,8 +147,10 @@ impl State { .map(|(k, v)| { ( k.to_string_lossy().into_owned(), - permanent_space - .allocate_string(v.to_string_lossy().into_owned()), + InkoString::alloc_permanent( + string_class, + v.to_string_lossy().into_owned(), + ), ) }) .collect::>(); @@ -102,6 +158,7 @@ impl State { let scheduler = Scheduler::new( config.process_threads as usize, config.backup_threads as usize, + config.stack_size as usize, ); let network_pollers = @@ -112,13 +169,21 @@ impl State { 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, + 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) @@ -127,12 +192,59 @@ impl 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); + } + } +} - pub(crate) fn set_exit_status(&self, new_status: i32) { - *self.exit_status.lock().unwrap() = new_status; +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + + macro_rules! offset_of { + ($value: expr, $field: ident) => {{ + (std::ptr::addr_of!($value.$field) as usize) + - (&*$value as *const _ as usize) + }}; + } + + #[test] + fn test_type_sizes() { + assert_eq!(size_of::(), 384); } - pub(crate) fn current_exit_status(&self) -> i32 { - *self.exit_status.lock().unwrap() + #[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/vm/src/test.rs index 39c5ab892..53a2e1a98 100644 --- a/vm/src/test.rs +++ b/vm/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 {