diff --git a/Cargo.lock b/Cargo.lock index 2e3af8e6b..15ca2bba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -49,9 +49,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", @@ -64,36 +64,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -185,7 +185,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tower 0.5.1", + "tower", "tower-layer", "tower-service", "tracing", @@ -214,17 +214,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -245,7 +245,7 @@ dependencies = [ "cexpr", "clang-sys", "itertools 0.13.0", - "log 0.4.21", + "log 0.4.22", "prettyplease", "proc-macro2", "quote", @@ -355,9 +355,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -401,21 +401,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "criterion" @@ -484,9 +484,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -554,9 +554,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" @@ -566,9 +566,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -626,36 +626,36 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -766,9 +766,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -776,12 +776,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -795,9 +795,9 @@ checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -813,9 +813,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -832,9 +832,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-util", @@ -842,17 +842,15 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "socket2", "tokio", - "tower 0.4.13", "tower-service", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -928,9 +926,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -958,18 +956,18 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1070,21 +1068,21 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets", @@ -1122,14 +1120,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logging" @@ -1190,9 +1188,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -1202,9 +1200,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1218,18 +1216,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", @@ -1278,24 +1276,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "open" @@ -1310,12 +1308,12 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.1.5" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1345,9 +1343,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" [[package]] name = "percent-encoding" @@ -1397,31 +1395,11 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1431,9 +1409,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -1444,15 +1422,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -1469,9 +1447,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", "syn", @@ -1488,9 +1466,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1541,23 +1519,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1571,13 +1549,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.5", ] [[package]] @@ -1588,9 +1566,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" @@ -1612,9 +1590,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags", "errno", @@ -1625,9 +1603,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -1738,12 +1716,12 @@ dependencies = [ [[package]] name = "shared_child" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" dependencies = [ "libc", - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -1813,9 +1791,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.82" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -1938,9 +1916,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -1949,22 +1927,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.1" @@ -2032,7 +1994,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log 0.4.21", + "log 0.4.22", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2065,7 +2027,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "log 0.4.21", + "log 0.4.22", "once_cell", "tracing-core", ] @@ -2121,9 +2083,9 @@ checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unsafe-libyaml" @@ -2133,9 +2095,9 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" @@ -2145,9 +2107,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -2167,22 +2129,23 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", - "log 0.4.21", + "log 0.4.22", "once_cell", "proc-macro2", "quote", @@ -2192,9 +2155,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2202,9 +2165,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -2215,15 +2178,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2247,11 +2210,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/bins/estree/src/builder/actions.yaml b/bins/estree/src/builder/actions.yaml index 974c84e84..957e23bef 100644 --- a/bins/estree/src/builder/actions.yaml +++ b/bins/estree/src/builder/actions.yaml @@ -14,6 +14,8 @@ action: null - rule: ArrowFormalParameters_Yield_Await -> LPAREN UniqueFormalParameters_Yield_Await RPAREN action: null +- rule: AsyncArrowHead -> ASYNC (!LINE_TERMINATOR_SEQUENCE) ArrowFormalParameters_Await + action: null - rule: ScriptBody -> StatementList action: nop - rule: ModuleBody -> ModuleItemList @@ -469,8 +471,9 @@ LBRACE GeneratorBody RBRACE action: generator_declaration - rule: >- - AsyncFunctionDeclaration -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION BindingIdentifier LPAREN - FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + AsyncFunctionDeclaration -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION BindingIdentifier + _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE + AsyncFunctionBody RBRACE action: async_function_declaration - rule: >- AsyncGeneratorDeclaration -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL BindingIdentifier @@ -544,11 +547,13 @@ action: anonymous_generator_declaration - rule: >- AsyncFunctionDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Await LPAREN FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + BindingIdentifier_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN + _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE action: async_function_declaration - rule: >- - AsyncFunctionDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION LPAREN - FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + AsyncFunctionDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION + _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE + AsyncFunctionBody RBRACE action: anonymous_async_function_declaration - rule: >- AsyncGeneratorDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL @@ -579,11 +584,11 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_In_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_In_Await -> CoverCallExpressionAndAsyncArrowHead_Await - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + AsyncArrowFunction_In_Await -> AsyncArrowHeadCCEAAAH_Await (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression - rule: LeftHandSideExpression_Await -> NewExpression_Await action: nop @@ -963,6 +968,8 @@ action: nop - rule: GeneratorBody -> FunctionBody_Yield action: nop +- rule: _ASYNC_FUNCTION_CONTEXT_ -> (empty) + action: nop - rule: AsyncFunctionBody -> FunctionBody_Await action: nop - rule: AsyncGeneratorBody -> FunctionBody_Yield_Await @@ -1113,7 +1120,8 @@ action: generator_declaration - rule: >- AsyncFunctionDeclaration_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Await LPAREN FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + BindingIdentifier_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN + _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE action: async_function_declaration - rule: >- AsyncGeneratorDeclaration_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL @@ -1148,7 +1156,7 @@ action: convert_to_expression - rule: AsyncConciseBody_In -> LBRACE AsyncFunctionBody RBRACE action: function_body_block -- rule: CoverCallExpressionAndAsyncArrowHead_Await -> MemberExpression_Await Arguments_Await +- rule: AsyncArrowHeadCCEAAAH_Await -> CoverCallExpressionAndAsyncArrowHead_Await action: nop - rule: NewExpression_Await -> MemberExpression_Await action: nop @@ -1358,11 +1366,11 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_In -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_In -> CoverCallExpressionAndAsyncArrowHead (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody_In + AsyncArrowFunction_In -> AsyncArrowHeadCCEAAAH (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression - rule: LeftHandSideExpression -> NewExpression action: nop @@ -1478,6 +1486,8 @@ action: nop - rule: ExpressionBody_In_Await -> AssignmentExpression_In_Await action: nop +- rule: CoverCallExpressionAndAsyncArrowHead_Await -> MemberExpression_Await Arguments_Await + action: nop - rule: MemberExpression_Await -> PrimaryExpression_Await action: nop - rule: MemberExpression_Await -> MemberExpression_Await LBRACK Expression_In_Await RBRACK @@ -1494,16 +1504,16 @@ action: new_expression_arguments - rule: MemberExpression_Await -> MemberExpression_Await DOT PRIVATE_IDENTIFIER action: member_expression_private +- rule: SuperCall_Await -> SUPER Arguments_Await + action: call_expression_super +- rule: ImportCall_Await -> IMPORT LPAREN AssignmentExpression_In_Await RPAREN + action: import_expression - rule: Arguments_Await -> LPAREN RPAREN action: arguments_empty - rule: Arguments_Await -> LPAREN ArgumentList_Await RPAREN action: arguments - rule: Arguments_Await -> LPAREN ArgumentList_Await COMMA RPAREN action: arguments_comma -- rule: SuperCall_Await -> SUPER Arguments_Await - action: call_expression_super -- rule: ImportCall_Await -> IMPORT LPAREN AssignmentExpression_In_Await RPAREN - action: import_expression - rule: TemplateLiteral_Await_Tagged -> NO_SUBSTITUTION_TEMPLATE action: template_literal_no_subst - rule: TemplateLiteral_Await_Tagged -> SubstitutionTemplate_Await_Tagged @@ -1695,11 +1705,12 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_In_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) - AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_In_Yield -> CoverCallExpressionAndAsyncArrowHead_Yield - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + AsyncArrowFunction_In_Yield -> AsyncArrowHeadCCEAAAH_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression - rule: LeftHandSideExpression_Yield -> NewExpression_Yield action: nop @@ -1746,11 +1757,12 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_In_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) - AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_In_Yield_Await -> CoverCallExpressionAndAsyncArrowHead_Yield_Await - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In + AsyncArrowFunction_In_Yield_Await -> AsyncArrowHeadCCEAAAH_Yield_Await + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In action: async_arrow_function_expression - rule: LeftHandSideExpression_Yield_Await -> NewExpression_Yield_Await action: nop @@ -1766,7 +1778,7 @@ action: create_list - rule: ArrowParameters -> CoverParenthesizedExpressionAndArrowParameterList action: arrow_parameters -- rule: CoverCallExpressionAndAsyncArrowHead -> MemberExpression Arguments +- rule: AsyncArrowHeadCCEAAAH -> CoverCallExpressionAndAsyncArrowHead action: nop - rule: NewExpression -> MemberExpression action: nop @@ -1984,7 +1996,7 @@ action: arrow_parameters - rule: AsyncArrowBindingIdentifier_Yield -> BindingIdentifier_Yield_Await action: nop -- rule: CoverCallExpressionAndAsyncArrowHead_Yield -> MemberExpression_Yield Arguments_Yield +- rule: AsyncArrowHeadCCEAAAH_Yield -> CoverCallExpressionAndAsyncArrowHead_Yield action: nop - rule: NewExpression_Yield -> MemberExpression_Yield action: nop @@ -2028,9 +2040,7 @@ action: create_list - rule: ArrowParameters_Yield_Await -> CoverParenthesizedExpressionAndArrowParameterList_Yield_Await action: arrow_parameters -- rule: >- - CoverCallExpressionAndAsyncArrowHead_Yield_Await -> MemberExpression_Yield_Await - Arguments_Yield_Await +- rule: AsyncArrowHeadCCEAAAH_Yield_Await -> CoverCallExpressionAndAsyncArrowHead_Yield_Await action: nop - rule: NewExpression_Yield_Await -> MemberExpression_Yield_Await action: nop @@ -2088,6 +2098,8 @@ CoverParenthesizedExpressionAndArrowParameterList -> LPAREN Expression_In COMMA ELLIPSIS BindingPattern RPAREN action: cpeaapl_expr_rest +- rule: CoverCallExpressionAndAsyncArrowHead -> MemberExpression Arguments + action: nop - rule: MemberExpression -> PrimaryExpression action: nop - rule: MemberExpression -> MemberExpression LBRACK Expression_In RBRACK @@ -2104,16 +2116,16 @@ action: new_expression_arguments - rule: MemberExpression -> MemberExpression DOT PRIVATE_IDENTIFIER action: member_expression_private +- rule: SuperCall -> SUPER Arguments + action: call_expression_super +- rule: ImportCall -> IMPORT LPAREN AssignmentExpression_In RPAREN + action: import_expression - rule: Arguments -> LPAREN RPAREN action: arguments_empty - rule: Arguments -> LPAREN ArgumentList RPAREN action: arguments - rule: Arguments -> LPAREN ArgumentList COMMA RPAREN action: arguments_comma -- rule: SuperCall -> SUPER Arguments - action: call_expression_super -- rule: ImportCall -> IMPORT LPAREN AssignmentExpression_In RPAREN - action: import_expression - rule: TemplateLiteral_Tagged -> NO_SUBSTITUTION_TEMPLATE action: template_literal_no_subst - rule: TemplateLiteral_Tagged -> SubstitutionTemplate_Tagged @@ -2150,11 +2162,11 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction -> CoverCallExpressionAndAsyncArrowHead (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody + AsyncArrowFunction -> AsyncArrowHeadCCEAAAH (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression - rule: Initializer -> ASSIGN AssignmentExpression action: initializer @@ -2283,12 +2295,13 @@ RPAREN LBRACE GeneratorBody RBRACE action: generator_expression - rule: >- - AsyncFunctionExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION LPAREN - FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + AsyncFunctionExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION _ASYNC_FUNCTION_CONTEXT_ + LPAREN FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE action: anonymous_async_function_expression - rule: >- AsyncFunctionExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION BindingIdentifier_Await - LPAREN FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE + AsyncFunctionBody RBRACE action: async_function_expression - rule: >- AsyncGeneratorExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL LPAREN @@ -2393,6 +2406,8 @@ CoverParenthesizedExpressionAndArrowParameterList_Yield -> LPAREN Expression_In_Yield COMMA ELLIPSIS BindingPattern_Yield RPAREN action: cpeaapl_expr_rest +- rule: CoverCallExpressionAndAsyncArrowHead_Yield -> MemberExpression_Yield Arguments_Yield + action: nop - rule: MemberExpression_Yield -> PrimaryExpression_Yield action: nop - rule: MemberExpression_Yield -> MemberExpression_Yield LBRACK Expression_In_Yield RBRACK @@ -2409,16 +2424,16 @@ action: new_expression_arguments - rule: MemberExpression_Yield -> MemberExpression_Yield DOT PRIVATE_IDENTIFIER action: member_expression_private +- rule: SuperCall_Yield -> SUPER Arguments_Yield + action: call_expression_super +- rule: ImportCall_Yield -> IMPORT LPAREN AssignmentExpression_In_Yield RPAREN + action: import_expression - rule: Arguments_Yield -> LPAREN RPAREN action: arguments_empty - rule: Arguments_Yield -> LPAREN ArgumentList_Yield RPAREN action: arguments - rule: Arguments_Yield -> LPAREN ArgumentList_Yield COMMA RPAREN action: arguments_comma -- rule: SuperCall_Yield -> SUPER Arguments_Yield - action: call_expression_super -- rule: ImportCall_Yield -> IMPORT LPAREN AssignmentExpression_In_Yield RPAREN - action: import_expression - rule: Expression_In_Yield -> AssignmentExpression_In_Yield action: nop - rule: Expression_In_Yield -> Expression_In_Yield COMMA AssignmentExpression_In_Yield @@ -2487,6 +2502,10 @@ CoverParenthesizedExpressionAndArrowParameterList_Yield_Await -> LPAREN Expression_In_Yield_Await COMMA ELLIPSIS BindingPattern_Yield_Await RPAREN action: cpeaapl_expr_rest +- rule: >- + CoverCallExpressionAndAsyncArrowHead_Yield_Await -> MemberExpression_Yield_Await + Arguments_Yield_Await + action: nop - rule: MemberExpression_Yield_Await -> PrimaryExpression_Yield_Await action: nop - rule: >- @@ -2505,16 +2524,16 @@ action: new_expression_arguments - rule: MemberExpression_Yield_Await -> MemberExpression_Yield_Await DOT PRIVATE_IDENTIFIER action: member_expression_private +- rule: SuperCall_Yield_Await -> SUPER Arguments_Yield_Await + action: call_expression_super +- rule: ImportCall_Yield_Await -> IMPORT LPAREN AssignmentExpression_In_Yield_Await RPAREN + action: import_expression - rule: Arguments_Yield_Await -> LPAREN RPAREN action: arguments_empty - rule: Arguments_Yield_Await -> LPAREN ArgumentList_Yield_Await RPAREN action: arguments - rule: Arguments_Yield_Await -> LPAREN ArgumentList_Yield_Await COMMA RPAREN action: arguments_comma -- rule: SuperCall_Yield_Await -> SUPER Arguments_Yield_Await - action: call_expression_super -- rule: ImportCall_Yield_Await -> IMPORT LPAREN AssignmentExpression_In_Yield_Await RPAREN - action: import_expression - rule: Expression_In_Yield_Await -> AssignmentExpression_In_Yield_Await action: nop - rule: >- @@ -2739,11 +2758,11 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_Await -> CoverCallExpressionAndAsyncArrowHead_Await - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + AsyncArrowFunction_Await -> AsyncArrowHeadCCEAAAH_Await (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression - rule: Initializer_Await -> ASSIGN AssignmentExpression_Await action: initializer @@ -3708,7 +3727,8 @@ action: generator_declaration - rule: >- AsyncFunctionDeclaration_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Yield LPAREN FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE + BindingIdentifier_Yield _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN + _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE action: async_function_declaration - rule: >- AsyncGeneratorDeclaration_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL @@ -3791,8 +3811,8 @@ action: generator_declaration - rule: >- AsyncFunctionDeclaration_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Yield_Await LPAREN FormalParameters_Await RPAREN LBRACE AsyncFunctionBody - RBRACE + BindingIdentifier_Yield_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN + _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE action: async_function_declaration - rule: >- AsyncGeneratorDeclaration_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL @@ -4824,11 +4844,11 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier_Yield - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_Yield -> CoverCallExpressionAndAsyncArrowHead_Yield - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + AsyncArrowFunction_Yield -> AsyncArrowHeadCCEAAAH_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression - rule: Initializer_Yield -> ASSIGN AssignmentExpression_Yield action: initializer @@ -4857,11 +4877,12 @@ action: arrow_function_expression - rule: >- AsyncArrowFunction_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) - AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression_single_param - rule: >- - AsyncArrowFunction_Yield_Await -> CoverCallExpressionAndAsyncArrowHead_Yield_Await - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody + AsyncArrowFunction_Yield_Await -> AsyncArrowHeadCCEAAAH_Yield_Await (!LINE_TERMINATOR_SEQUENCE) + ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody action: async_arrow_function_expression - rule: Initializer_Yield_Await -> ASSIGN AssignmentExpression_Yield_Await action: initializer diff --git a/bins/jstb/src/main.rs b/bins/jstb/src/main.rs index 6482f029b..8473e3586 100644 --- a/bins/jstb/src/main.rs +++ b/bins/jstb/src/main.rs @@ -12,6 +12,17 @@ struct CommandLine { #[command(subcommand)] command: Command, + /// The type of the source text. + /// + /// Specify `module` explicitly when the source text read from STDIN is parsed as a module. + #[arg( + global = true, + long = "as", + default_value = "auto", + value_name = "SOURCE_TYPE" + )] + parse_as: SourceType, + /// Enables the scope cleanup checker. #[arg(global = true, long)] scope_cleanup_checker: bool, @@ -65,6 +76,20 @@ struct Run { no_optimize: bool, } +#[derive(clap::ValueEnum, Clone)] +enum SourceType { + /// Parse as a script if the file extension of the input file is "js". + /// Parse as a module if the file extension of the input file is "mjs". + /// Otherwise, parse as a script. + Auto, + + /// Parse as a script. + Script, + + /// Parse as a module. + Module, +} + fn main() -> Result<()> { logging::init(); @@ -80,9 +105,26 @@ fn main() -> Result<()> { let source = read_source(cl.source.as_ref())?; + // This is not a good practice, but we define a macro instead of a function in order to avoid + // code clones. By using the macro, we can avoid additional `use` directives needed for the + // return type. + macro_rules! parse { + ($source:expr, $cl:expr) => { + match $cl.parse_as { + SourceType::Auto => match $cl.source.as_ref().and_then(|path| path.extension()) { + Some(ext) if ext == "js" => runtime.parse_script($source), + Some(ext) if ext == "mjs" => runtime.parse_module($source), + _ => runtime.parse_script($source), + }, + SourceType::Script => runtime.parse_script($source), + SourceType::Module => runtime.parse_module($source), + } + }; + } + match cl.command { Command::Parse(args) => { - let program = runtime.parse_script(&source)?; + let program = parse!(&source, cl)?; for kind in args.print.chars() { match kind { 'f' => { @@ -99,15 +141,16 @@ fn main() -> Result<()> { } Command::Compile(args) => { runtime.enable_llvmir_labels(); - let program = runtime.parse_script(&source)?; + let program = parse!(&source, cl)?; let module = runtime.compile(&program, !args.no_optimize)?; module.print(false); // to STDOUT } Command::Run(args) => { - let program = runtime.parse_script(&source)?; + let program = parse!(&source, cl)?; let module = runtime.compile(&program, !args.no_optimize)?; - if let Err(v) = runtime.evaluate(module) { - println!("Uncaught {v:?}"); + match runtime.evaluate(module) { + Ok(_) => runtime.run(), + Err(v) => println!("Uncaught {v:?}"), } } } diff --git a/libs/jsparser/src/parser/Makefile b/libs/jsparser/src/parser/Makefile index bb680eb90..d7f49a193 100644 --- a/libs/jsparser/src/parser/Makefile +++ b/libs/jsparser/src/parser/Makefile @@ -35,8 +35,8 @@ GOAL_SYMBOLS := \ ArrowFormalParameters \ ArrowFormalParameters_Yield \ ArrowFormalParameters_Await \ - ArrowFormalParameters_Yield_Await - + ArrowFormalParameters_Yield_Await \ + AsyncArrowHead # targets diff --git a/libs/jsparser/src/symbol/builtins.rs.njk b/libs/jsparser/src/symbol/builtins.rs.njk index ecc9cecac..aed17ae1c 100644 --- a/libs/jsparser/src/symbol/builtins.rs.njk +++ b/libs/jsparser/src/symbol/builtins.rs.njk @@ -8,14 +8,27 @@ use super::SymbolRegistry; impl Symbol { {%- for symbol in data %} - pub const {{ symbol | upper }}: Self = Self({{ loop.index0 + 1 }}); + {%- if symbol.startsWith('##') %} + pub const HIDDEN_{{ symbol | constantCase }}: Self = Self({{ loop.index }}); + {%- else %} + {#- Use `upper` instead of `constantCase` so that 'NaN' is converted into 'NAN'. #} + pub const {{ symbol | upper }}: Self = Self({{ loop.index }}); + {%- endif %} {%- endfor %} + + pub fn is_hidden(&self) -> bool { + self.0 <= {{ data | select('startsWith', '##') | length }} + } } impl SymbolRegistry { pub(super) fn register_builtin_symbols(&mut self) { {%- for symbol in data %} + {%- if symbol.startsWith('##') %} + self.register_builtin_symbol(Symbol::HIDDEN_{{ symbol | constantCase }}, "{{ symbol }}"); + {%- else %} self.register_builtin_symbol(Symbol::{{ symbol | upper }}, "{{ symbol }}"); + {%- endif %} {%- endfor %} } diff --git a/libs/jsparser/src/symbol/builtins.yaml b/libs/jsparser/src/symbol/builtins.yaml index 384597356..f0290e922 100644 --- a/libs/jsparser/src/symbol/builtins.yaml +++ b/libs/jsparser/src/symbol/builtins.yaml @@ -1,3 +1,11 @@ +# Hidden symbols must be defined before others. +# Hidden symbols must start with `##`. +- '##coroutine' +- '##promise' +- '##result' +- '##error' + +# Reserved words defined in the ECMA-262 specification. - Infinity - NaN - async diff --git a/libs/jsparser/src/symbol/mod.rs b/libs/jsparser/src/symbol/mod.rs index b0eb7e845..e945a67d9 100644 --- a/libs/jsparser/src/symbol/mod.rs +++ b/libs/jsparser/src/symbol/mod.rs @@ -10,7 +10,6 @@ pub struct Symbol(u32); impl Symbol { pub const NONE: Symbol = Symbol(0); - #[inline] pub fn id(&self) -> u32 { self.0 } diff --git a/libs/jsparser/src/syntax/actions.yaml b/libs/jsparser/src/syntax/actions.yaml index 1288312cb..6a409e4db 100644 --- a/libs/jsparser/src/syntax/actions.yaml +++ b/libs/jsparser/src/syntax/actions.yaml @@ -5,9 +5,9 @@ - rule: Script -> ScriptBody action: process_script - rule: Module -> (empty) - action: undefined + action: process_empty_module - rule: Module -> ModuleBody - action: undefined + action: process_module - rule: ArrowFormalParameters -> LPAREN UniqueFormalParameters RPAREN action: process_arrow_formal_parameters - rule: ArrowFormalParameters_Yield -> LPAREN UniqueFormalParameters_Yield RPAREN @@ -18,10 +18,14 @@ ArrowFormalParameters_Yield_Await -> LPAREN UniqueFormalParameters_Yield_Await RPAREN action: process_arrow_formal_parameters +- rule: >- + AsyncArrowHead -> ASYNC (!LINE_TERMINATOR_SEQUENCE) + ArrowFormalParameters_Await + action: process_async_arrow_head - rule: ScriptBody -> StatementList action: nop - rule: ModuleBody -> ModuleItemList - action: undefined + action: nop - rule: UniqueFormalParameters -> FormalParameters action: nop - rule: UniqueFormalParameters_Yield -> FormalParameters_Yield @@ -35,9 +39,9 @@ - rule: StatementList -> StatementList StatementListItem action: process_statement_list_item - rule: ModuleItemList -> ModuleItem - action: undefined + action: process_module_item_list_head - rule: ModuleItemList -> ModuleItemList ModuleItem - action: undefined + action: process_module_item_list_item - rule: FormalParameters -> (empty) action: process_formal_parameters_empty - rule: FormalParameters -> FunctionRestParameter @@ -93,7 +97,7 @@ - rule: ModuleItem -> ExportDeclaration action: undefined - rule: ModuleItem -> StatementListItem_Await - action: undefined + action: nop - rule: FunctionRestParameter -> BindingRestElement action: undefined - rule: FormalParameterList -> FormalParameter @@ -259,7 +263,7 @@ - rule: HoistableDeclaration -> GeneratorDeclaration action: undefined - rule: HoistableDeclaration -> AsyncFunctionDeclaration - action: undefined + action: process_hoistable_declaration - rule: HoistableDeclaration -> AsyncGeneratorDeclaration action: undefined - rule: ClassDeclaration -> CLASS BindingIdentifier ClassTail @@ -307,7 +311,7 @@ - rule: HoistableDeclaration_Await_Default -> GeneratorDeclaration_Await_Default action: undefined - rule: HoistableDeclaration_Await_Default -> AsyncFunctionDeclaration_Await_Default - action: undefined + action: process_hoistable_declaration - rule: >- HoistableDeclaration_Await_Default -> AsyncGeneratorDeclaration_Await_Default @@ -323,7 +327,7 @@ - rule: AssignmentExpression_In_Await -> ArrowFunction_In_Await action: nop - rule: AssignmentExpression_In_Await -> AsyncArrowFunction_In_Await - action: undefined + action: nop - rule: >- AssignmentExpression_In_Await -> LeftHandSideExpression_Await ASSIGN AssignmentExpression_In_Await @@ -499,9 +503,9 @@ action: undefined - rule: >- AsyncFunctionDeclaration -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier LPAREN FormalParameters_Await RPAREN LBRACE - AsyncFunctionBody RBRACE - action: undefined + BindingIdentifier _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await + RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE + action: process_async_function_declaration - rule: >- AsyncGeneratorDeclaration -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL BindingIdentifier LPAREN FormalParameters_Yield_Await RPAREN LBRACE @@ -548,7 +552,7 @@ - rule: HoistableDeclaration_Await -> GeneratorDeclaration_Await action: undefined - rule: HoistableDeclaration_Await -> AsyncFunctionDeclaration_Await - action: undefined + action: process_hoistable_declaration - rule: HoistableDeclaration_Await -> AsyncGeneratorDeclaration_Await action: undefined - rule: ClassDeclaration_Await -> CLASS BindingIdentifier_Await ClassTail_Await @@ -576,13 +580,14 @@ action: undefined - rule: >- AsyncFunctionDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) - FUNCTION BindingIdentifier_Await LPAREN FormalParameters_Await RPAREN LBRACE - AsyncFunctionBody RBRACE - action: undefined + FUNCTION BindingIdentifier_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN + FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody + RBRACE + action: process_async_function_declaration - rule: >- AsyncFunctionDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) - FUNCTION LPAREN FormalParameters_Await RPAREN LBRACE AsyncFunctionBody - RBRACE + FUNCTION _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN + _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE action: undefined - rule: >- AsyncGeneratorDeclaration_Await_Default -> ASYNC (!LINE_TERMINATOR_SEQUENCE) @@ -616,12 +621,13 @@ - rule: >- AsyncArrowFunction_In_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody_In - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_In_Await -> CoverCallExpressionAndAsyncArrowHead_Await - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In - action: undefined + AsyncArrowFunction_In_Await -> AsyncArrowHeadCCEAAAH_Await + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ + AsyncConciseBody_In + action: process_async_arrow_function_cceaaah - rule: LeftHandSideExpression_Await -> NewExpression_Await action: nop - rule: LeftHandSideExpression_Await -> CallExpression_Await @@ -909,7 +915,7 @@ - rule: AssignmentExpression_In -> ArrowFunction_In action: nop - rule: AssignmentExpression_In -> AsyncArrowFunction_In - action: undefined + action: nop - rule: >- AssignmentExpression_In -> LeftHandSideExpression ASSIGN AssignmentExpression_In @@ -1062,8 +1068,10 @@ action: nop - rule: GeneratorBody -> FunctionBody_Yield action: undefined +- rule: _ASYNC_FUNCTION_CONTEXT_ -> (empty) + action: process_async_function_context - rule: AsyncFunctionBody -> FunctionBody_Await - action: undefined + action: nop - rule: AsyncGeneratorBody -> FunctionBody_Yield_Await action: undefined - rule: ClassHeritage -> EXTENDS LeftHandSideExpression @@ -1213,9 +1221,10 @@ action: undefined - rule: >- AsyncFunctionDeclaration_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Await LPAREN FormalParameters_Await RPAREN LBRACE - AsyncFunctionBody RBRACE - action: undefined + BindingIdentifier_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN + FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody + RBRACE + action: process_async_function_declaration - rule: >- AsyncGeneratorDeclaration_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL BindingIdentifier_Await LPAREN FormalParameters_Yield_Await @@ -1246,15 +1255,13 @@ - rule: ConciseBody_In -> LBRACE FunctionBody RBRACE action: process_concise_body_function_body - rule: AsyncArrowBindingIdentifier -> BindingIdentifier_Await - action: undefined + action: process_async_arrow_binding_identifier - rule: 'AsyncConciseBody_In -> (?![LBRACE]) ExpressionBody_In_Await' - action: undefined + action: process_async_concise_body_expression_body - rule: AsyncConciseBody_In -> LBRACE AsyncFunctionBody RBRACE - action: undefined -- rule: >- - CoverCallExpressionAndAsyncArrowHead_Await -> MemberExpression_Await - Arguments_Await - action: process_cover_call_expression_and_async_arrow_head + action: process_async_concise_body_async_function_body +- rule: AsyncArrowHeadCCEAAAH_Await -> CoverCallExpressionAndAsyncArrowHead_Await + action: process_async_arrow_head_cceaaah - rule: NewExpression_Await -> MemberExpression_Await action: nop - rule: NewExpression_Await -> NEW NewExpression_Await @@ -1389,7 +1396,7 @@ - rule: AssignmentExpression_In_Yield -> ArrowFunction_In_Yield action: nop - rule: AssignmentExpression_In_Yield -> AsyncArrowFunction_In_Yield - action: undefined + action: nop - rule: >- AssignmentExpression_In_Yield -> LeftHandSideExpression_Yield ASSIGN AssignmentExpression_In_Yield @@ -1445,7 +1452,7 @@ - rule: AssignmentExpression_In_Yield_Await -> ArrowFunction_In_Yield_Await action: nop - rule: AssignmentExpression_In_Yield_Await -> AsyncArrowFunction_In_Yield_Await - action: undefined + action: nop - rule: >- AssignmentExpression_In_Yield_Await -> LeftHandSideExpression_Yield_Await ASSIGN AssignmentExpression_In_Yield_Await @@ -1483,12 +1490,12 @@ - rule: >- AsyncArrowFunction_In -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody_In - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_In -> CoverCallExpressionAndAsyncArrowHead - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In - action: undefined + AsyncArrowFunction_In -> AsyncArrowHeadCCEAAAH (!LINE_TERMINATOR_SEQUENCE) + ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In + action: process_async_arrow_function_cceaaah - rule: LeftHandSideExpression -> NewExpression action: nop - rule: LeftHandSideExpression -> CallExpression @@ -1605,6 +1612,10 @@ action: nop - rule: ExpressionBody_In_Await -> AssignmentExpression_In_Await action: nop +- rule: >- + CoverCallExpressionAndAsyncArrowHead_Await -> MemberExpression_Await + Arguments_Await + action: process_cover_call_expression_and_async_arrow_head - rule: MemberExpression_Await -> PrimaryExpression_Await action: nop - rule: >- @@ -1625,16 +1636,16 @@ action: undefined - rule: MemberExpression_Await -> MemberExpression_Await DOT PRIVATE_IDENTIFIER action: undefined +- rule: SuperCall_Await -> SUPER Arguments_Await + action: undefined +- rule: ImportCall_Await -> IMPORT LPAREN AssignmentExpression_In_Await RPAREN + action: undefined - rule: Arguments_Await -> LPAREN RPAREN action: process_arguments_empty - rule: Arguments_Await -> LPAREN ArgumentList_Await RPAREN action: process_arguments - rule: Arguments_Await -> LPAREN ArgumentList_Await COMMA RPAREN action: process_arguments_with_comma -- rule: SuperCall_Await -> SUPER Arguments_Await - action: undefined -- rule: ImportCall_Await -> IMPORT LPAREN AssignmentExpression_In_Await RPAREN - action: undefined - rule: TemplateLiteral_Await_Tagged -> NO_SUBSTITUTION_TEMPLATE action: undefined - rule: TemplateLiteral_Await_Tagged -> SubstitutionTemplate_Await_Tagged @@ -1660,9 +1671,9 @@ - rule: OptionalChain_Await -> OptionalChain_Await DOT PRIVATE_IDENTIFIER action: undefined - rule: StatementList_Await -> StatementListItem_Await - action: nop + action: process_statement_list_head - rule: StatementList_Await -> StatementList_Await StatementListItem_Await - action: undefined + action: process_statement_list_item - rule: >- DoWhileStatement_Await -> DO _LOOP_START_ Statement_Await _LOOP_BODY_ WHILE LPAREN Expression_In_Await RPAREN SEMICOLON @@ -1844,12 +1855,13 @@ - rule: >- AsyncArrowFunction_In_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody_In - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_In_Yield -> CoverCallExpressionAndAsyncArrowHead_Yield - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody_In - action: undefined + AsyncArrowFunction_In_Yield -> AsyncArrowHeadCCEAAAH_Yield + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ + AsyncConciseBody_In + action: process_async_arrow_function_cceaaah - rule: LeftHandSideExpression_Yield -> NewExpression_Yield action: nop - rule: LeftHandSideExpression_Yield -> CallExpression_Yield @@ -1902,13 +1914,13 @@ - rule: >- AsyncArrowFunction_In_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody_In - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody_In + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_In_Yield_Await -> - CoverCallExpressionAndAsyncArrowHead_Yield_Await (!LINE_TERMINATOR_SEQUENCE) - ARROW AsyncConciseBody_In - action: undefined + AsyncArrowFunction_In_Yield_Await -> AsyncArrowHeadCCEAAAH_Yield_Await + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ + AsyncConciseBody_In + action: process_async_arrow_function_cceaaah - rule: LeftHandSideExpression_Yield_Await -> NewExpression_Yield_Await action: nop - rule: LeftHandSideExpression_Yield_Await -> CallExpression_Yield_Await @@ -1923,8 +1935,8 @@ action: process_arrow_parameters_binding_identifier - rule: ArrowParameters -> CoverParenthesizedExpressionAndArrowParameterList action: process_arrow_parameters_cpeaapl -- rule: CoverCallExpressionAndAsyncArrowHead -> MemberExpression Arguments - action: process_cover_call_expression_and_async_arrow_head +- rule: AsyncArrowHeadCCEAAAH -> CoverCallExpressionAndAsyncArrowHead + action: process_async_arrow_head_cceaaah - rule: NewExpression -> MemberExpression action: nop - rule: NewExpression -> NEW NewExpression @@ -1956,7 +1968,7 @@ - rule: AssignmentExpression -> ArrowFunction action: nop - rule: AssignmentExpression -> AsyncArrowFunction - action: undefined + action: nop - rule: AssignmentExpression -> LeftHandSideExpression ASSIGN AssignmentExpression action: process_assignment - rule: >- @@ -2068,7 +2080,7 @@ - rule: PrimaryExpression_Await -> GeneratorExpression action: undefined - rule: PrimaryExpression_Await -> AsyncFunctionExpression - action: undefined + action: nop - rule: PrimaryExpression_Await -> AsyncGeneratorExpression action: undefined - rule: PrimaryExpression_Await -> REGULAR_EXPRESSION_LITERAL @@ -2150,11 +2162,9 @@ CoverParenthesizedExpressionAndArrowParameterList_Yield action: process_arrow_parameters_cpeaapl_yield - rule: AsyncArrowBindingIdentifier_Yield -> BindingIdentifier_Yield_Await - action: undefined -- rule: >- - CoverCallExpressionAndAsyncArrowHead_Yield -> MemberExpression_Yield - Arguments_Yield - action: process_cover_call_expression_and_async_arrow_head + action: process_async_arrow_binding_identifier +- rule: AsyncArrowHeadCCEAAAH_Yield -> CoverCallExpressionAndAsyncArrowHead_Yield + action: process_async_arrow_head_cceaaah - rule: NewExpression_Yield -> MemberExpression_Yield action: nop - rule: NewExpression_Yield -> NEW NewExpression_Yield @@ -2202,9 +2212,9 @@ CoverParenthesizedExpressionAndArrowParameterList_Yield_Await action: process_arrow_parameters_cpeaapl_yield_await - rule: >- - CoverCallExpressionAndAsyncArrowHead_Yield_Await -> - MemberExpression_Yield_Await Arguments_Yield_Await - action: process_cover_call_expression_and_async_arrow_head + AsyncArrowHeadCCEAAAH_Yield_Await -> + CoverCallExpressionAndAsyncArrowHead_Yield_Await + action: process_async_arrow_head_cceaaah - rule: NewExpression_Yield_Await -> MemberExpression_Yield_Await action: nop - rule: NewExpression_Yield_Await -> NEW NewExpression_Yield_Await @@ -2285,6 +2295,8 @@ CoverParenthesizedExpressionAndArrowParameterList -> LPAREN Expression_In COMMA ELLIPSIS BindingPattern RPAREN action: process_cpeaapl_formal_parameters_with_rest_pattern +- rule: CoverCallExpressionAndAsyncArrowHead -> MemberExpression Arguments + action: process_cover_call_expression_and_async_arrow_head - rule: MemberExpression -> PrimaryExpression action: nop - rule: MemberExpression -> MemberExpression LBRACK Expression_In RBRACK @@ -2301,16 +2313,16 @@ action: undefined - rule: MemberExpression -> MemberExpression DOT PRIVATE_IDENTIFIER action: undefined +- rule: SuperCall -> SUPER Arguments + action: undefined +- rule: ImportCall -> IMPORT LPAREN AssignmentExpression_In RPAREN + action: undefined - rule: Arguments -> LPAREN RPAREN action: process_arguments_empty - rule: Arguments -> LPAREN ArgumentList RPAREN action: process_arguments - rule: Arguments -> LPAREN ArgumentList COMMA RPAREN action: process_arguments_with_comma -- rule: SuperCall -> SUPER Arguments - action: undefined -- rule: ImportCall -> IMPORT LPAREN AssignmentExpression_In RPAREN - action: undefined - rule: TemplateLiteral_Tagged -> NO_SUBSTITUTION_TEMPLATE action: undefined - rule: TemplateLiteral_Tagged -> SubstitutionTemplate_Tagged @@ -2348,12 +2360,12 @@ - rule: >- AsyncArrowFunction -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody + action: process_async_arrow_function - rule: >- - AsyncArrowFunction -> CoverCallExpressionAndAsyncArrowHead - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody - action: undefined + AsyncArrowFunction -> AsyncArrowHeadCCEAAAH (!LINE_TERMINATOR_SEQUENCE) + ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody + action: process_async_arrow_function_cceaaah - rule: Initializer -> ASSIGN AssignmentExpression action: process_initializer - rule: LexicalBinding -> BindingIdentifier @@ -2489,14 +2501,16 @@ FormalParameters_Yield RPAREN LBRACE GeneratorBody RBRACE action: undefined - rule: >- - AsyncFunctionExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION LPAREN - FormalParameters_Await RPAREN LBRACE AsyncFunctionBody RBRACE - action: undefined + AsyncFunctionExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION + _ASYNC_FUNCTION_CONTEXT_ LPAREN FormalParameters_Await RPAREN + _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody RBRACE + action: process_anonymous_async_function_expression - rule: >- AsyncFunctionExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Await LPAREN FormalParameters_Await RPAREN LBRACE - AsyncFunctionBody RBRACE - action: undefined + BindingIdentifier_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN + FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody + RBRACE + action: process_async_function_expression - rule: >- AsyncGeneratorExpression -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL LPAREN FormalParameters_Yield_Await RPAREN LBRACE AsyncGeneratorBody RBRACE @@ -2523,7 +2537,7 @@ - rule: AssignmentExpression_Await -> ArrowFunction_Await action: nop - rule: AssignmentExpression_Await -> AsyncArrowFunction_Await - action: undefined + action: nop - rule: >- AssignmentExpression_Await -> LeftHandSideExpression_Await ASSIGN AssignmentExpression_Await @@ -2606,6 +2620,10 @@ CoverParenthesizedExpressionAndArrowParameterList_Yield -> LPAREN Expression_In_Yield COMMA ELLIPSIS BindingPattern_Yield RPAREN action: process_cpeaapl_formal_parameters_with_rest_pattern +- rule: >- + CoverCallExpressionAndAsyncArrowHead_Yield -> MemberExpression_Yield + Arguments_Yield + action: process_cover_call_expression_and_async_arrow_head - rule: MemberExpression_Yield -> PrimaryExpression_Yield action: nop - rule: >- @@ -2626,16 +2644,16 @@ action: undefined - rule: MemberExpression_Yield -> MemberExpression_Yield DOT PRIVATE_IDENTIFIER action: undefined +- rule: SuperCall_Yield -> SUPER Arguments_Yield + action: undefined +- rule: ImportCall_Yield -> IMPORT LPAREN AssignmentExpression_In_Yield RPAREN + action: undefined - rule: Arguments_Yield -> LPAREN RPAREN action: process_arguments_empty - rule: Arguments_Yield -> LPAREN ArgumentList_Yield RPAREN action: process_arguments - rule: Arguments_Yield -> LPAREN ArgumentList_Yield COMMA RPAREN action: process_arguments_with_comma -- rule: SuperCall_Yield -> SUPER Arguments_Yield - action: undefined -- rule: ImportCall_Yield -> IMPORT LPAREN AssignmentExpression_In_Yield RPAREN - action: undefined - rule: Expression_In_Yield -> AssignmentExpression_In_Yield action: nop - rule: >- @@ -2711,6 +2729,10 @@ CoverParenthesizedExpressionAndArrowParameterList_Yield_Await -> LPAREN Expression_In_Yield_Await COMMA ELLIPSIS BindingPattern_Yield_Await RPAREN action: process_cpeaapl_formal_parameters_with_rest_pattern +- rule: >- + CoverCallExpressionAndAsyncArrowHead_Yield_Await -> + MemberExpression_Yield_Await Arguments_Yield_Await + action: process_cover_call_expression_and_async_arrow_head - rule: MemberExpression_Yield_Await -> PrimaryExpression_Yield_Await action: nop - rule: >- @@ -2737,18 +2759,18 @@ MemberExpression_Yield_Await -> MemberExpression_Yield_Await DOT PRIVATE_IDENTIFIER action: undefined -- rule: Arguments_Yield_Await -> LPAREN RPAREN - action: process_arguments_empty -- rule: Arguments_Yield_Await -> LPAREN ArgumentList_Yield_Await RPAREN - action: process_arguments -- rule: Arguments_Yield_Await -> LPAREN ArgumentList_Yield_Await COMMA RPAREN - action: process_arguments_with_comma - rule: SuperCall_Yield_Await -> SUPER Arguments_Yield_Await action: undefined - rule: >- ImportCall_Yield_Await -> IMPORT LPAREN AssignmentExpression_In_Yield_Await RPAREN action: undefined +- rule: Arguments_Yield_Await -> LPAREN RPAREN + action: process_arguments_empty +- rule: Arguments_Yield_Await -> LPAREN ArgumentList_Yield_Await RPAREN + action: process_arguments +- rule: Arguments_Yield_Await -> LPAREN ArgumentList_Yield_Await COMMA RPAREN + action: process_arguments_with_comma - rule: Expression_In_Yield_Await -> AssignmentExpression_In_Yield_Await action: nop - rule: >- @@ -2826,7 +2848,7 @@ - rule: PrimaryExpression -> GeneratorExpression action: undefined - rule: PrimaryExpression -> AsyncFunctionExpression - action: undefined + action: nop - rule: PrimaryExpression -> AsyncGeneratorExpression action: undefined - rule: PrimaryExpression -> REGULAR_EXPRESSION_LITERAL @@ -2860,9 +2882,9 @@ - rule: ConciseBody -> LBRACE FunctionBody RBRACE action: process_concise_body_function_body - rule: 'AsyncConciseBody -> (?![LBRACE]) ExpressionBody_Await' - action: undefined + action: process_async_concise_body_expression_body - rule: AsyncConciseBody -> LBRACE AsyncFunctionBody RBRACE - action: undefined + action: process_async_concise_body_async_function_body - rule: Statement_Return -> BlockStatement_Return action: process_statement - rule: Statement_Return -> VariableStatement @@ -3002,12 +3024,13 @@ - rule: >- AsyncArrowFunction_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_Await -> CoverCallExpressionAndAsyncArrowHead_Await - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody - action: undefined + AsyncArrowFunction_Await -> AsyncArrowHeadCCEAAAH_Await + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ + AsyncConciseBody + action: process_async_arrow_function_cceaaah - rule: Initializer_Await -> ASSIGN AssignmentExpression_Await action: process_initializer - rule: LexicalBinding_Await -> BindingIdentifier_Await @@ -3049,7 +3072,7 @@ - rule: PrimaryExpression_Yield -> GeneratorExpression action: undefined - rule: PrimaryExpression_Yield -> AsyncFunctionExpression - action: undefined + action: nop - rule: PrimaryExpression_Yield -> AsyncGeneratorExpression action: undefined - rule: PrimaryExpression_Yield -> REGULAR_EXPRESSION_LITERAL @@ -3111,7 +3134,7 @@ - rule: PrimaryExpression_Yield_Await -> GeneratorExpression action: undefined - rule: PrimaryExpression_Yield_Await -> AsyncFunctionExpression - action: undefined + action: nop - rule: PrimaryExpression_Yield_Await -> AsyncGeneratorExpression action: undefined - rule: PrimaryExpression_Yield_Await -> REGULAR_EXPRESSION_LITERAL @@ -3600,7 +3623,7 @@ - rule: HoistableDeclaration_Yield -> GeneratorDeclaration_Yield action: undefined - rule: HoistableDeclaration_Yield -> AsyncFunctionDeclaration_Yield - action: undefined + action: process_hoistable_declaration - rule: HoistableDeclaration_Yield -> AsyncGeneratorDeclaration_Yield action: undefined - rule: ClassDeclaration_Yield -> CLASS BindingIdentifier_Yield ClassTail_Yield @@ -3723,7 +3746,7 @@ - rule: HoistableDeclaration_Yield_Await -> GeneratorDeclaration_Yield_Await action: undefined - rule: HoistableDeclaration_Yield_Await -> AsyncFunctionDeclaration_Yield_Await - action: undefined + action: process_hoistable_declaration - rule: HoistableDeclaration_Yield_Await -> AsyncGeneratorDeclaration_Yield_Await action: undefined - rule: >- @@ -4095,9 +4118,10 @@ action: undefined - rule: >- AsyncFunctionDeclaration_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION - BindingIdentifier_Yield LPAREN FormalParameters_Await RPAREN LBRACE - AsyncFunctionBody RBRACE - action: undefined + BindingIdentifier_Yield _ASYNC_FUNCTION_CONTEXT_ LPAREN + FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody + RBRACE + action: process_async_function_declaration - rule: >- AsyncGeneratorDeclaration_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL BindingIdentifier_Yield LPAREN FormalParameters_Yield_Await @@ -4188,9 +4212,10 @@ action: undefined - rule: >- AsyncFunctionDeclaration_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) - FUNCTION BindingIdentifier_Yield_Await LPAREN FormalParameters_Await RPAREN - LBRACE AsyncFunctionBody RBRACE - action: undefined + FUNCTION BindingIdentifier_Yield_Await _ASYNC_FUNCTION_CONTEXT_ LPAREN + FormalParameters_Await RPAREN _FUNCTION_SIGNATURE_ LBRACE AsyncFunctionBody + RBRACE + action: process_async_function_declaration - rule: >- AsyncGeneratorDeclaration_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) FUNCTION MUL BindingIdentifier_Yield_Await LPAREN @@ -5200,7 +5225,7 @@ - rule: AssignmentExpression_Yield -> ArrowFunction_Yield action: nop - rule: AssignmentExpression_Yield -> AsyncArrowFunction_Yield - action: undefined + action: nop - rule: >- AssignmentExpression_Yield -> LeftHandSideExpression_Yield ASSIGN AssignmentExpression_Yield @@ -5250,7 +5275,7 @@ - rule: AssignmentExpression_Yield_Await -> ArrowFunction_Yield_Await action: nop - rule: AssignmentExpression_Yield_Await -> AsyncArrowFunction_Yield_Await - action: undefined + action: nop - rule: >- AssignmentExpression_Yield_Await -> LeftHandSideExpression_Yield_Await ASSIGN AssignmentExpression_Yield_Await @@ -5388,12 +5413,13 @@ - rule: >- AsyncArrowFunction_Yield -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_Yield -> CoverCallExpressionAndAsyncArrowHead_Yield - (!LINE_TERMINATOR_SEQUENCE) ARROW AsyncConciseBody - action: undefined + AsyncArrowFunction_Yield -> AsyncArrowHeadCCEAAAH_Yield + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ + AsyncConciseBody + action: process_async_arrow_function_cceaaah - rule: Initializer_Yield -> ASSIGN AssignmentExpression_Yield action: process_initializer - rule: LexicalBinding_Yield -> BindingIdentifier_Yield @@ -5426,13 +5452,13 @@ - rule: >- AsyncArrowFunction_Yield_Await -> ASYNC (!LINE_TERMINATOR_SEQUENCE) AsyncArrowBindingIdentifier_Yield (!LINE_TERMINATOR_SEQUENCE) ARROW - AsyncConciseBody - action: undefined + _ANONYMOUS_FUNCTION_SIGNATURE_ AsyncConciseBody + action: process_async_arrow_function - rule: >- - AsyncArrowFunction_Yield_Await -> - CoverCallExpressionAndAsyncArrowHead_Yield_Await (!LINE_TERMINATOR_SEQUENCE) - ARROW AsyncConciseBody - action: undefined + AsyncArrowFunction_Yield_Await -> AsyncArrowHeadCCEAAAH_Yield_Await + (!LINE_TERMINATOR_SEQUENCE) ARROW _ANONYMOUS_FUNCTION_SIGNATURE_ + AsyncConciseBody + action: process_async_arrow_function_cceaaah - rule: Initializer_Yield_Await -> ASSIGN AssignmentExpression_Yield_Await action: process_initializer - rule: LexicalBinding_Yield_Await -> BindingIdentifier_Yield_Await @@ -5462,7 +5488,7 @@ - rule: UnaryExpression_Await -> NOT UnaryExpression_Await action: process_logical_not - rule: UnaryExpression_Await -> AwaitExpression - action: undefined + action: nop - rule: UpdateExpression_Await -> LeftHandSideExpression_Await action: nop - rule: >- @@ -5540,7 +5566,7 @@ - rule: ShortCircuitExpression_Yield_Await -> CoalesceExpression_Yield_Await action: nop - rule: AwaitExpression -> AWAIT UnaryExpression_Await - action: undefined + action: process_await - rule: ExponentiationExpression_Yield -> UnaryExpression_Yield action: nop - rule: >- @@ -5646,7 +5672,7 @@ - rule: UnaryExpression_Yield_Await -> NOT UnaryExpression_Yield_Await action: process_logical_not - rule: UnaryExpression_Yield_Await -> AwaitExpression_Yield - action: undefined + action: nop - rule: UpdateExpression_Yield_Await -> LeftHandSideExpression_Yield_Await action: nop - rule: >- @@ -5694,7 +5720,7 @@ BitwiseXORExpression_Yield_Await action: process_bitwise_or - rule: AwaitExpression_Yield -> AWAIT UnaryExpression_Yield_Await - action: undefined + action: process_await - rule: BitwiseXORExpression_Yield -> BitwiseANDExpression_Yield action: nop - rule: >- diff --git a/libs/jsparser/src/syntax/mod.rs b/libs/jsparser/src/syntax/mod.rs index 5b15b721d..a95f3e58b 100644 --- a/libs/jsparser/src/syntax/mod.rs +++ b/libs/jsparser/src/syntax/mod.rs @@ -121,8 +121,10 @@ enum Detail { Declaration, FormalParameters(SmallVec<[Symbol; 4]>), ConciseBody, + AsyncConciseBody, StatementList, CoverCallExpressionAndAsyncArrowHead, + ModuleItemList, } #[derive(Clone, Copy, Debug)] @@ -206,10 +208,15 @@ pub enum Node<'s> { FormalParameter, FormalParameters(u32), FunctionContext, + AsyncFunctionContext, FunctionSignature(Symbol), FunctionDeclaration, + AsyncFunctionDeclaration, FunctionExpression(bool), + AsyncFunctionExpression(bool), ArrowFunction, + AsyncArrowFunction, + AwaitExpression, ThenBlock, ElseBlock, FalsyShortCircuit, @@ -547,6 +554,12 @@ where Ok(()) } + // _ASYNC_FUNCTION_CONTEXT_ + fn process_async_function_context(&mut self) -> Result<(), Error> { + self.enqueue(Node::AsyncFunctionContext); + Ok(()) + } + // _FUNCTION_SIGNATURE_ fn process_function_signature(&mut self) -> Result<(), Error> { let func_name = match self.stack[self.stack.len() - 4].detail { @@ -1511,9 +1524,9 @@ where // LexicalBinding[In, Yield, Await] : // BindingIdentifier[?Yield, ?Await] Initializer[?In, ?Yield, ?Await] fn process_lexical_binding_identifier_with_initializer(&mut self) -> Result<(), Error> { - let symbol = match self.stack[self.stack.len() - 2].detail { - Detail::BindingIdentifier(symbol) => symbol, - _ => unreachable!(), + let symbol = match &self.stack[self.stack.len() - 2].detail { + Detail::BindingIdentifier(symbol) => *symbol, + detail => unreachable!("{detail:?}"), }; // 14.3.1.1 Static Semantics: Early Errors @@ -2229,15 +2242,6 @@ where Ok(()) } - // 15.9 Async Arrow Function Definitions - - // CoverCallExpressionAndAsyncArrowHead[Yield, Await] : - // MemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] - fn process_cover_call_expression_and_async_arrow_head(&mut self) -> Result<(), Error> { - self.replace(2, Detail::CoverCallExpressionAndAsyncArrowHead); - Ok(()) - } - // 15.1 Parameter Lists // FormalParameters[Yield, Await] : @@ -2372,9 +2376,13 @@ where // ArrowParameters[Yield, Await] : // BindingIdentifier[?Yield, ?Await] fn process_arrow_parameters_binding_identifier(&mut self) -> Result<(), Error> { - let i = self.enqueue(Node::FunctionContext); + self.process_single_arrow_parameter(Node::FunctionContext) + } + + fn process_single_arrow_parameter(&mut self, context_node: Node<'s>) -> Result<(), Error> { + let i = self.enqueue(context_node); debug_assert!(i > 0); - self.nodes.swap(i - 1, i); // swap BindingIdentifier and FunctionContext. + self.nodes.swap(i - 1, i); // swap BindingIdentifier and context_node. self.enqueue(Node::FormalParameter); self.enqueue(Node::FormalParameters(1)); let bound_names = match self.top().detail { @@ -2442,6 +2450,119 @@ where Ok(()) } + // 15.8 Async Function Definitions + + // AsyncFunctionDeclaration[Yield, Await, Default] : + // async [no LineTerminator here] function BindingIdentifier[?Yield, ?Await] + // ( FormalParameters[~Yield, +Await] ) { AsyncFunctionBody } + fn process_async_function_declaration(&mut self) -> Result<(), Error> { + self.enqueue(Node::AsyncFunctionDeclaration); + self.replace(9, Detail::Declaration); + Ok(()) + } + + // AsyncFunctionExpression : + // async [no LineTerminator here] function BindingIdentifier[~Yield, +Await] + // ( FormalParameters[~Yield, +Await] ) { AsyncFunctionBody } + fn process_async_function_expression(&mut self) -> Result<(), Error> { + self.enqueue(Node::AsyncFunctionExpression(true)); + self.replace(9, Detail::Expression); + Ok(()) + } + + // AsyncFunctionExpression : + // async [no LineTerminator here] function + // ( FormalParameters[~Yield, +Await] ) { AsyncFunctionBody } + fn process_anonymous_async_function_expression(&mut self) -> Result<(), Error> { + self.enqueue(Node::AsyncFunctionExpression(false)); + self.replace(8, Detail::Expression); + Ok(()) + } + + // AwaitExpression[Yield] : + // await UnaryExpression[?Yield, +Await] + fn process_await(&mut self) -> Result<(), Error> { + self.enqueue(Node::AwaitExpression); + self.replace(2, Detail::Expression); + Ok(()) + } + + // 15.9 Async Arrow Function Definitions + + // AsyncArrowFunction[In, Yield, Await] : + // async [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] + // [no LineTerminator here] => AsyncConciseBody[?In] + fn process_async_arrow_function(&mut self) -> Result<(), Error> { + self.enqueue(Node::AsyncArrowFunction); + self.replace(4, Detail::Expression); + Ok(()) + } + + // AsyncArrowFunction[In, Yield, Await] : + // AsyncArrowHeadCCEAAAH[?Yield, ?Await] [no LineTerminator here] => + // AsyncConciseBody[?In] + fn process_async_arrow_function_cceaaah(&mut self) -> Result<(), Error> { + self.enqueue(Node::AsyncArrowFunction); + self.replace(3, Detail::Expression); + Ok(()) + } + + // AsyncConciseBody[In] : + // [lookahead ≠ {] ExpressionBody[?In, +Await] + fn process_async_concise_body_expression_body(&mut self) -> Result<(), Error> { + self.enqueue(Node::ReturnStatement(1)); + self.replace(1, Detail::AsyncConciseBody); + Ok(()) + } + + // AsyncConciseBody[In] : + // { AsyncFunctionBody } + fn process_async_concise_body_async_function_body(&mut self) -> Result<(), Error> { + self.replace(3, Detail::AsyncConciseBody); + Ok(()) + } + + // AsyncArrowBindingIdentifier[Yield] : + // BindingIdentifier[?Yield, +Await] + fn process_async_arrow_binding_identifier(&mut self) -> Result<(), Error> { + self.process_single_arrow_parameter(Node::AsyncFunctionContext) + } + + // AsyncArrowHeadCCEAAAH[Yield, Await] : + // CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] + fn process_async_arrow_head_cceaaah(&mut self) -> Result<(), Error> { + self.refine_async_arrow_head() + } + + fn refine_async_arrow_head(&mut self) -> Result<(), Error> { + let syntax = self.pop(); + self.tokens.truncate(syntax.tokens_range.start); + self.nodes.truncate(syntax.nodes_range.start); + self.enqueue(Node::AsyncFunctionContext); + self.refine(&syntax, GoalSymbol::AsyncArrowHead) + } + + // AsyncArrowHead : + // async [no LineTerminator here] ArrowFormalParameters[~Yield, +Await] + fn process_async_arrow_head(&mut self) -> Result<(), Error> { + let formal_parameters = self.pop(); + let tokens_end = self.tokens.len(); + let nodes_end = self.nodes.len(); + let syntax = self.top_mut(); + syntax.detail = formal_parameters.detail; + syntax.tokens_range.end = tokens_end; + syntax.nodes_range.end = nodes_end; + syntax.source_range.end = formal_parameters.source_range.end; + Ok(()) + } + + // CoverCallExpressionAndAsyncArrowHead[Yield, Await] : + // MemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] + fn process_cover_call_expression_and_async_arrow_head(&mut self) -> Result<(), Error> { + self.replace(2, Detail::CoverCallExpressionAndAsyncArrowHead); + Ok(()) + } + // 16.1 Scripts // Script : @@ -2456,6 +2577,36 @@ where self.pop(); Ok(()) } + + // 16.2 Modules + + // Module : + // [empty] + fn process_empty_module(&mut self) -> Result<(), Error> { + Ok(()) + } + + // Module : + // ModuleBody + fn process_module(&mut self) -> Result<(), Error> { + self.pop(); + Ok(()) + } + + // ModuleItemList : + // ModuleItem + fn process_module_item_list_head(&mut self) -> Result<(), Error> { + self.top_mut().detail = Detail::ModuleItemList; + Ok(()) + } + + // ModuleItemList : + // ModuleItemList ModuleItem + fn process_module_item_list_item(&mut self) -> Result<(), Error> { + self.pop(); + self.update_ends(); + Ok(()) + } } impl<'s, H> SyntaxHandler<'s> for Processor<'s, H> diff --git a/libs/jsparser/src/transpile.js b/libs/jsparser/src/transpile.js index 0f6d94ea5..42ad248f8 100644 --- a/libs/jsparser/src/transpile.js +++ b/libs/jsparser/src/transpile.js @@ -163,11 +163,14 @@ class Transpiler { //rewriteCPEAAPL, addActions, modifyFunctionDeclaration, + modifyAsyncFunctionDeclaration, modifyIfStatement, modifyConditionalExpression, modifyShortCircuitExpressions, modifyFunctionExpression, + modifyAsyncFunctionExpression, modifyArrowFunction, + modifyAsyncArrowFunction, modifyDoWhileStatement, modifyWhileStatement, modifySwitchStatement, @@ -592,6 +595,7 @@ function addActions(rules) { const ACTIONS = [ '_FUNCTION_CONTEXT_', + '_ASYNC_FUNCTION_CONTEXT_', '_FUNCTION_SIGNATURE_', '_ANONYMOUS_FUNCTION_SIGNATURE_', '_ELSE_BLOCK_', @@ -630,30 +634,44 @@ function addActions(rules) { } function modifyFunctionDeclaration(rules) { - // The action will be inserted before the token. const TARGETS = [ { - token: '`(`', + term: '`(`', action: '_FUNCTION_CONTEXT_', + insertBefore: true, }, { - token: '`{`', + term: '`{`', action: '_FUNCTION_SIGNATURE_', + insertBefore: true, }, ]; - log.debug('Modifying FunctionDeclaration...'); - const rule = rules.find((rule) => rule.name === 'FunctionDeclaration[Yield, Await, Default]'); assert(rule !== undefined); + modifyTargetsInRule(rule, TARGETS); + return rules; +} - for (let i = 0; i < rule.values.length; ++i) { - for (const target of TARGETS) { - const [head, tail] = rule.values[i].split(target.token); - rule.values[i] = [head, target.action, target.token, tail].join(' '); - } - } - +function modifyAsyncFunctionDeclaration(rules) { + const TARGETS = [ + { + term: '`(`', + action: '_ASYNC_FUNCTION_CONTEXT_', + insertBefore: true, + }, + { + term: '`{`', + action: '_FUNCTION_SIGNATURE_', + insertBefore: true, + }, + ]; + log.debug('Modifying AsyncFunctionDeclaration...'); + const rule = rules.find((rule) => + rule.name === 'AsyncFunctionDeclaration[Yield, Await, Default]' + ); + assert(rule !== undefined); + modifyTargetsInRule(rule, TARGETS); return rules; } @@ -772,6 +790,26 @@ function modifyFunctionExpression(rules) { return rules; } +function modifyAsyncFunctionExpression(rules) { + const TARGETS = [ + { + term: '`(`', + action: '_ASYNC_FUNCTION_CONTEXT_', + insertBefore: true, + }, + { + term: '`{`', + action: '_FUNCTION_SIGNATURE_', + insertBefore: true, + }, + ]; + log.debug('Modifying AsyncFunctionExpression...'); + const rule = rules.find((rule) => rule.name === 'AsyncFunctionExpression'); + assert(rule !== undefined); + modifyTargetsInRule(rule, TARGETS); + return rules; +} + function modifyArrowFunction(rules) { const TARGETS = [ // _FUNCION_CONTEXT_ will be inserted in the syntax module. @@ -788,6 +826,35 @@ function modifyArrowFunction(rules) { return rules; } +function modifyAsyncArrowFunction(rules) { + const TARGETS = [ + // _ASYNC_FUNCION_CONTEXT_ will be inserted in the syntax module. + { + term: '`=>`', + action: '_ANONYMOUS_FUNCTION_SIGNATURE_', + insertBefore: false, + }, + ]; + log.debug('Modifying AsyncArrowFunction...'); + const rule = rules.find((rule) => rule.name === 'AsyncArrowFunction[In, Yield, Await]'); + assert(rule !== undefined); + modifyTargetsInRule(rule, TARGETS); + + rule.values = rule.values.map((value) => { + return value.replace( + 'CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await]', + 'AsyncArrowHeadCCEAAAH[?Yield, ?Await]', + ); + }); + + rules.push({ + name: 'AsyncArrowHeadCCEAAAH[Yield, Await]', + values: ['CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await]'], + }); + + return rules; +} + // CAUTION: You MUST update `isAutoSemicolonDoWhile()` in parser/lalr.js when you change the // production rule of `DoWhileStatement`. function modifyDoWhileStatement(rules) { diff --git a/libs/jsruntime/docs/internals.md b/libs/jsruntime/docs/internals.md index d0989de0e..c0835fc7d 100644 --- a/libs/jsruntime/docs/internals.md +++ b/libs/jsruntime/docs/internals.md @@ -101,3 +101,207 @@ The second part is implemented in the `compiler` and `bridge` modules. A `compi interprets the `semantics::CompileCommand`s and calls functions of `bridge::Compiler` in order to build LLVM IR instructions for closures. The logical location of each free variable on the stack is computed and the runtime part of the original algorithm are coded in this phase. + +## async/await + +There are multiple feasible ways to implement the async/await features, but we decided not to use +[the LLVM coroutines](https://llvm.org/docs/Coroutines.html). The reasons are as follows: + +* We have a plan to switch IR to others written in Rust, such as cranelift IR + * It's better to use only instructions that commonly used in others +* The optimizer for llvm.coro.* is great but it does not improve instructions generated from a + JavaScript program + * In many cases, we cannot do inlining JavaScript functions because these are bound to mutable + variables and can be changed at runtime + * Of course, functions bound to immutable variables declared with const can be done inlining at + compile time, but it's probably minority cases + +Instead, we implement the async/await features in a way similar to Rust: + +* [Async fn as a Future](https://tokio.rs/tokio/tutorial/async#async-fn-as-a-future) + +An async function: + +```javascript +async function func() { + +} +``` + +is ideally translated into: + +```javascript +// The ramp function translated from the original async function. +function func() { + // The inner coroutine is registered to some kind of job system in order to perform resuming the + // coroutine asynchronously. + const ##promise = runtime.register_promise( + runtime.create_coroutine( + // The arguments of the original async function are captured by the inner closure wrapping + // the coroutine. + // + // A special variable called ##coroutine can be used inside the coroutine. + // + // The lambda function of the coroutine will be called with the following arguments the + // coroutine resumes: + // + // ##promise: The promise returned from the ramp function. + // ##result: Available if the promise is fulfilled. + // ##error: Available if the promise is rejected. + // + (##promise, ##result, ##error) => { + // Load local, temporal and captured variables from ##coroutine. + + // Jump to the entry basic block for each coroutine state. + + + { + // Values of variables must be held over suspend points will be saved into ##coroutine + // before the coroutine suspends. + + } + }, + // Local variables used in the coroutine must be held over suspend points. + // So, those will be placed in the heap memory. + NUM_LOCALS, + // Temporal variables used in expressions must be held over suspend points, too. + // Those will be placed in the special memory called the scratch buffer allocated in the heap + // memory. + SCRATCH_BUFFER_LEN, + ), + ); + + // Perform the function body of the coroutine until the first suspend point (or the entire + // function body if there is no suspend point in the original function body). + runtime.resume(##promise); + + return ##promise; +} +``` + +The async function is translated into a *regular* function that returns *a promise* (it's not a +`Promise` object at this point because there is no `Object` implementation in the runtime). This +kind of function is generally called a *ramp* function in a coroutine implementation methodology. + +Inside the ramp function, a [*coroutine*](https://en.wikipedia.org/wiki/Coroutine) is created. +Like the Rust compiler does, we implements the coroutine with a state machine. In the `semantics` +module, compile commends for creating a jump table and suspending/resuming the coroutine execution +are generated and these commands are processed in the `llvmir::compiler` module in order to build +corresponding LLVM IR instructions. + +A closure for the lambda function of the coroutine is created in order to capture the arguments of +the original async function. The coroutine lambda function takes special *internal* arguments +instead of the original ones. Inside the coroutine lambda function, the original arguments can be +accessible via **an environment** that contains the captured original arguments. + +A `Coroutine` data which is passed as the environment to the coroutine lambda function is created. +The pointer to the `Coroutine` data is specified in the `context` formal parameter of the coroutine +lambda function. An important member variable of the `Coroutine` is `Coroutine::state`. This +member variable keeps and tracks the current state of the state machine used for the coroutine +implementation and used as the index for the jump table of the state machine implementation. + +In addition, the `Coroutine` data has enough memory area for local and temporal variables used in +the coroutine lambda function. Some of these variables must be held over suspend points. Values +of such variables will be saved into the memory area of the `Coroutine` data before the coroutine +suspends and loaded from it when the coroutine resumes. The size of the memory area can be +computed at compile time. + +### Generating LLVM IR instructions implementing the state machine for a coroutine + +Here we are going to explain how `await` expressions are translated into [LLVM IR instructions]. +For simplicity, we treat only the case of the following simple `await` expression: + +```javascript +await 0 +``` + +The same strategy can work for any kind of JavaScript control structure including conditional and +loop statements. + +LLVM IR instructions for `await` expressions can be divided into two groups: + +* Instructions building the jump table for the state machine +* Instructions to suspend and resume the execution + +As you saw above, the instructions for the jump table is placed before instructions for the +modified function body. The jump table can be built with an LLVM IR `switch` instruction: + +```llvm +bb.body: ; preds = %bb.args + %co.state.ptr = getelementptr inbounds %Coroutine, ptr %context, i32 0, i32 1 + %co.state = load i32, ptr %co.state.ptr, align 4 + switch i32 %co.state, label %bb.co.dormant [ + i32 0, label %bb.co.initial + i32 1, label %bb.scope.2.resume + ] +``` + +At the suspend point, the LLVM IR compiler generates a LLVM IR `store` instruction to update the +current state of the coroutine and a LLVM IR `ret` instruction to suspend the execution: + +```llvm + ; Save temporal variables to the scratch buffer. + %co.state.ptr4 = getelementptr inbounds %Coroutine, ptr %context, i32 0, i32 1 + store i32 1, ptr %co.state.ptr4, align 4 + ret i32 2 +``` + +A basic block from which the execution resumes is inserted after the basic block containing the +suspend point: + +```llvm +bb.scope.2.resume: ; preds = %bb.body + ; Load temporal variables the scratch buffer. +``` + +You can see the entire LLVM IR instructions by running the following command: + +```shell +echo 'await 0' | cargo run --bin=jstb -- compile --as=module --no-optimize +``` + +### The scratch buffer + +`await` is an expression. So, we have to think about how to hold temporal values used in an +expression including `await` expressions over suspend points. + +Given the following JavaScript expression: + +```javascript +a + b + await c; +``` + +`a + b` is evaluated before `+ await c`. The result of `a + b` must be held over the suspend point +at `await c`. Then, the value of `c` is added to the result of `a + b`. The result of `a + b` is +a temporal value and it's never assigned to any local variable. + +Holding temporal values over suspend points, we introduce a special memory area in the `Coroutine` +data, called *the scratch buffer*. Values saved into the scratch buffer are available only before +the next suspend point. Existing values in the scratch buffer will always be **overwritten** with +new temporal values used in an expression which is being evaluated just before the next suspend +point. + +The following LLVM IR instructions store the result of `a + b` into the scratch buffer and load it +from the scratch buffer: + +```llvm + %co.scratch_buffer.ptr = getelementptr inbounds i8, ptr %context, i64 %co.scratch_buffer.offsetof + %scratch.number.ptr = getelementptr inbounds i8, ptr %co.scratch_buffer.ptr, i32 0 + store double %add, ptr %scratch.number.ptr, align 8 + ... + +bb.scope.2.resume: ; preds = %bb.body + ... + %co.scratch_buffer.ptr26 = getelementptr inbounds i8, ptr %context, i64 %co.scratch_buffer.offsetof25 + %scratch.number.ptr27 = getelementptr inbounds i8, ptr %co.scratch_buffer.ptr26, i32 0 + %scratch.number = load double, ptr %scratch.number.ptr27, align 8 +``` + +Run the following command if you want to see the entire LLVM IR instructions: + +```shell +echo 'const a = 1, b = 2, c = 3; a + b + await c' | \ + cargo run --bin=jstb -- compile --no-optimize --as=module +``` + +[LLVM IR instructions]: https://llvm.org/docs/LangRef.html diff --git a/libs/jsruntime/src/function.rs b/libs/jsruntime/src/function.rs index 6de61fd1a..79edf4a45 100644 --- a/libs/jsruntime/src/function.rs +++ b/libs/jsruntime/src/function.rs @@ -1,39 +1,44 @@ -use std::ffi::CString; +use jsparser::Symbol; /// The identifier of a function. #[derive(Clone, Copy, Default, Eq, PartialEq)] pub struct FunctionId(u32); impl FunctionId { - const HOST_BIT: u32 = 0x80000000; - const VALUE_MASK: u32 = !Self::HOST_BIT; + const HOST_BIT: u32 = 1 << 31; + const COROUTINE_BIT: u32 = 1 << 30; + + const VALUE_MASK: u32 = !(Self::HOST_BIT | Self::COROUTINE_BIT); const MAX_INDEX: usize = Self::VALUE_MASK as usize; - pub const MAIN: Self = Self::native(0); + pub const MAIN: Self = Self::native(0, false); - #[inline(always)] pub const fn is_native(&self) -> bool { (self.0 & Self::HOST_BIT) == 0 } - #[inline(always)] pub const fn is_host(&self) -> bool { (self.0 & Self::HOST_BIT) != 0 } - #[inline(always)] - const fn native(index: usize) -> Self { + pub const fn is_coroutine(&self) -> bool { + (self.0 & Self::COROUTINE_BIT) != 0 + } + + const fn native(index: usize, coroutine: bool) -> Self { debug_assert!(index <= Self::MAX_INDEX); - Self(index as u32) + Self(if coroutine { + index as u32 | Self::COROUTINE_BIT + } else { + index as u32 + }) } - #[inline(always)] const fn host(index: usize) -> Self { debug_assert!(index <= Self::MAX_INDEX); Self(index as u32 | Self::HOST_BIT) } - #[inline(always)] const fn index(&self) -> usize { (self.0 & Self::VALUE_MASK) as usize } @@ -55,67 +60,84 @@ impl std::fmt::Debug for FunctionId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let index = self.index(); if self.is_native() { - write!(f, "FunctionId::Native({index})") + write!(f, "FunctionId::Native")?; } else { - write!(f, "FunctionId::Host({index})") + write!(f, "FunctionId::Host")?; } + if self.is_coroutine() { + write!(f, "Coroutine")?; + } + write!(f, "({index})") } } pub struct FunctionRegistry { - native_functions: Vec, - host_functions: Vec, + functions: Vec, } impl FunctionRegistry { pub fn new() -> Self { - Self { - native_functions: vec![], - host_functions: vec![], - } + Self { functions: vec![] } } - pub fn get_native(&self, id: FunctionId) -> &NativeFunction { - debug_assert!(id.is_native()); - &self.native_functions[id.index()] + pub fn create_native_function(&mut self, coroutine: bool) -> FunctionId { + let index = self.functions.len(); + assert!(index <= FunctionId::MAX_INDEX); + self.functions.push(Function::Native(NativeFunction { + scratch_buffer_len: 0, + })); + FunctionId::native(index, coroutine) } - pub fn get_host(&self, id: FunctionId) -> &HostFunction { - debug_assert!(id.is_host()); - &self.host_functions[id.index()] + pub fn register_host_function(&mut self, symbol: Symbol) -> FunctionId { + let index = self.functions.len(); + assert!(index <= FunctionId::MAX_INDEX); + self.functions.push(Function::Host(HostFunction { symbol })); + FunctionId::host(index) } - pub fn create_native_function(&mut self) -> FunctionId { - let index = self.native_functions.len(); - assert!(index <= FunctionId::MAX_INDEX); - let name = CString::new(format!("fn{index}")).unwrap(); - self.native_functions.push(NativeFunction { name }); - FunctionId::native(index) + pub fn get_native(&self, func_id: FunctionId) -> &NativeFunction { + debug_assert!(func_id.is_native()); + match self.functions.get(func_id.index()) { + Some(Function::Native(func)) => func, + _ => unreachable!(), + } } - pub fn register_host_function(&mut self, name: &str) -> FunctionId { - let index = self.host_functions.len(); - assert!(index <= FunctionId::MAX_INDEX); - let name = CString::new(name).unwrap(); - self.host_functions.push(HostFunction { name }); - FunctionId::host(index) + pub fn get_native_mut(&mut self, func_id: FunctionId) -> &mut NativeFunction { + debug_assert!(func_id.is_native()); + match self.functions.get_mut(func_id.index()) { + Some(Function::Native(func)) => func, + _ => unreachable!(), + } } pub fn enumerate_host_function(&self) -> impl Iterator { - self.host_functions.iter().enumerate().map(|(index, func)| { - debug_assert!(index <= FunctionId::MAX_INDEX); - (FunctionId::host(index), func) - }) + self.functions + .iter() + .enumerate() + .filter_map(|(index, func)| { + debug_assert!(index <= FunctionId::MAX_INDEX); + match func { + Function::Host(func) => Some((FunctionId::host(index), func)), + _ => None, + } + }) } } +enum Function { + Native(NativeFunction), + Host(HostFunction), +} + pub struct NativeFunction { // [[ECMAScriptCode]] - pub name: CString, + pub scratch_buffer_len: u32, } pub struct HostFunction { - pub name: CString, + pub symbol: Symbol, } #[cfg(test)] @@ -125,6 +147,9 @@ mod tests { #[test] fn test_function_id_max() { // TODO: checking at compile time is better. - assert_eq!(FunctionId::MAX_INDEX, (FunctionId::HOST_BIT - 1) as usize); + assert_eq!( + FunctionId::MAX_INDEX, + (FunctionId::COROUTINE_BIT - 1) as usize + ); } } diff --git a/libs/jsruntime/src/lib.rs b/libs/jsruntime/src/lib.rs index 3cf28f12d..666367d27 100644 --- a/libs/jsruntime/src/lib.rs +++ b/libs/jsruntime/src/lib.rs @@ -2,6 +2,7 @@ mod function; mod llvmir; mod logger; mod semantics; +mod tasklet; use jsparser::SymbolRegistry; @@ -16,6 +17,8 @@ pub use llvmir::Module; pub use llvmir::Value; pub use semantics::Program; +type VoidPtr = *mut std::ffi::c_void; + pub fn initialize() { llvmir::initialize(); } @@ -53,6 +56,7 @@ pub struct Runtime { executor: Executor, // TODO: GcArena allocator: bumpalo::Bump, + tasklet_system: tasklet::System, extension: X, } @@ -65,6 +69,7 @@ impl Runtime { function_registry: FunctionRegistry::new(), executor: Executor::with_runtime_bridge(&runtime_bridge), allocator: bumpalo::Bump::new(), + tasklet_system: tasklet::System::new(), extension, } } @@ -91,35 +96,38 @@ impl Runtime { R: Clone + ReturnValue, { let symbol = self.symbol_registry.intern_str(name); - let func_id = self.function_registry.register_host_function(name); + let func_id = self.function_registry.register_host_function(symbol); self.executor - .register_host_function(name, into_host_lambda(host_fn)); + .register_host_function(func_id, into_host_lambda(host_fn)); logger::debug!(event = "register_host_function", name, ?symbol, ?func_id); } pub fn evaluate(&mut self, module: Module) -> Result { logger::debug!(event = "evaluate"); self.executor.register_module(module); - let main = self.function_registry.get_native(FunctionId::MAIN); - let mut ret = Value::UNDEFINED; - let status = match self.executor.get_native_function(&main.name) { + let mut retv = Value::UNDEFINED; + let status = match self.executor.get_native_function(FunctionId::MAIN) { Some(main) => unsafe { main( - // ctx - self as *mut Self as *mut std::ffi::c_void, - // caps + // runtime + self.as_void_ptr(), + // context std::ptr::null_mut(), // argc 0, // argv std::ptr::null_mut(), - // ret - &mut ret as *mut Value, + // retv + &mut retv as *mut Value, ) }, None => unreachable!(), }; - ret.into_result(status) + retv.into_result(status) + } + + fn as_void_ptr(&mut self) -> VoidPtr { + self as *mut Self as VoidPtr } fn allocator(&self) -> &bumpalo::Bump { @@ -138,13 +146,7 @@ where // See https://www.reddit.com/r/rust/comments/ksfk4j/comment/gifzlhg/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button -type HostLambda = unsafe extern "C" fn( - *mut std::ffi::c_void, - *mut std::ffi::c_void, - usize, - *mut Value, - *mut Value, -) -> Status; +type HostLambda = unsafe extern "C" fn(VoidPtr, VoidPtr, usize, *mut Value, *mut Value) -> Status; // This function generates a wrapper function for each `host_func` at compile time. #[inline(always)] @@ -159,11 +161,11 @@ where } unsafe extern "C" fn host_fn_wrapper( - ctx: *mut std::ffi::c_void, - _caps: *mut std::ffi::c_void, + runtime: VoidPtr, + _context: VoidPtr, argc: usize, argv: *mut Value, - ret: *mut Value, + retv: *mut Value, ) -> Status where F: Fn(&mut Runtime, &[Value]) -> R + Send + Sync + 'static, @@ -171,10 +173,10 @@ where { #[allow(clippy::uninit_assumed_init)] let host_fn = std::mem::MaybeUninit::::uninit().assume_init(); - let runtime = &mut *(ctx as *mut Runtime); + let runtime = &mut *(runtime as *mut Runtime); let args = std::slice::from_raw_parts(argv as *const Value, argc); - // TODO: the return value is copied twice. that's inefficient. - let retval = host_fn(runtime, args); - *ret = retval.value(); - retval.status() + // TODO: The return value is copied twice. That's inefficient. + let result = host_fn(runtime, args); + *retv = result.value(); + result.status() } diff --git a/libs/jsruntime/src/llvmir/bridge.cc b/libs/jsruntime/src/llvmir/bridge.cc index 94f5cf5f6..af37a3b55 100644 --- a/libs/jsruntime/src/llvmir/bridge.cc +++ b/libs/jsruntime/src/llvmir/bridge.cc @@ -3,6 +3,7 @@ #include #include +#include #include "compiler.hh" #include "executor.hh" @@ -19,11 +20,16 @@ #define PEER_BOOLEAN(value) (reinterpret_cast(value)) #define PEER_NUMBER(value) (reinterpret_cast(value)) #define PEER_CLOSURE(value) (reinterpret_cast(value)) +#define PEER_COROUTINE(value) (reinterpret_cast(value)) +#define PEER_PROMISE(value) (reinterpret_cast(value)) #define PEER_VALUE(value) (reinterpret_cast(value)) #define PEER_ARGV(value) (reinterpret_cast(value)) #define PEER_STATUS(value) (reinterpret_cast(value)) #define PEER_CAPTURE(value) (reinterpret_cast(value)) +#define LLVM_SWITCH(inst) (reinterpret_cast(inst)) +#define PEER_SWITCH(inst) (reinterpret_cast(inst)) + void llvmir_initialize() { // Uncomment if you want to enable LLVM_DEBUG(). // llvm::DebugFlag = true; @@ -71,8 +77,8 @@ void compiler_peer_set_target_triple(Compiler* self, const char* triple) { self->SetTargetTriple(triple); } -void compiler_peer_start_function(Compiler* self, const char* name) { - self->StartFunction(name); +void compiler_peer_start_function(Compiler* self, uint32_t func_id) { + self->StartFunction(func_id); } void compiler_peer_end_function(Compiler* self, bool optimize) { @@ -83,8 +89,8 @@ void compiler_peer_set_locals_block(Compiler* self, BasicBlock* block) { self->SetLocalsBlock(LLVM_BB(block)); } -LambdaIr* compiler_peer_get_function(Compiler* self, uint32_t func_id, const char* name) { - return PEER_LAMBDA(self->GetFunction(func_id, name)); +LambdaIr* compiler_peer_get_function(Compiler* self, uint32_t func_id) { + return PEER_LAMBDA(self->GetFunction(func_id)); } // basic block @@ -325,8 +331,40 @@ ClosureIr* compiler_peer_create_closure_phi(Compiler* self, LLVM_VALUE(then_value), LLVM_BB(then_block), LLVM_VALUE(else_value), LLVM_BB(else_block))); } +// promise + +BooleanIr* compiler_peer_create_is_promise(Compiler* self, ValueIr* value) { + return PEER_BOOLEAN(self->CreateIsPromise(LLVM_VALUE(value))); +} + +BooleanIr* compiler_peer_create_is_same_promise(Compiler* self, PromiseIr* a, PromiseIr* b) { + return PEER_BOOLEAN(self->CreateIsSamePromise(LLVM_VALUE(a), LLVM_VALUE(b))); +} + +PromiseIr* compiler_peer_create_register_promise(Compiler* self, CoroutineIr* coroutine) { + return PEER_PROMISE(self->CreateRegisterPromise(LLVM_VALUE(coroutine))); +} + +void compiler_peer_create_await_promise(Compiler* self, PromiseIr* promise, PromiseIr* awaiting) { + self->CreateAwaitPromise(LLVM_VALUE(promise), LLVM_VALUE(awaiting)); +} + +void compiler_peer_create_resume(Compiler* self, PromiseIr* promise) { + self->CreateResume(LLVM_VALUE(promise)); +} + +void compiler_peer_create_emit_promise_resolved(Compiler* self, + PromiseIr* promise, + ValueIr* result) { + self->CreateEmitPromiseResolved(LLVM_VALUE(promise), LLVM_VALUE(result)); +} + // value +BooleanIr* compiler_peer_create_has_value(Compiler* self, ValueIr* value) { + return PEER_BOOLEAN(self->CreateHasValue(LLVM_VALUE(value))); +} + BooleanIr* compiler_peer_create_is_loosely_equal(Compiler* self, ValueIr* lhs, ValueIr* rhs) { return PEER_BOOLEAN(self->CreateIsLooselyEqual(LLVM_VALUE(lhs), LLVM_VALUE(rhs))); } @@ -353,6 +391,12 @@ BooleanIr* compiler_peer_create_is_same_closure_value(Compiler* self, return PEER_BOOLEAN(self->CreateIsSameClosureValue(LLVM_VALUE(value), LLVM_VALUE(closure))); } +BooleanIr* compiler_peer_create_is_same_promise_value(Compiler* self, + ValueIr* value, + PromiseIr* promise) { + return PEER_BOOLEAN(self->CreateIsSamePromiseValue(LLVM_VALUE(value), LLVM_VALUE(promise))); +} + ValueIr* compiler_peer_create_undefined_to_any(Compiler* self) { return PEER_VALUE(self->CreateUndefinedToAny()); } @@ -410,6 +454,10 @@ void compiler_peer_create_store_closure_to_value(Compiler* self, ClosureIr* valu self->CreateStoreClosureToValue(LLVM_VALUE(value), LLVM_VALUE(dest)); } +void compiler_peer_create_store_promise_to_value(Compiler* self, PromiseIr* value, ValueIr* dest) { + self->CreateStorePromiseToValue(LLVM_VALUE(value), LLVM_VALUE(dest)); +} + void compiler_peer_create_store_value_to_value(Compiler* self, ValueIr* value, ValueIr* dest) { self->CreateStoreValueToValue(LLVM_VALUE(value), LLVM_VALUE(dest)); } @@ -418,6 +466,10 @@ ClosureIr* compiler_peer_create_load_closure_from_value(Compiler* self, ValueIr* return PEER_CLOSURE(self->CreateLoadClosureFromValue(LLVM_VALUE(value))); } +PromiseIr* compiler_peer_create_load_promise_from_value(Compiler* self, ValueIr* value) { + return PEER_PROMISE(self->CreateLoadPromiseFromValue(LLVM_VALUE(value))); +} + // argv ArgvIr* compiler_peer_get_argv_nullptr(Compiler* self) { @@ -462,6 +514,10 @@ void compiler_peer_create_store_closure_to_retv(Compiler* self, ClosureIr* value self->CreateStoreClosureToRetv(LLVM_VALUE(value)); } +void compiler_peer_create_store_promise_to_retv(Compiler* self, PromiseIr* value) { + self->CreateStorePromiseToRetv(LLVM_VALUE(value)); +} + void compiler_peer_create_store_value_to_retv(Compiler* self, ValueIr* value) { self->CreateStoreValueToRetv(LLVM_VALUE(value)); } @@ -550,18 +606,120 @@ CaptureIr* compiler_peer_create_load_capture(Compiler* self, uint16_t index) { return PEER_CAPTURE(self->CreateLoadCapture(index)); } +// coroutine + +CoroutineIr* compiler_peer_create_coroutine(Compiler* self, + ClosureIr* closure, + uint16_t num_locals, + uint16_t scratch_buffer_len) { + return PEER_COROUTINE( + self->CreateCoroutine(LLVM_VALUE(closure), num_locals, scratch_buffer_len)); +} + +SwitchIr* compiler_peer_create_switch_for_coroutine(Compiler* self, + BasicBlock* block, + uint32_t num_states) { + return PEER_SWITCH(self->CreateSwitchForCoroutine(LLVM_BB(block), num_states)); +} + +void compiler_peer_create_add_state_for_coroutine(Compiler* self, + SwitchIr* inst, + uint32_t state, + BasicBlock* block) { + self->CreateAddStateForCoroutine(LLVM_SWITCH(inst), state, LLVM_BB(block)); +} + +void compiler_peer_create_suspend(Compiler* self) { + self->CreateSuspend(); +} + +void compiler_peer_create_set_coroutine_state(Compiler* self, uint32_t state) { + self->CreateSetCoroutineState(state); +} + +void compiler_peer_create_set_captures_for_coroutine(Compiler* self) { + self->CreateSetCapturesForCoroutine(); +} + +ValueIr* compiler_peer_create_get_local_ptr_from_coroutine(Compiler* self, uint16_t index) { + return PEER_VALUE(self->CreateGetLocalPtrFromCoroutine(index)); +} + +void compiler_peer_create_write_boolean_to_scratch_buffer(Compiler* self, + uint32_t offset, + BooleanIr* value) { + self->CreateWriteBooleanToScratchBuffer(offset, LLVM_VALUE(value)); +} + +BooleanIr* compiler_peer_create_read_boolean_from_scratch_buffer(Compiler* self, uint32_t offset) { + return PEER_BOOLEAN(self->CreateReadBooleanFromScratchBuffer(offset)); +} + +void compiler_peer_create_write_number_to_scratch_buffer(Compiler* self, + uint32_t offset, + NumberIr* value) { + self->CreateWriteNumberToScratchBuffer(offset, LLVM_VALUE(value)); +} + +NumberIr* compiler_peer_create_read_number_from_scratch_buffer(Compiler* self, uint32_t offset) { + return PEER_NUMBER(self->CreateReadNumberFromScratchBuffer(offset)); +} + +void compiler_peer_create_write_closure_to_scratch_buffer(Compiler* self, + uint32_t offset, + ClosureIr* value) { + self->CreateWriteClosureToScratchBuffer(offset, LLVM_VALUE(value)); +} + +ClosureIr* compiler_peer_create_read_closure_from_scratch_buffer(Compiler* self, uint32_t offset) { + return PEER_CLOSURE(self->CreateReadClosureFromScratchBuffer(offset)); +} + +void compiler_peer_create_write_promise_to_scratch_buffer(Compiler* self, + uint32_t offset, + PromiseIr* value) { + self->CreateWritePromiseToScratchBuffer(offset, LLVM_VALUE(value)); +} + +PromiseIr* compiler_peer_create_read_promise_from_scratch_buffer(Compiler* self, uint32_t offset) { + return PEER_PROMISE(self->CreateReadPromiseFromScratchBuffer(offset)); +} + +void compiler_peer_create_write_value_to_scratch_buffer(Compiler* self, + uint32_t offset, + ValueIr* value) { + self->CreateWriteValueToScratchBuffer(offset, LLVM_VALUE(value)); +} + +ValueIr* compiler_peer_create_read_value_from_scratch_buffer(Compiler* self, uint32_t offset) { + return PEER_VALUE(self->CreateReadValueFromScratchBuffer(offset)); +} + // scope cleanup checker -void compiler_peer_setup_scope_cleanup_checker(Compiler* self, uint16_t stack_size) { - self->SetupScopeCleanupChecker(stack_size); +void compiler_peer_enable_scope_cleanup_checker(Compiler* self, bool is_coroutine) { + self->EnableScopeCleanupChecker(is_coroutine); +} + +void compiler_peer_set_scope_id_for_checker(Compiler* self, uint16_t scope_id) { + self->SetScopeIdForChecker(scope_id); } -void compiler_peer_perform_scope_cleanup_precheck(Compiler* self, uint16_t scope_id) { - self->PerformScopeCleanupPrecheck(scope_id); +void compiler_peer_assert_scope_id(Compiler* self, uint16_t expected) { + self->AssertScopeId(expected); } -void compiler_peer_perform_scope_cleanup_postcheck(Compiler* self, uint16_t scope_id) { - self->PerformScopeCleanupPostcheck(scope_id); +// print + +void compiler_peer_create_print_value(Compiler* self, ValueIr* value, const char* msg) { + assert(msg != nullptr); + self->CreatePrintValue(LLVM_VALUE(value), msg); +} + +// unreachable + +void compiler_peer_create_unreachable(Compiler* self, const char* msg) { + self->CreateUnreachable(msg); } // executor @@ -578,8 +736,8 @@ void executor_peer_register_runtime(Executor* self, const Runtime* runtime) { self->RegisterRuntime(runtime); } -void executor_peer_register_host_function(Executor* self, const char* name, Lambda lambda) { - self->RegisterHostFunction(name, lambda); +void executor_peer_register_host_function(Executor* self, uint32_t func_id, Lambda lambda) { + self->RegisterHostFunction(func_id, lambda); } void executor_peer_register_module(Executor* self, Module* mod) { @@ -594,8 +752,8 @@ const char* executor_peer_get_target_triple(const Executor* self) { return self->target_triple().getTriple().c_str(); } -Lambda executor_peer_get_native_function(Executor* self, const char* name) { - return self->GetNativeFunction(name); +Lambda executor_peer_get_native_function(Executor* self, uint32_t func_id) { + return self->GetNativeFunction(func_id); } // helper functions diff --git a/libs/jsruntime/src/llvmir/bridge.hh b/libs/jsruntime/src/llvmir/bridge.hh index a5de7a0d8..805b6e09f 100644 --- a/libs/jsruntime/src/llvmir/bridge.hh +++ b/libs/jsruntime/src/llvmir/bridge.hh @@ -9,11 +9,13 @@ struct Closure; #define STATUS_MASK 0x0F #define STATUS_NORMAL 0x00 #define STATUS_EXCEPTION 0x01 +#define STATUS_SUSPEND 0x02 #define STATUS_UNSET (STATUS_UNSET_BIT | STATUS_NORMAL) enum class Status : uint32_t { Normal = STATUS_NORMAL, Exception = STATUS_EXCEPTION, + Suspend = STATUS_SUSPEND, }; static_assert(sizeof(Status) == sizeof(uint32_t), "size mismatched"); @@ -27,6 +29,7 @@ enum class ValueKind : uint8_t { Boolean, Number, Closure, + Promise, }; static_assert(sizeof(ValueKind) == sizeof(uint8_t), "size mismatched"); @@ -37,6 +40,7 @@ union ValueHolder { double number; // TODO(issue#237): GcCellRef Closure* closure; + uint32_t promise; }; static_assert(sizeof(ValueHolder) == sizeof(uint64_t), "size mismatched"); @@ -49,7 +53,12 @@ struct Value { static_assert(sizeof(Value) == sizeof(uint64_t) * 2, "size mismatched"); -typedef Status (*Lambda)(void* ctx, void* caps, size_t argc, Value* argv, Value* ret); +// The actual type of `context` varies depending on usage of the lambda function: +// +// Regular functions: Capture** +// Coroutine functions: Coroutine* +// +typedef Status (*Lambda)(void* runtime, void* context, size_t argc, Value* argv, Value* ret); // TODO(issue#237): GcCell struct Capture { @@ -66,23 +75,41 @@ struct Closure { // A pointer to a function compiled from a JavaScript function. Lambda lambda; - // The number of elements in `storage[]`. + // The number of captures. // // Usually, this field does not used in the compiled function, but we add this field here for // debugging purposes. If we need to reduce the heap memory usage and `Closure`s dominant, we // can remove this field. uint16_t num_captures; - // uint8_t padding[6]; - - // Using the following definition instead of `Capture* captures[]`, we can avoid accessing the - // `num_captures` field and comparison and conditional branch instructions that are needed for - // checking whether `captures` is empty or not. - Capture** captures; - // `Capture* storage[num_captures]` is placed here if it's not empty. + // A variable-length list of captures used in the lambda function. + // TODO(issue#237): GcCellRef + Capture* captures[32]; }; -static_assert(sizeof(Closure) == sizeof(uint64_t) * 3, "size mismatched"); +// TODO(issue#237): GcCell +struct Coroutine { + // The closure of the coroutine. + // TODO(issue#237): GcCellRef + Closure* closure; + + // The state of the coroutine. + uint32_t state; + + // The number of local variables. + uint16_t num_locals; + + // The current scope id used by the scope cleanup checker. + uint16_t scope_id; + + // The size of the scratch buffer in bytes. + uint16_t scratch_buffer_len; + + // A variable-length list of local variables used in the coroutine. + Value locals[32]; + + // The scratch_buffer starts from &locals[num_locals]. +}; #include "runtime.hh" @@ -103,10 +130,13 @@ struct LambdaIr; struct BooleanIr; struct NumberIr; struct ClosureIr; +struct CoroutineIr; +struct PromiseIr; struct ValueIr; struct ArgvIr; struct StatusIr; struct CaptureIr; +struct SwitchIr; Compiler* compiler_peer_new(); void compiler_peer_delete(Compiler* self); @@ -117,10 +147,10 @@ void compiler_peer_set_data_layout(Compiler* self, const char* data_layout); void compiler_peer_set_target_triple(Compiler* self, const char* triple); // function -void compiler_peer_start_function(Compiler* self, const char* name); +void compiler_peer_start_function(Compiler* self, uint32_t func_id); void compiler_peer_end_function(Compiler* self, bool optimize); void compiler_peer_set_locals_block(Compiler* self, BasicBlock* block); -LambdaIr* compiler_peer_get_function(Compiler* self, uint32_t func_id, const char* name); +LambdaIr* compiler_peer_get_function(Compiler* self, uint32_t func_id); // basic block BasicBlock* compiler_peer_create_basic_block(Compiler* self, const char* name, size_t name_len); @@ -209,7 +239,18 @@ ClosureIr* compiler_peer_create_closure_phi(Compiler* self, ClosureIr* else_value, BasicBlock* else_block); +// promise +BooleanIr* compiler_peer_create_is_promise(Compiler* self, ValueIr* value); +BooleanIr* compiler_peer_create_is_same_promise(Compiler* self, PromiseIr* a, PromiseIr* b); +PromiseIr* compiler_peer_create_register_promise(Compiler* self, CoroutineIr* coroutine); +void compiler_peer_create_await_promise(Compiler* self, PromiseIr* promise, PromiseIr* awaiting); +void compiler_peer_create_resume(Compiler* self, PromiseIr* promise); +void compiler_peer_create_emit_promise_resolved(Compiler* self, + PromiseIr* promise, + ValueIr* result); + // value +BooleanIr* compiler_peer_create_has_value(Compiler* self, ValueIr* value); BooleanIr* compiler_peer_create_is_loosely_equal(Compiler* self, ValueIr* lhs, ValueIr* rhs); BooleanIr* compiler_peer_create_is_strictly_equal(Compiler* self, ValueIr* lhs, ValueIr* rhs); BooleanIr* compiler_peer_create_is_same_boolean_value(Compiler* self, @@ -221,6 +262,9 @@ BooleanIr* compiler_peer_create_is_same_number_value(Compiler* self, BooleanIr* compiler_peer_create_is_same_closure_value(Compiler* self, ValueIr* value, ClosureIr* closure); +BooleanIr* compiler_peer_create_is_same_promise_value(Compiler* self, + ValueIr* value, + PromiseIr* promise); ValueIr* compiler_peer_create_undefined_to_any(Compiler* self); ValueIr* compiler_peer_create_null_to_any(Compiler* self); ValueIr* compiler_peer_create_boolean_to_any(Compiler* self, BooleanIr* boolean); @@ -238,8 +282,10 @@ void compiler_peer_create_store_null_to_value(Compiler* self, ValueIr* dest); void compiler_peer_create_store_boolean_to_value(Compiler* self, BooleanIr* value, ValueIr* dest); void compiler_peer_create_store_number_to_value(Compiler* self, NumberIr* value, ValueIr* dest); void compiler_peer_create_store_closure_to_value(Compiler* self, ClosureIr* value, ValueIr* dest); +void compiler_peer_create_store_promise_to_value(Compiler* self, PromiseIr* value, ValueIr* dest); void compiler_peer_create_store_value_to_value(Compiler* self, ValueIr* value, ValueIr* dest); ClosureIr* compiler_peer_create_load_closure_from_value(Compiler* self, ValueIr* value); +PromiseIr* compiler_peer_create_load_promise_from_value(Compiler* self, ValueIr* value); // argv ArgvIr* compiler_peer_get_argv_nullptr(Compiler* self); @@ -256,6 +302,7 @@ void compiler_peer_create_store_null_to_retv(Compiler* self); void compiler_peer_create_store_boolean_to_retv(Compiler* self, BooleanIr* value); void compiler_peer_create_store_number_to_retv(Compiler* self, NumberIr* value); void compiler_peer_create_store_closure_to_retv(Compiler* self, ClosureIr* value); +void compiler_peer_create_store_promise_to_retv(Compiler* self, PromiseIr* value); void compiler_peer_create_store_value_to_retv(Compiler* self, ValueIr* value); ValueIr* compiler_peer_get_exception(Compiler* self); @@ -288,10 +335,53 @@ void compiler_peer_create_escape_value(Compiler* self, CaptureIr* capture, Value ValueIr* compiler_peer_create_get_capture_value_ptr(Compiler* self, uint16_t index); CaptureIr* compiler_peer_create_load_capture(Compiler* self, uint16_t index); +// coroutine +CoroutineIr* compiler_peer_create_coroutine(Compiler* self, + ClosureIr* closure, + uint16_t num_locals, + uint16_t scratch_buffer_len); +SwitchIr* compiler_peer_create_switch_for_coroutine(Compiler* self, + BasicBlock* block, + uint32_t num_states); +void compiler_peer_create_add_state_for_coroutine(Compiler* self, + SwitchIr* switch_ir, + uint32_t state, + BasicBlock* block); +void compiler_peer_create_suspend(Compiler* self); +void compiler_peer_create_set_coroutine_state(Compiler* self, uint32_t state); +void compiler_peer_create_set_captures_for_coroutine(Compiler* self); +ValueIr* compiler_peer_create_get_local_ptr_from_coroutine(Compiler* self, uint16_t index); +void compiler_peer_create_write_boolean_to_scratch_buffer(Compiler* self, + uint32_t offset, + BooleanIr* value); +BooleanIr* compiler_peer_create_read_boolean_from_scratch_buffer(Compiler* self, uint32_t offset); +void compiler_peer_create_write_number_to_scratch_buffer(Compiler* self, + uint32_t offset, + NumberIr* value); +NumberIr* compiler_peer_create_read_number_from_scratch_buffer(Compiler* self, uint32_t offset); +void compiler_peer_create_write_closure_to_scratch_buffer(Compiler* self, + uint32_t offset, + ClosureIr* value); +ClosureIr* compiler_peer_create_read_closure_from_scratch_buffer(Compiler* self, uint32_t offset); +void compiler_peer_create_write_promise_to_scratch_buffer(Compiler* self, + uint32_t offset, + PromiseIr* value); +PromiseIr* compiler_peer_create_read_promise_from_scratch_buffer(Compiler* self, uint32_t offset); +void compiler_peer_create_write_value_to_scratch_buffer(Compiler* self, + uint32_t offset, + ValueIr* value); +ValueIr* compiler_peer_create_read_value_from_scratch_buffer(Compiler* self, uint32_t offset); + // scope cleanup checker -void compiler_peer_setup_scope_cleanup_checker(Compiler* self, uint16_t stack_size); -void compiler_peer_perform_scope_cleanup_precheck(Compiler* self, uint16_t scope_id); -void compiler_peer_perform_scope_cleanup_postcheck(Compiler* self, uint16_t scope_id); +void compiler_peer_enable_scope_cleanup_checker(Compiler* self, bool is_coroutine); +void compiler_peer_set_scope_id_for_checker(Compiler* self, uint16_t scope_id); +void compiler_peer_assert_scope_id(Compiler* self, uint16_t expected); + +// print +void compiler_peer_create_print_value(Compiler* self, ValueIr* value, const char* msg); + +// unreachable +void compiler_peer_create_unreachable(Compiler* self, const char* msg); // Execution @@ -299,11 +389,11 @@ class Executor; Executor* executor_peer_new(); void executor_peer_delete(Executor* self); void executor_peer_register_runtime(Executor* self, const Runtime* runtime); -void executor_peer_register_host_function(Executor* self, const char* name, Lambda func); +void executor_peer_register_host_function(Executor* self, uint32_t func_id, Lambda func); void executor_peer_register_module(Executor* self, Module* mod); const char* executor_peer_get_data_layout(const Executor* self); const char* executor_peer_get_target_triple(const Executor* self); -Lambda executor_peer_get_native_function(Executor* self, const char* name); +Lambda executor_peer_get_native_function(Executor* self, uint32_t func_id); // Hepler Functions diff --git a/libs/jsruntime/src/llvmir/bridge.rs b/libs/jsruntime/src/llvmir/bridge.rs index 7a6ce8b26..49a167fb7 100644 --- a/libs/jsruntime/src/llvmir/bridge.rs +++ b/libs/jsruntime/src/llvmir/bridge.rs @@ -3,15 +3,23 @@ #![allow(non_snake_case)] #![allow(non_upper_case_globals)] +use crate::tasklet::Promise; +use crate::VoidPtr; + include!(concat!(env!("OUT_DIR"), "/bridge.rs")); macro_rules! into_runtime { - ($context:expr, $extension:ident) => { - &mut *($context as *mut crate::Runtime<$extension>) + ($runtime:expr, $extension:ident) => { + &mut *($runtime as *mut crate::Runtime<$extension>) }; } impl Value { + pub const NONE: Self = Self { + kind: ValueKind_None, + holder: ValueHolder { opaque: 0 }, + }; + pub const UNDEFINED: Self = Self { kind: ValueKind_Undefined, holder: ValueHolder { opaque: 0 }, @@ -39,6 +47,13 @@ impl Value { } } + pub const fn promise(promise: u32) -> Self { + Self { + kind: ValueKind_Promise, + holder: ValueHolder { promise }, + } + } + pub fn into_result(self, status: Status) -> Result { match status { Status_Normal => Ok(self), @@ -78,6 +93,12 @@ impl From for Value { } } +impl From for Value { + fn from(value: Promise) -> Self { + Self::promise(value.into()) + } +} + impl std::fmt::Debug for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // `unsafe` is needed for accessing the `holder` field. @@ -93,8 +114,8 @@ impl std::fmt::Debug for Value { let lambda = (*self.holder.closure).lambda.unwrap(); write!(f, "closure({lambda:?}, [")?; let len = (*self.holder.closure).num_captures as usize; - let data = (*self.holder.closure).captures; - let mut captures = std::slice::from_raw_parts_mut(data, len) + let data = (*self.holder.closure).captures.as_ptr(); + let mut captures = std::slice::from_raw_parts(data, len) .iter() .map(|capture| capture.as_ref().unwrap()); if let Some(capture) = captures.next() { @@ -105,7 +126,8 @@ impl std::fmt::Debug for Value { } write!(f, "])") } - _ => unreachable!(), + ValueKind_Promise => write!(f, "promise({:04X})", self.holder.promise), + _ => unreachable!("invalid kind: {:?}", self.kind), } } } @@ -166,6 +188,41 @@ where } } +impl Coroutine { + pub fn resume( + runtime: VoidPtr, + coroutine: *mut Coroutine, + promise: Promise, + result: &Value, + error: &Value, + ) -> CoroutineStatus { + unsafe { + let lambda = (*(*coroutine).closure).lambda.unwrap(); + let mut args = [promise.into(), *result, *error]; + let mut retv = Value::NONE; + let status = lambda( + runtime, + coroutine as VoidPtr, + args.len(), + args.as_mut_ptr(), + &mut retv as *mut Value, + ); + match status { + STATUS_NORMAL => CoroutineStatus::Done(retv), + STATUS_EXCEPTION => CoroutineStatus::Error(retv), + STATUS_SUSPEND => CoroutineStatus::Suspend, + _ => unreachable!(), + } + } + } +} + +pub enum CoroutineStatus { + Done(Value), + Error(Value), + Suspend, +} + pub fn runtime_bridge() -> Runtime { Runtime { to_boolean: Some(runtime_to_boolean), @@ -176,13 +233,20 @@ pub fn runtime_bridge() -> Runtime { is_strictly_equal: Some(runtime_is_strictly_equal), create_capture: Some(runtime_create_capture::), create_closure: Some(runtime_create_closure::), + create_coroutine: Some(runtime_create_coroutine::), + register_promise: Some(runtime_register_promise::), + await_promise: Some(runtime_await_promise::), + resume: Some(runtime_resume::), + emit_promise_resolved: Some(runtime_emit_promise_resolved::), assert: Some(runtime_assert), print_u32: Some(runtime_print_u32), + print_f64: Some(runtime_print_f64), + print_value: Some(runtime_print_value), } } // 7.1.2 ToBoolean ( argument ) -unsafe extern "C" fn runtime_to_boolean(_: usize, value: *const Value) -> bool { +unsafe extern "C" fn runtime_to_boolean(_runtime: VoidPtr, value: *const Value) -> bool { let value = &*value; match value.kind { ValueKind_Undefined => false, @@ -192,13 +256,14 @@ unsafe extern "C" fn runtime_to_boolean(_: usize, value: *const Value) -> bool { ValueKind_Number if value.holder.number.is_nan() => false, ValueKind_Number => true, ValueKind_Closure => true, - _ => unreachable!(), + ValueKind_Promise => true, + _ => unreachable!("invalid value: {value:?}"), } } // 7.1.3 ToNumeric ( value ) // 7.1.4 ToNumber ( argument ) -unsafe extern "C" fn runtime_to_numeric(_: usize, value: *const Value) -> f64 { +unsafe extern "C" fn runtime_to_numeric(_runtime: VoidPtr, value: *const Value) -> f64 { let value = &*value; match value.kind { ValueKind_Undefined => f64::NAN, @@ -207,12 +272,13 @@ unsafe extern "C" fn runtime_to_numeric(_: usize, value: *const Value) -> f64 { ValueKind_Boolean => 0.0, ValueKind_Number => value.holder.number, ValueKind_Closure => f64::NAN, - _ => unreachable!(), + ValueKind_Promise => f64::NAN, + _ => unreachable!("invalid value: {value:?}"), } } // 7.1.6 ToInt32 ( argument ) -unsafe extern "C" fn runtime_to_int32(_: usize, value: f64) -> i32 { +unsafe extern "C" fn runtime_to_int32(_runtime: VoidPtr, value: f64) -> i32 { const EXP2_31: f64 = (2u64 << 31) as f64; const EXP2_32: f64 = (2u64 << 32) as f64; @@ -237,7 +303,7 @@ unsafe extern "C" fn runtime_to_int32(_: usize, value: f64) -> i32 { } // 7.1.7 ToUint32 ( argument ) -unsafe extern "C" fn runtime_to_uint32(_: usize, value: f64) -> u32 { +unsafe extern "C" fn runtime_to_uint32(_runtime: VoidPtr, value: f64) -> u32 { const EXP2_31: f64 = (2u64 << 31) as f64; const EXP2_32: f64 = (2u64 << 32) as f64; @@ -263,7 +329,7 @@ unsafe extern "C" fn runtime_to_uint32(_: usize, value: f64) -> u32 { // 7.2.13 IsLooselyEqual ( x, y ) unsafe extern "C" fn runtime_is_loosely_equal( - runtime: usize, + runtime: VoidPtr, a: *const Value, b: *const Value, ) -> bool { @@ -299,7 +365,11 @@ unsafe extern "C" fn runtime_is_loosely_equal( } // 7.2.14 IsStrictlyEqual ( x, y ) -unsafe extern "C" fn runtime_is_strictly_equal(_: usize, a: *const Value, b: *const Value) -> bool { +unsafe extern "C" fn runtime_is_strictly_equal( + _runtime: VoidPtr, + a: *const Value, + b: *const Value, +) -> bool { let x = &*a; let y = &*b; if x.kind != y.kind { @@ -311,11 +381,15 @@ unsafe extern "C" fn runtime_is_strictly_equal(_: usize, a: *const Value, b: *co ValueKind_Boolean => x.holder.boolean == y.holder.boolean, ValueKind_Number => x.holder.number == y.holder.number, ValueKind_Closure => x.holder.closure == y.holder.closure, - _ => unreachable!(), + ValueKind_Promise => x.holder.promise == y.holder.promise, + _ => unreachable!("invalid value: {x:?}"), } } -unsafe extern "C" fn runtime_create_capture(context: usize, target: *mut Value) -> *mut Capture { +unsafe extern "C" fn runtime_create_capture( + runtime: VoidPtr, + target: *mut Value, +) -> *mut Capture { const LAYOUT: std::alloc::Layout = unsafe { std::alloc::Layout::from_size_align_unchecked( std::mem::size_of::(), @@ -323,7 +397,7 @@ unsafe extern "C" fn runtime_create_capture(context: usize, target: *mut Valu ) }; - let runtime = into_runtime!(context, X); + let runtime = into_runtime!(runtime, X); let allocator = runtime.allocator(); // TODO: GC @@ -338,21 +412,21 @@ unsafe extern "C" fn runtime_create_capture(context: usize, target: *mut Valu } unsafe extern "C" fn runtime_create_closure( - context: usize, + runtime: VoidPtr, lambda: Lambda, num_captures: u16, ) -> *mut Closure { const BASE_LAYOUT: std::alloc::Layout = unsafe { std::alloc::Layout::from_size_align_unchecked( - std::mem::size_of::(), + std::mem::offset_of!(Closure, captures), std::mem::align_of::(), ) }; let storage_layout = std::alloc::Layout::array::<*mut Capture>(num_captures as usize).unwrap(); - let (layout, offset) = BASE_LAYOUT.extend(storage_layout).unwrap(); + let (layout, _) = BASE_LAYOUT.extend(storage_layout).unwrap(); - let runtime = into_runtime!(context, X); + let runtime = into_runtime!(runtime, X); let allocator = runtime.allocator(); // TODO: GC @@ -361,19 +435,81 @@ unsafe extern "C" fn runtime_create_closure( let closure = ptr.cast::().as_ptr(); (*closure).lambda = lambda; (*closure).num_captures = num_captures; - if num_captures == 0 { - (*closure).captures = std::ptr::null_mut(); - } else { - (*closure).captures = ptr.as_ptr().wrapping_add(offset).cast::<*mut Capture>(); - } - - // `closure.storage[]` will be filled with actual pointers to `Captures`. + // `(*closure).captures[]` will be filled with actual pointers to `Captures`. closure } +unsafe extern "C" fn runtime_create_coroutine( + runtime: VoidPtr, + closure: *mut Closure, + num_locals: u16, + scratch_buffer_len: u16, +) -> *mut Coroutine { + const BASE_LAYOUT: std::alloc::Layout = unsafe { + std::alloc::Layout::from_size_align_unchecked( + std::mem::offset_of!(Coroutine, locals), + std::mem::align_of::(), + ) + }; + + // num_locals may be 0. + let locals_layout = std::alloc::Layout::array::(num_locals as usize).unwrap(); + let (layout, _) = BASE_LAYOUT.extend(locals_layout).unwrap(); + + // scratch_buffer_len may be 0. + debug_assert_eq!(scratch_buffer_len as usize % size_of::(), 0); + let n = scratch_buffer_len as usize / size_of::(); + let scratch_buffer_layout = std::alloc::Layout::array::(n).unwrap(); + let (layout, _) = layout.extend(scratch_buffer_layout).unwrap(); + + let runtime = into_runtime!(runtime, X); + let allocator = runtime.allocator(); + + // TODO: GC + let ptr = allocator.alloc_layout(layout); + + let coroutine = ptr.cast::().as_ptr(); + (*coroutine).closure = closure; + (*coroutine).state = 0; + (*coroutine).num_locals = num_locals; + (*coroutine).scope_id = 0; + (*coroutine).scratch_buffer_len = scratch_buffer_len; + // `(*coroutine).locals[]` will be initialized in the coroutine. + + coroutine +} + +unsafe extern "C" fn runtime_register_promise( + runtime: VoidPtr, + coroutine: *mut Coroutine, +) -> u32 { + let runtime = into_runtime!(runtime, X); + runtime.register_promise(coroutine).into() +} + +unsafe extern "C" fn runtime_resume(runtime: VoidPtr, promise: u32) { + let runtime = into_runtime!(runtime, X); + runtime.process_promise(promise.into(), &Value::NONE, &Value::NONE); +} + +unsafe extern "C" fn runtime_await_promise(runtime: VoidPtr, promise: u32, awaiting: u32) { + let runtime = into_runtime!(runtime, X); + runtime.await_promise(promise.into(), awaiting.into()); +} + +unsafe extern "C" fn runtime_emit_promise_resolved( + runtime: VoidPtr, + promise: u32, + result: *const Value, +) { + let runtime = into_runtime!(runtime, X); + let cloned = *result; + runtime.emit_promise_resolved(promise.into(), cloned); +} + unsafe extern "C" fn runtime_assert( - _context: usize, + _runtime: VoidPtr, assertion: bool, msg: *const std::os::raw::c_char, ) { @@ -384,7 +520,7 @@ unsafe extern "C" fn runtime_assert( } unsafe extern "C" fn runtime_print_u32( - _context: usize, + _runtime: VoidPtr, value: u32, msg: *const std::os::raw::c_char, ) { @@ -395,3 +531,30 @@ unsafe extern "C" fn runtime_print_u32( crate::logger::debug!("runtime_print_u32: {value:08X}: {msg:?}"); } } + +unsafe extern "C" fn runtime_print_f64( + _runtime: VoidPtr, + value: f64, + msg: *const std::os::raw::c_char, +) { + let msg = std::ffi::CStr::from_ptr(msg); + if msg.is_empty() { + crate::logger::debug!("runtime_print_f64: {value}"); + } else { + crate::logger::debug!("runtime_print_f64: {value}: {msg:?}"); + } +} + +unsafe extern "C" fn runtime_print_value( + _runtime: VoidPtr, + value: *const Value, + msg: *const std::os::raw::c_char, +) { + let value = &*value; + let msg = std::ffi::CStr::from_ptr(msg); + if msg.is_empty() { + crate::logger::debug!("runtime_print_value: {value:?}"); + } else { + crate::logger::debug!("runtime_print_value: {value:?}: {msg:?}"); + } +} diff --git a/libs/jsruntime/src/llvmir/compiler.hh b/libs/jsruntime/src/llvmir/compiler.hh index 553ba67e5..aa5efd43f 100644 --- a/libs/jsruntime/src/llvmir/compiler.hh +++ b/libs/jsruntime/src/llvmir/compiler.hh @@ -65,10 +65,10 @@ struct Module; class Compiler { public: Compiler() { - context_ = std::make_unique(); - module_ = std::make_unique("
", *context_); - builder_ = std::make_unique>(*context_); - types_ = std::make_unique(*context_, *module_, *builder_); + llvmctx_ = std::make_unique(); + module_ = std::make_unique("
", *llvmctx_); + builder_ = std::make_unique>(*llvmctx_); + types_ = std::make_unique(*llvmctx_, *module_, *builder_); // Took from toy.cpp in the Kaleidoscope tutorial. fpm_ = std::make_unique(); @@ -77,7 +77,7 @@ class Compiler { cgam_ = std::make_unique(); mam_ = std::make_unique(); pic_ = std::make_unique(); - si_ = std::make_unique(*context_, true); // with debug logs + si_ = std::make_unique(*llvmctx_, true); // with debug logs si_->registerCallbacks(*pic_, mam_.get()); fpm_->addPass(llvm::PromotePass()); @@ -109,7 +109,7 @@ class Compiler { std::abort(); } - llvm::orc::ThreadSafeModule mod(std::move(module_), std::move(context_)); + llvm::orc::ThreadSafeModule mod(std::move(module_), std::move(llvmctx_)); return new Module(std::move(mod)); } @@ -127,23 +127,22 @@ class Compiler { // function - void StartFunction(const char* name) { - function_ = CreateLambda(name); + void StartFunction(uint32_t func_id) { + function_ = CreateLambda(func_id); - exec_context_ = function_->getArg(0); - caps_ = function_->getArg(1); + runtime_ = function_->getArg(0); + context_ = function_->getArg(1); argc_ = function_->getArg(2); argv_ = function_->getArg(3); retv_ = function_->getArg(4); - ClearScopeCleanupStack(); + // captures_ will be overridden if this lambda function is a coroutine. + captures_ = context_; + + ResetScopeCleanupChecker(); } void EndFunction(bool optimize) { - if (IsScopeCleanupCheckerEnabled()) { - CreateAssertScopeCleanupStackIsEmpty(); - } - auto* status = builder_->CreateLoad(builder_->getInt32Ty(), status_, REG_NAME("status")); // Convert STATUS_XXX into Status. auto* masked = @@ -166,20 +165,19 @@ class Compiler { locals_block_ = block; } - llvm::Function* GetFunction(uint32_t func_id, const char* name) { - UNUSED(func_id); - return CreateLambda(name); + llvm::Function* GetFunction(uint32_t func_id) { + return CreateLambda(func_id); } // basic block llvm::BasicBlock* CreateBasicBlock(const char* name) { - return llvm::BasicBlock::Create(*context_, name, function_); + return llvm::BasicBlock::Create(*llvmctx_, name, function_); } llvm::BasicBlock* CreateBasicBlock(const char* name, size_t name_len) { return llvm::BasicBlock::Create( - *context_, llvm::Twine(llvm::StringRef(name, name_len)), function_); + *llvmctx_, llvm::Twine(llvm::StringRef(name, name_len)), function_); } llvm::BasicBlock* GetBasicBlock() const { @@ -256,11 +254,11 @@ class Compiler { // 7.1.2 ToBoolean ( argument ) llvm::Value* CreateToBoolean(llvm::Value* value_ptr) { auto* func = types_->CreateRuntimeToBoolean(); - return builder_->CreateCall(func, {exec_context_, value_ptr}, REG_NAME("boolean")); + return builder_->CreateCall(func, {runtime_, value_ptr}, REG_NAME("boolean")); } llvm::Value* GetBoolean(bool value) { - return llvm::ConstantInt::getBool(*context_, value); + return llvm::ConstantInt::getBool(*llvmctx_, value); } llvm::Value* CreateLogicalNot(llvm::Value* boolean) { @@ -296,7 +294,7 @@ class Compiler { // 7.1.4 ToNumber ( argument ) llvm::Value* ToNumeric(llvm::Value* value_ptr) { auto* call = types_->CreateRuntimeToNumeric(); - return builder_->CreateCall(call, {exec_context_, value_ptr}, REG_NAME("numeric")); + return builder_->CreateCall(call, {runtime_, value_ptr}, REG_NAME("numeric")); } llvm::Value* GetNan() { @@ -308,7 +306,7 @@ class Compiler { } llvm::Value* GetNumber(double value) { - return llvm::ConstantFP::get(*context_, llvm::APFloat(value)); + return llvm::ConstantFP::get(*llvmctx_, llvm::APFloat(value)); } // 6.1.6.1.2 Number::bitwiseNOT ( x ) @@ -431,13 +429,13 @@ class Compiler { llvm::Value* CreateClosure(llvm::Value* lambda, uint16_t num_captures) { auto* func = types_->CreateRuntimeCreateClosure(); return builder_->CreateCall( - func, {exec_context_, lambda, builder_->getInt16(num_captures)}, REG_NAME("closure.ptr")); + func, {runtime_, lambda, builder_->getInt16(num_captures)}, REG_NAME("closure.ptr")); } - inline void CreateStoreCapturePtrToClosure(llvm::Value* capture_ptr, + void CreateStoreCapturePtrToClosure(llvm::Value* capture_ptr, llvm::Value* closure_ptr, uint16_t index) { - auto* ptr = CreateLoadCapturesFromClosure(closure_ptr); + auto* ptr = CreateGetCapturesPtrOfClosure(closure_ptr); CreateStoreCapturePtrToCaptures(capture_ptr, ptr, index); } @@ -447,9 +445,9 @@ class Compiler { llvm::Value* retv) { auto* prototype = types_->CreateLambdaType(); auto* lambda = CreateLoadLambdaFromClosure(closure); - auto* caps = CreateLoadCapturesFromClosure(closure); + auto* context = CreateGetCapturesPtrOfClosure(closure); return builder_->CreateCall(prototype, lambda, - {exec_context_, caps, types_->GetWord(argc), argv, retv}, REG_NAME("status")); + {runtime_, context, types_->GetWord(argc), argv, retv}, REG_NAME("status")); } llvm::Value* CreateClosurePhi(llvm::Value* then_value, @@ -462,20 +460,58 @@ class Compiler { return phi; } + // promise + + llvm::Value* CreateIsPromise(llvm::Value* value_ptr) { + auto* kind = CreateLoadValueKindFromValue(value_ptr); + return builder_->CreateICmpEQ( + kind, builder_->getInt8(kValueKindPromise), REG_NAME("is_promise")); + } + + llvm::Value* CreateIsSamePromise(llvm::Value* a, llvm::Value* b) { + return builder_->CreateICmpEQ(a, b, REG_NAME("is_same_promise")); + } + + llvm::Value* CreateRegisterPromise(llvm::Value* coroutine) { + auto* func = types_->CreateRuntimeRegisterPromise(); + return builder_->CreateCall(func, {runtime_, coroutine}, REG_NAME("promise")); + } + + void CreateAwaitPromise(llvm::Value* promise, llvm::Value* awaiting) { + auto* func = types_->CreateRuntimeAwaitPromise(); + builder_->CreateCall(func, {runtime_, promise, awaiting}); + } + + void CreateResume(llvm::Value* promise) { + auto* func = types_->CreateRuntimeResume(); + builder_->CreateCall(func, {runtime_, promise}); + } + + void CreateEmitPromiseResolved(llvm::Value* promise, llvm::Value* result) { + auto* func = types_->CreateRuntimeEmitPromiseResolved(); + builder_->CreateCall(func, {runtime_, promise, result}); + } + // value + llvm::Value* CreateHasValue(llvm::Value* value) { + auto* kind = CreateLoadValueKindFromValue(value); + return builder_->CreateICmpNE( + kind, builder_->getInt8(kValueKindNone), REG_NAME("value.has_value")); + } + // 7.2.13 IsLooselyEqual ( x, y ) llvm::Value* CreateIsLooselyEqual(llvm::Value* x, llvm::Value* y) { // TODO: Create inline instructions if runtime_is_loosely_equal() is slow. auto* func = types_->CreateRuntimeIsLooselyEqual(); - return builder_->CreateCall(func, {exec_context_, x, y}, REG_NAME("is_loosely_equal.retval")); + return builder_->CreateCall(func, {runtime_, x, y}, REG_NAME("is_loosely_equal.retval")); } // 7.2.14 IsStrictlyEqual ( x, y ) llvm::Value* CreateIsStrictlyEqual(llvm::Value* x, llvm::Value* y) { // TODO: Create inline instructions if runtime_is_strictly_equal() is slow. auto* func = types_->CreateRuntimeIsStrictlyEqual(); - return builder_->CreateCall(func, {exec_context_, x, y}, REG_NAME("is_strictly_equal.retval")); + return builder_->CreateCall(func, {runtime_, x, y}, REG_NAME("is_strictly_equal.retval")); } llvm::Value* CreateIsSameBooleanValue(llvm::Value* value_ptr, llvm::Value* boolean) { @@ -493,6 +529,11 @@ class Compiler { return builder_->CreateICmpEQ(value, closure, REG_NAME("is_same_closure_value")); } + llvm::Value* CreateIsSamePromiseValue(llvm::Value* value_ptr, llvm::Value* promise) { + auto* value = CreateLoadPromiseFromValue(value_ptr); + return builder_->CreateICmpEQ(value, promise, REG_NAME("is_same_promise_value")); + } + llvm::Value* CreateUndefinedToAny() { auto* value_ptr = CreateAlloc1(types_->CreateValueType(), REG_NAME("any.ptr")); CreateStoreUndefinedToValue(value_ptr); @@ -538,51 +579,61 @@ class Compiler { types_->CreateValueType(), REG_NAME("local" + llvm::Twine(index) + ".ptr")); } - inline void CreateStoreNoneToValue(llvm::Value* dest) { + void CreateStoreNoneToValue(llvm::Value* dest) { CreateStoreValueKindToValue(ValueKind::None, dest); // zeroinitializer can be used in optimization by filling the holder with zero. CreateStoreValueHolderToValue(builder_->getInt64(0), dest); } - inline void CreateStoreUndefinedToValue(llvm::Value* dest) { + void CreateStoreUndefinedToValue(llvm::Value* dest) { CreateStoreValueKindToValue(ValueKind::Undefined, dest); // zeroinitializer can be used in optimization by filling the holder with zero. CreateStoreValueHolderToValue(builder_->getInt64(0), dest); } - inline void CreateStoreNullToValue(llvm::Value* dest) { + void CreateStoreNullToValue(llvm::Value* dest) { CreateStoreValueKindToValue(ValueKind::Null, dest); // zeroinitializer can be used in optimization by filling the holder with zero. CreateStoreValueHolderToValue(builder_->getInt64(0), dest); } - inline void CreateStoreBooleanToValue(llvm::Value* value, llvm::Value* dest) { + void CreateStoreBooleanToValue(llvm::Value* value, llvm::Value* dest) { CreateStoreValueKindToValue(ValueKind::Boolean, dest); CreateStoreValueHolderToValue(value, dest); } - inline void CreateStoreNumberToValue(llvm::Value* value, llvm::Value* dest) { + void CreateStoreNumberToValue(llvm::Value* value, llvm::Value* dest) { CreateStoreValueKindToValue(ValueKind::Number, dest); CreateStoreValueHolderToValue(value, dest); } - inline void CreateStoreClosureToValue(llvm::Value* value, llvm::Value* dest) { + void CreateStoreClosureToValue(llvm::Value* value, llvm::Value* dest) { CreateStoreValueKindToValue(ValueKind::Closure, dest); CreateStoreValueHolderToValue(value, dest); } - inline void CreateStoreValueToValue(llvm::Value* value_ptr, llvm::Value* dest) { + void CreateStorePromiseToValue(llvm::Value* value, llvm::Value* dest) { + CreateStoreValueKindToValue(ValueKind::Promise, dest); + CreateStoreValueHolderToValue(value, dest); + } + + void CreateStoreValueToValue(llvm::Value* value_ptr, llvm::Value* dest) { auto* kind = CreateLoadValueKindFromValue(value_ptr); CreateStoreValueKindToValue(kind, dest); auto* holder = CreateLoadValueHolderFromValue(value_ptr); CreateStoreValueHolderToValue(holder, dest); } - inline llvm::Value* CreateLoadClosureFromValue(llvm::Value* value_ptr) { + llvm::Value* CreateLoadClosureFromValue(llvm::Value* value_ptr) { auto* ptr = CreateGetValueHolderPtrOfValue(value_ptr); return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("value.closure")); } + llvm::Value* CreateLoadPromiseFromValue(llvm::Value* value_ptr) { + auto* ptr = CreateGetValueHolderPtrOfValue(value_ptr); + return builder_->CreateLoad(builder_->getInt32Ty(), ptr, REG_NAME("value.promise")); + } + // argv llvm::Value* CreateArgv(uint16_t argc) { @@ -595,7 +646,7 @@ class Compiler { types_->CreateValueType(), argv, index, REG_NAME("argv." + llvm::Twine(index) + ".ptr")); } - inline llvm::Value* CreateGetArgumentValuePtr(uint16_t index) { + llvm::Value* CreateGetArgumentValuePtr(uint16_t index) { return builder_->CreateConstInBoundsGEP1_32( types_->CreateValueType(), argv_, index, REG_NAME("argv." + llvm::Twine(index) + ".ptr")); } @@ -606,27 +657,31 @@ class Compiler { return CreateAlloc1(types_->CreateValueType(), REG_NAME("retv.ptr")); } - inline void CreateStoreUndefinedToRetv() { + void CreateStoreUndefinedToRetv() { CreateStoreUndefinedToValue(retv_); } - inline void CreateStoreNullToRetv() { + void CreateStoreNullToRetv() { CreateStoreNullToValue(retv_); } - inline void CreateStoreBooleanToRetv(llvm::Value* value) { + void CreateStoreBooleanToRetv(llvm::Value* value) { CreateStoreBooleanToValue(value, retv_); } - inline void CreateStoreNumberToRetv(llvm::Value* value) { + void CreateStoreNumberToRetv(llvm::Value* value) { CreateStoreNumberToValue(value, retv_); } - inline void CreateStoreClosureToRetv(llvm::Value* value) { + void CreateStoreClosureToRetv(llvm::Value* value) { CreateStoreClosureToValue(value, retv_); } - inline void CreateStoreValueToRetv(llvm::Value* value) { + void CreateStorePromiseToRetv(llvm::Value* value) { + CreateStorePromiseToValue(value, retv_); + } + + void CreateStoreValueToRetv(llvm::Value* value) { CreateStoreValueToValue(value, retv_); } @@ -718,7 +773,7 @@ class Compiler { llvm::Value* CreateCapture(llvm::Value* value_ptr) { auto* func = types_->CreateRuntimeCreateCapture(); - return builder_->CreateCall(func, {exec_context_, value_ptr}, REG_NAME("capture.ptr")); + return builder_->CreateCall(func, {runtime_, value_ptr}, REG_NAME("capture.ptr")); } void CreateEscapeValue(llvm::Value* capture, llvm::Value* value) { @@ -728,51 +783,176 @@ class Compiler { builder_->CreateMemCpy(escaped_ptr, align, value, align, types_->GetWord(sizeof(Value))); } - inline llvm::Value* CreateGetCaptureValuePtr(uint16_t index) { - auto* ptr = CreateLoadCapturePtrFromCaptures(caps_, index); + llvm::Value* CreateGetCaptureValuePtr(uint16_t index) { + auto* ptr = CreateLoadCapturePtrFromCaptures(captures_, index); return CreateLoadTargetFromCapture(ptr); } llvm::Value* CreateLoadCapture(uintptr_t index) { - return CreateLoadCapturePtrFromCaptures(caps_, index); + return CreateLoadCapturePtrFromCaptures(captures_, index); + } + + // coroutine + + llvm::Value* CreateCoroutine(llvm::Value* closure, + uint16_t num_locals, + uint16_t scratch_buffer_len) { + auto* func = types_->CreateRuntimeCreateCoroutine(); + return builder_->CreateCall(func, + {runtime_, closure, builder_->getInt16(num_locals), + builder_->getInt16(scratch_buffer_len)}, + REG_NAME("coroutine")); + } + + llvm::SwitchInst* CreateSwitchForCoroutine(llvm::BasicBlock* block, uint32_t num_states) { + auto* state = CreateLoadStateFromCoroutine(); + return builder_->CreateSwitch(state, block, num_states); + } + + void CreateAddStateForCoroutine(llvm::SwitchInst* inst, + uint32_t state, + llvm::BasicBlock* block) { + inst->addCase(builder_->getInt32(state), block); + } + + void CreateSuspend() { + builder_->CreateRet(builder_->getInt32(STATUS_SUSPEND)); + } + + void CreateSetCoroutineState(uint32_t state) { + auto* ptr = CreateGetStatePtrOfCoroutine(); + builder_->CreateStore(builder_->getInt32(state), ptr); + } + + void CreateSetCapturesForCoroutine() { + captures_ = CreateGetCapturesPtrOfCoroutine(); + } + + llvm::Value* CreateGetLocalPtrFromCoroutine(uint16_t index) { + auto* ptr = CreateGetLocalsPtrOfCoroutine(); + return builder_->CreateConstInBoundsGEP1_32(types_->CreateValueType(), ptr, index, + REG_NAME("co.locals." + llvm::Twine(index) + ".ptr")); + } + + void CreateWriteBooleanToScratchBuffer(uint32_t offset, llvm::Value* value) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.boolean.ptr")); + builder_->CreateStore(value, ptr); + } + + llvm::Value* CreateReadBooleanFromScratchBuffer(uint32_t offset) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.boolean.ptr")); + return builder_->CreateLoad(builder_->getInt1Ty(), ptr, REG_NAME("scratch.boolean")); + } + + void CreateWriteNumberToScratchBuffer(uint32_t offset, llvm::Value* value) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.number.ptr")); + builder_->CreateStore(value, ptr); + } + + llvm::Value* CreateReadNumberFromScratchBuffer(uint32_t offset) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.number.ptr")); + return builder_->CreateLoad(builder_->getDoubleTy(), ptr, REG_NAME("scratch.number")); + } + + void CreateWriteClosureToScratchBuffer(uint32_t offset, llvm::Value* value) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.closure.ptr")); + builder_->CreateStore(value, ptr); + } + + llvm::Value* CreateReadClosureFromScratchBuffer(uint32_t offset) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.closure.ptr")); + return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("scratch.closure")); + } + + void CreateWritePromiseToScratchBuffer(uint32_t offset, llvm::Value* value) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.promise.ptr")); + builder_->CreateStore(value, ptr); + } + + llvm::Value* CreateReadPromiseFromScratchBuffer(uint32_t offset) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.promise.ptr")); + return builder_->CreateLoad(builder_->getInt32Ty(), ptr, REG_NAME("scratch.promise")); + } + + void CreateWriteValueToScratchBuffer(uint32_t offset, llvm::Value* value) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + auto* ptr = builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.value.ptr")); + CreateStoreValueToValue(value, ptr); + } + + llvm::Value* CreateReadValueFromScratchBuffer(uint32_t offset) { + auto* scratch_ptr = CreateGetScratchBufferPtrOfCoroutine(); + return builder_->CreateInBoundsPtrAdd( + scratch_ptr, builder_->getInt32(offset), REG_NAME("scratch.value.ptr")); } // scope cleanup checker - void SetupScopeCleanupChecker(uint32_t stack_size) { - scope_cleanup_stack_type_ = llvm::ArrayType::get(builder_->getInt16Ty(), stack_size); - scope_cleanup_stack_ = - CreateAllocN(builder_->getInt16Ty(), stack_size, REG_NAME("scope_cleanup_stack")); - scope_cleanup_stack_top_ = - CreateAlloc1(builder_->getInt32Ty(), REG_NAME("scope_cleanup_stack_top")); - builder_->CreateStore(builder_->getInt32(0), scope_cleanup_stack_top_); - scope_cleanup_stack_size_ = stack_size; - } - - void PerformScopeCleanupPrecheck(uint16_t scope_id) { - if (IsScopeCleanupCheckerEnabled()) { - // We assumed here that the control flow does not enter into a scope which is already - // entered. However, it may be better to check that explicitly here before pushing the scope - // ID. - CreateAssertScopeCleanupStackBounds(); - CreatePushOntoScopeCleanupStack(scope_id); + void EnableScopeCleanupChecker(bool is_coroutine) { + if (is_coroutine) { + scope_id_ = CreateGetScopeIdPtrOfCoroutine(); + } else { + scope_id_ = CreateAlloc1(builder_->getInt16Ty(), REG_NAME("scope_id.ptr")); + builder_->CreateStore(builder_->getInt16(0), scope_id_); } } - void PerformScopeCleanupPostcheck(uint16_t scope_id) { - if (IsScopeCleanupCheckerEnabled()) { - CreateAssertScopeCleanupStackHasItem(); - auto* popped = CreatePopFromScopeCleanupStack(); - CreateAssertScopeCleanupStackPoppedValue(popped, scope_id); - } + void SetScopeIdForChecker(uint16_t scope_id) { + assert(IsScopeCleanupCheckerEnabled()); + builder_->CreateStore(builder_->getInt16(scope_id), scope_id_); + } + + // assert(scope_id == expected) + void AssertScopeId(uint16_t expected) { + assert(IsScopeCleanupCheckerEnabled()); + auto* scope_id = builder_->CreateLoad(builder_->getInt16Ty(), scope_id_, REG_NAME("scope_id")); + auto* assertion = builder_->CreateICmpEQ( + scope_id, builder_->getInt16(expected), REG_NAME("assertion.scope_id")); + std::stringstream ss; + ss << "scope_id == " << expected; + CreateAssert(assertion, ss.str().c_str()); + } + + // print + + void CreatePrintValue(llvm::Value* value, const char* msg = "") { + auto* msg_value = builder_->CreateGlobalString(msg, REG_NAME("runtime.print_value.msg")); + auto* func = types_->CreateRuntimePrintValue(); + builder_->CreateCall(func, {runtime_, value, msg_value}); + } + + // unreachable + + void CreateUnreachable(const char* msg = "") { + CreateAssert(builder_->getFalse(), msg); + builder_->CreateUnreachable(); } private: + static constexpr uint8_t kValueKindNone = static_cast(ValueKind::None); static constexpr uint8_t kValueKindUndefined = static_cast(ValueKind::Undefined); static constexpr uint8_t kValueKindNull = static_cast(ValueKind::Null); static constexpr uint8_t kValueKindBoolean = static_cast(ValueKind::Boolean); static constexpr uint8_t kValueKindNumber = static_cast(ValueKind::Number); static constexpr uint8_t kValueKindClosure = static_cast(ValueKind::Closure); + static constexpr uint8_t kValueKindPromise = static_cast(ValueKind::Promise); void CreateStore(llvm::Value* value, llvm::Value* dest) { builder_->CreateStore(value, dest); @@ -807,7 +987,7 @@ class Compiler { // We assumed that `number` holds a number value. // TODO: Create inline instructions if runtime_to_int32() is slow. auto* func = types_->CreateRuntimeToInt32(); - return builder_->CreateCall(func, {exec_context_, number}, REG_NAME("int32")); + return builder_->CreateCall(func, {runtime_, number}, REG_NAME("int32")); } // 7.1.7 ToUint32 ( argument ) @@ -816,7 +996,7 @@ class Compiler { // We assumed that `number` holds a number value. // TODO: Create inline instructions if runtime_to_uint32() is slow. auto* func = types_->CreateRuntimeToUint32(); - return builder_->CreateCall(func, {exec_context_, number}, REG_NAME("uint32")); + return builder_->CreateCall(func, {runtime_, number}, REG_NAME("uint32")); } llvm::AllocaInst* CreateAlloc1(llvm::Type* ty, const llvm::Twine& name) { @@ -835,21 +1015,22 @@ class Compiler { return alloca; } - llvm::Function* CreateLambda(const char* name) { - const auto& found = functions_.find(name); + llvm::Function* CreateLambda(uint32_t func_id) { + const auto& found = functions_.find(func_id); if (found != functions_.end()) { return found->second; } auto* prototype = types_->CreateLambdaType(); + auto name = llvm::Twine("fn") + llvm::Twine(func_id); auto* lambda = llvm::Function::Create(prototype, llvm::Function::ExternalLinkage, name, *module_); - lambda->getArg(0)->setName(REG_NAME("ctx")); - lambda->getArg(1)->setName(REG_NAME("caps")); + lambda->getArg(0)->setName(REG_NAME("runtime")); + lambda->getArg(1)->setName(REG_NAME("context")); lambda->getArg(2)->setName(REG_NAME("argc")); lambda->getArg(3)->setName(REG_NAME("argv")); lambda->getArg(4)->setName(REG_NAME("retv")); - functions_[name] = lambda; + functions_[func_id] = lambda; return lambda; } @@ -869,105 +1050,100 @@ class Compiler { // value - inline llvm::Value* CreateGetValueKindPtrOfValue(llvm::Value* value_ptr) { + llvm::Value* CreateGetValueKindPtrOfValue(llvm::Value* value_ptr) { return builder_->CreateStructGEP( types_->CreateValueType(), value_ptr, 0, REG_NAME("value.kind.ptr")); } - inline llvm::Value* CreateGetValueHolderPtrOfValue(llvm::Value* value_ptr) { + llvm::Value* CreateGetValueHolderPtrOfValue(llvm::Value* value_ptr) { return builder_->CreateStructGEP( types_->CreateValueType(), value_ptr, 1, REG_NAME("value.holder.ptr")); } - inline llvm::Value* CreateLoadValueKindFromValue(llvm::Value* value_ptr) { + llvm::Value* CreateLoadValueKindFromValue(llvm::Value* value_ptr) { auto* ptr = CreateGetValueKindPtrOfValue(value_ptr); return builder_->CreateLoad(builder_->getInt8Ty(), ptr, REG_NAME("value.kind")); } - inline llvm::Value* CreateLoadValueHolderFromValue(llvm::Value* value_ptr) { + llvm::Value* CreateLoadValueHolderFromValue(llvm::Value* value_ptr) { auto* ptr = CreateGetValueHolderPtrOfValue(value_ptr); return builder_->CreateLoad(builder_->getInt64Ty(), ptr, REG_NAME("value.holder")); } - inline llvm::Value* CreateLoadBooleanFromValue(llvm::Value* value_ptr) { + llvm::Value* CreateLoadBooleanFromValue(llvm::Value* value_ptr) { auto* ptr = CreateGetValueHolderPtrOfValue(value_ptr); return builder_->CreateLoad(builder_->getInt1Ty(), ptr, REG_NAME("value.boolean")); } - inline llvm::Value* CreateLoadNumberFromValue(llvm::Value* value_ptr) { + llvm::Value* CreateLoadNumberFromValue(llvm::Value* value_ptr) { auto* ptr = CreateGetValueHolderPtrOfValue(value_ptr); return builder_->CreateLoad(builder_->getDoubleTy(), ptr, REG_NAME("value.number")); } - inline void CreateStoreValueKindToValue(ValueKind value, llvm::Value* dest) { + void CreateStoreValueKindToValue(ValueKind value, llvm::Value* dest) { CreateStoreValueKindToValue(builder_->getInt8(static_cast(value)), dest); } - inline void CreateStoreValueKindToValue(llvm::Value* value, llvm::Value* dest) { + void CreateStoreValueKindToValue(llvm::Value* value, llvm::Value* dest) { auto* ptr = CreateGetValueKindPtrOfValue(dest); builder_->CreateStore(value, ptr); } - inline void CreateStoreValueHolderToValue(llvm::Value* holder, llvm::Value* dest) { + void CreateStoreValueHolderToValue(llvm::Value* holder, llvm::Value* dest) { auto* ptr = CreateGetValueHolderPtrOfValue(dest); builder_->CreateStore(holder, ptr); } // closure - inline llvm::Value* CreateGetLambdaPtrOfClosure(llvm::Value* closure_ptr) { + llvm::Value* CreateGetLambdaPtrOfClosure(llvm::Value* closure_ptr) { return builder_->CreateStructGEP( types_->CreateClosureType(), closure_ptr, 0, REG_NAME("closure.lambda.ptr")); } - inline llvm::Value* CreateGetNumCapturesPtrOfClosure(llvm::Value* closure_ptr) { + llvm::Value* CreateGetNumCapturesPtrOfClosure(llvm::Value* closure_ptr) { return builder_->CreateStructGEP( types_->CreateClosureType(), closure_ptr, 1, REG_NAME("closure.num_captures.ptr")); } - inline llvm::Value* CreateGetCapturesPtrOfClosure(llvm::Value* closure_ptr) { + llvm::Value* CreateGetCapturesPtrOfClosure(llvm::Value* closure_ptr) { return builder_->CreateStructGEP( types_->CreateClosureType(), closure_ptr, 2, REG_NAME("closure.captures.ptr")); } - inline llvm::Value* CreateLoadLambdaFromClosure(llvm::Value* closure_ptr) { + llvm::Value* CreateLoadLambdaFromClosure(llvm::Value* closure_ptr) { auto* ptr = CreateGetLambdaPtrOfClosure(closure_ptr); return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("closure.lambda")); } - inline llvm::Value* CreateLoadNumCapturesFromClosure(llvm::Value* closure_ptr) { + llvm::Value* CreateLoadNumCapturesFromClosure(llvm::Value* closure_ptr) { auto* ptr = CreateGetNumCapturesPtrOfClosure(closure_ptr); return builder_->CreateLoad(builder_->getInt16Ty(), ptr, REG_NAME("closure.num_captures")); } - inline llvm::Value* CreateLoadCapturesFromClosure(llvm::Value* closure_ptr) { - auto* ptr = CreateGetCapturesPtrOfClosure(closure_ptr); - return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("closure.captures")); - } - // capture - inline llvm::Value* CreateGetTargetPtrOfCapture(llvm::Value* capture_ptr) { + llvm::Value* CreateGetTargetPtrOfCapture(llvm::Value* capture_ptr) { return builder_->CreateStructGEP( types_->CreateCaptureType(), capture_ptr, 0, REG_NAME("capture.target.ptr")); } - inline llvm::Value* CreateGetEscapedPtrOfCapture(llvm::Value* capture_ptr) { + llvm::Value* CreateGetEscapedPtrOfCapture(llvm::Value* capture_ptr) { return builder_->CreateStructGEP( types_->CreateCaptureType(), capture_ptr, 1, REG_NAME("capture.escaped.ptr")); } - inline llvm::Value* CreateLoadTargetFromCapture(llvm::Value* capture_ptr) { + llvm::Value* CreateLoadTargetFromCapture(llvm::Value* capture_ptr) { auto* ptr = CreateGetTargetPtrOfCapture(capture_ptr); return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("capture.target")); } - inline void CreateStoreTargetToCapture(llvm::Value* value_ptr, llvm::Value* capture_ptr) { + void CreateStoreTargetToCapture(llvm::Value* value_ptr, llvm::Value* capture_ptr) { auto* ptr = CreateGetTargetPtrOfCapture(capture_ptr); builder_->CreateStore(value_ptr, ptr); } - inline void CreateStoreEscapedToCapture(llvm::Value* value_ptr, llvm::Value* capture_ptr) { + void CreateStoreEscapedToCapture(llvm::Value* value_ptr, llvm::Value* capture_ptr) { auto* ptr = CreateGetEscapedPtrOfCapture(capture_ptr); auto align = llvm::Align(sizeof(double)); builder_->CreateMemCpy(ptr, align, value_ptr, align, types_->GetWord(sizeof(Value))); @@ -975,101 +1151,96 @@ class Compiler { // captures - inline llvm::Value* CreateGetCapturePtrPtrOfCaptures(llvm::Value* captures, uint16_t index) { - return builder_->CreateConstInBoundsGEP1_32( - builder_->getPtrTy(), captures, index, REG_NAME("caps." + llvm::Twine(index) + ".ptr")); + llvm::Value* CreateGetCapturePtrPtrOfCaptures(llvm::Value* captures, uint16_t index) { + return builder_->CreateConstInBoundsGEP1_32(builder_->getPtrTy(), captures, index, + REG_NAME("captures." + llvm::Twine(index) + ".ptr")); } - inline llvm::Value* CreateLoadCapturePtrFromCaptures(llvm::Value* captures, uint16_t index) { + llvm::Value* CreateLoadCapturePtrFromCaptures(llvm::Value* captures, uint16_t index) { auto* ptr = CreateGetCapturePtrPtrOfCaptures(captures, index); - return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("caps." + llvm::Twine(index))); + return builder_->CreateLoad( + builder_->getPtrTy(), ptr, REG_NAME("captures." + llvm::Twine(index))); } - inline void CreateStoreCapturePtrToCaptures(llvm::Value* capture_ptr, + void CreateStoreCapturePtrToCaptures(llvm::Value* capture_ptr, llvm::Value* captures, uint16_t index) { auto* ptr = CreateGetCapturePtrPtrOfCaptures(captures, index); builder_->CreateStore(capture_ptr, ptr); } - // scope cleanup cheker - - void CreatePushOntoScopeCleanupStack(uint16_t scope_id) { - auto* top = CreateLoadScopeCleanupStackTop(); - // scope_cleanup_stack_[scope_cleanup_stack_top_] = scope_id; - auto* ptr = builder_->CreateInBoundsGEP(scope_cleanup_stack_type_, scope_cleanup_stack_, - {builder_->getInt32(0), top}, REG_NAME("scope_cleanup_stack.pushed.ptr")); - builder_->CreateStore(builder_->getInt16(scope_id), ptr); - // scope_cleanup_stack_top_++; - auto* incr = - builder_->CreateAdd(top, builder_->getInt32(1), REG_NAME("scope_cleanup_stack.top.incr")); - CreateStoreScopeCleanupStackTop(incr); - } - - llvm::Value* CreatePopFromScopeCleanupStack() { - auto* top = CreateLoadScopeCleanupStackTop(); - // scope_cleanup_stack_top_--; - auto* decr = - builder_->CreateSub(top, builder_->getInt32(1), REG_NAME("scope_cleanup_stack.top.decr")); - CreateStoreScopeCleanupStackTop(decr); - // return scope_cleanup_stack_[scope_cleanup_stack_top_]; - auto* ptr = builder_->CreateInBoundsGEP(scope_cleanup_stack_type_, scope_cleanup_stack_, - {builder_->getInt32(0), decr}, REG_NAME("scope_cleanup_stack.popped.ptr")); - return builder_->CreateLoad( - builder_->getInt16Ty(), ptr, REG_NAME("scope_cleanup_stack.popped")); + // coroutine + + llvm::Value* CreateGetClosurePtrOfCoroutine() { + return builder_->CreateStructGEP( + types_->CreateCoroutineType(), context_, 0, REG_NAME("co.closure.ptr")); } - // assert(scope_cleanup_stack_top_ <= scope_cleanup_stack_size_); - void CreateAssertScopeCleanupStackBounds() { - auto* top = CreateLoadScopeCleanupStackTop(); - auto* assertion = builder_->CreateICmpULE(top, builder_->getInt32(scope_cleanup_stack_size_), - REG_NAME("assertion.scope_cleanup_stack.size")); - CreateAssert(assertion, "scope_cleanup_stack_top_ <= scoke_cleanup_stack_size_"); + llvm::Value* CreateGetStatePtrOfCoroutine() { + return builder_->CreateStructGEP( + types_->CreateCoroutineType(), context_, 1, REG_NAME("co.state.ptr")); } - // assert(popped == scope_id); - void CreateAssertScopeCleanupStackPoppedValue(llvm::Value* actual, uint16_t expected) { - auto* assertion = builder_->CreateICmpEQ( - actual, builder_->getInt16(expected), REG_NAME("assertion.scope_cleanup_stack.popped")); - std::stringstream ss; - ss << "popped == " << expected; - CreateAssert(assertion, ss.str().c_str()); + llvm::Value* CreateGetNumLocalsPtrOfCoroutine() { + return builder_->CreateStructGEP( + types_->CreateCoroutineType(), context_, 2, REG_NAME("co.num_locals.ptr")); } - // assert(scope_cleanup_stack_top_ == 0); - void CreateAssertScopeCleanupStackIsEmpty() { - auto* top = CreateLoadScopeCleanupStackTop(); - auto* assertion = builder_->CreateICmpEQ( - top, builder_->getInt32(0), REG_NAME("assertion.scope_cleanup_stack.is_empty")); - CreateAssert(assertion, "scope_cleanup_stack_top_ == 0"); + llvm::Value* CreateGetScopeIdPtrOfCoroutine() { + return builder_->CreateStructGEP( + types_->CreateCoroutineType(), context_, 3, REG_NAME("co.scope_id.ptr")); } - // assert(scope_cleanup_stack_top_ != 0); - void CreateAssertScopeCleanupStackHasItem() { - auto* top = CreateLoadScopeCleanupStackTop(); - auto* assertion = builder_->CreateICmpNE( - top, builder_->getInt32(0), REG_NAME("assertion.scope_cleanup_stack.has_item")); - CreateAssert(assertion, "scope_cleanup_stack_top_ != 0"); + llvm::Value* CreateGetScrachBufferLenPtrOfCoroutine() { + return builder_->CreateStructGEP( + types_->CreateCoroutineType(), context_, 4, REG_NAME("co.locals.scratch_buffer_len.ptr")); } - bool IsScopeCleanupCheckerEnabled() const { - return scope_cleanup_stack_ != nullptr; + llvm::Value* CreateGetLocalsPtrOfCoroutine() { + return builder_->CreateStructGEP( + types_->CreateCoroutineType(), context_, 5, REG_NAME("co.locals.ptr")); } - llvm::Value* CreateLoadScopeCleanupStackTop() { - return builder_->CreateLoad( - builder_->getInt32Ty(), scope_cleanup_stack_top_, REG_NAME("scope_cleanup_stack.top")); + llvm::Value* CreateLoadClosureFromCoroutine() { + auto* ptr = CreateGetClosurePtrOfCoroutine(); + return builder_->CreateLoad(builder_->getPtrTy(), ptr, REG_NAME("co.closure")); + } + + llvm::Value* CreateLoadStateFromCoroutine() { + auto* ptr = CreateGetStatePtrOfCoroutine(); + return builder_->CreateLoad(builder_->getInt32Ty(), ptr, REG_NAME("co.state")); + } + + llvm::Value* CreateLoadNumLocalsFromCoroutine() { + auto* ptr = CreateGetNumLocalsPtrOfCoroutine(); + return builder_->CreateLoad(builder_->getInt16Ty(), ptr, REG_NAME("co.num_locals")); + } + + llvm::Value* CreateGetCapturesPtrOfCoroutine() { + auto* closure = CreateLoadClosureFromCoroutine(); + return CreateGetCapturesPtrOfClosure(closure); } - void CreateStoreScopeCleanupStackTop(llvm::Value* value) { - builder_->CreateStore(value, scope_cleanup_stack_top_); + llvm::Value* CreateGetScratchBufferPtrOfCoroutine() { + auto* num_locals = CreateLoadNumLocalsFromCoroutine(); + auto* num_locals_usize = + builder_->CreateSExt(num_locals, types_->GetWordType(), REG_NAME("co.num_locals.usize")); + auto* sizeof_locals = builder_->CreateMul( + types_->GetWord(sizeof(Value)), num_locals_usize, REG_NAME("co.locals.sizeof")); + auto* offsetof_locals = types_->GetWord(offsetof(Coroutine, locals)); + auto* offset = builder_->CreateAdd( + offsetof_locals, sizeof_locals, REG_NAME("co.scratch_buffer.offsetof")); + return builder_->CreateInBoundsPtrAdd(context_, offset, REG_NAME("co.scratch_buffer.ptr")); } - void ClearScopeCleanupStack() { - scope_cleanup_stack_type_ = nullptr; - scope_cleanup_stack_ = nullptr; - scope_cleanup_stack_top_ = nullptr; - scope_cleanup_stack_size_ = 0; + // scope cleanup checker + + bool IsScopeCleanupCheckerEnabled() const { + return scope_id_ != nullptr; + } + + void ResetScopeCleanupChecker() { + scope_id_ = nullptr; } // helpers @@ -1077,18 +1248,24 @@ class Compiler { void CreateAssert(llvm::Value* assertion, const char* msg = "") { auto* msg_value = builder_->CreateGlobalString(msg, REG_NAME("runtime.assert.msg")); auto* func = types_->CreateRuntimeAssert(); - builder_->CreateCall(func, {exec_context_, assertion, msg_value}); + builder_->CreateCall(func, {runtime_, assertion, msg_value}); } void CreatePrintU32(llvm::Value* value, const char* msg = "") { auto* msg_value = builder_->CreateGlobalString(msg, REG_NAME("runtime.print_u32.msg")); auto* func = types_->CreateRuntimePrintU32(); - builder_->CreateCall(func, {exec_context_, value, msg_value}); + builder_->CreateCall(func, {runtime_, value, msg_value}); + } + + void CreatePrintF64(llvm::Value* value, const char* msg = "") { + auto* msg_value = builder_->CreateGlobalString(msg, REG_NAME("runtime.print_f64.msg")); + auto* func = types_->CreateRuntimePrintF64(); + builder_->CreateCall(func, {runtime_, value, msg_value}); } // TODO: separate values that must be reset in EndFunction() from others. - std::unique_ptr context_ = nullptr; + std::unique_ptr llvmctx_ = nullptr; std::unique_ptr module_ = nullptr; std::unique_ptr> builder_ = nullptr; std::unique_ptr types_ = nullptr; @@ -1096,23 +1273,21 @@ class Compiler { // The following values are reset for each function. llvm::Function* function_ = nullptr; llvm::BasicBlock* locals_block_ = nullptr; - llvm::Value* exec_context_ = nullptr; - llvm::Value* caps_ = nullptr; + llvm::Value* runtime_ = nullptr; + llvm::Value* context_ = nullptr; llvm::Value* argc_ = nullptr; llvm::Value* argv_ = nullptr; llvm::Value* retv_ = nullptr; + llvm::Value* captures_ = nullptr; // Holds one of STATUS_XXX values, not Status::*. llvm::Value* status_ = nullptr; llvm::Value* flow_selector_ = nullptr; // scope cleanup checker - llvm::Type* scope_cleanup_stack_type_ = nullptr; - llvm::Value* scope_cleanup_stack_ = nullptr; - llvm::Value* scope_cleanup_stack_top_ = nullptr; - uint16_t scope_cleanup_stack_size_ = 0; + llvm::Value* scope_id_ = nullptr; // A cache of functions does not reset in the end of compilation for each function. - std::unordered_map functions_; + std::unordered_map functions_; // for optimization std::unique_ptr fpm_; diff --git a/libs/jsruntime/src/llvmir/compiler/control_flow.rs b/libs/jsruntime/src/llvmir/compiler/control_flow.rs index c5c17c71f..5383c1a48 100644 --- a/libs/jsruntime/src/llvmir/compiler/control_flow.rs +++ b/libs/jsruntime/src/llvmir/compiler/control_flow.rs @@ -5,6 +5,8 @@ use jsparser::Symbol; use super::BasicBlock; use super::Dump; +use super::ScopeRef; +use super::SwitchIr; macro_rules! bb2cstr { ($bb:expr, $buf:expr, $len:expr) => { @@ -49,19 +51,26 @@ impl ControlFlowStack { self.stack.is_empty() && self.exit_stack.is_empty() } + pub fn has_scope_flow(&self) -> bool { + self.scope_index != 0 + } + pub fn push_function_flow( &mut self, locals_block: BasicBlock, + init_block: BasicBlock, args_block: BasicBlock, body_block: BasicBlock, return_block: BasicBlock, ) { debug_assert_ne!(locals_block, BasicBlock::NONE); + debug_assert_ne!(init_block, BasicBlock::NONE); debug_assert_ne!(args_block, BasicBlock::NONE); debug_assert_ne!(body_block, BasicBlock::NONE); debug_assert_ne!(return_block, BasicBlock::NONE); self.stack.push(ControlFlow::Function(FunctionFlow { locals_block, + init_block, args_block, body_block, return_block, @@ -88,8 +97,52 @@ impl ControlFlowStack { } } + pub fn push_coroutine_flow( + &mut self, + switch_inst: SwitchIr, + dormant_block: BasicBlock, + num_states: u32, + ) { + self.stack.push(ControlFlow::Coroutine(CoroutineFlow { + switch_inst, + dormant_block, + num_states, + next_state: 1, + })); + } + + pub fn pop_coroutine_flow(&mut self) -> CoroutineFlow { + match self.stack.pop() { + Some(ControlFlow::Coroutine(flow)) => { + debug_assert_eq!(flow.next_state, flow.num_states - 1); + flow + } + _ => unreachable!(), + } + } + + pub fn coroutine_switch_inst(&self) -> SwitchIr { + debug_assert!(self.stack.len() >= 2); + match self.stack[1] { + ControlFlow::Coroutine(ref flow) => flow.switch_inst, + _ => unreachable!(), + } + } + + pub fn coroutine_next_state(&mut self) -> u32 { + debug_assert!(self.stack.len() >= 2); + let flow = match self.stack[1] { + ControlFlow::Coroutine(ref mut flow) => flow, + _ => unreachable!(), + }; + let next_state = flow.next_state; + flow.next_state += 1; + next_state + } + pub fn push_scope_flow( &mut self, + scope_ref: ScopeRef, init_block: BasicBlock, hoisted_block: BasicBlock, body_block: BasicBlock, @@ -102,6 +155,7 @@ impl ControlFlowStack { let outer_index = self.scope_index; self.scope_index = self.stack.len(); self.stack.push(ControlFlow::Scope(ScopeFlow { + scope_ref, init_block, hoisted_block, body_block, @@ -130,18 +184,29 @@ impl ControlFlowStack { .unwrap() } - pub fn push_branch_flow(&mut self, before_block: BasicBlock, after_block: BasicBlock) { - debug_assert_ne!(before_block, BasicBlock::NONE); - debug_assert_ne!(after_block, BasicBlock::NONE); - self.stack.push(ControlFlow::Branch(BranchFlow { - before_block, - after_block, + pub fn push_if_then_else_flow(&mut self, then_block: BasicBlock, else_block: BasicBlock) { + debug_assert_ne!(then_block, BasicBlock::NONE); + debug_assert_ne!(else_block, BasicBlock::NONE); + self.stack.push(ControlFlow::IfThenElse(IfThenElseFlow { + then_block, + else_block, })); } - pub fn pop_branch_flow(&mut self) -> BranchFlow { + pub fn pop_if_then_else_flow(&mut self) -> IfThenElseFlow { match self.stack.pop() { - Some(ControlFlow::Branch(flow)) => flow, + Some(ControlFlow::IfThenElse(flow)) => flow, + _ => unreachable!(), + } + } + + pub fn update_then_block(&mut self, then_block: BasicBlock) -> BasicBlock { + debug_assert_ne!(then_block, BasicBlock::NONE); + match self.stack.last_mut() { + Some(ControlFlow::IfThenElse(flow)) => { + flow.then_block = then_block; + flow.else_block + } _ => unreachable!(), } } @@ -260,18 +325,36 @@ impl ControlFlowStack { .unwrap() } - pub fn push_case_banch_flow(&mut self, before_block: BasicBlock, after_block: BasicBlock) { - debug_assert_ne!(before_block, BasicBlock::NONE); - debug_assert_ne!(after_block, BasicBlock::NONE); - self.stack.push(ControlFlow::CaseBranch(CaseBranchFlow { - before_block, - after_block, + pub fn push_case_flow(&mut self, next_case_block: BasicBlock, clause_start_block: BasicBlock) { + debug_assert_ne!(next_case_block, BasicBlock::NONE); + debug_assert_ne!(clause_start_block, BasicBlock::NONE); + self.stack.push(ControlFlow::Case(CaseFlow { + next_case_block, + clause_start_block, + clause_end_block: BasicBlock::NONE, + clause_has_statement: false, })); } - pub fn pop_case_branch_flow(&mut self) -> CaseBranchFlow { + pub fn pop_case_flow(&mut self) -> CaseFlow { match self.stack.pop() { - Some(ControlFlow::CaseBranch(flow)) => flow, + Some(ControlFlow::Case(flow)) => flow, + _ => unreachable!(), + } + } + + pub fn update_case_flow( + &mut self, + clause_end_block: BasicBlock, + clause_has_statement: bool, + ) -> BasicBlock { + debug_assert_ne!(clause_end_block, BasicBlock::NONE); + match self.stack.last_mut() { + Some(ControlFlow::Case(flow)) => { + flow.clause_end_block = clause_end_block; + flow.clause_has_statement = clause_has_statement; + flow.next_case_block + } _ => unreachable!(), } } @@ -425,6 +508,9 @@ impl ControlFlowStack { bb!(flow, body_block); bb!(flow, return_block); } + ControlFlow::Coroutine(flow) => { + eprintln!("coroutine: state={}/{}", flow.next_state, flow.num_states); + } ControlFlow::Scope(flow) => { eprint!("scope:"); eprintln!(); @@ -433,10 +519,10 @@ impl ControlFlowStack { bb!(flow, body_block); bb!(flow, cleanup_block); } - ControlFlow::Branch(flow) => { - eprintln!("branch:"); - bb!(flow, before_block); - bb!(flow, after_block); + ControlFlow::IfThenElse(flow) => { + eprintln!("then-else:"); + bb!(flow, then_block); + bb!(flow, else_block); } ControlFlow::LoopInit(flow) => { eprintln!("loop-init:"); @@ -466,10 +552,17 @@ impl ControlFlowStack { } bb!(flow, end_block); } - ControlFlow::CaseBranch(flow) => { - eprintln!("case-branch:"); - bb!(flow, before_block); - bb!(flow, after_block); + ControlFlow::Case(flow) => { + if flow.clause_has_statement { + eprintln!("case: has-statement"); + } else { + eprintln!("case:"); + } + bb!(flow, next_case_block); + bb!(flow, clause_start_block); + if flow.clause_end_block != BasicBlock::NONE { + bb!(flow, clause_end_block); + } } ControlFlow::Exception(flow) => { eprint!("exception:"); @@ -531,14 +624,15 @@ impl Dump for ControlFlowStack { enum ControlFlow { Function(FunctionFlow), + Coroutine(CoroutineFlow), Scope(ScopeFlow), - Branch(BranchFlow), + IfThenElse(IfThenElseFlow), LoopInit(LoopInitFlow), LoopTest(LoopTestFlow), LoopNext(LoopNextFlow), LoopBody(LoopBodyFlow), Switch(SwitchFlow), - CaseBranch(CaseBranchFlow), + Case(CaseFlow), Exception(ExceptionFlow), } @@ -547,14 +641,26 @@ pub struct FunctionFlow { #[allow(unused)] pub locals_block: BasicBlock, #[allow(unused)] + pub init_block: BasicBlock, + #[allow(unused)] pub args_block: BasicBlock, #[allow(unused)] pub body_block: BasicBlock, pub return_block: BasicBlock, } +pub struct CoroutineFlow { + switch_inst: SwitchIr, + pub dormant_block: BasicBlock, + num_states: u32, + next_state: u32, +} + /// Contains data used for building a region representing a lexical scope. pub struct ScopeFlow { + /// The reference to the scope in the scope tree. + pub scope_ref: ScopeRef, + /// The entry block of the scope flow. pub init_block: BasicBlock, @@ -572,9 +678,9 @@ pub struct ScopeFlow { outer_index: usize, } -pub struct BranchFlow { - pub before_block: BasicBlock, - pub after_block: BasicBlock, +pub struct IfThenElseFlow { + pub then_block: BasicBlock, + pub else_block: BasicBlock, } pub struct LoopInitFlow { @@ -604,16 +710,11 @@ pub struct SwitchFlow { outer_index: usize, } -pub struct CaseBranchFlow { - /// The last basic block in the statement lists of a case/default clause before the branch. - /// - /// This will be connected to `after_block` if it's not terminated and there is a subsequent - /// case/default clause in the current `SelectFlow`. If it's not terminated and there is no - /// subsequent case/default clause, it will be connected to `SelectFlow::end_block`. - pub before_block: BasicBlock, - - /// The first basic block in the statement lists of a case/default clause after the branch. - pub after_block: BasicBlock, +pub struct CaseFlow { + pub next_case_block: BasicBlock, + pub clause_start_block: BasicBlock, + pub clause_end_block: BasicBlock, + pub clause_has_statement: bool, } pub struct ExceptionFlow { diff --git a/libs/jsruntime/src/llvmir/compiler/mod.rs b/libs/jsruntime/src/llvmir/compiler/mod.rs index 859f81b4a..f3e20a581 100644 --- a/libs/jsruntime/src/llvmir/compiler/mod.rs +++ b/libs/jsruntime/src/llvmir/compiler/mod.rs @@ -22,34 +22,49 @@ use crate::Program; use crate::Runtime; use super::bridge; +use super::bridge::Value; +use super::bridge::ValueHolder; use super::Module; use control_flow::ControlFlowStack; -use peer::ArgvIr; use peer::BasicBlock; use peer::BooleanIr; use peer::CaptureIr; use peer::ClosureIr; +use peer::CoroutineIr; use peer::LambdaIr; use peer::NumberIr; +use peer::PromiseIr; use peer::StatusIr; +use peer::SwitchIr; use peer::ValueIr; +const VALUE_SIZE: u32 = size_of::() as u32; +const VALUE_HOLDER_SIZE: u32 = size_of::() as u32; + impl Runtime { pub fn compile(&mut self, program: &Program, optimize: bool) -> Result { logger::debug!(event = "compile"); // TODO: Deferring the compilation until it's actually called improves the performance. // Because the program may contain unused functions. - let mut compiler = Compiler::new(&self.function_registry, &program.scope_tree); + let mut compiler = Compiler::new(&mut self.function_registry, &program.scope_tree); + if self.pref.enable_scope_cleanup_checker { + compiler.enable_scope_cleanup_checker(); + } compiler.start_compile(self.pref.enable_llvmir_labels); compiler.set_data_layout(self.executor.get_data_layout()); compiler.set_target_triple(self.executor.get_target_triple()); - for func in program.functions.iter() { + // Compile native functions in reverse order in order to compile a coroutine function + // before its ramp function so that the size of the scratch buffer for the coroutine + // function is available when the ramp function is compiled. + // + // TODO: We should manage dependencies between functions in a more general way. + for func in program.functions.iter().rev() { compiler.start_function(func.symbol, func.id); for command in func.commands.iter() { compiler.process_command(command); } - compiler.end_function(optimize); + compiler.end_function(func.id, optimize); } Ok(compiler.end_compile()) } @@ -61,7 +76,7 @@ struct Compiler<'r, 's> { peer: peer::Compiler, /// The function registry of the JavaScript program to compile. - function_registry: &'r FunctionRegistry, + function_registry: &'r mut FunctionRegistry, /// The scope tree of the JavaScript program to compile. scope_tree: &'s ScopeTree, @@ -87,7 +102,11 @@ struct Compiler<'r, 's> { locals: Vec, captures: IndexMap, + max_scratch_buffer_len: u32, + dump_buffer: Option>, + + enable_scope_cleanup_checker: bool, } #[derive(Debug, thiserror::Error)] @@ -128,12 +147,15 @@ macro_rules! bb_name { macro_rules! dump_enabled { () => { - cfg!(debug_assertions) && std::env::var_os("BEE_DEBUG_JSRUNTIME_COMPILER_DUMP").is_some() + cfg!(debug_assertions) && matches!( + std::env::var_os("BEE_DEBUG_JSRUNTIME_COMPILER_DUMP"), + Some(v) if v == "1", + ) }; } impl<'r, 's> Compiler<'r, 's> { - pub fn new(function_registry: &'r FunctionRegistry, scope_tree: &'s ScopeTree) -> Self { + pub fn new(function_registry: &'r mut FunctionRegistry, scope_tree: &'s ScopeTree) -> Self { const DUMP_BUFFER_SIZE: usize = 512; Self { peer: Default::default(), @@ -145,14 +167,20 @@ impl<'r, 's> Compiler<'r, 's> { pending_labels: Default::default(), locals: Default::default(), captures: Default::default(), + max_scratch_buffer_len: 0, dump_buffer: if dump_enabled!() { Some(Vec::with_capacity(DUMP_BUFFER_SIZE)) } else { None }, + enable_scope_cleanup_checker: false, } } + fn enable_scope_cleanup_checker(&mut self) { + self.enable_scope_cleanup_checker = true; + } + fn start_compile(&mut self, enable_labels: bool) { logger::debug!(event = "start_compile", enable_labels); if enable_labels { @@ -179,16 +207,17 @@ impl<'r, 's> Compiler<'r, 's> { fn start_function(&mut self, symbol: Symbol, func_id: FunctionId) { logger::debug!(event = "start_function", ?symbol, ?func_id); - let native = self.function_registry.get_native(func_id); - self.peer.start_function(&native.name); + self.peer.start_function(func_id); let locals_block = self.create_basic_block("locals"); + let init_block = self.create_basic_block("init"); let args_block = self.create_basic_block("args"); let body_block = self.create_basic_block("body"); let return_block = self.create_basic_block("return"); self.control_flow_stack.push_function_flow( locals_block, + init_block, args_block, body_block, return_block, @@ -200,14 +229,24 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.set_locals_block(locals_block); - self.peer.set_basic_block(body_block); + self.peer.set_basic_block(init_block); self.peer.create_store_undefined_to_retv(); self.peer.create_alloc_status(); self.peer.create_alloc_flow_selector(); + if self.enable_scope_cleanup_checker { + self.peer + .enable_scope_cleanup_checker(func_id.is_coroutine()); + } + + self.peer.set_basic_block(body_block); } - fn end_function(&mut self, optimize: bool) { - logger::debug!(event = "end_function", optimize); + fn end_function(&mut self, func_id: FunctionId, optimize: bool) { + logger::debug!(event = "end_function", ?func_id, optimize); + + let dormant_block = func_id + .is_coroutine() + .then(|| self.control_flow_stack.pop_coroutine_flow().dormant_block); self.control_flow_stack.pop_exit_target(); let flow = self.control_flow_stack.pop_function_flow(); @@ -216,6 +255,10 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.move_basic_block_after(flow.return_block); self.peer.set_basic_block(flow.locals_block); + self.peer.create_br(flow.init_block); + self.peer.move_basic_block_after(flow.init_block); + + self.peer.set_basic_block(flow.init_block); self.peer.create_br(flow.args_block); self.peer.move_basic_block_after(flow.args_block); @@ -224,6 +267,13 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.move_basic_block_after(flow.body_block); self.peer.set_basic_block(flow.return_block); + if let Some(block) = dormant_block { + self.peer.move_basic_block_after(block); + } + + if self.enable_scope_cleanup_checker { + self.peer.assert_scope_id(ScopeRef::NONE); + } self.peer.end_function(optimize); @@ -234,6 +284,13 @@ impl<'r, 's> Compiler<'r, 's> { debug_assert!(self.control_flow_stack.is_empty()); self.control_flow_stack.clear(); + + if func_id.is_coroutine() { + self.function_registry + .get_native_mut(func_id) + .scratch_buffer_len = self.max_scratch_buffer_len; + } + self.max_scratch_buffer_len = 0; } fn process_command(&mut self, command: &CompileCommand) { @@ -249,6 +306,10 @@ impl<'r, 's> Compiler<'r, 's> { CompileCommand::Closure(prologue, num_captures) => { self.process_closure(*prologue, *num_captures) } + CompileCommand::Coroutine(func_id, num_locals) => { + self.process_coroutine(*func_id, *num_locals) + } + CompileCommand::Promise => self.process_promise(), CompileCommand::Reference(symbol, locator) => self.process_reference(*symbol, *locator), CompileCommand::Exception => self.process_exception(), CompileCommand::AllocateLocals(num_locals) => self.process_allocate_locals(*num_locals), @@ -256,8 +317,6 @@ impl<'r, 's> Compiler<'r, 's> { CompileCommand::ImmutableBinding => self.process_immutable_binding(), CompileCommand::DeclareFunction => self.process_declare_function(), CompileCommand::DeclareClosure => self.process_declare_closure(), - CompileCommand::Arguments(nargs) => self.process_arguments(*nargs), - CompileCommand::Argument(index) => self.process_argument(*index), CompileCommand::Call(nargs) => self.process_call(*nargs), CompileCommand::PushScope(scope_ref) => self.process_push_scope(*scope_ref), CompileCommand::PopScope(scope_ref) => self.process_pop_scope(*scope_ref), @@ -299,11 +358,11 @@ impl<'r, 's> Compiler<'r, 's> { CompileCommand::BitwiseOr => self.process_bitwise_or(), CompileCommand::Ternary => self.process_ternary(), CompileCommand::Assignment => self.process_assignment(), - CompileCommand::Truthy => self.process_truthy(), CompileCommand::FalsyShortCircuit => self.process_falsy_short_circuit(), CompileCommand::TruthyShortCircuit => self.process_truthy_short_circuit(), CompileCommand::NullishShortCircuit => self.process_nullish_short_circuit(), - CompileCommand::Then => self.process_then(), + CompileCommand::Truthy => self.process_truthy(), + CompileCommand::IfThen => self.process_if_then(), CompileCommand::Else => self.process_else(), CompileCommand::IfElseStatement => self.process_if_else_statement(), CompileCommand::IfStatement => self.process_if_statement(), @@ -316,10 +375,9 @@ impl<'r, 's> Compiler<'r, 's> { CompileCommand::LoopBody => self.process_loop_body(), CompileCommand::LoopEnd => self.process_loop_end(), CompileCommand::CaseBlock(id, num_cases) => self.process_case_block(*id, *num_cases), + CompileCommand::Case => self.process_case(), + CompileCommand::Default => self.process_default(), CompileCommand::CaseClause(has_statement) => self.process_case_clause(*has_statement), - CompileCommand::DefaultClause(has_statement) => { - self.process_default_clause(*has_statement) - } CompileCommand::Switch(id, num_cases, default_index) => { self.process_switch(*id, *num_cases, *default_index) } @@ -337,12 +395,13 @@ impl<'r, 's> Compiler<'r, 's> { CompileCommand::Break(symbol) => self.process_break(*symbol), CompileCommand::Return(n) => self.process_return(*n), CompileCommand::Throw => self.process_throw(), + CompileCommand::Environment(num_locals) => self.process_environment(*num_locals), + CompileCommand::JumpTable(num_states) => self.process_jump_table(*num_states), + CompileCommand::Await(next_state) => self.process_await(*next_state), + CompileCommand::Resume => self.process_resume(), CompileCommand::Discard => self.process_discard(), CompileCommand::Swap => self.process_swap(), CompileCommand::Duplicate(offset) => self.process_duplicate(*offset), - CompileCommand::SetupScopeCleanupChecker(stack_size) => { - self.process_setup_scope_cleanup_checker(*stack_size) - } CompileCommand::PlaceHolder => unreachable!(), } @@ -381,12 +440,7 @@ impl<'r, 's> Compiler<'r, 's> { } fn process_function(&mut self, func_id: FunctionId) { - let name = if func_id.is_native() { - &self.function_registry.get_native(func_id).name - } else { - &self.function_registry.get_host(func_id).name - }; - let lambda = self.peer.get_function(func_id, name); + let lambda = self.peer.get_function(func_id); self.operand_stack.push(Operand::Function(lambda)); } @@ -429,6 +483,32 @@ impl<'r, 's> Compiler<'r, 's> { } } + fn pop_closure(&mut self) -> ClosureIr { + match self.operand_stack.pop() { + Some(Operand::Closure(closure)) => closure, + _ => unreachable!(), + } + } + + fn process_coroutine(&mut self, func_id: FunctionId, num_locals: u16) { + let scrach_buffer_len = self + .function_registry + .get_native(func_id) + .scratch_buffer_len; + debug_assert!(scrach_buffer_len <= u16::MAX as u32); + let closure = self.pop_closure(); + let coroutine = self + .peer + .create_coroutine(closure, num_locals, scrach_buffer_len as u16); + self.operand_stack.push(Operand::Coroutine(coroutine)); + } + + fn process_promise(&mut self) { + let coroutine = self.pop_coroutine(); + let promise = self.peer.create_register_promise(coroutine); + self.operand_stack.push(Operand::Promise(promise)); + } + fn process_reference(&mut self, symbol: Symbol, locator: Locator) { debug_assert!(!matches!(locator, Locator::None)); self.operand_stack.push(Operand::Reference(symbol, locator)); @@ -441,8 +521,6 @@ impl<'r, 's> Compiler<'r, 's> { } fn process_allocate_locals(&mut self, num_locals: u16) { - debug_assert!(num_locals > 0); - for i in 0..num_locals { let local = self.peer.create_local_value(i); self.locals.push(local); @@ -497,6 +575,7 @@ impl<'r, 's> Compiler<'r, 's> { Operand::Boolean(value) => self.peer.create_store_boolean_to_value(value, dest), Operand::Number(value) => self.peer.create_store_number_to_value(value, dest), Operand::Closure(value) => self.peer.create_store_closure_to_value(value, dest), + Operand::Promise(value) => self.peer.create_store_promise_to_value(value, dest), Operand::Any(value) => self.peer.create_store_value_to_value(value, dest), _ => unreachable!(), } @@ -554,13 +633,6 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.set_basic_block(backup); } - fn process_arguments(&mut self, nargs: u16) { - if nargs > 0 { - let argv = self.peer.create_argv(nargs); - self.operand_stack.push(Operand::Argv(argv)); - } - } - fn swap(&mut self) { logger::debug!(event = "swap"); debug_assert!(self.operand_stack.len() > 1); @@ -575,23 +647,15 @@ impl<'r, 's> Compiler<'r, 's> { self.operand_stack.duplicate(index); } - fn process_argument(&mut self, index: u16) { - let (operand, _) = self.dereference(); - let argv = self.peek_argv(); - let arg = self.peer.create_get_arg_in_argv(argv, index); - self.create_store_operand_to_value(operand, arg); - } - - fn peek_argv(&self) -> ArgvIr { - match self.operand_stack.last().unwrap() { - Operand::Argv(value) => *value, - _ => unreachable!(), - } - } - fn process_call(&mut self, argc: u16) { let argv = if argc > 0 { - self.pop_argv() + let argv = self.peer.create_argv(argc); + for i in (0..argc).rev() { + let (operand, _) = self.dereference(); + let ptr = self.peer.create_get_arg_in_argv(argv, i); + self.create_store_operand_to_value(operand, ptr); + } + argv } else { self.peer.get_argv_nullptr() }; @@ -616,13 +680,6 @@ impl<'r, 's> Compiler<'r, 's> { self.operand_stack.push(Operand::Any(retv)); } - fn pop_argv(&mut self) -> ArgvIr { - match self.operand_stack.pop().unwrap() { - Operand::Argv(value) => value, - _ => unreachable!(), - } - } - fn create_load_closure_from_value_or_throw_type_error(&mut self, value: ValueIr) -> ClosureIr { let then_block = self.create_basic_block("is_closure.then"); let else_block = self.create_basic_block("is_closure.else"); @@ -687,6 +744,7 @@ impl<'r, 's> Compiler<'r, 's> { let cleanup_block = self.create_basic_block("cleanup"); self.control_flow_stack.push_scope_flow( + scope_ref, init_block, hoisted_block, body_block, @@ -698,14 +756,16 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.create_br(init_block); self.peer.move_basic_block_after(init_block); - self.peer.set_basic_block(body_block); - let backup = self.peer.get_basic_block(); self.peer.set_basic_block(init_block); + let scope = self.scope_tree.scope(scope_ref); for binding in scope.bindings.iter() { + if binding.is_hidden() { + continue; + } let locator = binding.locator(); - if binding.captured { + if binding.is_captured() { let value = match locator { Locator::Argument(index) => self.peer.create_get_argument_value_ptr(index), Locator::Local(index) => self.locals[index as usize], @@ -720,7 +780,8 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.create_store_none_to_value(value); } } - self.peer.set_basic_block(backup); + + self.peer.set_basic_block(body_block); } fn process_pop_scope(&mut self, scope_ref: ScopeRef) { @@ -737,6 +798,7 @@ impl<'r, 's> Compiler<'r, 's> { let parent_exit_block = self.control_flow_stack.exit_block(); let flow = self.control_flow_stack.pop_scope_flow(); + debug_assert_eq!(flow.scope_ref, scope_ref); self.peer.create_br(flow.cleanup_block); self.peer.move_basic_block_after(flow.cleanup_block); @@ -746,7 +808,9 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.move_basic_block_after(precheck_block); self.peer.set_basic_block(precheck_block); - self.peer.perform_scope_cleanup_precheck(scope_ref); + if self.enable_scope_cleanup_checker { + self.peer.set_scope_id_for_checker(scope_ref); + } self.peer.create_br(flow.hoisted_block); self.peer.move_basic_block_after(flow.hoisted_block); @@ -757,7 +821,7 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.set_basic_block(flow.cleanup_block); let scope = self.scope_tree.scope(scope_ref); for binding in scope.bindings.iter() { - if binding.captured { + if binding.is_captured() { self.escape_value(binding.locator()); } if binding.is_local() { @@ -769,7 +833,15 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.move_basic_block_after(postcheck_block); self.peer.set_basic_block(postcheck_block); - self.peer.perform_scope_cleanup_postcheck(scope_ref); + if self.enable_scope_cleanup_checker { + self.peer.assert_scope_id(scope_ref); + if self.control_flow_stack.has_scope_flow() { + let outer_scope_ref = self.control_flow_stack.scope_flow().scope_ref; + self.peer.set_scope_id_for_checker(outer_scope_ref); + } else { + self.peer.set_scope_id_for_checker(ScopeRef::NONE); + } + } self.peer.create_br(ctrl_block); self.peer.move_basic_block_after(ctrl_block); @@ -1204,6 +1276,9 @@ impl<'r, 's> Compiler<'r, 's> { (Operand::Closure(lhs), Operand::Closure(rhs)) => { self.peer.create_is_same_closure(lhs, rhs) } + (Operand::Promise(lhs), Operand::Promise(rhs)) => { + self.peer.create_is_same_promise(lhs, rhs) + } _ => unreachable!(), } } @@ -1216,6 +1291,7 @@ impl<'r, 's> Compiler<'r, 's> { Operand::Boolean(rhs) => self.create_is_same_boolean_value(lhs, rhs), Operand::Number(rhs) => self.create_is_same_number_value(lhs, rhs), Operand::Closure(rhs) => self.create_is_same_closure_value(lhs, rhs), + Operand::Promise(rhs) => self.create_is_same_promise_value(lhs, rhs), Operand::Any(rhs) => self.peer.create_is_strictly_equal(lhs, rhs), _ => unreachable!(), } @@ -1289,6 +1365,28 @@ impl<'r, 's> Compiler<'r, 's> { .create_boolean_phi(then_value, then_block, else_value, else_block) } + fn create_is_same_promise_value(&mut self, value: ValueIr, promise: PromiseIr) -> BooleanIr { + let then_block = self.create_basic_block("is_promise.then"); + let else_block = self.create_basic_block("is_promise.else"); + let merge_block = self.create_basic_block("is_promise"); + + // if value.kind == ValueKind::Promise + let cond = self.peer.create_is_promise(value); + self.peer.create_cond_br(cond, then_block, else_block); + // { + self.peer.set_basic_block(then_block); + let then_value = self.peer.create_is_same_promise_value(value, promise); + self.peer.create_br(merge_block); + // } else { + self.peer.set_basic_block(else_block); + let else_value = self.peer.get_boolean(false); + self.peer.create_br(merge_block); + // } + self.peer.set_basic_block(merge_block); + self.peer + .create_boolean_phi(then_value, then_block, else_value, else_block) + } + // 13.11.1 Runtime Semantics: Evaluation fn process_inequality(&mut self) { // TODO: comparing references improves the performance. @@ -1367,32 +1465,22 @@ impl<'r, 's> Compiler<'r, 's> { } fn process_ternary(&mut self) { - let else_branch = self.control_flow_stack.pop_branch_flow(); - let then_branch = self.control_flow_stack.pop_branch_flow(); - - let test_block = then_branch.before_block; - let then_head_block = then_branch.after_block; - let then_tail_block = else_branch.before_block; - let else_head_block = else_branch.after_block; - let else_tail_block = self.peer.get_basic_block(); + let flow = self.control_flow_stack.pop_if_then_else_flow(); + let then_block = flow.then_block; + let else_block = self.peer.get_basic_block(); let (else_operand, _) = self.dereference(); - self.peer.set_basic_block(then_tail_block); + self.peer.set_basic_block(then_block); let (then_operand, _) = self.dereference(); - self.peer.set_basic_block(test_block); - let cond_value = self.pop_boolean(); - self.peer - .create_cond_br(cond_value, then_head_block, else_head_block); - let block = self.create_basic_block("ternary"); if std::mem::discriminant(&then_operand) == std::mem::discriminant(&else_operand) { - self.peer.set_basic_block(then_tail_block); + self.peer.set_basic_block(then_block); self.peer.create_br(block); - self.peer.set_basic_block(else_tail_block); + self.peer.set_basic_block(else_block); self.peer.create_br(block); self.peer.set_basic_block(block); @@ -1408,32 +1496,23 @@ impl<'r, 's> Compiler<'r, 's> { return; } (Operand::Boolean(then_value), Operand::Boolean(else_value)) => { - let boolean = self.peer.create_boolean_phi( - then_value, - then_tail_block, - else_value, - else_tail_block, - ); + let boolean = self + .peer + .create_boolean_phi(then_value, then_block, else_value, else_block); self.operand_stack.push(Operand::Boolean(boolean)); return; } (Operand::Number(then_value), Operand::Number(else_value)) => { - let number = self.peer.create_number_phi( - then_value, - then_tail_block, - else_value, - else_tail_block, - ); + let number = self + .peer + .create_number_phi(then_value, then_block, else_value, else_block); self.operand_stack.push(Operand::Number(number)); return; } (Operand::Any(then_value), Operand::Any(else_value)) => { - let any = self.peer.create_value_phi( - then_value, - then_tail_block, - else_value, - else_tail_block, - ); + let any = self + .peer + .create_value_phi(then_value, then_block, else_value, else_block); self.operand_stack.push(Operand::Any(any)); return; } @@ -1443,18 +1522,18 @@ impl<'r, 's> Compiler<'r, 's> { // We have to convert the value before the branch in each block. - self.peer.set_basic_block(then_tail_block); + self.peer.set_basic_block(then_block); let then_value = self.create_to_any(then_operand); self.peer.create_br(block); - self.peer.set_basic_block(else_tail_block); + self.peer.set_basic_block(else_block); let else_value = self.create_to_any(else_operand); self.peer.create_br(block); self.peer.set_basic_block(block); - let any = - self.peer - .create_value_phi(then_value, then_tail_block, else_value, else_tail_block); + let any = self + .peer + .create_value_phi(then_value, then_block, else_value, else_block); self.operand_stack.push(Operand::Any(any)); } @@ -1479,38 +1558,32 @@ impl<'r, 's> Compiler<'r, 's> { self.operand_stack.push(rhs); } - fn process_truthy(&mut self) { - let (operand, _) = self.dereference(); - let boolean = self.create_to_boolean(operand); - self.operand_stack.push(Operand::Boolean(boolean)); - } - fn process_falsy_short_circuit(&mut self) { let (operand, _) = self.dereference(); let boolean = self.create_to_boolean(operand.clone()); let boolean = self.peer.create_logical_not(boolean); self.operand_stack.push(Operand::Boolean(boolean)); - self.branch(); // then + self.process_if_then(); self.operand_stack.push(operand); - self.branch(); // else + self.process_else(); } fn process_truthy_short_circuit(&mut self) { let (operand, _) = self.dereference(); let boolean = self.create_to_boolean(operand.clone()); self.operand_stack.push(Operand::Boolean(boolean)); - self.branch(); // then + self.process_if_then(); self.operand_stack.push(operand); - self.branch(); // else + self.process_else(); } fn process_nullish_short_circuit(&mut self) { let (operand, _) = self.dereference(); let boolean = self.create_is_non_nullish(operand.clone()); self.operand_stack.push(Operand::Boolean(boolean)); - self.branch(); // then + self.process_if_then(); self.operand_stack.push(operand); - self.branch(); // else + self.process_else(); } fn create_is_non_nullish(&mut self, operand: Operand) -> BooleanIr { @@ -1524,72 +1597,72 @@ impl<'r, 's> Compiler<'r, 's> { } } - fn process_then(&mut self) { - self.branch(); + fn process_truthy(&mut self) { + let (operand, _) = self.dereference(); + let boolean = self.create_to_boolean(operand); + self.operand_stack.push(Operand::Boolean(boolean)); + } + + fn process_if_then(&mut self) { + let cond_value = self.pop_boolean(); + let then_block = self.create_basic_block("then"); + let else_block = self.create_basic_block("else"); + self.peer.create_cond_br(cond_value, then_block, else_block); + self.peer.set_basic_block(then_block); + self.control_flow_stack + .push_if_then_else_flow(then_block, else_block); } fn process_else(&mut self) { - self.branch(); + let then_block = self.peer.get_basic_block(); + let else_block = self.control_flow_stack.update_then_block(then_block); + self.peer.move_basic_block_after(else_block); + self.peer.set_basic_block(else_block); } fn process_if_else_statement(&mut self) { - let else_branch = self.control_flow_stack.pop_branch_flow(); - let then_branch = self.control_flow_stack.pop_branch_flow(); - - let test_block = then_branch.before_block; - let then_head_block = then_branch.after_block; - let then_tail_block = else_branch.before_block; - let else_head_block = else_branch.after_block; - let else_tail_block = self.peer.get_basic_block(); + let flow = self.control_flow_stack.pop_if_then_else_flow(); + let else_block = self.peer.get_basic_block(); let mut block = BasicBlock::NONE; - if self.peer.is_basic_block_terminated(else_tail_block) { + if self.peer.is_basic_block_terminated(else_block) { // We should not append any instructions after a terminator instruction such as `ret`. } else { - block = self.create_basic_block("block"); + block = self.create_basic_block("merge"); self.peer.create_br(block); } - if self.peer.is_basic_block_terminated(then_tail_block) { + if self.peer.is_basic_block_terminated(flow.then_block) { // We should not append any instructions after a terminator instruction such as `ret`. } else { if block == BasicBlock::NONE { - block = self.create_basic_block("block"); + block = self.create_basic_block("merge"); } - self.peer.set_basic_block(then_tail_block); + self.peer.set_basic_block(flow.then_block); self.peer.create_br(block); } - let cond_value = self.pop_boolean(); - - self.peer.set_basic_block(test_block); - self.peer - .create_cond_br(cond_value, then_head_block, else_head_block); - if block != BasicBlock::NONE { self.peer.set_basic_block(block); } } fn process_if_statement(&mut self) { - let branch = self.control_flow_stack.pop_branch_flow(); + let flow = self.control_flow_stack.pop_if_then_else_flow(); + let then_block = self.peer.get_basic_block(); - let test_block = branch.before_block; - let then_head_block = branch.after_block; - let then_tail_block = self.peer.get_basic_block(); - let block = self.create_basic_block("block"); + let block = self.create_basic_block("merge"); - if self.peer.is_basic_block_terminated(then_tail_block) { + if self.peer.is_basic_block_terminated(then_block) { // We should not append any instructions after a terminator instruction such as `ret`. } else { self.peer.create_br(block); } - let cond_value = self.pop_boolean(); - - self.peer.set_basic_block(test_block); - self.peer.create_cond_br(cond_value, then_head_block, block); + self.peer.move_basic_block_after(flow.else_block); + self.peer.set_basic_block(flow.else_block); + self.peer.create_br(block); self.peer.set_basic_block(block); } @@ -1803,7 +1876,7 @@ impl<'r, 's> Compiler<'r, 's> { push_bb_name!(self, "switch", id); - let start_block = self.create_basic_block("start"); + let case_block = self.create_basic_block("case"); let ctrl_block = self.create_basic_block("ctrl"); let set_normal_block = self.create_basic_block("ctrl.set_normal"); let end_block = self.create_basic_block("end"); @@ -1813,12 +1886,7 @@ impl<'r, 's> Compiler<'r, 's> { debug_assert!(self.pending_labels.is_empty()); let exit_id = self.control_flow_stack.exit_id(); - let (operand, _) = self.dereference(); - // TODO: item.SetLabel("switch-value"); - self.operand_stack.push(operand); - self.duplicate(0); // Dup for test on CaseClause - - self.peer.create_br(start_block); + self.peer.create_br(case_block); self.peer.set_basic_block(ctrl_block); let is_break = self.peer.create_is_flow_selector_break(exit_id.depth()); @@ -1829,70 +1897,59 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.create_set_flow_selector_normal(); self.peer.create_br(end_block); - self.peer.set_basic_block(start_block); + self.peer.set_basic_block(case_block); } - fn process_case_clause(&mut self, _has_statement: bool) { - let branch = self.control_flow_stack.pop_branch_flow(); - - let test_block = branch.before_block; - let then_block = branch.after_block; - let else_block = self.create_basic_block("else"); - let end_block = self.peer.get_basic_block(); - - let cond = self.pop_boolean(); - - self.peer.set_basic_block(test_block); - self.peer.create_cond_br(cond, then_block, else_block); - self.peer.set_basic_block(else_block); - - self.duplicate(0); - + fn process_case(&mut self) { + let clause_start_block = self.create_basic_block("case.clause"); + let next_case_block = self.create_basic_block("case"); + let cond_value = self.pop_boolean(); + self.peer + .create_cond_br(cond_value, clause_start_block, next_case_block); + self.peer.set_basic_block(clause_start_block); self.control_flow_stack - .push_case_banch_flow(end_block, then_block); + .push_case_flow(next_case_block, clause_start_block); } - fn process_default_clause(&mut self, _has_statement: bool) { - let branch = self.control_flow_stack.pop_branch_flow(); - - let test_block = branch.before_block; - let then_block = branch.after_block; - let end_block = self.peer.get_basic_block(); - - self.peer.set_basic_block(test_block); - - self.duplicate(0); - + fn process_default(&mut self) { + let next_case_block = self.peer.get_basic_block(); + let clause_start_block = self.create_basic_block("default.clause"); + self.peer.set_basic_block(clause_start_block); self.control_flow_stack - .push_case_banch_flow(end_block, then_block); - self.control_flow_stack.set_default_case_block(then_block); + .push_case_flow(next_case_block, clause_start_block); + self.control_flow_stack + .set_default_case_block(clause_start_block) + } + + fn process_case_clause(&mut self, has_statement: bool) { + let clause_end_block = self.peer.get_basic_block(); + let next_case_block = self + .control_flow_stack + .update_case_flow(clause_end_block, has_statement); + self.peer.set_basic_block(next_case_block); } fn process_switch(&mut self, _id: u16, num_cases: u16, _default_index: Option) { pop_bb_name!(self); - let case_block = self.peer.get_basic_block(); - - // Discard the switch-values - self.process_discard(); - self.process_discard(); + let last_case_block = self.peer.get_basic_block(); // Connect the last basic blocks of each case/default clause to the first basic block of // the statement lists of the next case/default clause if it's not terminated. // // The last basic blocks has been stored in the control flow stack in reverse order. let mut fall_through_block = self.control_flow_stack.switch_flow().end_block; + debug_assert_ne!(fall_through_block, BasicBlock::NONE); for _ in 0..num_cases { - let case_branch = self.control_flow_stack.pop_case_branch_flow(); - let terminated = self - .peer - .is_basic_block_terminated(case_branch.before_block); + let flow = self.control_flow_stack.pop_case_flow(); + let terminated = self.peer.is_basic_block_terminated(flow.clause_end_block); if !terminated { - self.peer.set_basic_block(case_branch.before_block); + self.peer.set_basic_block(flow.clause_end_block); self.peer.create_br(fall_through_block); self.peer.move_basic_block_after(fall_through_block); } - fall_through_block = case_branch.after_block; + fall_through_block = flow.clause_start_block; + debug_assert_ne!(fall_through_block, BasicBlock::NONE); } self.control_flow_stack.pop_exit_target(); @@ -1900,7 +1957,7 @@ impl<'r, 's> Compiler<'r, 's> { // Create an unconditional jump to the statement of the default clause if it exists. // Otherwise, jump to the end block. - self.peer.set_basic_block(case_block); + self.peer.set_basic_block(last_case_block); self.peer .create_br(if switch.default_block != BasicBlock::NONE { switch.default_block @@ -1912,18 +1969,6 @@ impl<'r, 's> Compiler<'r, 's> { self.peer.set_basic_block(switch.end_block); } - fn branch(&mut self) { - let before_block = self.peer.get_basic_block(); - - // Push a newly created block. - // This will be used in ConditionalExpression() in order to build a branch instruction. - let after_block = self.create_basic_block("block"); - self.peer.set_basic_block(after_block); - - self.control_flow_stack - .push_branch_flow(before_block, after_block); - } - fn process_try(&mut self) { let try_block = self.create_basic_block("try"); let catch_block = self.create_basic_block("catch"); @@ -2084,6 +2129,7 @@ impl<'r, 's> Compiler<'r, 's> { Operand::Boolean(value) => self.peer.create_store_boolean_to_retv(value), Operand::Number(value) => self.peer.create_store_number_to_retv(value), Operand::Closure(value) => self.peer.create_store_closure_to_retv(value), + Operand::Promise(value) => self.peer.create_store_promise_to_retv(value), Operand::Any(value) => self.peer.create_store_value_to_retv(value), _ => unreachable!(), } @@ -2103,6 +2149,234 @@ impl<'r, 's> Compiler<'r, 's> { self.create_basic_block_for_deadcode(); } + fn process_environment(&mut self, num_locals: u16) { + let flow = self.control_flow_stack.function_flow(); + let backup = self.peer.get_basic_block(); + + // Local variables and captured variables living outer scopes are loaded here from the + // `Coroutine` data passed via the `env` argument of the coroutine lambda function to be + // generated by the compiler. + self.peer.set_basic_block(flow.init_block); + self.peer.create_set_captures_for_coroutine(); + for i in 0..num_locals { + let local = self.peer.create_get_local_ptr_from_coroutine(i); + self.locals.push(local); + } + + self.peer.set_basic_block(backup); + } + + fn process_jump_table(&mut self, num_states: u32) { + debug_assert!(num_states >= 2); + let initial_block = self.create_basic_block("co.initial"); + let done_block = self.create_basic_block("co.done"); + let inst = self + .peer + .create_switch_for_coroutine(done_block, num_states); + self.peer + .create_add_state_for_coroutine(inst, 0, initial_block); + + self.peer.set_basic_block(done_block); + self.peer + .create_unreachable(c"the coroutine has already done"); + + self.peer.set_basic_block(initial_block); + + self.control_flow_stack + .push_coroutine_flow(inst, done_block, num_states); + } + + fn process_await(&mut self, next_state: u32) { + self.resolve_promise(); + self.save_operands_to_scratch_buffer(); + self.peer.create_set_coroutine_state(next_state); + self.peer.create_suspend(); + + // resume block + let block = self.create_basic_block("resume"); + let inst = self.control_flow_stack.coroutine_switch_inst(); + let state = self.control_flow_stack.coroutine_next_state(); + self.peer.create_add_state_for_coroutine(inst, state, block); + self.peer.set_basic_block(block); + + self.load_operands_from_scratch_buffer(); + + let has_error_block = self.create_basic_block("has_error"); + let result_block = self.create_basic_block("result"); + + // if ##error.has_value() + let error = self.peer.create_get_argument_value_ptr(2); // ##error + let has_error = self.peer.create_has_value(error); + self.peer + .create_cond_br(has_error, has_error_block, result_block); + { + // throw ##error; + self.peer.set_basic_block(has_error_block); + self.operand_stack.push(Operand::Any(error)); + self.process_throw(); + self.peer.create_br(result_block); + } + + self.peer.set_basic_block(result_block); + let result = self.peer.create_get_argument_value_ptr(1); // ##result + self.operand_stack.push(Operand::Any(result)); + } + + fn resolve_promise(&mut self) { + let promise = self.peer.create_get_argument_value_ptr(0); // ##promise + let promise = self.peer.create_load_promise_from_value(promise); + + let (operand, _) = self.dereference(); + match operand { + Operand::Undefined => { + let result = self.peer.create_undefined_to_any(); + self.peer.create_emit_promise_resolved(promise, result); + } + Operand::Null => { + let result = self.peer.create_null_to_any(); + self.peer.create_emit_promise_resolved(promise, result); + } + Operand::Boolean(value) => { + let result = self.peer.create_boolean_to_any(value); + self.peer.create_emit_promise_resolved(promise, result); + } + Operand::Number(value) => { + let result = self.peer.create_number_to_any(value); + self.peer.create_emit_promise_resolved(promise, result); + } + Operand::Closure(value) => { + let result = self.peer.create_closure_to_any(value); + self.peer.create_emit_promise_resolved(promise, result); + } + Operand::Promise(value) => { + self.peer.create_await_promise(value, promise); + } + Operand::Any(value) => { + let then_block = self.create_basic_block("is_promise.then"); + let else_block = self.create_basic_block("is_promise.else"); + let block = self.create_basic_block("block"); + // if value.is_promise() + let is_promise = self.peer.create_is_promise(value); + self.peer.create_cond_br(is_promise, then_block, else_block); + // { + self.peer.set_basic_block(then_block); + let target = self.peer.create_load_promise_from_value(value); + self.peer.create_await_promise(target, promise); + self.peer.create_br(block); + // } else { + self.peer.set_basic_block(else_block); + self.peer.create_emit_promise_resolved(promise, value); + self.peer.create_br(block); + // } + self.peer.set_basic_block(block); + } + _ => unreachable!("{operand:?}"), + } + } + + // TODO(perf): Currently, we have to save all values (except for special cases) on the operand + // stack into the scratch buffer before the execution of the coroutine suspends. However, we + // don't need to save some of them. For example, there may be constant values on the operand + // stack. Additionally, there may be values which will be computed to constant values after + // the LLVM IR compiler performs constant folding in optimization passes. + // + // NOTE: It's best to implement a special pass to determine which value on the operand stack + // has to be saved, but we don't like to tightly depend on LLVM. Because we plan to replace it + // with another library written in Rust such as Cranelift in the future. + fn save_operands_to_scratch_buffer(&mut self) { + let mut offset = 0u32; + for operand in self.operand_stack.iter() { + match operand { + Operand::Undefined => (), + Operand::Null => (), + Operand::Boolean(value) => { + self.peer + .create_write_boolean_to_scratch_buffer(offset, *value); + offset += VALUE_HOLDER_SIZE; + } + Operand::Number(value) => { + self.peer + .create_write_number_to_scratch_buffer(offset, *value); + offset += VALUE_HOLDER_SIZE; + } + Operand::Closure(value) => { + // TODO(issue#237): GcCellRef + self.peer + .create_write_closure_to_scratch_buffer(offset, *value); + offset += VALUE_HOLDER_SIZE; + } + Operand::Promise(value) => { + self.peer + .create_write_promise_to_scratch_buffer(offset, *value); + offset += VALUE_HOLDER_SIZE; + } + Operand::Any(value) => { + self.peer + .create_write_value_to_scratch_buffer(offset, *value); + offset += VALUE_SIZE; + } + Operand::Reference(..) => (), + _ => unreachable!("{operand:?}"), + } + } + + // TODO: Should return a compile error. + assert!(offset <= u16::MAX as u32); + self.max_scratch_buffer_len = self.max_scratch_buffer_len.max(offset); + } + + fn load_operands_from_scratch_buffer(&mut self) { + let mut offset = 0u32; + for operand in self.operand_stack.iter_mut() { + match operand { + Operand::Undefined => (), + Operand::Null => (), + Operand::Boolean(ref mut value) => { + *value = self.peer.create_read_boolean_from_scratch_buffer(offset); + offset += VALUE_HOLDER_SIZE; + } + Operand::Number(ref mut value) => { + *value = self.peer.create_read_number_from_scratch_buffer(offset); + offset += VALUE_HOLDER_SIZE; + } + Operand::Closure(ref mut value) => { + // TODO(issue#237): GcCellRef + *value = self.peer.create_read_closure_from_scratch_buffer(offset); + offset += VALUE_HOLDER_SIZE; + } + Operand::Promise(ref mut value) => { + *value = self.peer.create_read_promise_from_scratch_buffer(offset); + offset += VALUE_HOLDER_SIZE; + } + Operand::Any(ref mut value) => { + *value = self.peer.create_read_value_from_scratch_buffer(offset); + offset += VALUE_SIZE; + } + Operand::Reference(..) => (), + _ => unreachable!("{operand:?}"), + } + } + } + + fn process_resume(&mut self) { + let promise = self.pop_promise(); + self.peer.create_resume(promise); + } + + fn pop_coroutine(&mut self) -> CoroutineIr { + match self.operand_stack.pop().unwrap() { + Operand::Coroutine(value) => value, + _ => unreachable!(), + } + } + + fn pop_promise(&mut self) -> PromiseIr { + match self.operand_stack.pop().unwrap() { + Operand::Promise(value) => value, + _ => unreachable!(), + } + } + fn process_discard(&mut self) { debug_assert!(!self.operand_stack.is_empty()); self.operand_stack.pop(); @@ -2116,10 +2390,6 @@ impl<'r, 's> Compiler<'r, 's> { self.duplicate(offset); } - fn process_setup_scope_cleanup_checker(&mut self, stack_size: u16) { - self.peer.setup_scope_cleanup_checker(stack_size) - } - fn create_basic_block(&mut self, name: &str) -> BasicBlock { push_bb_name!(self, name); let (name, name_len) = bb_name!(self); @@ -2196,9 +2466,10 @@ enum Operand { Number(NumberIr), Function(LambdaIr), Closure(ClosureIr), + Coroutine(CoroutineIr), + Promise(PromiseIr), Any(ValueIr), Reference(Symbol, Locator), - Argv(ArgvIr), Capture(CaptureIr), } @@ -2215,11 +2486,12 @@ impl Dump for Operand { Self::Null => eprintln!("Null"), Self::Boolean(value) => eprintln!("Boolean({:?})", ir2cstr!(value)), Self::Number(value) => eprintln!("Number({:?})", ir2cstr!(value)), - Self::Function(lambda) => eprintln!("Function({:?})", ir2cstr!(lambda)), + Self::Function(value) => eprintln!("Function({:?})", ir2cstr!(value)), Self::Closure(value) => eprintln!("Closure({:?})", ir2cstr!(value)), + Self::Coroutine(value) => eprintln!("Coroutine({:?})", ir2cstr!(value)), + Self::Promise(value) => eprintln!("Promise({:?})", ir2cstr!(value)), Self::Any(value) => eprintln!("Any({:?})", ir2cstr!(value)), Self::Reference(symbol, locator) => eprintln!("Reference({symbol}, {locator:?})"), - Self::Argv(value) => eprintln!("Argv({:?})", ir2cstr!(value)), Self::Capture(value) => eprintln!("Capture({:?})", ir2cstr!(value)), } } diff --git a/libs/jsruntime/src/llvmir/compiler/peer.rs b/libs/jsruntime/src/llvmir/compiler/peer.rs index f2fd8a43f..57fc5826e 100644 --- a/libs/jsruntime/src/llvmir/compiler/peer.rs +++ b/libs/jsruntime/src/llvmir/compiler/peer.rs @@ -24,6 +24,12 @@ pub struct NumberIr(*mut bridge::NumberIr); #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ClosureIr(*mut bridge::ClosureIr); +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CoroutineIr(*mut bridge::CoroutineIr); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PromiseIr(*mut bridge::PromiseIr); + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ValueIr(*mut bridge::ValueIr); @@ -36,6 +42,21 @@ pub struct StatusIr(*mut bridge::StatusIr); #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CaptureIr(*mut bridge::CaptureIr); +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SwitchIr(*mut bridge::SwitchIr); + +macro_rules! basic_block { + ($inner:expr) => { + BasicBlock(unsafe { $inner }) + }; +} + +macro_rules! lambda_ir { + ($inner:expr) => { + LambdaIr(unsafe { $inner }) + }; +} + macro_rules! boolean_ir { ($inner:expr) => { BooleanIr(unsafe { $inner }) @@ -54,6 +75,18 @@ macro_rules! closure_ir { }; } +macro_rules! coroutine_ir { + ($inner:expr) => { + CoroutineIr(unsafe { $inner }) + }; +} + +macro_rules! promise_ir { + ($inner:expr) => { + PromiseIr(unsafe { $inner }) + }; +} + macro_rules! value_ir { ($inner:expr) => { ValueIr(unsafe { $inner }) @@ -78,6 +111,12 @@ macro_rules! capture_ir { }; } +macro_rules! switch_ir { + ($inner:expr) => { + SwitchIr(unsafe { $inner }) + }; +} + impl Compiler { pub fn new() -> Self { Self(unsafe { bridge::compiler_peer_new() }) @@ -109,9 +148,9 @@ impl Compiler { // function - pub fn start_function(&self, name: &CStr) { + pub fn start_function(&self, func_id: FunctionId) { unsafe { - bridge::compiler_peer_start_function(self.0, name.as_ptr()); + bridge::compiler_peer_start_function(self.0, func_id.into()); } } @@ -128,28 +167,20 @@ impl Compiler { } } - pub fn get_function(&self, func_id: FunctionId, name: &CStr) -> LambdaIr { - unsafe { - LambdaIr(bridge::compiler_peer_get_function( - self.0, - func_id.into(), - name.as_ptr(), - )) - } + pub fn get_function(&self, func_id: FunctionId) -> LambdaIr { + lambda_ir!(bridge::compiler_peer_get_function(self.0, func_id.into())) } // basic block pub fn create_basic_block(&self, name: *const std::ffi::c_char, name_len: usize) -> BasicBlock { - unsafe { - BasicBlock(bridge::compiler_peer_create_basic_block( - self.0, name, name_len, - )) - } + basic_block!(bridge::compiler_peer_create_basic_block( + self.0, name, name_len + )) } pub fn get_basic_block(&self) -> BasicBlock { - unsafe { BasicBlock(bridge::compiler_peer_get_basic_block(self.0)) } + basic_block!(bridge::compiler_peer_get_basic_block(self.0)) } pub fn set_basic_block(&self, block: BasicBlock) { @@ -503,8 +534,52 @@ impl Compiler { } } + // promise + + pub fn create_is_promise(&self, value: ValueIr) -> BooleanIr { + boolean_ir! { + bridge::compiler_peer_create_is_promise(self.0, value.0) + } + } + + pub fn create_is_same_promise(&self, a: PromiseIr, b: PromiseIr) -> BooleanIr { + boolean_ir! { + bridge::compiler_peer_create_is_same_promise(self.0, a.0, b.0) + } + } + + pub fn create_register_promise(&self, coroutine: CoroutineIr) -> PromiseIr { + promise_ir! { + bridge::compiler_peer_create_register_promise(self.0, coroutine.0) + } + } + + pub fn create_await_promise(&self, promise: PromiseIr, awaiting: PromiseIr) { + unsafe { + bridge::compiler_peer_create_await_promise(self.0, promise.0, awaiting.0); + } + } + + pub fn create_resume(&self, promise: PromiseIr) { + unsafe { + bridge::compiler_peer_create_resume(self.0, promise.0); + } + } + + pub fn create_emit_promise_resolved(&self, promise: PromiseIr, result: ValueIr) { + unsafe { + bridge::compiler_peer_create_emit_promise_resolved(self.0, promise.0, result.0); + } + } + // value + pub fn create_has_value(&self, value: ValueIr) -> BooleanIr { + boolean_ir! { + bridge::compiler_peer_create_has_value(self.0, value.0) + } + } + pub fn create_is_loosely_equal(&self, a: ValueIr, b: ValueIr) -> BooleanIr { boolean_ir! { bridge::compiler_peer_create_is_loosely_equal(self.0, a.0, b.0) @@ -535,6 +610,12 @@ impl Compiler { } } + pub fn create_is_same_promise_value(&self, any: ValueIr, promise: PromiseIr) -> BooleanIr { + boolean_ir! { + bridge::compiler_peer_create_is_same_promise_value(self.0, any.0, promise.0) + } + } + pub fn create_undefined_to_any(&self) -> ValueIr { value_ir! { bridge::compiler_peer_create_undefined_to_any(self.0) @@ -627,6 +708,13 @@ impl Compiler { } } + pub fn create_store_promise_to_value(&self, value: PromiseIr, dest: ValueIr) { + debug_assert_ne!(value, PromiseIr::NONE); + unsafe { + bridge::compiler_peer_create_store_promise_to_value(self.0, value.0, dest.0); + } + } + pub fn create_store_value_to_value(&self, value: ValueIr, dest: ValueIr) { debug_assert_ne!(value, ValueIr::NONE); unsafe { @@ -640,6 +728,12 @@ impl Compiler { } } + pub fn create_load_promise_from_value(&self, value: ValueIr) -> PromiseIr { + promise_ir! { + bridge::compiler_peer_create_load_promise_from_value(self.0, value.0) + } + } + // argv pub fn get_argv_nullptr(&self) -> ArgvIr { @@ -710,6 +804,13 @@ impl Compiler { } } + pub fn create_store_promise_to_retv(&self, value: PromiseIr) { + debug_assert_ne!(value, PromiseIr::NONE); + unsafe { + bridge::compiler_peer_create_store_promise_to_retv(self.0, value.0); + } + } + pub fn create_store_value_to_retv(&self, value: ValueIr) { debug_assert_ne!(value, ValueIr::NONE); unsafe { @@ -867,24 +968,149 @@ impl Compiler { } } + // coroutine + + pub fn create_coroutine( + &self, + closure: ClosureIr, + num_locals: u16, + scratch_buffer_len: u16, + ) -> CoroutineIr { + coroutine_ir! { + bridge::compiler_peer_create_coroutine(self.0, closure.0, num_locals, scratch_buffer_len) + } + } + + pub fn create_switch_for_coroutine(&self, block: BasicBlock, num_states: u32) -> SwitchIr { + switch_ir! { + bridge::compiler_peer_create_switch_for_coroutine(self.0, block.0, num_states) + } + } + + pub fn create_add_state_for_coroutine(&self, inst: SwitchIr, state: u32, block: BasicBlock) { + unsafe { + bridge::compiler_peer_create_add_state_for_coroutine(self.0, inst.0, state, block.0); + } + } + + pub fn create_suspend(&self) { + unsafe { + bridge::compiler_peer_create_suspend(self.0); + } + } + + pub fn create_set_coroutine_state(&self, state: u32) { + unsafe { + bridge::compiler_peer_create_set_coroutine_state(self.0, state); + } + } + + pub fn create_set_captures_for_coroutine(&self) { + unsafe { + bridge::compiler_peer_create_set_captures_for_coroutine(self.0); + } + } + + pub fn create_get_local_ptr_from_coroutine(&self, index: u16) -> ValueIr { + value_ir! { + bridge::compiler_peer_create_get_local_ptr_from_coroutine(self.0, index) + } + } + + pub fn create_write_boolean_to_scratch_buffer(&self, offset: u32, value: BooleanIr) { + unsafe { + bridge::compiler_peer_create_write_boolean_to_scratch_buffer(self.0, offset, value.0); + } + } + + pub fn create_read_boolean_from_scratch_buffer(&self, offset: u32) -> BooleanIr { + boolean_ir! { + bridge::compiler_peer_create_read_boolean_from_scratch_buffer(self.0, offset) + } + } + + pub fn create_write_number_to_scratch_buffer(&self, offset: u32, value: NumberIr) { + unsafe { + bridge::compiler_peer_create_write_number_to_scratch_buffer(self.0, offset, value.0); + } + } + + pub fn create_read_number_from_scratch_buffer(&self, offset: u32) -> NumberIr { + number_ir! { + bridge::compiler_peer_create_read_number_from_scratch_buffer(self.0, offset) + } + } + + pub fn create_write_closure_to_scratch_buffer(&self, offset: u32, value: ClosureIr) { + unsafe { + bridge::compiler_peer_create_write_closure_to_scratch_buffer(self.0, offset, value.0); + } + } + + pub fn create_read_closure_from_scratch_buffer(&self, offset: u32) -> ClosureIr { + closure_ir! { + bridge::compiler_peer_create_read_closure_from_scratch_buffer(self.0, offset) + } + } + + pub fn create_write_promise_to_scratch_buffer(&self, offset: u32, value: PromiseIr) { + unsafe { + bridge::compiler_peer_create_write_promise_to_scratch_buffer(self.0, offset, value.0); + } + } + + pub fn create_read_promise_from_scratch_buffer(&self, offset: u32) -> PromiseIr { + promise_ir! { + bridge::compiler_peer_create_read_promise_from_scratch_buffer(self.0, offset) + } + } + + pub fn create_write_value_to_scratch_buffer(&self, offset: u32, value: ValueIr) { + unsafe { + bridge::compiler_peer_create_write_value_to_scratch_buffer(self.0, offset, value.0); + } + } + + pub fn create_read_value_from_scratch_buffer(&self, offset: u32) -> ValueIr { + value_ir! { + bridge::compiler_peer_create_read_value_from_scratch_buffer(self.0, offset) + } + } + // scope cleanup checker - pub fn setup_scope_cleanup_checker(&self, stack_size: u16) { - debug_assert!(stack_size > 0); + pub fn enable_scope_cleanup_checker(&self, is_coroutine: bool) { + unsafe { + bridge::compiler_peer_enable_scope_cleanup_checker(self.0, is_coroutine); + } + } + + pub fn set_scope_id_for_checker(&self, scope_ref: ScopeRef) { unsafe { - bridge::compiler_peer_setup_scope_cleanup_checker(self.0, stack_size); + bridge::compiler_peer_set_scope_id_for_checker(self.0, scope_ref.id()); } } - pub fn perform_scope_cleanup_precheck(&self, scope_ref: ScopeRef) { + pub fn assert_scope_id(&self, expected: ScopeRef) { unsafe { - bridge::compiler_peer_perform_scope_cleanup_precheck(self.0, scope_ref.id()); + bridge::compiler_peer_assert_scope_id(self.0, expected.id()); } } - pub fn perform_scope_cleanup_postcheck(&self, scope_ref: ScopeRef) { + // print + + #[allow(unused)] + pub fn create_print_value(&self, value: ValueIr, msg: &CStr) { unsafe { - bridge::compiler_peer_perform_scope_cleanup_postcheck(self.0, scope_ref.id()); + bridge::compiler_peer_create_print_value(self.0, value.0, msg.as_ptr()); + } + } + + // unreachable + + pub fn create_unreachable(&self, msg: &CStr) { + unsafe { + bridge::compiler_peer_create_unreachable(self.0, msg.as_ptr()); } } } @@ -974,18 +1200,20 @@ impl ClosureIr { } } -impl ValueIr { - pub const NONE: Self = Self(std::ptr::null_mut()); - +impl CoroutineIr { pub fn get_name_or_as_operand<'a>(&self, buf: *mut std::ffi::c_char, len: usize) -> &'a CStr { unsafe { - bridge::helper_peer_get_value_name_or_as_operand(self.0, buf, len); + bridge::helper_peer_get_value_name_or_as_operand( + self.0 as *mut bridge::ValueIr, + buf, + len, + ); std::ffi::CStr::from_ptr(buf) } } } -impl ArgvIr { +impl PromiseIr { pub const NONE: Self = Self(std::ptr::null_mut()); pub fn get_name_or_as_operand<'a>(&self, buf: *mut std::ffi::c_char, len: usize) -> &'a CStr { @@ -1000,6 +1228,21 @@ impl ArgvIr { } } +impl ValueIr { + pub const NONE: Self = Self(std::ptr::null_mut()); + + pub fn get_name_or_as_operand<'a>(&self, buf: *mut std::ffi::c_char, len: usize) -> &'a CStr { + unsafe { + bridge::helper_peer_get_value_name_or_as_operand(self.0, buf, len); + std::ffi::CStr::from_ptr(buf) + } + } +} + +impl ArgvIr { + pub const NONE: Self = Self(std::ptr::null_mut()); +} + impl CaptureIr { pub fn get_name_or_as_operand<'a>(&self, buf: *mut std::ffi::c_char, len: usize) -> &'a CStr { unsafe { diff --git a/libs/jsruntime/src/llvmir/executor.cc b/libs/jsruntime/src/llvmir/executor.cc index 1ee8b5eac..1d374e5d2 100644 --- a/libs/jsruntime/src/llvmir/executor.cc +++ b/libs/jsruntime/src/llvmir/executor.cc @@ -1,9 +1,21 @@ #include "executor.hh" #include +#include #include "module.hh" +namespace { + +// TODO(perf): Inefficient. Use a fixed size buffer for formatting func_id. +std::string FuncIdToName(uint32_t func_id) { + std::stringstream ss; + ss << "fn" << func_id; + return ss.str(); +} + +} // namespace + static llvm::ExitOnError ExitOnErr; // static @@ -12,8 +24,9 @@ llvm::Expected Executor::Create() { return new Executor(std::move(jit)); } -void Executor::RegisterHostFunction(const char* name, Lambda lambda) { +void Executor::RegisterHostFunction(uint32_t func_id, Lambda lambda) { llvm::orc::SymbolMap symbols; + auto name = FuncIdToName(func_id); symbols[exec_session().intern(name)] = { llvm::orc::ExecutorAddr::fromPtr(lambda), llvm::JITSymbolFlags::Exported, @@ -25,7 +38,8 @@ void Executor::RegisterModule(Module* mod) { ExitOnErr(jit_->addIRModule(std::move(mod->mod))); } -Lambda Executor::GetNativeFunction(const char* name) { +Lambda Executor::GetNativeFunction(uint32_t func_id) { + auto name = FuncIdToName(func_id); auto addr = ExitOnErr(jit_->lookup(name)); return addr.toPtr(); } diff --git a/libs/jsruntime/src/llvmir/executor.hh b/libs/jsruntime/src/llvmir/executor.hh index cec983aea..6bfb3dc73 100644 --- a/libs/jsruntime/src/llvmir/executor.hh +++ b/libs/jsruntime/src/llvmir/executor.hh @@ -24,9 +24,9 @@ class Executor { ~Executor() = default; void RegisterRuntime(const Runtime* runtime); - void RegisterHostFunction(const char* name, Lambda lambda); + void RegisterHostFunction(uint32_t func_id, Lambda lambda); void RegisterModule(Module* mod); - Lambda GetNativeFunction(const char* name); + Lambda GetNativeFunction(uint32_t func_id); llvm::orc::ExecutionSession& exec_session() { return jit_->getExecutionSession(); diff --git a/libs/jsruntime/src/llvmir/executor.rs b/libs/jsruntime/src/llvmir/executor.rs index fef56b49b..72642bef3 100644 --- a/libs/jsruntime/src/llvmir/executor.rs +++ b/libs/jsruntime/src/llvmir/executor.rs @@ -1,6 +1,6 @@ use std::ffi::CStr; -use std::ffi::CString; +use crate::FunctionId; use crate::HostLambda; use super::bridge; @@ -20,10 +20,9 @@ impl Executor { Self { peer } } - pub fn register_host_function(&self, name: &str, func: HostLambda) { - let name = CString::new(name).unwrap(); + pub fn register_host_function(&self, func_id: FunctionId, func: HostLambda) { unsafe { - bridge::executor_peer_register_host_function(self.peer, name.as_ptr(), Some(func)); + bridge::executor_peer_register_host_function(self.peer, func_id.into(), Some(func)); } } @@ -41,8 +40,8 @@ impl Executor { unsafe { CStr::from_ptr(bridge::executor_peer_get_target_triple(self.peer)) } } - pub fn get_native_function(&self, name: &CStr) -> bridge::Lambda { - unsafe { bridge::executor_peer_get_native_function(self.peer, name.as_ptr()) } + pub fn get_native_function(&self, func_id: FunctionId) -> bridge::Lambda { + unsafe { bridge::executor_peer_get_native_function(self.peer, func_id.into()) } } } diff --git a/libs/jsruntime/src/llvmir/mod.rs b/libs/jsruntime/src/llvmir/mod.rs index 2e302347c..b04ad5407 100644 --- a/libs/jsruntime/src/llvmir/mod.rs +++ b/libs/jsruntime/src/llvmir/mod.rs @@ -3,6 +3,8 @@ mod compiler; mod executor; pub use bridge::runtime_bridge; +pub use bridge::Coroutine; +pub use bridge::CoroutineStatus; pub use bridge::ReturnValue; pub use bridge::Status; pub use bridge::Value; diff --git a/libs/jsruntime/src/llvmir/runtime.js b/libs/jsruntime/src/llvmir/runtime.js index de87593cc..3e5bec290 100644 --- a/libs/jsruntime/src/llvmir/runtime.js +++ b/libs/jsruntime/src/llvmir/runtime.js @@ -37,7 +37,7 @@ async function main(args, options) { const runtimeSpec = yaml.parse(runtimeYaml); for (const func of runtimeSpec.functions) { - func.args = [{ name: 'context', type: 'opaque' }].concat(func.args).map(({ name, type }) => { + func.args = [{ name: 'runtime', type: 'VoidPtr' }].concat(func.args).map(({ name, type }) => { return { name, type, ctype: makeCType(type), llvmir_type: makeLLVMIRType(type) }; }); func.c_type = makeCFunc(func); @@ -68,10 +68,12 @@ function makeLLVMIRType(type) { case '&mut Variable': case '&mut Capture': case '&mut Closure': + case '&mut Coroutine': case '&Value': case '&mut Value': + case '*mut Value': case 'Lambda': - case 'opaque': + case 'VoidPtr': return 'builder_.getPtrTy()'; case undefined: return 'builder_.getVoidTy()'; @@ -101,14 +103,17 @@ function makeCType(type) { return 'Capture*'; case '&mut Closure': return 'Closure*'; + case '&mut Coroutine': + return 'Coroutine*'; case '&Value': return 'const Value*'; case '&mut Value': + case '*mut Value': return 'Value*'; case 'Lambda': return 'Lambda'; - case 'opaque': - return 'uintptr_t'; + case 'VoidPtr': + return 'void*'; case undefined: return 'void'; default: diff --git a/libs/jsruntime/src/llvmir/runtime.yaml b/libs/jsruntime/src/llvmir/runtime.yaml index 0055f4e3c..3a3b430d3 100644 --- a/libs/jsruntime/src/llvmir/runtime.yaml +++ b/libs/jsruntime/src/llvmir/runtime.yaml @@ -41,10 +41,40 @@ functions: - name: create_closure args: - name: lambda - type: 'Lambda' + type: Lambda - name: num_captures - type: 'u16' + type: u16 ret: '&mut Closure' + - name: create_coroutine + args: + - name: closure + type: '&mut Closure' + - name: num_locals + type: u16 + - name: scratch_buffer_len + type: u16 + ret: '&mut Coroutine' + - name: register_promise + args: + - name: coroutine + type: '&mut Coroutine' + ret: 'u32' + - name: await_promise + args: + - name: promise + type: u32 + - name: awaiting + type: u32 + - name: resume + args: + - name: promise + type: u32 + - name: emit_promise_resolved + args: + - name: promise + type: u32 + - name: result + type: '&Value' - name: assert args: - name: assertion @@ -54,6 +84,18 @@ functions: - name: print_u32 args: - name: value - type: 'u32' + type: u32 + - name: msg + type: '&std::ffi::CStr' + - name: print_f64 + args: + - name: value + type: f64 + - name: msg + type: '&std::ffi::CStr' + - name: print_value + args: + - name: value + type: '&Value' - name: msg type: '&std::ffi::CStr' diff --git a/libs/jsruntime/src/llvmir/type_holder.cc.njk b/libs/jsruntime/src/llvmir/type_holder.cc.njk index ae478285a..6a0c3fc22 100644 --- a/libs/jsruntime/src/llvmir/type_holder.cc.njk +++ b/libs/jsruntime/src/llvmir/type_holder.cc.njk @@ -55,28 +55,49 @@ llvm::StructType* TypeHolder::CreateClosureType() { builder_.getPtrTy(), // num_captures builder_.getInt16Ty(), - // captures + // captures[] builder_.getPtrTy(), }); } return closure_type_; } +llvm::StructType* TypeHolder::CreateCoroutineType() { + if (coroutine_type_ == nullptr) { + coroutine_type_ = llvm::StructType::create(context_, "Coroutine"); + coroutine_type_->setBody({ + // closure + builder_.getPtrTy(), + // state + builder_.getInt32Ty(), + // num_locals + builder_.getInt16Ty(), + // scope_id + builder_.getInt16Ty(), + // scrach_buffer_len, + builder_.getInt16Ty(), + // locals[] + CreateValueType(), + }); + } + return coroutine_type_; +} + llvm::FunctionType* TypeHolder::CreateLambdaType() { if (lambda_type_ == nullptr) { lambda_type_ = llvm::FunctionType::get( // status code builder_.getInt32Ty(), { - // ctx (pointer to the exec context) + // runtime (pointer to the runtime) builder_.getPtrTy(), - // caps (pointer to a list of captures) + // context (pointer to a contextual data) builder_.getPtrTy(), // argc GetWordType(), // argv (pointer to a list of values) builder_.getPtrTy(), - // ret (pointer to a return value) + // retv (pointer to a return value) builder_.getPtrTy(), }, false); diff --git a/libs/jsruntime/src/llvmir/type_holder.hh.njk b/libs/jsruntime/src/llvmir/type_holder.hh.njk index 075717bc3..2a60f047c 100644 --- a/libs/jsruntime/src/llvmir/type_holder.hh.njk +++ b/libs/jsruntime/src/llvmir/type_holder.hh.njk @@ -28,6 +28,7 @@ class TypeHolder { llvm::StructType* CreateValueType(); llvm::StructType* CreateCaptureType(); llvm::StructType* CreateClosureType(); + llvm::StructType* CreateCoroutineType(); llvm::FunctionType* CreateLambdaType(); {%- for function in data.functions %} llvm::Function* CreateRuntime{{ function.name | pascalCase }}(); @@ -40,6 +41,7 @@ class TypeHolder { llvm::StructType* value_type_ = nullptr; llvm::StructType* capture_type_ = nullptr; llvm::StructType* closure_type_ = nullptr; + llvm::StructType* coroutine_type_ = nullptr; llvm::FunctionType* lambda_type_ = nullptr; {%- for function in data.functions %} llvm::Function* runtime_{{ function.name }}_ = nullptr; diff --git a/libs/jsruntime/src/semantics/mod.rs b/libs/jsruntime/src/semantics/mod.rs index 108da9190..8c3935899 100644 --- a/libs/jsruntime/src/semantics/mod.rs +++ b/libs/jsruntime/src/semantics/mod.rs @@ -1,5 +1,6 @@ mod scope; +use bitflags::bitflags; use indexmap::IndexMap; use jsparser::syntax::AssignmentOperator; @@ -30,7 +31,7 @@ impl Runtime { /// Parses a given source text as a script. pub fn parse_script(&mut self, source: &str) -> Result { logger::debug!(event = "parse", source_kind = "script"); - let mut analyzer = Analyzer::new( + let mut analyzer = Analyzer::new_for_script( &self.pref, &mut self.symbol_registry, &mut self.function_registry, @@ -39,6 +40,19 @@ impl Runtime { let processor = Processor::new(analyzer, false); Parser::for_script(source, processor).parse() } + + /// Parses a given source text as a module. + pub fn parse_module(&mut self, source: &str) -> Result { + logger::debug!(event = "parse", source_kind = "module"); + let mut analyzer = Analyzer::new_for_module( + &self.pref, + &mut self.symbol_registry, + &mut self.function_registry, + ); + analyzer.use_global_bindings(); + let processor = Processor::new(analyzer, true); + Parser::for_module(source, processor).parse() + } } /// A type representing a JavaScript program after the semantic analysis. @@ -62,7 +76,7 @@ impl Program { /// A type representing a JavaScript function after the semantic analysis. #[derive(Default)] pub struct FunctionRecipe { - /// TODO: remove? + // TODO: remove? pub symbol: Symbol, /// The function ID of the function. @@ -106,6 +120,7 @@ pub struct Capture { /// /// A semantic analyzer analyzes semantics of a JavaScript program. struct Analyzer<'r> { + #[allow(unused)] runtime_pref: &'r RuntimePref, /// A mutable reference to a symbol registry. @@ -125,16 +140,37 @@ struct Analyzer<'r> { scope_tree_builder: ScopeTreeBuilder, use_global_bindings: bool, + + module: bool, } impl<'r> Analyzer<'r> { /// Creates a semantic analyzer. - pub fn new( + fn new_for_script( + runtime_pref: &'r RuntimePref, + symbol_registry: &'r mut SymbolRegistry, + function_registry: &'r mut FunctionRegistry, + ) -> Self { + Self::new(runtime_pref, symbol_registry, function_registry, false) + } + + /// Creates a semantic analyzer. + fn new_for_module( runtime_pref: &'r RuntimePref, symbol_registry: &'r mut SymbolRegistry, function_registry: &'r mut FunctionRegistry, ) -> Self { - let _ = function_registry.create_native_function(); + Self::new(runtime_pref, symbol_registry, function_registry, true) + } + + fn new( + runtime_pref: &'r RuntimePref, + symbol_registry: &'r mut SymbolRegistry, + function_registry: &'r mut FunctionRegistry, + module: bool, + ) -> Self { + // TODO: modules including await expressions. + let _ = function_registry.create_native_function(false); Self { runtime_pref, symbol_registry, @@ -143,13 +179,38 @@ impl<'r> Analyzer<'r> { functions: vec![], scope_tree_builder: Default::default(), use_global_bindings: false, + module, } } - pub fn use_global_bindings(&mut self) { + fn use_global_bindings(&mut self) { self.use_global_bindings = true; } + fn set_in_body(&mut self) { + self.context_stack + .last_mut() + .unwrap() + .flags + .insert(FunctionContextFlags::IN_BODY); + } + + fn set_async(&mut self) { + self.context_stack + .last_mut() + .unwrap() + .flags + .insert(FunctionContextFlags::ASYNC); + } + + fn is_async(&self) -> bool { + self.context_stack + .last() + .unwrap() + .flags + .contains(FunctionContextFlags::ASYNC) + } + /// Handles an AST node coming from a parser. fn handle_node(&mut self, node: Node<'_>) { logger::debug!(event = "handle_node", ?node); @@ -219,8 +280,12 @@ impl<'r> Analyzer<'r> { Node::FormalParameter => self.handle_formal_parameter(), Node::FormalParameters(n) => self.handle_formal_parameters(n), Node::FunctionDeclaration => self.handle_function_declaration(), + Node::AsyncFunctionDeclaration => self.handle_async_function_declaration(), Node::FunctionExpression(named) => self.handle_function_expression(named), + Node::AsyncFunctionExpression(named) => self.handle_async_function_expression(named), Node::ArrowFunction => self.handle_arrow_function(), + Node::AsyncArrowFunction => self.handle_async_arrow_function(), + Node::AwaitExpression => self.handle_await_expression(), Node::ThenBlock => self.handle_then_block(), Node::ElseBlock => self.handle_else_block(), Node::FalsyShortCircuit => self.handle_falsy_short_circuit(), @@ -239,6 +304,7 @@ impl<'r> Analyzer<'r> { Node::StartBlockScope => self.handle_start_block_scope(), Node::EndBlockScope => self.handle_end_block_scope(), Node::FunctionContext => self.handle_function_context(), + Node::AsyncFunctionContext => self.handle_async_function_context(), Node::FunctionSignature(symbol) => self.handle_function_signature(symbol), } } @@ -530,16 +596,16 @@ impl<'r> Analyzer<'r> { self.scope_tree_builder.pop(); self.resolve_references(&mut context); - if context.num_locals > 0 { + if context.flags.contains(FunctionContextFlags::COROUTINE) { + // The local variables allocated on the heap will be passed as arguments for the + // coroutine. Load the local variables from the environment at first. + context.commands[0] = CompileCommand::Environment(context.num_locals); + debug_assert!(context.coroutine.state <= u16::MAX as u32); + context.commands[1] = CompileCommand::JumpTable(context.coroutine.state + 2); + } else { context.commands[0] = CompileCommand::AllocateLocals(context.num_locals); } - if self.runtime_pref.enable_scope_cleanup_checker { - let stack_size = self.scope_tree_builder.max_stack_size(context.scope_ref); - debug_assert!(stack_size > 0); - context.commands[1] = CompileCommand::SetupScopeCleanupChecker(stack_size); - } - let func_index = context.func_index; let func = &mut self.functions[func_index]; func.commands = context.commands; @@ -562,6 +628,13 @@ impl<'r> Analyzer<'r> { ); } + fn handle_async_function_declaration(&mut self) { + self.end_coroutine_body(); + + // Node::FunctionDeclaration for the outer ramp function. + self.handle_function_declaration(); + } + fn handle_function_expression(&mut self, named: bool) { let func_index = self.end_function_scope(); let func = &self.functions[func_index]; @@ -577,6 +650,13 @@ impl<'r> Analyzer<'r> { ); } + fn handle_async_function_expression(&mut self, named: bool) { + self.end_coroutine_body(); + + // Node::FunctionExpression for the outer ramp function. + self.handle_function_expression(named); + } + fn handle_arrow_function(&mut self) { // TODO: An ArrowFunction does not define local bindings for arguments, super, this, or // new.target. Any reference to arguments, super, this, or new.target within an @@ -596,10 +676,23 @@ impl<'r> Analyzer<'r> { ); } + fn handle_async_arrow_function(&mut self) { + self.end_coroutine_body(); + + // Node::ArrowFunction for the outer ramp function. + self.handle_arrow_function() + } + + fn handle_await_expression(&mut self) { + let next_state = self.context_stack.last().unwrap().coroutine.state + 1; + self.put_command(CompileCommand::Await(next_state)); + self.context_stack.last_mut().unwrap().coroutine.state = next_state; + } + fn handle_then_block(&mut self) { let context = self.context_stack.last_mut().unwrap(); context.put_command(CompileCommand::Truthy); - context.put_command(CompileCommand::Then); + context.put_command(CompileCommand::IfThen); } fn handle_else_block(&mut self) { @@ -694,7 +787,7 @@ impl<'r> Analyzer<'r> { self.scope_tree_builder.pop(); } - fn start_function_scope(&mut self, in_body: bool) { + fn start_function_scope(&mut self) { let scope_ref = self.scope_tree_builder.push_function(); // TODO: the compilation should fail if the following condition is unmet. @@ -703,17 +796,13 @@ impl<'r> Analyzer<'r> { let mut context = FunctionContext { func_index, scope_ref, - in_body, ..Default::default() }; // `commands[0]` will be replaced with `AllocateLocals` if the function has local // variables. context.commands.push(CompileCommand::Nop); - if self.runtime_pref.enable_scope_cleanup_checker { - // Put a placeholder command which will be replaced with `SetupScopeCleanupChecker`. - let index = context.put_command(CompileCommand::Nop); - debug_assert_eq!(index, 1); - } + // `commands[1]` will be replaced with `JumpTable` if the function is a coroutine. + context.commands.push(CompileCommand::Nop); context.start_scope(scope_ref); self.context_stack.push(context); // Push a placeholder data which will be filled later. @@ -721,16 +810,70 @@ impl<'r> Analyzer<'r> { } fn handle_function_context(&mut self) { - self.start_function_scope(false); + self.start_function_scope(); } - fn handle_function_signature(&mut self, symbol: Symbol) { - let context = self.context_stack.last_mut().unwrap(); - let id = self.function_registry.create_native_function(); - let func_index = context.func_index; + fn handle_async_function_context(&mut self) { + self.start_function_scope(); + self.set_async(); + } + + fn set_function_symbol(&mut self, symbol: Symbol) { + let id = self + .function_registry + .create_native_function(symbol == Symbol::HIDDEN_COROUTINE); + let func_index = self.context_stack.last().unwrap().func_index; self.functions[func_index].symbol = symbol; self.functions[func_index].id = id; - context.in_body = true; + } + + fn handle_function_signature(&mut self, symbol: Symbol) { + self.set_function_symbol(symbol); + self.set_in_body(); + + if self.is_async() { + self.start_coroutine_body(); + } + } + + // The async function is translated into a ramp function. The ramp function creates a + // coroutine every time it's called. The coroutine function body is built from the async + // function body. It will be rewritten into a state machine for the coroutine. + // + // See //libs/jsruntime/docs/internals.md for details. + // + // TODO(perf): We never optimize an async function which has no await expression in the body. + // Such an async function don't need to be rewritten into a state machine. + fn start_coroutine_body(&mut self) { + self.handle_function_context(); + self.scope_tree_builder.set_coroutine(); + self.handle_binding_identifier(Symbol::HIDDEN_PROMISE); + self.handle_formal_parameter(); + self.handle_binding_identifier(Symbol::HIDDEN_RESULT); + self.handle_formal_parameter(); + self.handle_binding_identifier(Symbol::HIDDEN_ERROR); + self.handle_formal_parameter(); + self.handle_formal_parameters(3); + self.handle_function_signature(Symbol::HIDDEN_COROUTINE); + + let context = self.context_stack.last_mut().unwrap(); + + context.flags.insert(FunctionContextFlags::COROUTINE); + } + + // Generate compile commands for the bottom-half of the coroutine. + // See //libs/jsruntime/docs/internals.md. + fn end_coroutine_body(&mut self) { + // TODO(perf): Some of the local variables can be placed on the stack. + let context = self.context_stack.last().unwrap(); + let func_id = self.functions[context.func_index].id; + let num_locals = context.num_locals; + self.handle_function_expression(false); + self.put_command(CompileCommand::Coroutine(func_id, num_locals)); + self.put_command(CompileCommand::Promise); + self.put_command(CompileCommand::Duplicate(0)); + self.put_command(CompileCommand::Resume); + self.put_command(CompileCommand::Return(1)); } fn put_command(&mut self, command: CompileCommand) { @@ -776,11 +919,10 @@ impl<'r> Analyzer<'r> { let context = self.context_stack.last_mut().unwrap(); for (func_id, host_func) in self.function_registry.enumerate_host_function() { - let symbol = self.symbol_registry.intern_cstr(&host_func.name); - context.put_reference(symbol, self.scope_tree_builder.current()); + context.put_reference(host_func.symbol, self.scope_tree_builder.current()); context.process_closure_declaration(self.scope_tree_builder.current(), func_id, &[]); self.scope_tree_builder - .add_immutable(symbol, context.num_locals); + .add_immutable(host_func.symbol, context.num_locals); context.num_locals += 1; } } @@ -794,7 +936,7 @@ impl<'r> Analyzer<'r> { // TODO: refactoring fn resolve_reference(&mut self, context: &mut FunctionContext, reference: &Reference) { let binding_ref = self.scope_tree_builder.resolve_reference(reference); - logger::debug!(event = "resolve-reference", ?reference, ?binding_ref); + logger::debug!(event = "resolve_reference", ?reference, ?binding_ref); if binding_ref == BindingRef::NONE { // This is a reference to a free variable. @@ -852,18 +994,29 @@ impl<'r, 's> NodeHandler<'s> for Analyzer<'r> { fn start(&mut self) { logger::debug!(event = "start"); - self.start_function_scope(true); + self.start_function_scope(); + + self.set_in_body(); if self.use_global_bindings { self.put_global_bindings(); } self.register_host_functions(); + + // The module is always treated as an async function body. + if self.module { + self.start_coroutine_body(); + } } fn accept(&mut self) -> Result { logger::debug!(event = "accept"); + if self.module { + self.end_coroutine_body(); + } + let mut context = self.context_stack.pop().unwrap(); context.end_scope(); @@ -871,15 +1024,7 @@ impl<'r, 's> NodeHandler<'s> for Analyzer<'r> { self.resolve_references(&mut context); debug_assert!(context.captures.is_empty()); - if context.num_locals > 0 { - context.commands[0] = CompileCommand::AllocateLocals(context.num_locals); - } - - if self.runtime_pref.enable_scope_cleanup_checker { - let stack_size = self.scope_tree_builder.max_stack_size(context.scope_ref); - debug_assert!(stack_size > 0); - context.commands[1] = CompileCommand::SetupScopeCleanupChecker(stack_size); - } + context.commands[0] = CompileCommand::AllocateLocals(context.num_locals); self.functions[context.func_index].commands = context.commands; self.functions[context.func_index].captures = context.captures.into_values().collect(); @@ -946,13 +1091,16 @@ struct FunctionContext { /// A stack to hold [`TryContext`]s. try_stack: Vec, + coroutine: CoroutineContext, + /// A stack to hold the number of arguments of a function call. - nargs_stack: Vec<(usize, u16)>, + nargs_stack: Vec, /// The index of the function in [`Analyzer::functions`]. func_index: usize, - /// A reference to a function scope in the scope tree. + /// A reference to the function scope in the scope tree. + #[allow(unused)] scope_ref: ScopeRef, num_locals: u16, @@ -961,8 +1109,21 @@ struct FunctionContext { num_for_statements: u16, num_switch_statements: u16, - /// `false` while analyzing formal parameters, `true` while analyzing the function body. - in_body: bool, + flags: FunctionContextFlags, +} + +bitflags! { + #[derive(Debug, Default)] + struct FunctionContextFlags: u8 { + /// Enabled while analyzing the function body. + const IN_BODY = 0b00000001; + + /// Enabled if the context is the ramp function for an async function. + const ASYNC = 0b00000010; + + /// Enabled if the context is the coroutine function for an async function. + const COROUTINE = 0b00000100; + } } impl FunctionContext { @@ -1005,31 +1166,16 @@ impl FunctionContext { fn process_argument_list_head(&mut self, empty: bool, _spread: bool) { // TODO: spread - - // The placeholder command will be replaced with `CompileCommand::Arguments` in in - // `process_call_expression()`. - let index = self.put_command(CompileCommand::PlaceHolder); - let nargs = if empty { - 0 - } else { - self.commands.push(CompileCommand::Swap); - self.commands.push(CompileCommand::Argument(0)); - 1 - }; - self.nargs_stack.push((index, nargs)); + self.nargs_stack.push(if empty { 0 } else { 1 }); } fn put_argument(&mut self, _spread: bool) { // TODO: spread - let tuple = self.nargs_stack.last_mut().unwrap(); - self.commands.push(CompileCommand::Argument(tuple.1)); - tuple.1 += 1; + *self.nargs_stack.last_mut().unwrap() += 1; } fn process_call_expression(&mut self) { - let (index, nargs) = self.nargs_stack.pop().unwrap(); - debug_assert!(matches!(self.commands[index], CompileCommand::PlaceHolder)); - self.commands[index] = CompileCommand::Arguments(nargs); + let nargs = self.nargs_stack.pop().unwrap(); self.commands.push(CompileCommand::Call(nargs)); } @@ -1051,7 +1197,7 @@ impl FunctionContext { } fn process_binding_identifier(&mut self, symbol: Symbol, builder: &mut ScopeTreeBuilder) { - if self.in_body { + if self.flags.contains(FunctionContextFlags::IN_BODY) { self.put_reference(symbol, builder.current()); // The BindingKind may change later by `builder.set_immutable()`. builder.add_mutable(symbol, self.num_locals); @@ -1211,7 +1357,7 @@ impl FunctionContext { // Step#3..7 in 14.12.4 Runtime Semantics: Evaluation self.start_scope(scope_ref); - // The placeholder command will be replaced in `process_switch_statement()`. + // The placeholder commands will be replaced in `process_switch_statement()`. let case_block_index = self.put_command(CompileCommand::PlaceHolder); self.switch_stack.push(SwitchContext { case_block_index, @@ -1220,8 +1366,10 @@ impl FunctionContext { } fn process_case_selector(&mut self) { + // Make a duplicate of the `switchValue` for the evaluation on the case selector. + self.put_command(CompileCommand::Duplicate(1)); self.put_command(CompileCommand::StrictEquality); - self.put_command(CompileCommand::Then); + self.put_command(CompileCommand::Case); } fn process_case_clause(&mut self, has_statement: bool) { @@ -1230,13 +1378,11 @@ impl FunctionContext { } fn process_default_selector(&mut self) { - self.put_command(CompileCommand::Discard); - // TODO: refactoring - self.put_command(CompileCommand::Then); + self.put_command(CompileCommand::Default); } fn process_default_clause(&mut self, has_statement: bool) { - self.put_command(CompileCommand::DefaultClause(has_statement)); + self.put_command(CompileCommand::CaseClause(has_statement)); let context = self.switch_stack.last_mut().unwrap(); context.default_index = Some(context.num_cases); context.num_cases += 1; @@ -1256,13 +1402,13 @@ impl FunctionContext { CompileCommand::PlaceHolder )); if num_cases == 0 { - // empty case block - // Discard the `switchValue`. + // An empty case block. Just discard the `switchValue`. self.commands[case_block_index] = CompileCommand::Discard; } else { self.commands[case_block_index] = CompileCommand::CaseBlock(id, num_cases); - let i = default_index; - self.put_command(CompileCommand::Switch(id, num_cases, i)); + // Discard the `switchValue` remaining on the stack. + self.put_command(CompileCommand::Discard); + self.put_command(CompileCommand::Switch(id, num_cases, default_index)); self.num_switch_statements += 1; } @@ -1387,6 +1533,11 @@ struct TryContext { finally_index: usize, } +#[derive(Default)] +struct CoroutineContext { + state: u32, +} + /// A compile command. #[derive(Debug, PartialEq)] pub enum CompileCommand { @@ -1399,6 +1550,8 @@ pub enum CompileCommand { String(Vec), Function(FunctionId), Closure(bool, u16), + Coroutine(FunctionId, u16), + Promise, Reference(Symbol, Locator), Exception, @@ -1407,8 +1560,6 @@ pub enum CompileCommand { ImmutableBinding, DeclareFunction, DeclareClosure, - Arguments(u16), - Argument(u16), Call(u16), PushScope(ScopeRef), PopScope(ScopeRef), @@ -1491,7 +1642,7 @@ pub enum CompileCommand { // conditional Truthy, - Then, + IfThen, Else, IfElseStatement, IfStatement, @@ -1508,8 +1659,9 @@ pub enum CompileCommand { // switch CaseBlock(u16, u16), + Case, + Default, CaseClause(bool), - DefaultClause(bool), Switch(u16, u16, Option), // label @@ -1527,12 +1679,16 @@ pub enum CompileCommand { Return(u32), Throw, + // coroutine + Environment(u16), + JumpTable(u32), + Await(u32), + Resume, + Discard, Swap, Duplicate(u8), // 0 or 1 - SetupScopeCleanupChecker(u16), - // A special command used as a placeholder in a command list, which will be replaced actual // command later. The final command list must not contain placeholder commands. PlaceHolder, @@ -1711,72 +1867,90 @@ mod tests { }; } + macro_rules! script { + ($src:literal) => { + Source::Script($src) + }; + } + + macro_rules! module { + ($src:literal) => { + Source::Module($src) + }; + } + #[test] fn test_lexical_declarations() { - test("let a, b = 2; const c = 3, d = 4;", |reg, program| { - assert_eq!( - program.functions[0].commands, - [ - CompileCommand::AllocateLocals(4), - CompileCommand::SetupScopeCleanupChecker(1), - CompileCommand::PushScope(scope_ref!(1)), - CompileCommand::Reference(symbol!(reg, "a"), locator!(local: 0)), - CompileCommand::Undefined, - CompileCommand::MutableBinding, - CompileCommand::Reference(symbol!(reg, "b"), locator!(local: 1)), - CompileCommand::Number(2.0), - CompileCommand::MutableBinding, - CompileCommand::Reference(symbol!(reg, "c"), locator!(local: 2)), - CompileCommand::Number(3.0), - CompileCommand::ImmutableBinding, - CompileCommand::Reference(symbol!(reg, "d"), locator!(local: 3)), - CompileCommand::Number(4.0), - CompileCommand::ImmutableBinding, - CompileCommand::PopScope(scope_ref!(1)), - ] - ); - }); + test( + script!("let a, b = 2; const c = 3, d = 4;"), + |program, sreg, _freg| { + assert_eq!( + program.functions[0].commands, + [ + CompileCommand::AllocateLocals(4), + CompileCommand::Nop, + CompileCommand::PushScope(scope_ref!(1)), + CompileCommand::Reference(symbol!(sreg, "a"), locator!(local: 0)), + CompileCommand::Undefined, + CompileCommand::MutableBinding, + CompileCommand::Reference(symbol!(sreg, "b"), locator!(local: 1)), + CompileCommand::Number(2.0), + CompileCommand::MutableBinding, + CompileCommand::Reference(symbol!(sreg, "c"), locator!(local: 2)), + CompileCommand::Number(3.0), + CompileCommand::ImmutableBinding, + CompileCommand::Reference(symbol!(sreg, "d"), locator!(local: 3)), + CompileCommand::Number(4.0), + CompileCommand::ImmutableBinding, + CompileCommand::PopScope(scope_ref!(1)), + ] + ); + }, + ); } #[test] fn test_lexical_declarations_in_scopes() { - test("let a; { let a; } { let a, b; }", |reg, program| { - assert_eq!( - program.functions[0].commands, - [ - CompileCommand::AllocateLocals(4), - CompileCommand::SetupScopeCleanupChecker(2), - CompileCommand::PushScope(scope_ref!(1)), - CompileCommand::Reference(symbol!(reg, "a"), locator!(local: 0)), - CompileCommand::Undefined, - CompileCommand::MutableBinding, - CompileCommand::PushScope(scope_ref!(2)), - CompileCommand::Reference(symbol!(reg, "a"), locator!(local: 1)), - CompileCommand::Undefined, - CompileCommand::MutableBinding, - CompileCommand::PopScope(scope_ref!(2)), - CompileCommand::PushScope(scope_ref!(3)), - CompileCommand::Reference(symbol!(reg, "a"), locator!(local: 2)), - CompileCommand::Undefined, - CompileCommand::MutableBinding, - CompileCommand::Reference(symbol!(reg, "b"), locator!(local: 3)), - CompileCommand::Undefined, - CompileCommand::MutableBinding, - CompileCommand::PopScope(scope_ref!(3)), - CompileCommand::PopScope(scope_ref!(1)), - ] - ); - }); + test( + script!("let a; { let a; } { let a, b; }"), + |program, sreg, _freg| { + assert_eq!( + program.functions[0].commands, + [ + CompileCommand::AllocateLocals(4), + CompileCommand::Nop, + CompileCommand::PushScope(scope_ref!(1)), + CompileCommand::Reference(symbol!(sreg, "a"), locator!(local: 0)), + CompileCommand::Undefined, + CompileCommand::MutableBinding, + CompileCommand::PushScope(scope_ref!(2)), + CompileCommand::Reference(symbol!(sreg, "a"), locator!(local: 1)), + CompileCommand::Undefined, + CompileCommand::MutableBinding, + CompileCommand::PopScope(scope_ref!(2)), + CompileCommand::PushScope(scope_ref!(3)), + CompileCommand::Reference(symbol!(sreg, "a"), locator!(local: 2)), + CompileCommand::Undefined, + CompileCommand::MutableBinding, + CompileCommand::Reference(symbol!(sreg, "b"), locator!(local: 3)), + CompileCommand::Undefined, + CompileCommand::MutableBinding, + CompileCommand::PopScope(scope_ref!(3)), + CompileCommand::PopScope(scope_ref!(1)), + ] + ); + }, + ); } #[test] fn test_binary_operator() { - test("1 + 2", |_reg, program| { + test(script!("1 + 2"), |program, _sreg, _freg| { assert_eq!( program.functions[0].commands, [ + CompileCommand::AllocateLocals(0), CompileCommand::Nop, - CompileCommand::SetupScopeCleanupChecker(1), CompileCommand::PushScope(scope_ref!(1)), CompileCommand::Number(1.0), CompileCommand::Number(2.0), @@ -1791,17 +1965,17 @@ mod tests { #[test] fn test_shorthand_assignment_operator() { - test("let a = 1; a += 2", |reg, program| { + test(script!("let a = 1; a += 2"), |program, sreg, _freg| { assert_eq!( program.functions[0].commands, [ CompileCommand::AllocateLocals(1), - CompileCommand::SetupScopeCleanupChecker(1), + CompileCommand::Nop, CompileCommand::PushScope(scope_ref!(1)), - CompileCommand::Reference(symbol!(reg, "a"), locator!(local: 0)), + CompileCommand::Reference(symbol!(sreg, "a"), locator!(local: 0)), CompileCommand::Number(1.0), CompileCommand::MutableBinding, - CompileCommand::Reference(symbol!(reg, "a"), locator!(local: 0)), + CompileCommand::Reference(symbol!(sreg, "a"), locator!(local: 0)), CompileCommand::Number(2.0), CompileCommand::Duplicate(1), CompileCommand::Addition, @@ -1813,24 +1987,81 @@ mod tests { }); } - fn test(regc: &str, validate: fn(symbol_registry: &SymbolRegistry, program: &Program)) { + #[test] + fn test_await() { + test(module!("await 0"), |program, _sreg, _freg| { + assert_eq!(program.functions.len(), 2); + assert_eq!( + program.functions[0].commands, + [ + CompileCommand::AllocateLocals(0), + CompileCommand::Nop, + CompileCommand::PushScope(scope_ref!(1)), + CompileCommand::Function(program.functions[1].id), + CompileCommand::Closure(false, 0), + CompileCommand::Coroutine(program.functions[1].id, 0), + CompileCommand::Promise, + CompileCommand::Duplicate(0), + CompileCommand::Resume, + CompileCommand::Return(1), + CompileCommand::PopScope(scope_ref!(1)), + ], + ); + assert_eq!( + program.functions[1].commands, + [ + CompileCommand::Environment(0), + CompileCommand::JumpTable(3), + CompileCommand::PushScope(scope_ref!(2)), + CompileCommand::Number(0.0), + CompileCommand::Await(1), + CompileCommand::Discard, + CompileCommand::PopScope(scope_ref!(2)), + ], + ); + }); + } + + fn test(src: Source, validate: fn(&Program, &SymbolRegistry, &FunctionRegistry)) { let runtime_pref = RuntimePref { enable_scope_cleanup_checker: true, ..Default::default() }; let mut symbol_registry = Default::default(); let mut function_registry = FunctionRegistry::new(); - let result = Parser::for_script( - regc, - Processor::new( - Analyzer::new(&runtime_pref, &mut symbol_registry, &mut function_registry), - false, + let mut parser = match src { + Source::Script(src) => Parser::for_script( + src, + Processor::new( + Analyzer::new_for_script( + &runtime_pref, + &mut symbol_registry, + &mut function_registry, + ), + false, + ), ), - ) - .parse(); + Source::Module(src) => Parser::for_module( + src, + Processor::new( + Analyzer::new_for_module( + &runtime_pref, + &mut symbol_registry, + &mut function_registry, + ), + true, + ), + ), + }; + let result = parser.parse(); assert!(result.is_ok()); if let Ok(program) = result { - validate(&symbol_registry, &program) + validate(&program, &symbol_registry, &function_registry) } } + + enum Source { + Script(&'static str), + Module(&'static str), + } } diff --git a/libs/jsruntime/src/semantics/scope.rs b/libs/jsruntime/src/semantics/scope.rs index 36e62033c..44ce6d429 100644 --- a/libs/jsruntime/src/semantics/scope.rs +++ b/libs/jsruntime/src/semantics/scope.rs @@ -89,15 +89,6 @@ impl ScopeTree { scope.bindings[binding_ref.binding_index()].symbol } - pub fn compute_locator(&self, binding_ref: BindingRef) -> Locator { - let scope = &self.scopes[binding_ref.scope_index()]; - let binding = &scope.bindings[binding_ref.binding_index()]; - match binding.kind { - BindingKind::FormalParameter => Locator::Argument(binding.index), - _ => Locator::Local(binding.index), - } - } - #[allow(unused)] pub fn print(&self, indent: &str) { for (index, scope) in self.scopes.iter().enumerate().skip(1) { @@ -122,6 +113,12 @@ impl ScopeTreeBuilder { self.push(ScopeFlags::FUNCTION, "") } + pub fn set_coroutine(&mut self) { + let scope = &mut self.scopes[self.current.index()]; + debug_assert!(scope.is_function()); + scope.flags.insert(ScopeFlags::COROUTINE); + } + pub fn push_block(&mut self, label: &'static str) -> ScopeRef { self.push(ScopeFlags::empty(), label) } @@ -168,7 +165,7 @@ impl ScopeTreeBuilder { symbol, index: index as u16, kind: BindingKind::FormalParameter, - captured: false, + flags: BindingFlags::empty(), }); scope.num_formal_parameters += 1; } @@ -180,7 +177,7 @@ impl ScopeTreeBuilder { symbol, index, kind: BindingKind::Mutable, - captured: false, + flags: BindingFlags::empty(), }); scope.num_locals += 1; } @@ -192,7 +189,20 @@ impl ScopeTreeBuilder { symbol, index, kind: BindingKind::Immutable, - captured: false, + flags: BindingFlags::empty(), + }); + scope.num_locals += 1; + } + + #[allow(unused)] + pub fn add_hidden(&mut self, symbol: Symbol, index: u16) { + let scope = &mut self.scopes[self.current.index()]; + debug_assert!(!scope.is_sorted()); + scope.bindings.push(Binding { + symbol, + index, + kind: BindingKind::Mutable, + flags: BindingFlags::HIDDEN, }); scope.num_locals += 1; } @@ -209,10 +219,11 @@ impl ScopeTreeBuilder { pub fn set_captured(&mut self, binding_ref: BindingRef) { let scope = &mut self.scopes[binding_ref.scope_index()]; debug_assert!(scope.is_sorted()); - scope.bindings[binding_ref.binding_index()].captured = true; + scope.bindings[binding_ref.binding_index()].set_captured(); } - pub fn max_stack_size(&self, scope_ref: ScopeRef) -> u16 { + #[allow(unused)] + pub fn max_scope_depth(&self, scope_ref: ScopeRef) -> u16 { let scope = &self.scopes[scope_ref.index()]; debug_assert!(scope.max_child_block_depth >= scope.depth); scope.max_child_block_depth - scope.depth + 1 @@ -299,6 +310,10 @@ impl Scope { self.flags.contains(ScopeFlags::FUNCTION) } + pub fn is_coroutine(&self) -> bool { + self.flags.contains(ScopeFlags::COROUTINE) + } + fn is_sorted(&self) -> bool { self.flags.contains(ScopeFlags::SORTED) } @@ -315,7 +330,9 @@ impl<'a> std::fmt::Display for ScopePrinter<'a> { if !self.scope.is_sorted() { write!(f, "*")?; } - if self.scope.is_function() { + if self.scope.is_coroutine() { + write!(f, "C")?; + } else if self.scope.is_function() { write!(f, "F")?; } else { write!(f, "B")?; @@ -333,8 +350,9 @@ impl<'a> std::fmt::Display for ScopePrinter<'a> { bitflags! { struct ScopeFlags: u8 { - const FUNCTION = 0b01; - const SORTED = 0b10; + const FUNCTION = 0b00000001; + const COROUTINE = 0b00000010; + const SORTED = 0b10000000; } } @@ -343,7 +361,7 @@ pub struct Binding { pub symbol: Symbol, pub index: u16, pub kind: BindingKind, - pub captured: bool, + flags: BindingFlags, } impl Binding { @@ -357,25 +375,48 @@ impl Binding { _ => Locator::Local(self.index), } } + + pub fn is_captured(&self) -> bool { + self.flags.contains(BindingFlags::CAPTURED) + } + + fn set_captured(&mut self) { + self.flags.insert(BindingFlags::CAPTURED) + } + + pub fn is_hidden(&self) -> bool { + self.flags.contains(BindingFlags::HIDDEN) + } } impl std::fmt::Display for Binding { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_captured() { + write!(f, "*")?; + } + if self.is_hidden() { + write!(f, "?")?; + } match self.kind { BindingKind::FormalParameter => write!(f, "P@{}:{}", self.index, self.symbol)?, BindingKind::Mutable => write!(f, "M@{}:{}", self.index, self.symbol)?, BindingKind::Immutable => write!(f, "I@{}:{}", self.index, self.symbol)?, } - if self.captured { - write!(f, "*")?; - } Ok(()) } } -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum BindingKind { FormalParameter, Mutable, Immutable, } + +bitflags! { + #[derive(Debug)] + struct BindingFlags: u8 { + const CAPTURED = 0b00000001; + const HIDDEN = 0b10000000; + } +} diff --git a/libs/jsruntime/src/tasklet.rs b/libs/jsruntime/src/tasklet.rs new file mode 100644 index 000000000..72d02a08b --- /dev/null +++ b/libs/jsruntime/src/tasklet.rs @@ -0,0 +1,203 @@ +use std::collections::VecDeque; + +use rustc_hash::FxHashMap; + +use crate::llvmir::Coroutine; +use crate::llvmir::CoroutineStatus; +use crate::Runtime; +use crate::Value; + +impl Runtime { + /// Perform all tasklets. + pub fn run(&mut self) { + while let Some(msg) = self.tasklet_system.next_msg() { + self.handle_message(msg); + } + } + + fn handle_message(&mut self, msg: Message) { + crate::logger::debug!(event = "handle_message", ?msg); + match msg { + Message::PromiseResolved { + promise, + ref result, + } => self.process_promise(promise, result, &Value::NONE), + Message::PromiseRejected { promise, ref error } => { + self.process_promise(promise, &Value::NONE, error) + } + } + } + + // promise + + pub fn register_promise(&mut self, coroutine: *mut Coroutine) -> Promise { + crate::logger::debug!(event = "register_promise", ?coroutine); + self.tasklet_system.register_promise(coroutine) + } + + pub fn await_promise(&mut self, promise: Promise, awaiting: Promise) { + crate::logger::debug!(event = "await_promise", ?promise, ?awaiting); + self.tasklet_system.await_promise(promise, awaiting); + } + + pub fn process_promise(&mut self, promise: Promise, result: &Value, error: &Value) { + crate::logger::debug!(event = "process_promise", ?promise, ?result, ?error); + let coroutine = self.tasklet_system.get_coroutine(promise); + match Coroutine::resume(self.as_void_ptr(), coroutine, promise, result, error) { + CoroutineStatus::Done(result) => self.tasklet_system.resolve_promise(promise, result), + CoroutineStatus::Error(error) => self.tasklet_system.reject_promise(promise, error), + CoroutineStatus::Suspend => (), + } + } + + pub fn emit_promise_resolved(&mut self, promise: Promise, result: Value) { + self.tasklet_system.emit_promise_resolved(promise, result); + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Promise(u32); + +impl From for Promise { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: Promise) -> Self { + value.0 + } +} + +pub struct System { + messages: VecDeque, + promises: FxHashMap, + next_promise: u32, +} + +impl System { + pub fn new() -> Self { + Self { + messages: Default::default(), + promises: Default::default(), + next_promise: 0, + } + } + + // promises + + fn register_promise(&mut self, coroutine: *mut Coroutine) -> Promise { + let promise = self.new_promise(); + self.promises.insert(promise, PromiseDriver::new(coroutine)); + promise + } + + fn new_promise(&mut self) -> Promise { + assert!(self.promises.len() < u32::MAX as usize); + loop { + let promise = Promise(self.next_promise); + if !self.promises.contains_key(&promise) { + return promise; + } + self.next_promise = self.next_promise.wrapping_add(1); + } + // never reach here + } + + fn await_promise(&mut self, promise: Promise, awaiting: Promise) { + debug_assert!(self.promises.contains_key(&promise)); + debug_assert!(self.promises.contains_key(&awaiting)); + let driver = self.promises.get_mut(&promise).unwrap(); + debug_assert!(driver.awaiting.is_none()); + match driver.state { + PromiseState::Pending => driver.awaiting = Some(awaiting), + PromiseState::Resolved(result) => { + self.emit_promise_resolved(awaiting, result); + self.promises.remove(&promise); + } + PromiseState::Rejected(error) => { + self.emit_promise_rejected(awaiting, error); + self.promises.remove(&promise); + } + } + } + + fn get_coroutine(&self, promise: Promise) -> *mut Coroutine { + self.promises.get(&promise).unwrap().coroutine + } + + fn emit_promise_resolved(&mut self, promise: Promise, result: Value) { + crate::logger::debug!(event = "emit_promise_resolved", ?promise, ?result); + self.messages + .push_back(Message::PromiseResolved { promise, result }); + } + + fn emit_promise_rejected(&mut self, promise: Promise, error: Value) { + crate::logger::debug!(event = "emit_promise_rejected", ?promise, ?error); + self.messages + .push_back(Message::PromiseRejected { promise, error }); + } + + fn next_msg(&mut self) -> Option { + self.messages.pop_front() + } + + fn resolve_promise(&mut self, promise: Promise, result: Value) { + crate::logger::debug!(event = "resolve_promise", ?promise, ?result); + let driver = self.promises.get_mut(&promise).unwrap(); + debug_assert!(matches!(driver.state, PromiseState::Pending)); + if let Some(awaiting) = driver.awaiting { + self.promises.remove(&promise); + self.emit_promise_resolved(awaiting, result); + } else { + driver.state = PromiseState::Resolved(result); + } + } + + fn reject_promise(&mut self, promise: Promise, error: Value) { + crate::logger::debug!(event = "reject_promise", ?promise, ?error); + let driver = self.promises.get_mut(&promise).unwrap(); + debug_assert!(matches!(driver.state, PromiseState::Pending)); + if let Some(awaiting) = driver.awaiting { + self.promises.remove(&promise); + self.emit_promise_rejected(awaiting, error); + } else { + driver.state = PromiseState::Rejected(error); + } + } +} + +// messages + +#[derive(Debug)] +enum Message { + PromiseResolved { promise: Promise, result: Value }, + PromiseRejected { promise: Promise, error: Value }, +} + +// promise + +// TODO: should the coroutine be separated from the promise? +struct PromiseDriver { + // TODO(issue#237): GcCellRef + coroutine: *mut Coroutine, + awaiting: Option, + state: PromiseState, +} + +impl PromiseDriver { + fn new(coroutine: *mut Coroutine) -> Self { + Self { + coroutine, + awaiting: None, + state: PromiseState::Pending, + } + } +} + +enum PromiseState { + Pending, + Resolved(Value), + Rejected(Value), +} diff --git a/libs/jsruntime/tests/Makefile b/libs/jsruntime/tests/Makefile index 3e23dd406..47e2bf1ac 100644 --- a/libs/jsruntime/tests/Makefile +++ b/libs/jsruntime/tests/Makefile @@ -5,7 +5,7 @@ TOOLS_BIN := $(PROJ_DIR)/tools/bin CODEGEN_TARGETS := evaluate.rs -EVALUATE_TESTS := $(wildcard evaluate_*.js) +EVALUATE_TESTS := $(wildcard scripts/*.js) $(wildcard modules/*.mjs) .PHONY: codegen codegen: evaluate.rs diff --git a/libs/jsruntime/tests/README.md b/libs/jsruntime/tests/README.md index 07abc29cf..886415eb2 100644 --- a/libs/jsruntime/tests/README.md +++ b/libs/jsruntime/tests/README.md @@ -2,7 +2,8 @@ ## How to write a test for `Runtime::evaluate()` -Add a JavaScript file `evaluate_.js` and put JavaScript code: +Add a JavaScript file `scripts/test_name.js` or `modules/test_name.mjs` and put JavaScript +code: ```javascript print(undefined); ///=Value::UNDEFINED @@ -19,5 +20,8 @@ as template parameters for [`evaluate.rs.njk`](./evalute.rs.njk) and rendered in An uncaught exception is expressed in a special line comment starting with `///!`. `collect_evaluate_test.js` also collects its value. +For testing `async` functions, use `///#=` instead. This set the expected +value at the specified position in the list of expected values. + When you modify a test, you **MUST** run `make codegen` in order to create or update `evalute.rs`. Then, `make test`. diff --git a/libs/jsruntime/tests/collect_evaluate_tests.js b/libs/jsruntime/tests/collect_evaluate_tests.js index 72b3f1881..818b38d9b 100644 --- a/libs/jsruntime/tests/collect_evaluate_tests.js +++ b/libs/jsruntime/tests/collect_evaluate_tests.js @@ -35,11 +35,20 @@ async function main(args, options) { log.debug(`Reading ${test}...`); const script = await Deno.readTextFile(test); const lines = script.split('\n').map((line) => line.trim()); - const expectedValues = lines.filter((line) => line.includes('///=')).map((line) => line.split('///=')[1].trim()); + const sequencedValues = lines.filter((line) => line.includes('///=')) + .map((line) => line.split('///=')[1].trim()); + const orderedValues = lines + .filter((line) => line.includes('///#')) + .map((line) => line.split('///#')[1].trim().split('=')) + .reduce((acc, [i, v]) => { acc[i] = v; return acc; }, []); const throws = lines.find((line) => line.includes('///!'))?.split('///!')[1].trim(); + const name = path.basename(test).replace('.', '_'); + const module = test.endsWith('.js') ? false : true; tests.push({ - name: path.basename(test, '.js'), - expectedValues, + filename: test, + name, + module, + expectedValues: sequencedValues.length > 0 ? sequencedValues : orderedValues, throws, }); } diff --git a/libs/jsruntime/tests/evaluate.rs.njk b/libs/jsruntime/tests/evaluate.rs.njk index ba4d2c04b..ab99544ba 100644 --- a/libs/jsruntime/tests/evaluate.rs.njk +++ b/libs/jsruntime/tests/evaluate.rs.njk @@ -35,7 +35,7 @@ impl Drop for Validator { } } -fn evaluate(src: &str, optimize: bool, enable_labels: bool, expected_values: Vec) -> Result { +fn evaluate(src: &str, module: bool, optimize: bool, enable_labels: bool, expected_values: Vec) -> Result { let mut runtime = Runtime::with_extension(Validator { expected_values, actual_values: vec![], @@ -55,9 +55,15 @@ fn evaluate(src: &str, optimize: bool, enable_labels: bool, expected_values: Vec #[allow(clippy::clone_on_copy)] runtime.extension_mut().actual_values.push(args[0].clone()); }); - let program = runtime.parse_script(src).unwrap(); + let program = if module { + runtime.parse_module(src).unwrap() + } else { + runtime.parse_script(src).unwrap() + }; let module = runtime.compile(&program, optimize).unwrap(); - runtime.evaluate(module) + let result = runtime.evaluate(module)?; + runtime.run(); + Ok(result) } {%- for test in data.tests %} @@ -77,8 +83,8 @@ fn {{ test.name }}_with_labels() { } fn {{ test.name }}(optimize: bool, enable_labels: bool) { - let src = include_str!("{{ test.name }}.js"); - let result = evaluate(src, optimize, enable_labels, vec![ + let src = include_str!("{{ test.filename }}"); + let result = evaluate(src, {{ test.module }}, optimize, enable_labels, vec![ {%- for expected in test.expectedValues %} {%- if expected === "Value::UNDEFINED" %} {{ expected }}, diff --git a/libs/jsruntime/tests/modules/async_temp_vars.mjs b/libs/jsruntime/tests/modules/async_temp_vars.mjs new file mode 100644 index 000000000..70e0e356f --- /dev/null +++ b/libs/jsruntime/tests/modules/async_temp_vars.mjs @@ -0,0 +1,2 @@ +const a = 1; +print(1 + a + await 1); ///=3 diff --git a/libs/jsruntime/tests/modules/await.mjs b/libs/jsruntime/tests/modules/await.mjs new file mode 100644 index 000000000..7d2b8279e --- /dev/null +++ b/libs/jsruntime/tests/modules/await.mjs @@ -0,0 +1,9 @@ +print(0); ///#0=0 +await a(); +print(3); ///#3=3 + +async function a() { + print(1); ///#1=1 + await 0; + print(2); ///#2=2 +} diff --git a/libs/jsruntime/tests/modules/await_in_arguments.mjs b/libs/jsruntime/tests/modules/await_in_arguments.mjs new file mode 100644 index 000000000..91eecd52e --- /dev/null +++ b/libs/jsruntime/tests/modules/await_in_arguments.mjs @@ -0,0 +1,20 @@ +test(await undefined, await null, await true, await 1, await x, y(), await 300); + +async function test(undef, nul, bool, number, closure, promise, last) { + print(undef); ///=Value::UNDEFINED + print(nul); ///=Value::NULL + print(bool); ///=true + print(number); ///=1 + closure(); ///=100 + print(await promise); ///=200 + print(last); ///=300 +} + +function x() { + print(100); +} + +async function y() { + await 0; + return 200; +} diff --git a/libs/jsruntime/tests/modules/await_in_if_else.mjs b/libs/jsruntime/tests/modules/await_in_if_else.mjs new file mode 100644 index 000000000..4b9b00f12 --- /dev/null +++ b/libs/jsruntime/tests/modules/await_in_if_else.mjs @@ -0,0 +1,9 @@ +print(0); ///#0=0 +if (false) { + await 0; +} else { + print(1); ///#1=1 + await 0; + print(2); ///#2=2 +} +print(3); ///#3=3 diff --git a/libs/jsruntime/tests/modules/await_in_loop.mjs b/libs/jsruntime/tests/modules/await_in_loop.mjs new file mode 100644 index 000000000..fec4de2fa --- /dev/null +++ b/libs/jsruntime/tests/modules/await_in_loop.mjs @@ -0,0 +1,9 @@ +for (let i = 0; i < 5; ++i) { + await 0; + ///=0 + ///=1 + ///=2 + ///=3 + ///=4 + print(i); +} diff --git a/libs/jsruntime/tests/modules/await_in_switch_case.mjs b/libs/jsruntime/tests/modules/await_in_switch_case.mjs new file mode 100644 index 000000000..b15223b9a --- /dev/null +++ b/libs/jsruntime/tests/modules/await_in_switch_case.mjs @@ -0,0 +1,12 @@ +print(0); ///#0=0 +switch (await 0) { + case await 0: + print(1); ///#1=1 + await 0; + print(2); ///#2=2 + break; + default: + print(100); + break; +} +print(3); ///#3=3 diff --git a/libs/jsruntime/tests/evaluate_addition.js b/libs/jsruntime/tests/scripts/addition.js similarity index 100% rename from libs/jsruntime/tests/evaluate_addition.js rename to libs/jsruntime/tests/scripts/addition.js diff --git a/libs/jsruntime/tests/evaluate_addition_assignment.js b/libs/jsruntime/tests/scripts/addition_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_addition_assignment.js rename to libs/jsruntime/tests/scripts/addition_assignment.js diff --git a/libs/jsruntime/tests/evaluate_anonymous_function_expression.js b/libs/jsruntime/tests/scripts/anonymous_function_expression.js similarity index 100% rename from libs/jsruntime/tests/evaluate_anonymous_function_expression.js rename to libs/jsruntime/tests/scripts/anonymous_function_expression.js diff --git a/libs/jsruntime/tests/evaluate_argument_assignment.js b/libs/jsruntime/tests/scripts/argument_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_argument_assignment.js rename to libs/jsruntime/tests/scripts/argument_assignment.js diff --git a/libs/jsruntime/tests/evaluate_argument_of_enclosing_function.js b/libs/jsruntime/tests/scripts/argument_of_enclosing_function.js similarity index 100% rename from libs/jsruntime/tests/evaluate_argument_of_enclosing_function.js rename to libs/jsruntime/tests/scripts/argument_of_enclosing_function.js diff --git a/libs/jsruntime/tests/evaluate_arithmetic_operations_with_variables.js b/libs/jsruntime/tests/scripts/arithmetic_operations_with_variables.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arithmetic_operations_with_variables.js rename to libs/jsruntime/tests/scripts/arithmetic_operations_with_variables.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_expression_body.js b/libs/jsruntime/tests/scripts/arrow_function_binding_identifier_expression_body.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_expression_body.js rename to libs/jsruntime/tests/scripts/arrow_function_binding_identifier_expression_body.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_expression_body_iife.js b/libs/jsruntime/tests/scripts/arrow_function_binding_identifier_expression_body_iife.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_expression_body_iife.js rename to libs/jsruntime/tests/scripts/arrow_function_binding_identifier_expression_body_iife.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_function_body.js b/libs/jsruntime/tests/scripts/arrow_function_binding_identifier_function_body.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_function_body.js rename to libs/jsruntime/tests/scripts/arrow_function_binding_identifier_function_body.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_function_body_iife.js b/libs/jsruntime/tests/scripts/arrow_function_binding_identifier_function_body_iife.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_binding_identifier_function_body_iife.js rename to libs/jsruntime/tests/scripts/arrow_function_binding_identifier_function_body_iife.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_empty_parameter_list.js b/libs/jsruntime/tests/scripts/arrow_function_empty_parameter_list.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_empty_parameter_list.js rename to libs/jsruntime/tests/scripts/arrow_function_empty_parameter_list.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_expression_body.js b/libs/jsruntime/tests/scripts/arrow_function_expression_body.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_expression_body.js rename to libs/jsruntime/tests/scripts/arrow_function_expression_body.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_expression_body_iife.js b/libs/jsruntime/tests/scripts/arrow_function_expression_body_iife.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_expression_body_iife.js rename to libs/jsruntime/tests/scripts/arrow_function_expression_body_iife.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_function_body.js b/libs/jsruntime/tests/scripts/arrow_function_function_body.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_function_body.js rename to libs/jsruntime/tests/scripts/arrow_function_function_body.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_function_body_iife.js b/libs/jsruntime/tests/scripts/arrow_function_function_body_iife.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_function_body_iife.js rename to libs/jsruntime/tests/scripts/arrow_function_function_body_iife.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_multiple_parameters.js b/libs/jsruntime/tests/scripts/arrow_function_multiple_parameters.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_multiple_parameters.js rename to libs/jsruntime/tests/scripts/arrow_function_multiple_parameters.js diff --git a/libs/jsruntime/tests/evaluate_arrow_function_trailing_comma.js b/libs/jsruntime/tests/scripts/arrow_function_trailing_comma.js similarity index 100% rename from libs/jsruntime/tests/evaluate_arrow_function_trailing_comma.js rename to libs/jsruntime/tests/scripts/arrow_function_trailing_comma.js diff --git a/libs/jsruntime/tests/scripts/async_throw.js b/libs/jsruntime/tests/scripts/async_throw.js new file mode 100644 index 000000000..7b00e6475 --- /dev/null +++ b/libs/jsruntime/tests/scripts/async_throw.js @@ -0,0 +1,8 @@ +print(0); ///#0=0 +a(); +print(2); ///#2=2 + +async function a() { + print(1); ///#1=1 + throw 100; +} diff --git a/libs/jsruntime/tests/scripts/await.js b/libs/jsruntime/tests/scripts/await.js new file mode 100644 index 000000000..223221ffb --- /dev/null +++ b/libs/jsruntime/tests/scripts/await.js @@ -0,0 +1,9 @@ +print(0); ///#0=0 +a(); +print(2); ///#2=2 + +async function a() { + print(1); ///#1=1 + await 0; + print(3); ///#3=3 +} diff --git a/libs/jsruntime/tests/scripts/await_async_arrow_function.js b/libs/jsruntime/tests/scripts/await_async_arrow_function.js new file mode 100644 index 000000000..e72e64721 --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_arrow_function.js @@ -0,0 +1,8 @@ +print(0); ///#0=0 +let a = async () => { + print(1); ///#1=1 + await 0; + print(3); ///#3=3 +}; +a(); +print(2); ///#2=2 diff --git a/libs/jsruntime/tests/scripts/await_async_arrow_function_iife.js b/libs/jsruntime/tests/scripts/await_async_arrow_function_iife.js new file mode 100644 index 000000000..45d11fdcd --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_arrow_function_iife.js @@ -0,0 +1,7 @@ +print(0); ///#0=0 +(async () => { + print(1); ///#1=1 + await 0; + print(3); ///#3=3 +})(); +print(2); ///#2=2 diff --git a/libs/jsruntime/tests/scripts/await_async_arrow_function_single_param.js b/libs/jsruntime/tests/scripts/await_async_arrow_function_single_param.js new file mode 100644 index 000000000..7aa5d7851 --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_arrow_function_single_param.js @@ -0,0 +1,8 @@ +print(0); ///#0=0 +let a = async x => { + print(1); ///#1=1 + await x; + print(3); ///#3=3 +}; +a(0); +print(2); ///#2=2 diff --git a/libs/jsruntime/tests/scripts/await_async_arrow_function_single_param_iife.js b/libs/jsruntime/tests/scripts/await_async_arrow_function_single_param_iife.js new file mode 100644 index 000000000..c1eb2cd67 --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_arrow_function_single_param_iife.js @@ -0,0 +1,7 @@ +print(0); ///#0=0 +(async x => { + print(1); ///#1=1 + await x; + print(3); ///#3=3 +})(0); +print(2); ///#2=2 diff --git a/libs/jsruntime/tests/scripts/await_async_func.js b/libs/jsruntime/tests/scripts/await_async_func.js new file mode 100644 index 000000000..c1f8fd960 --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_func.js @@ -0,0 +1,15 @@ +print(0); ///#0=0 +a(); +print(3); ///#3=3 + +async function a() { + print(1); ///#1=1 + await b(); + print(5); ///#5=5 +} + +async function b() { + print(2); ///#2=2 + await 0; + print(4); ///#4=4 +} diff --git a/libs/jsruntime/tests/scripts/await_async_function_expression.js b/libs/jsruntime/tests/scripts/await_async_function_expression.js new file mode 100644 index 000000000..bb13befbc --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_function_expression.js @@ -0,0 +1,8 @@ +print(0); ///#0=0 +let a = async function() { + print(1); ///#1=1 + await 0; + print(3); ///#3=3 +}; +a(); +print(2); ///#2=2 diff --git a/libs/jsruntime/tests/scripts/await_async_function_expression_iife.js b/libs/jsruntime/tests/scripts/await_async_function_expression_iife.js new file mode 100644 index 000000000..b6eeeeb78 --- /dev/null +++ b/libs/jsruntime/tests/scripts/await_async_function_expression_iife.js @@ -0,0 +1,7 @@ +print(0); ///#0=0 +(async function() { + print(1); ///#1=1 + await 0; + print(3); ///#3=3 +})(); +print(2); ///#2=2 diff --git a/libs/jsruntime/tests/evaluate_bitwise_and.js b/libs/jsruntime/tests/scripts/bitwise_and.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_and.js rename to libs/jsruntime/tests/scripts/bitwise_and.js diff --git a/libs/jsruntime/tests/evaluate_bitwise_and_assignment.js b/libs/jsruntime/tests/scripts/bitwise_and_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_and_assignment.js rename to libs/jsruntime/tests/scripts/bitwise_and_assignment.js diff --git a/libs/jsruntime/tests/evaluate_bitwise_not.js b/libs/jsruntime/tests/scripts/bitwise_not.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_not.js rename to libs/jsruntime/tests/scripts/bitwise_not.js diff --git a/libs/jsruntime/tests/evaluate_bitwise_or.js b/libs/jsruntime/tests/scripts/bitwise_or.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_or.js rename to libs/jsruntime/tests/scripts/bitwise_or.js diff --git a/libs/jsruntime/tests/evaluate_bitwise_or_assignment.js b/libs/jsruntime/tests/scripts/bitwise_or_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_or_assignment.js rename to libs/jsruntime/tests/scripts/bitwise_or_assignment.js diff --git a/libs/jsruntime/tests/evaluate_bitwise_xor.js b/libs/jsruntime/tests/scripts/bitwise_xor.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_xor.js rename to libs/jsruntime/tests/scripts/bitwise_xor.js diff --git a/libs/jsruntime/tests/evaluate_bitwise_xor_assignment.js b/libs/jsruntime/tests/scripts/bitwise_xor_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_bitwise_xor_assignment.js rename to libs/jsruntime/tests/scripts/bitwise_xor_assignment.js diff --git a/libs/jsruntime/tests/evaluate_block_statement.js b/libs/jsruntime/tests/scripts/block_statement.js similarity index 100% rename from libs/jsruntime/tests/evaluate_block_statement.js rename to libs/jsruntime/tests/scripts/block_statement.js diff --git a/libs/jsruntime/tests/evaluate_block_statement_2.js b/libs/jsruntime/tests/scripts/block_statement_2.js similarity index 100% rename from libs/jsruntime/tests/evaluate_block_statement_2.js rename to libs/jsruntime/tests/scripts/block_statement_2.js diff --git a/libs/jsruntime/tests/evaluate_boolean.js b/libs/jsruntime/tests/scripts/boolean.js similarity index 100% rename from libs/jsruntime/tests/evaluate_boolean.js rename to libs/jsruntime/tests/scripts/boolean.js diff --git a/libs/jsruntime/tests/evaluate_break.js b/libs/jsruntime/tests/scripts/break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_break.js rename to libs/jsruntime/tests/scripts/break.js diff --git a/libs/jsruntime/tests/evaluate_call_another_function.js b/libs/jsruntime/tests/scripts/call_another_function.js similarity index 100% rename from libs/jsruntime/tests/evaluate_call_another_function.js rename to libs/jsruntime/tests/scripts/call_another_function.js diff --git a/libs/jsruntime/tests/evaluate_call_with_no_argument.js b/libs/jsruntime/tests/scripts/call_with_no_argument.js similarity index 100% rename from libs/jsruntime/tests/evaluate_call_with_no_argument.js rename to libs/jsruntime/tests/scripts/call_with_no_argument.js diff --git a/libs/jsruntime/tests/evaluate_call_with_no_argument_hoistable_declaration.js b/libs/jsruntime/tests/scripts/call_with_no_argument_hoistable_declaration.js similarity index 100% rename from libs/jsruntime/tests/evaluate_call_with_no_argument_hoistable_declaration.js rename to libs/jsruntime/tests/scripts/call_with_no_argument_hoistable_declaration.js diff --git a/libs/jsruntime/tests/evaluate_closure_assignment.js b/libs/jsruntime/tests/scripts/closure_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_closure_assignment.js rename to libs/jsruntime/tests/scripts/closure_assignment.js diff --git a/libs/jsruntime/tests/evaluate_closure_assignment_workaround.js b/libs/jsruntime/tests/scripts/closure_assignment_workaround.js similarity index 100% rename from libs/jsruntime/tests/evaluate_closure_assignment_workaround.js rename to libs/jsruntime/tests/scripts/closure_assignment_workaround.js diff --git a/libs/jsruntime/tests/evaluate_closure_escape.js b/libs/jsruntime/tests/scripts/closure_escape.js similarity index 100% rename from libs/jsruntime/tests/evaluate_closure_escape.js rename to libs/jsruntime/tests/scripts/closure_escape.js diff --git a/libs/jsruntime/tests/evaluate_closure_escape_workaround.js b/libs/jsruntime/tests/scripts/closure_escape_workaround.js similarity index 100% rename from libs/jsruntime/tests/evaluate_closure_escape_workaround.js rename to libs/jsruntime/tests/scripts/closure_escape_workaround.js diff --git a/libs/jsruntime/tests/evaluate_closure_nested.js b/libs/jsruntime/tests/scripts/closure_nested.js similarity index 100% rename from libs/jsruntime/tests/evaluate_closure_nested.js rename to libs/jsruntime/tests/scripts/closure_nested.js diff --git a/libs/jsruntime/tests/evaluate_closure_nested_workaround.js b/libs/jsruntime/tests/scripts/closure_nested_workaround.js similarity index 100% rename from libs/jsruntime/tests/evaluate_closure_nested_workaround.js rename to libs/jsruntime/tests/scripts/closure_nested_workaround.js diff --git a/libs/jsruntime/tests/evaluate_comma_operator.js b/libs/jsruntime/tests/scripts/comma_operator.js similarity index 100% rename from libs/jsruntime/tests/evaluate_comma_operator.js rename to libs/jsruntime/tests/scripts/comma_operator.js diff --git a/libs/jsruntime/tests/evaluate_const_declaration.js b/libs/jsruntime/tests/scripts/const_declaration.js similarity index 100% rename from libs/jsruntime/tests/evaluate_const_declaration.js rename to libs/jsruntime/tests/scripts/const_declaration.js diff --git a/libs/jsruntime/tests/evaluate_continue.js b/libs/jsruntime/tests/scripts/continue.js similarity index 100% rename from libs/jsruntime/tests/evaluate_continue.js rename to libs/jsruntime/tests/scripts/continue.js diff --git a/libs/jsruntime/tests/evaluate_deadcode_after_break.js b/libs/jsruntime/tests/scripts/deadcode_after_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_deadcode_after_break.js rename to libs/jsruntime/tests/scripts/deadcode_after_break.js diff --git a/libs/jsruntime/tests/evaluate_deadcode_after_continue.js b/libs/jsruntime/tests/scripts/deadcode_after_continue.js similarity index 100% rename from libs/jsruntime/tests/evaluate_deadcode_after_continue.js rename to libs/jsruntime/tests/scripts/deadcode_after_continue.js diff --git a/libs/jsruntime/tests/evaluate_division.js b/libs/jsruntime/tests/scripts/division.js similarity index 100% rename from libs/jsruntime/tests/evaluate_division.js rename to libs/jsruntime/tests/scripts/division.js diff --git a/libs/jsruntime/tests/evaluate_division_assignment.js b/libs/jsruntime/tests/scripts/division_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_division_assignment.js rename to libs/jsruntime/tests/scripts/division_assignment.js diff --git a/libs/jsruntime/tests/evaluate_do_while_statement.js b/libs/jsruntime/tests/scripts/do_while_statement.js similarity index 100% rename from libs/jsruntime/tests/evaluate_do_while_statement.js rename to libs/jsruntime/tests/scripts/do_while_statement.js diff --git a/libs/jsruntime/tests/evaluate_equality.js b/libs/jsruntime/tests/scripts/equality.js similarity index 100% rename from libs/jsruntime/tests/evaluate_equality.js rename to libs/jsruntime/tests/scripts/equality.js diff --git a/libs/jsruntime/tests/evaluate_fibonacci.js b/libs/jsruntime/tests/scripts/fibonacci.js similarity index 100% rename from libs/jsruntime/tests/evaluate_fibonacci.js rename to libs/jsruntime/tests/scripts/fibonacci.js diff --git a/libs/jsruntime/tests/evaluate_for_statement.js b/libs/jsruntime/tests/scripts/for_statement.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement.js rename to libs/jsruntime/tests/scripts/for_statement.js diff --git a/libs/jsruntime/tests/evaluate_for_statement_no_init.js b/libs/jsruntime/tests/scripts/for_statement_no_init.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement_no_init.js rename to libs/jsruntime/tests/scripts/for_statement_no_init.js diff --git a/libs/jsruntime/tests/evaluate_for_statement_no_init_next.js b/libs/jsruntime/tests/scripts/for_statement_no_init_next.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement_no_init_next.js rename to libs/jsruntime/tests/scripts/for_statement_no_init_next.js diff --git a/libs/jsruntime/tests/evaluate_for_statement_no_init_test.js b/libs/jsruntime/tests/scripts/for_statement_no_init_test.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement_no_init_test.js rename to libs/jsruntime/tests/scripts/for_statement_no_init_test.js diff --git a/libs/jsruntime/tests/evaluate_for_statement_no_init_test_next.js b/libs/jsruntime/tests/scripts/for_statement_no_init_test_next.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement_no_init_test_next.js rename to libs/jsruntime/tests/scripts/for_statement_no_init_test_next.js diff --git a/libs/jsruntime/tests/evaluate_for_statement_no_test.js b/libs/jsruntime/tests/scripts/for_statement_no_test.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement_no_test.js rename to libs/jsruntime/tests/scripts/for_statement_no_test.js diff --git a/libs/jsruntime/tests/evaluate_for_statement_no_test_next.js b/libs/jsruntime/tests/scripts/for_statement_no_test_next.js similarity index 100% rename from libs/jsruntime/tests/evaluate_for_statement_no_test_next.js rename to libs/jsruntime/tests/scripts/for_statement_no_test_next.js diff --git a/libs/jsruntime/tests/evaluate_function_expression.js b/libs/jsruntime/tests/scripts/function_expression.js similarity index 100% rename from libs/jsruntime/tests/evaluate_function_expression.js rename to libs/jsruntime/tests/scripts/function_expression.js diff --git a/libs/jsruntime/tests/evaluate_function_single_name_binding.js b/libs/jsruntime/tests/scripts/function_single_name_binding.js similarity index 100% rename from libs/jsruntime/tests/evaluate_function_single_name_binding.js rename to libs/jsruntime/tests/scripts/function_single_name_binding.js diff --git a/libs/jsruntime/tests/evaluate_grouping.js b/libs/jsruntime/tests/scripts/grouping.js similarity index 100% rename from libs/jsruntime/tests/evaluate_grouping.js rename to libs/jsruntime/tests/scripts/grouping.js diff --git a/libs/jsruntime/tests/evaluate_if_else_statement_false.js b/libs/jsruntime/tests/scripts/if_else_statement_false.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_else_statement_false.js rename to libs/jsruntime/tests/scripts/if_else_statement_false.js diff --git a/libs/jsruntime/tests/evaluate_if_else_statement_return_in_block_false.js b/libs/jsruntime/tests/scripts/if_else_statement_return_in_block_false.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_else_statement_return_in_block_false.js rename to libs/jsruntime/tests/scripts/if_else_statement_return_in_block_false.js diff --git a/libs/jsruntime/tests/evaluate_if_else_statement_return_in_block_true.js b/libs/jsruntime/tests/scripts/if_else_statement_return_in_block_true.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_else_statement_return_in_block_true.js rename to libs/jsruntime/tests/scripts/if_else_statement_return_in_block_true.js diff --git a/libs/jsruntime/tests/evaluate_if_else_statement_true.js b/libs/jsruntime/tests/scripts/if_else_statement_true.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_else_statement_true.js rename to libs/jsruntime/tests/scripts/if_else_statement_true.js diff --git a/libs/jsruntime/tests/evaluate_if_statement_false.js b/libs/jsruntime/tests/scripts/if_statement_false.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_statement_false.js rename to libs/jsruntime/tests/scripts/if_statement_false.js diff --git a/libs/jsruntime/tests/evaluate_if_statement_return.js b/libs/jsruntime/tests/scripts/if_statement_return.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_statement_return.js rename to libs/jsruntime/tests/scripts/if_statement_return.js diff --git a/libs/jsruntime/tests/evaluate_if_statement_return_in_block.js b/libs/jsruntime/tests/scripts/if_statement_return_in_block.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_statement_return_in_block.js rename to libs/jsruntime/tests/scripts/if_statement_return_in_block.js diff --git a/libs/jsruntime/tests/evaluate_if_statement_throw.js b/libs/jsruntime/tests/scripts/if_statement_throw.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_statement_throw.js rename to libs/jsruntime/tests/scripts/if_statement_throw.js diff --git a/libs/jsruntime/tests/evaluate_if_statement_throw_in_block.js b/libs/jsruntime/tests/scripts/if_statement_throw_in_block.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_statement_throw_in_block.js rename to libs/jsruntime/tests/scripts/if_statement_throw_in_block.js diff --git a/libs/jsruntime/tests/evaluate_if_statement_true.js b/libs/jsruntime/tests/scripts/if_statement_true.js similarity index 100% rename from libs/jsruntime/tests/evaluate_if_statement_true.js rename to libs/jsruntime/tests/scripts/if_statement_true.js diff --git a/libs/jsruntime/tests/evaluate_iife.js b/libs/jsruntime/tests/scripts/iife.js similarity index 100% rename from libs/jsruntime/tests/evaluate_iife.js rename to libs/jsruntime/tests/scripts/iife.js diff --git a/libs/jsruntime/tests/evaluate_iife_with_name.js b/libs/jsruntime/tests/scripts/iife_with_name.js similarity index 100% rename from libs/jsruntime/tests/evaluate_iife_with_name.js rename to libs/jsruntime/tests/scripts/iife_with_name.js diff --git a/libs/jsruntime/tests/evaluate_inequality.js b/libs/jsruntime/tests/scripts/inequality.js similarity index 100% rename from libs/jsruntime/tests/evaluate_inequality.js rename to libs/jsruntime/tests/scripts/inequality.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_break_iteration.js b/libs/jsruntime/tests/scripts/labelled_statement_break_iteration.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_break_iteration.js rename to libs/jsruntime/tests/scripts/labelled_statement_break_iteration.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_break_nested.js b/libs/jsruntime/tests/scripts/labelled_statement_break_nested.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_break_nested.js rename to libs/jsruntime/tests/scripts/labelled_statement_break_nested.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_break_switch.js b/libs/jsruntime/tests/scripts/labelled_statement_break_switch.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_break_switch.js rename to libs/jsruntime/tests/scripts/labelled_statement_break_switch.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_continue_do_while.js b/libs/jsruntime/tests/scripts/labelled_statement_continue_do_while.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_continue_do_while.js rename to libs/jsruntime/tests/scripts/labelled_statement_continue_do_while.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_continue_for.js b/libs/jsruntime/tests/scripts/labelled_statement_continue_for.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_continue_for.js rename to libs/jsruntime/tests/scripts/labelled_statement_continue_for.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_continue_nested.js b/libs/jsruntime/tests/scripts/labelled_statement_continue_nested.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_continue_nested.js rename to libs/jsruntime/tests/scripts/labelled_statement_continue_nested.js diff --git a/libs/jsruntime/tests/evaluate_labelled_statement_continue_while.js b/libs/jsruntime/tests/scripts/labelled_statement_continue_while.js similarity index 100% rename from libs/jsruntime/tests/evaluate_labelled_statement_continue_while.js rename to libs/jsruntime/tests/scripts/labelled_statement_continue_while.js diff --git a/libs/jsruntime/tests/evaluate_left_shift.js b/libs/jsruntime/tests/scripts/left_shift.js similarity index 100% rename from libs/jsruntime/tests/evaluate_left_shift.js rename to libs/jsruntime/tests/scripts/left_shift.js diff --git a/libs/jsruntime/tests/evaluate_left_shift_assignment.js b/libs/jsruntime/tests/scripts/left_shift_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_left_shift_assignment.js rename to libs/jsruntime/tests/scripts/left_shift_assignment.js diff --git a/libs/jsruntime/tests/evaluate_let_declaration.js b/libs/jsruntime/tests/scripts/let_declaration.js similarity index 100% rename from libs/jsruntime/tests/evaluate_let_declaration.js rename to libs/jsruntime/tests/scripts/let_declaration.js diff --git a/libs/jsruntime/tests/evaluate_logical_and.js b/libs/jsruntime/tests/scripts/logical_and.js similarity index 100% rename from libs/jsruntime/tests/evaluate_logical_and.js rename to libs/jsruntime/tests/scripts/logical_and.js diff --git a/libs/jsruntime/tests/evaluate_logical_and_assignment.js b/libs/jsruntime/tests/scripts/logical_and_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_logical_and_assignment.js rename to libs/jsruntime/tests/scripts/logical_and_assignment.js diff --git a/libs/jsruntime/tests/evaluate_logical_not.js b/libs/jsruntime/tests/scripts/logical_not.js similarity index 100% rename from libs/jsruntime/tests/evaluate_logical_not.js rename to libs/jsruntime/tests/scripts/logical_not.js diff --git a/libs/jsruntime/tests/evaluate_logical_or.js b/libs/jsruntime/tests/scripts/logical_or.js similarity index 100% rename from libs/jsruntime/tests/evaluate_logical_or.js rename to libs/jsruntime/tests/scripts/logical_or.js diff --git a/libs/jsruntime/tests/evaluate_logical_or_assignment.js b/libs/jsruntime/tests/scripts/logical_or_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_logical_or_assignment.js rename to libs/jsruntime/tests/scripts/logical_or_assignment.js diff --git a/libs/jsruntime/tests/evaluate_multiplication.js b/libs/jsruntime/tests/scripts/multiplication.js similarity index 100% rename from libs/jsruntime/tests/evaluate_multiplication.js rename to libs/jsruntime/tests/scripts/multiplication.js diff --git a/libs/jsruntime/tests/evaluate_multiplication_assignment.js b/libs/jsruntime/tests/scripts/multiplication_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_multiplication_assignment.js rename to libs/jsruntime/tests/scripts/multiplication_assignment.js diff --git a/libs/jsruntime/tests/evaluate_nested_function.js b/libs/jsruntime/tests/scripts/nested_function.js similarity index 100% rename from libs/jsruntime/tests/evaluate_nested_function.js rename to libs/jsruntime/tests/scripts/nested_function.js diff --git a/libs/jsruntime/tests/evaluate_null.js b/libs/jsruntime/tests/scripts/null.js similarity index 100% rename from libs/jsruntime/tests/evaluate_null.js rename to libs/jsruntime/tests/scripts/null.js diff --git a/libs/jsruntime/tests/evaluate_nullish_coalescing.js b/libs/jsruntime/tests/scripts/nullish_coalescing.js similarity index 100% rename from libs/jsruntime/tests/evaluate_nullish_coalescing.js rename to libs/jsruntime/tests/scripts/nullish_coalescing.js diff --git a/libs/jsruntime/tests/evaluate_nullish_coalescing_assignment.js b/libs/jsruntime/tests/scripts/nullish_coalescing_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_nullish_coalescing_assignment.js rename to libs/jsruntime/tests/scripts/nullish_coalescing_assignment.js diff --git a/libs/jsruntime/tests/evaluate_number.js b/libs/jsruntime/tests/scripts/number.js similarity index 100% rename from libs/jsruntime/tests/evaluate_number.js rename to libs/jsruntime/tests/scripts/number.js diff --git a/libs/jsruntime/tests/evaluate_postfix_decrement.js b/libs/jsruntime/tests/scripts/postfix_decrement.js similarity index 100% rename from libs/jsruntime/tests/evaluate_postfix_decrement.js rename to libs/jsruntime/tests/scripts/postfix_decrement.js diff --git a/libs/jsruntime/tests/evaluate_postfix_increment.js b/libs/jsruntime/tests/scripts/postfix_increment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_postfix_increment.js rename to libs/jsruntime/tests/scripts/postfix_increment.js diff --git a/libs/jsruntime/tests/evaluate_prefix_decrement.js b/libs/jsruntime/tests/scripts/prefix_decrement.js similarity index 100% rename from libs/jsruntime/tests/evaluate_prefix_decrement.js rename to libs/jsruntime/tests/scripts/prefix_decrement.js diff --git a/libs/jsruntime/tests/evaluate_prefix_increment.js b/libs/jsruntime/tests/scripts/prefix_increment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_prefix_increment.js rename to libs/jsruntime/tests/scripts/prefix_increment.js diff --git a/libs/jsruntime/tests/evaluate_remainder.js b/libs/jsruntime/tests/scripts/remainder.js similarity index 100% rename from libs/jsruntime/tests/evaluate_remainder.js rename to libs/jsruntime/tests/scripts/remainder.js diff --git a/libs/jsruntime/tests/evaluate_remainder_assignment.js b/libs/jsruntime/tests/scripts/remainder_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_remainder_assignment.js rename to libs/jsruntime/tests/scripts/remainder_assignment.js diff --git a/libs/jsruntime/tests/evaluate_return_statement_in_block.js b/libs/jsruntime/tests/scripts/return_statement_in_block.js similarity index 100% rename from libs/jsruntime/tests/evaluate_return_statement_in_block.js rename to libs/jsruntime/tests/scripts/return_statement_in_block.js diff --git a/libs/jsruntime/tests/evaluate_signed_right_shift.js b/libs/jsruntime/tests/scripts/signed_right_shift.js similarity index 100% rename from libs/jsruntime/tests/evaluate_signed_right_shift.js rename to libs/jsruntime/tests/scripts/signed_right_shift.js diff --git a/libs/jsruntime/tests/evaluate_signed_right_shift_assignment.js b/libs/jsruntime/tests/scripts/signed_right_shift_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_signed_right_shift_assignment.js rename to libs/jsruntime/tests/scripts/signed_right_shift_assignment.js diff --git a/libs/jsruntime/tests/evaluate_strict_equality.js b/libs/jsruntime/tests/scripts/strict_equality.js similarity index 100% rename from libs/jsruntime/tests/evaluate_strict_equality.js rename to libs/jsruntime/tests/scripts/strict_equality.js diff --git a/libs/jsruntime/tests/evaluate_strict_inequality.js b/libs/jsruntime/tests/scripts/strict_inequality.js similarity index 100% rename from libs/jsruntime/tests/evaluate_strict_inequality.js rename to libs/jsruntime/tests/scripts/strict_inequality.js diff --git a/libs/jsruntime/tests/evaluate_subtraction.js b/libs/jsruntime/tests/scripts/subtraction.js similarity index 100% rename from libs/jsruntime/tests/evaluate_subtraction.js rename to libs/jsruntime/tests/scripts/subtraction.js diff --git a/libs/jsruntime/tests/evaluate_subtraction_assignment.js b/libs/jsruntime/tests/scripts/subtraction_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_subtraction_assignment.js rename to libs/jsruntime/tests/scripts/subtraction_assignment.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_cases_break.js b/libs/jsruntime/tests/scripts/switch_statement_cases_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_cases_break.js rename to libs/jsruntime/tests/scripts/switch_statement_cases_break.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_cases_default_break.js b/libs/jsruntime/tests/scripts/switch_statement_cases_default_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_cases_default_break.js rename to libs/jsruntime/tests/scripts/switch_statement_cases_default_break.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_cases_default_cases_break.js b/libs/jsruntime/tests/scripts/switch_statement_cases_default_cases_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_cases_default_cases_break.js rename to libs/jsruntime/tests/scripts/switch_statement_cases_default_cases_break.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_cases_default_cases_fall_through.js b/libs/jsruntime/tests/scripts/switch_statement_cases_default_cases_fall_through.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_cases_default_cases_fall_through.js rename to libs/jsruntime/tests/scripts/switch_statement_cases_default_cases_fall_through.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_cases_default_fall_through.js b/libs/jsruntime/tests/scripts/switch_statement_cases_default_fall_through.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_cases_default_fall_through.js rename to libs/jsruntime/tests/scripts/switch_statement_cases_default_fall_through.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_cases_fall_through.js b/libs/jsruntime/tests/scripts/switch_statement_cases_fall_through.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_cases_fall_through.js rename to libs/jsruntime/tests/scripts/switch_statement_cases_fall_through.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_default_break.js b/libs/jsruntime/tests/scripts/switch_statement_default_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_default_break.js rename to libs/jsruntime/tests/scripts/switch_statement_default_break.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_default_cases_break.js b/libs/jsruntime/tests/scripts/switch_statement_default_cases_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_default_cases_break.js rename to libs/jsruntime/tests/scripts/switch_statement_default_cases_break.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_default_cases_fall_through.js b/libs/jsruntime/tests/scripts/switch_statement_default_cases_fall_through.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_default_cases_fall_through.js rename to libs/jsruntime/tests/scripts/switch_statement_default_cases_fall_through.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_default_fall_through.js b/libs/jsruntime/tests/scripts/switch_statement_default_fall_through.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_default_fall_through.js rename to libs/jsruntime/tests/scripts/switch_statement_default_fall_through.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_empty.js b/libs/jsruntime/tests/scripts/switch_statement_empty.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_empty.js rename to libs/jsruntime/tests/scripts/switch_statement_empty.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_single_case_break.js b/libs/jsruntime/tests/scripts/switch_statement_single_case_break.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_single_case_break.js rename to libs/jsruntime/tests/scripts/switch_statement_single_case_break.js diff --git a/libs/jsruntime/tests/evaluate_switch_statement_single_case_fall_through.js b/libs/jsruntime/tests/scripts/switch_statement_single_case_fall_through.js similarity index 100% rename from libs/jsruntime/tests/evaluate_switch_statement_single_case_fall_through.js rename to libs/jsruntime/tests/scripts/switch_statement_single_case_fall_through.js diff --git a/libs/jsruntime/tests/evaluated_terminated_basic_block.js b/libs/jsruntime/tests/scripts/terminated_basic_block.js similarity index 100% rename from libs/jsruntime/tests/evaluated_terminated_basic_block.js rename to libs/jsruntime/tests/scripts/terminated_basic_block.js diff --git a/libs/jsruntime/tests/evaluate_ternary.js b/libs/jsruntime/tests/scripts/ternary.js similarity index 100% rename from libs/jsruntime/tests/evaluate_ternary.js rename to libs/jsruntime/tests/scripts/ternary.js diff --git a/libs/jsruntime/tests/evaluate_ternary_mixed_types.js b/libs/jsruntime/tests/scripts/ternary_mixed_types.js similarity index 100% rename from libs/jsruntime/tests/evaluate_ternary_mixed_types.js rename to libs/jsruntime/tests/scripts/ternary_mixed_types.js diff --git a/libs/jsruntime/tests/evaluate_ternary_nested.js b/libs/jsruntime/tests/scripts/ternary_nested.js similarity index 100% rename from libs/jsruntime/tests/evaluate_ternary_nested.js rename to libs/jsruntime/tests/scripts/ternary_nested.js diff --git a/libs/jsruntime/tests/evaluate_throw_false.js b/libs/jsruntime/tests/scripts/throw_false.js similarity index 100% rename from libs/jsruntime/tests/evaluate_throw_false.js rename to libs/jsruntime/tests/scripts/throw_false.js diff --git a/libs/jsruntime/tests/evaluate_throw_null.js b/libs/jsruntime/tests/scripts/throw_null.js similarity index 100% rename from libs/jsruntime/tests/evaluate_throw_null.js rename to libs/jsruntime/tests/scripts/throw_null.js diff --git a/libs/jsruntime/tests/evaluate_throw_number.js b/libs/jsruntime/tests/scripts/throw_number.js similarity index 100% rename from libs/jsruntime/tests/evaluate_throw_number.js rename to libs/jsruntime/tests/scripts/throw_number.js diff --git a/libs/jsruntime/tests/evaluate_throw_true.js b/libs/jsruntime/tests/scripts/throw_true.js similarity index 100% rename from libs/jsruntime/tests/evaluate_throw_true.js rename to libs/jsruntime/tests/scripts/throw_true.js diff --git a/libs/jsruntime/tests/evaluate_throw_undefined.js b/libs/jsruntime/tests/scripts/throw_undefined.js similarity index 100% rename from libs/jsruntime/tests/evaluate_throw_undefined.js rename to libs/jsruntime/tests/scripts/throw_undefined.js diff --git a/libs/jsruntime/tests/evaluate_to_numeric.js b/libs/jsruntime/tests/scripts/to_numeric.js similarity index 100% rename from libs/jsruntime/tests/evaluate_to_numeric.js rename to libs/jsruntime/tests/scripts/to_numeric.js diff --git a/libs/jsruntime/tests/evaluate_try_call_throw.js b/libs/jsruntime/tests/scripts/try_call_throw.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_call_throw.js rename to libs/jsruntime/tests/scripts/try_call_throw.js diff --git a/libs/jsruntime/tests/evaluate_try_catch.js b/libs/jsruntime/tests/scripts/try_catch.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch.js rename to libs/jsruntime/tests/scripts/try_catch.js diff --git a/libs/jsruntime/tests/evaluate_try_catch_finally.js b/libs/jsruntime/tests/scripts/try_catch_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch_finally.js rename to libs/jsruntime/tests/scripts/try_catch_finally.js diff --git a/libs/jsruntime/tests/evaluate_try_catch_finally_throw.js b/libs/jsruntime/tests/scripts/try_catch_finally_throw.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch_finally_throw.js rename to libs/jsruntime/tests/scripts/try_catch_finally_throw.js diff --git a/libs/jsruntime/tests/evaluate_try_catch_no_parameter.js b/libs/jsruntime/tests/scripts/try_catch_no_parameter.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch_no_parameter.js rename to libs/jsruntime/tests/scripts/try_catch_no_parameter.js diff --git a/libs/jsruntime/tests/evaluate_try_catch_throw.js b/libs/jsruntime/tests/scripts/try_catch_throw.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch_throw.js rename to libs/jsruntime/tests/scripts/try_catch_throw.js diff --git a/libs/jsruntime/tests/evaluate_try_catch_throw_finally.js b/libs/jsruntime/tests/scripts/try_catch_throw_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch_throw_finally.js rename to libs/jsruntime/tests/scripts/try_catch_throw_finally.js diff --git a/libs/jsruntime/tests/evaluate_try_catch_throw_finally_throw.js b/libs/jsruntime/tests/scripts/try_catch_throw_finally_throw.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_catch_throw_finally_throw.js rename to libs/jsruntime/tests/scripts/try_catch_throw_finally_throw.js diff --git a/libs/jsruntime/tests/evaluate_try_finally.js b/libs/jsruntime/tests/scripts/try_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_finally.js rename to libs/jsruntime/tests/scripts/try_finally.js diff --git a/libs/jsruntime/tests/evaluate_try_finally_throw.js b/libs/jsruntime/tests/scripts/try_finally_throw.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_finally_throw.js rename to libs/jsruntime/tests/scripts/try_finally_throw.js diff --git a/libs/jsruntime/tests/evaluate_try_for_throw_catch.js b/libs/jsruntime/tests/scripts/try_for_throw_catch.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_for_throw_catch.js rename to libs/jsruntime/tests/scripts/try_for_throw_catch.js diff --git a/libs/jsruntime/tests/evaluate_try_for_throw_finally.js b/libs/jsruntime/tests/scripts/try_for_throw_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_for_throw_finally.js rename to libs/jsruntime/tests/scripts/try_for_throw_finally.js diff --git a/libs/jsruntime/tests/evaluate_try_if_throw_catch.js b/libs/jsruntime/tests/scripts/try_if_throw_catch.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_if_throw_catch.js rename to libs/jsruntime/tests/scripts/try_if_throw_catch.js diff --git a/libs/jsruntime/tests/evaluate_try_if_throw_finally.js b/libs/jsruntime/tests/scripts/try_if_throw_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_if_throw_finally.js rename to libs/jsruntime/tests/scripts/try_if_throw_finally.js diff --git a/libs/jsruntime/tests/evaluate_try_nested.js b/libs/jsruntime/tests/scripts/try_nested.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_nested.js rename to libs/jsruntime/tests/scripts/try_nested.js diff --git a/libs/jsruntime/tests/evaluate_try_switch_throw_catch.js b/libs/jsruntime/tests/scripts/try_switch_throw_catch.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_switch_throw_catch.js rename to libs/jsruntime/tests/scripts/try_switch_throw_catch.js diff --git a/libs/jsruntime/tests/evaluate_try_switch_throw_finally.js b/libs/jsruntime/tests/scripts/try_switch_throw_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_switch_throw_finally.js rename to libs/jsruntime/tests/scripts/try_switch_throw_finally.js diff --git a/libs/jsruntime/tests/evaluate_try_throw_in_block_catch.js b/libs/jsruntime/tests/scripts/try_throw_in_block_catch.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_throw_in_block_catch.js rename to libs/jsruntime/tests/scripts/try_throw_in_block_catch.js diff --git a/libs/jsruntime/tests/evaluate_try_throw_in_block_finally.js b/libs/jsruntime/tests/scripts/try_throw_in_block_finally.js similarity index 100% rename from libs/jsruntime/tests/evaluate_try_throw_in_block_finally.js rename to libs/jsruntime/tests/scripts/try_throw_in_block_finally.js diff --git a/libs/jsruntime/tests/evaluate_unary_minus.js b/libs/jsruntime/tests/scripts/unary_minus.js similarity index 100% rename from libs/jsruntime/tests/evaluate_unary_minus.js rename to libs/jsruntime/tests/scripts/unary_minus.js diff --git a/libs/jsruntime/tests/evaluate_unary_plus.js b/libs/jsruntime/tests/scripts/unary_plus.js similarity index 100% rename from libs/jsruntime/tests/evaluate_unary_plus.js rename to libs/jsruntime/tests/scripts/unary_plus.js diff --git a/libs/jsruntime/tests/evaluate_undefined.js b/libs/jsruntime/tests/scripts/undefined.js similarity index 100% rename from libs/jsruntime/tests/evaluate_undefined.js rename to libs/jsruntime/tests/scripts/undefined.js diff --git a/libs/jsruntime/tests/evaluate_unsigned_right_shift.js b/libs/jsruntime/tests/scripts/unsigned_right_shift.js similarity index 100% rename from libs/jsruntime/tests/evaluate_unsigned_right_shift.js rename to libs/jsruntime/tests/scripts/unsigned_right_shift.js diff --git a/libs/jsruntime/tests/evaluate_unsigned_right_shift_assignment.js b/libs/jsruntime/tests/scripts/unsigned_right_shift_assignment.js similarity index 100% rename from libs/jsruntime/tests/evaluate_unsigned_right_shift_assignment.js rename to libs/jsruntime/tests/scripts/unsigned_right_shift_assignment.js diff --git a/libs/jsruntime/tests/evaluate_void.js b/libs/jsruntime/tests/scripts/void.js similarity index 100% rename from libs/jsruntime/tests/evaluate_void.js rename to libs/jsruntime/tests/scripts/void.js diff --git a/libs/jsruntime/tests/evaluate_while_statement.js b/libs/jsruntime/tests/scripts/while_statement.js similarity index 100% rename from libs/jsruntime/tests/evaluate_while_statement.js rename to libs/jsruntime/tests/scripts/while_statement.js diff --git a/tools/bin/nunjucks.js b/tools/bin/nunjucks.js index dfae6ea54..b8b740c2c 100644 --- a/tools/bin/nunjucks.js +++ b/tools/bin/nunjucks.js @@ -46,9 +46,12 @@ Custom @data: @template Relatie path to the template file from the project root. -HELPERS: +FILTERS: * npm:change-case +TESTS: + * startsWith + EXAMPLES: The following commands output the same result: echo '{ "name": "value1" }' | ${PROGNAME} template.njk @@ -67,7 +70,8 @@ async function run(args, options) { const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(options.partialDirs), { autoescape: options.escape, }); - registerHelpers(env); + registerFilters(env); + registerTests(env); console.log( env.renderString(template, { data, @@ -78,12 +82,21 @@ async function run(args, options) { return 0; } -function registerHelpers(env) { +function registerFilters(env) { for (var name in changeCase) { env.addFilter(name, changeCase[name]); } } +function registerTests(env) { + env.addTest('startsWith', (value, prefix) => { + if (typeof value !== 'string') { + return false; + } + return value.startsWith(prefix); + }); +} + async function loadJson(data, options) { if (data === null) { return JSON.parse(await readAllText(Deno.stdin));