Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f7131079e5
|
|||
|
2fbd2a26a9
|
|||
|
65bcfcb47b
|
|||
| 3c7722a3d2 | |||
| d8ad7fe904 | |||
| fd182b6233 | |||
| a9cfddbf5c | |||
| 67cdcfea33 | |||
| f946cb2fd1 | |||
|
32c602f21c
|
|||
|
64f650b695
|
|||
|
7afb2a7b1c
|
|||
|
78e3c5a26e
|
|||
| e06bcf3f9d | |||
|
b556f1ea2d
|
|||
|
74e819c678
|
|||
|
781f701891
|
|||
|
dedf84a1a9
|
|||
|
2909483afb
|
|||
|
f6ae509c13
|
|||
|
4b567ab022
|
|||
|
5625f28e9b
|
|||
|
e26789f3b7
|
|||
|
f679ff2ec9
|
|||
|
20b5516101
|
|||
|
75e8705098
|
|||
|
d875951c09
|
|||
|
3e9f0a72a0
|
|||
|
7f6848c9e5
|
|||
|
b2d2490327
|
|||
|
7b6db44207
|
|||
|
49255948ff
|
|||
|
7293cb9f75
|
|||
|
3797544fc2
|
|||
|
0fd846e844
|
|||
|
484cfa4610
|
|||
|
51f8df9cca
|
|||
|
d3442e87e7
|
|||
|
20b2b6f1ef
|
|||
|
7d6168fdae
|
|||
|
c548c4c6ac
|
|||
|
541db02361
|
|||
|
c8276c1729
|
|||
|
f3bf44ab97
|
|||
|
99dce2e778
|
|||
|
c3ace28af1
|
|||
|
319c12c1f4
|
|||
|
cc06369c5e
|
|||
|
b41fd38bcc
|
|||
|
5291e49313
|
|||
|
a47a08b051
|
|||
|
53cbb37b00
|
|||
|
f380e5fd70
|
|||
|
b0b73439fd
|
|||
|
6bb86ca2cf
|
|||
|
c898b577b0
|
|||
|
dcd22ad1f3
|
|||
|
2a19ddb279
|
|||
|
177acfabcf
|
|||
|
36f29a9cac
|
|||
|
9b3c3d6fe9
|
|||
|
736402dc53
|
|||
|
b4249ccd11
|
|||
|
96fb6033a4
|
|||
|
d0298ce2a6
|
|||
|
b4db46d48a
|
|||
|
9e172bf013
|
|||
|
6d26716412
|
|||
|
e17c48f2d9
|
|||
|
4124156d52
|
|||
|
af5a312e1e
|
|||
|
f98d623c13
|
|||
|
29e959894d
|
|||
|
95ebddf272
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
|||||||
target/
|
target/
|
||||||
|
|
||||||
/.direnv/
|
/.direnv/
|
||||||
|
.env
|
||||||
|
|
||||||
|
/flamegraph.svg
|
||||||
|
/perf.data*
|
||||||
|
|||||||
718
Cargo.lock
generated
718
Cargo.lock
generated
@@ -24,53 +24,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "archery"
|
name = "arbitrary"
|
||||||
version = "1.2.0"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8967cd1cc9e9e1954f644e14fbd6042fe9a37da96c52a67e44a2ac18261f8561"
|
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||||
dependencies = [
|
|
||||||
"static_assertions",
|
|
||||||
"triomphe",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.0"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.21"
|
version = "1.2.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
|
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg_aliases"
|
name = "cfg_aliases"
|
||||||
version = "0.2.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "5.4.0"
|
version = "5.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
|
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"error-code",
|
"error-code",
|
||||||
]
|
]
|
||||||
@@ -84,12 +95,192 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "countme"
|
name = "countme"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0849f998d4e04e6dd056a75268636e39a58ffe57a295bc69d351a424343a79e"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-codegen",
|
||||||
|
"cranelift-frontend",
|
||||||
|
"cranelift-module",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-assembler-x64"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ae7b60ec3fd7162427d3b3801520a1908bef7c035b52983cd3ca11b8e7deb51"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-assembler-x64-meta",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-assembler-x64-meta"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6511c200fed36452697b4b6b161eae57d917a2044e6333b1c1389ed63ccadeee"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-srcgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-bforest"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f7086a645aa58bae979312f64e3029ac760ac1b577f5cd2417844842a2ca07f"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-entity",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-bitset"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5225b4dec45f3f3dbf383f12560fac5ce8d780f399893607e21406e12e77f491"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-codegen"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "858fb3331e53492a95979378d6df5208dd1d0d315f19c052be8115f4efc888e0"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"cranelift-assembler-x64",
|
||||||
|
"cranelift-bforest",
|
||||||
|
"cranelift-bitset",
|
||||||
|
"cranelift-codegen-meta",
|
||||||
|
"cranelift-codegen-shared",
|
||||||
|
"cranelift-control",
|
||||||
|
"cranelift-entity",
|
||||||
|
"cranelift-isle",
|
||||||
|
"gimli",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"log",
|
||||||
|
"regalloc2",
|
||||||
|
"rustc-hash 2.1.1",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
"wasmtime-internal-math",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-codegen-meta"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "456715b9d5f12398f156d5081096e7b5d039f01b9ecc49790a011c8e43e65b5f"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-assembler-x64-meta",
|
||||||
|
"cranelift-codegen-shared",
|
||||||
|
"cranelift-srcgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-codegen-shared"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0306041099499833f167a0ddb707e1e54100f1a84eab5631bc3dad249708f482"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-control"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1672945e1f9afc2297f49c92623f5eabc64398e2cb0d824f8f72a2db2df5af23"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-entity"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa3cd55eb5f3825b9ae5de1530887907360a6334caccdc124c52f6d75246c98a"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-bitset",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-frontend"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "781f9905f8139b8de22987b66b522b416fe63eb76d823f0b3a8c02c8fd9500c7"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-codegen",
|
||||||
|
"log",
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-isle"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a05337a2b02c3df00b4dd9a263a027a07b3dff49f61f7da3b5d195c21eaa633d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-jit"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "593f8ff2c1a1785d9ab61a4b112ec1c9e8a3b976d8857ed1e70a79d4a07dd5ba"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"cranelift-control",
|
||||||
|
"cranelift-entity",
|
||||||
|
"cranelift-module",
|
||||||
|
"cranelift-native",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"region",
|
||||||
|
"target-lexicon",
|
||||||
|
"wasmtime-internal-jit-icache-coherence",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-module"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9f7a4b804066f3e62d8fc943e25adc135acbb39288aa6c68e67021a9f6a0c58"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"cranelift-control",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-native"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eee7a496dd66380082c9c5b6f2d5fa149cec0ec383feec5caf079ca2b3671c2"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift-codegen",
|
||||||
|
"libc",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cranelift-srcgen"
|
||||||
|
version = "0.122.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b530783809a55cb68d070e0de60cfbb3db0dc94c8850dd5725411422bedcf6bb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -105,24 +296,18 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case 0.7.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ecow"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "54bfbb1708988623190a6c4dbedaeaf0f53c20c6395abd6a01feb327b3146f4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.10.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endian-type"
|
name = "endian-type"
|
||||||
@@ -138,19 +323,25 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.11"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-code"
|
name = "error-code"
|
||||||
version = "3.3.1"
|
version = "3.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fd-lock"
|
name = "fd-lock"
|
||||||
@@ -160,9 +351,15 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -170,16 +367,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "gimli"
|
||||||
version = "0.14.3"
|
version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
dependencies = [
|
||||||
|
"fallible-iterator",
|
||||||
|
"indexmap",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.3"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
"equivalent",
|
"equivalent",
|
||||||
@@ -192,32 +400,17 @@ version = "0.5.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inkwell"
|
name = "indexmap"
|
||||||
version = "0.6.0"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e67349bd7578d4afebbe15eaa642a80b884e8623db74b1716611b131feb1deef"
|
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"equivalent",
|
||||||
"inkwell_internals",
|
"hashbrown 0.15.4",
|
||||||
"libc",
|
|
||||||
"llvm-sys",
|
|
||||||
"once_cell",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "inkwell_internals"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f365c8de536236cfdebd0ba2130de22acefed18b1fb99c32783b3840aec5fb46"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -230,16 +423,26 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "libc"
|
||||||
version = "1.5.0"
|
version = "0.2.174"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libm"
|
||||||
version = "0.2.172"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libmimalloc-sys"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@@ -247,20 +450,6 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "llvm-sys"
|
|
||||||
version = "181.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d320f9d2723c97d4b78f9190a61ed25cc7cfbe456668c08e6e7dd8e50ceb8500"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"cc",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"regex-lite",
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
@@ -268,10 +457,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "mach2"
|
||||||
version = "2.7.4"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
@@ -282,6 +480,15 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mimalloc"
|
||||||
|
version = "0.1.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8"
|
||||||
|
dependencies = [
|
||||||
|
"libmimalloc-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nibble_vec"
|
name = "nibble_vec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -293,11 +500,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.29.0"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -305,25 +512,159 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nixjit"
|
name = "nixjit"
|
||||||
version = "0.0.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_more",
|
"anyhow",
|
||||||
"ecow",
|
"bumpalo",
|
||||||
"hashbrown 0.15.3",
|
"mimalloc",
|
||||||
"inkwell",
|
"nixjit_context",
|
||||||
"itertools",
|
"nixjit_value",
|
||||||
"regex",
|
"regex",
|
||||||
"rnix",
|
|
||||||
"rpds",
|
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"thiserror 2.0.12",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "nixjit_builtins"
|
||||||
version = "1.21.3"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nixjit_error",
|
||||||
|
"nixjit_eval",
|
||||||
|
"nixjit_macros",
|
||||||
|
"nixjit_value",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_context"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"cranelift",
|
||||||
|
"cranelift-jit",
|
||||||
|
"cranelift-module",
|
||||||
|
"cranelift-native",
|
||||||
|
"derive_more",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"itertools",
|
||||||
|
"nixjit_builtins",
|
||||||
|
"nixjit_error",
|
||||||
|
"nixjit_eval",
|
||||||
|
"nixjit_hir",
|
||||||
|
"nixjit_ir",
|
||||||
|
"nixjit_jit",
|
||||||
|
"nixjit_lir",
|
||||||
|
"nixjit_value",
|
||||||
|
"petgraph",
|
||||||
|
"replace_with",
|
||||||
|
"rnix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_error"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rnix",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_eval"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"itertools",
|
||||||
|
"nixjit_error",
|
||||||
|
"nixjit_ir",
|
||||||
|
"nixjit_lir",
|
||||||
|
"nixjit_value",
|
||||||
|
"replace_with",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_hir"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"nixjit_error",
|
||||||
|
"nixjit_ir",
|
||||||
|
"nixjit_macros",
|
||||||
|
"nixjit_value",
|
||||||
|
"rnix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_ir"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"nixjit_value",
|
||||||
|
"rnix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_jit"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cranelift",
|
||||||
|
"cranelift-jit",
|
||||||
|
"cranelift-module",
|
||||||
|
"cranelift-native",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"nixjit_error",
|
||||||
|
"nixjit_eval",
|
||||||
|
"nixjit_hir",
|
||||||
|
"nixjit_ir",
|
||||||
|
"nixjit_lir",
|
||||||
|
"nixjit_value",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_lir"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"itertools",
|
||||||
|
"nixjit_error",
|
||||||
|
"nixjit_hir",
|
||||||
|
"nixjit_ir",
|
||||||
|
"nixjit_macros",
|
||||||
|
"nixjit_value",
|
||||||
|
"rnix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case 0.8.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nixjit_value"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
@@ -353,6 +694,20 @@ dependencies = [
|
|||||||
"nibble_vec",
|
"nibble_vec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regalloc2"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5216b1837de2149f8bc8e6d5f88a9326b63b8c836ed58ce4a0a29ec736a59734"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"bumpalo",
|
||||||
|
"hashbrown 0.15.4",
|
||||||
|
"log",
|
||||||
|
"rustc-hash 2.1.1",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -376,18 +731,30 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-lite"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "region"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
"mach2",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "replace_with"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rnix"
|
name = "rnix"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -399,26 +766,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rowan"
|
name = "rowan"
|
||||||
version = "0.15.15"
|
version = "0.15.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49"
|
checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"countme",
|
"countme",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.5",
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"text-size",
|
"text-size",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rpds"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0e15515d3ce3313324d842629ea4905c25a13f81953eadb88f85516f59290a4"
|
|
||||||
dependencies = [
|
|
||||||
"archery",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -426,25 +784,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustc-hash"
|
||||||
version = "1.0.7"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustyline"
|
name = "rustyline"
|
||||||
version = "15.0.0"
|
version = "14.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f"
|
checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"fd-lock",
|
"fd-lock",
|
||||||
@@ -457,14 +821,28 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "serde"
|
||||||
version = "1.0.26"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
@@ -474,60 +852,46 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.2"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "stable_deref_trait"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.101"
|
version = "2.0.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "target-lexicon"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text-size"
|
name = "text-size"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.12",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -541,12 +905,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "triomphe"
|
|
||||||
version = "0.1.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -561,9 +919,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.0"
|
version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -577,6 +935,36 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-jit-icache-coherence"
|
||||||
|
version = "35.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4417e06b7f80baff87d9770852c757a39b8d7f11d78b2620ca992b8725f16f50"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-internal-math"
|
||||||
|
version = "35.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7710d5c4ecdaa772927fd11e5dc30a9a62d1fc8fe933e11ad5576ad596ab6612"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
|||||||
44
Cargo.toml
44
Cargo.toml
@@ -1,29 +1,15 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "nixjit"
|
resolver = "3"
|
||||||
version = "0.0.0"
|
members = [
|
||||||
edition = "2024"
|
"evaluator/nixjit",
|
||||||
|
"evaluator/nixjit_builtins",
|
||||||
[features]
|
"evaluator/nixjit_context",
|
||||||
repl = ["dep:rustyline"]
|
"evaluator/nixjit_error",
|
||||||
|
"evaluator/nixjit_eval",
|
||||||
[[bin]]
|
"evaluator/nixjit_hir",
|
||||||
name = "repl"
|
"evaluator/nixjit_ir",
|
||||||
required-features = ["repl"]
|
"evaluator/nixjit_jit",
|
||||||
|
"evaluator/nixjit_lir",
|
||||||
[profile.perf]
|
"evaluator/nixjit_macros",
|
||||||
debug = 1
|
"evaluator/nixjit_value",
|
||||||
inherits = "release"
|
]
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rnix = "0.12"
|
|
||||||
thiserror = "2.0"
|
|
||||||
itertools = "0.14"
|
|
||||||
rpds = "1.1"
|
|
||||||
derive_more = { version = "2.0", features = [ "full" ] }
|
|
||||||
ecow = "0.2"
|
|
||||||
regex = "1.11"
|
|
||||||
hashbrown = "0.15"
|
|
||||||
|
|
||||||
inkwell = { version = "0.6.0", features = ["llvm18-1"] }
|
|
||||||
|
|
||||||
rustyline = { version = "15.0", optional = true }
|
|
||||||
|
|||||||
38
TODO.md
Normal file
38
TODO.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
- [x] downgrade stage
|
||||||
|
- [ ] resolve stage
|
||||||
|
- [ ] dynamic attr resolution
|
||||||
|
- [ ] import resolution
|
||||||
|
- [ ] static path resolution
|
||||||
|
- [ ] eval stage
|
||||||
|
- [ ] dynamic path resolution
|
||||||
|
- [ ] graph update
|
||||||
|
- [ ] stack storage
|
||||||
|
|
||||||
|
依赖类型:
|
||||||
|
1. 强依赖 (x!)
|
||||||
|
- builtins.toString x => x!
|
||||||
|
- x + y => x! y!
|
||||||
|
- { x = 1; ${sym} = 2; } => sym!
|
||||||
|
2. 弱依赖 a.k.a. thunk (x?)
|
||||||
|
- builtins.seq x y => x! y?
|
||||||
|
3. 递归依赖 (x!!)
|
||||||
|
- builtins.deepSeq x y => x!! y?
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
- let a? = { inherit a?; }; in a! => a!
|
||||||
|
- a! => { a = <THUNK> }
|
||||||
|
- f: let x? = f! x?; in x! => f! x!
|
||||||
|
- let ret? = (self: { x? = 1; y? = self.x! + 1; }) ret?; in ret.y! => ret! x! y!
|
||||||
|
|
||||||
|
工作流程:
|
||||||
|
1. string -> AST
|
||||||
|
2. AST -> HIR (alloc ExprId)
|
||||||
|
3. HIR -> LIR (resolve var, build graph)
|
||||||
|
4. LIR -> Value
|
||||||
|
|
||||||
|
为每个值分配 ID 的难点在于对动态表达式的引用。
|
||||||
|
动态表达式有:
|
||||||
|
- 依赖于函数参数的表达式
|
||||||
|
- 依赖于 with 的表达式
|
||||||
|
- 依赖于动态表达式的表达式
|
||||||
|
而这些表达式在每一次分配 ValueId 时指向的 ValueId 都不同,因此需要追踪这些变量。
|
||||||
96
docs/eval/eval.mmd
Normal file
96
docs/eval/eval.mmd
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
flowchart TD
|
||||||
|
Start([EvalCtx::eval]) --> Eval[EvalContext::eval]
|
||||||
|
Eval --> AddNode[为本即将求值表达式分配 ValueId,添加至图中]
|
||||||
|
AddNode --> CheckType{检查表达式类型}
|
||||||
|
|
||||||
|
CheckType -->|AttrSet| A_EvalKeys[强制求值所有键]
|
||||||
|
A_EvalKeys --> A_Construct[构造 AttrSet]
|
||||||
|
A_Construct --> End
|
||||||
|
A_EvalKeys -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|List| L_Construct[构造列表]
|
||||||
|
L_Construct --> End
|
||||||
|
|
||||||
|
CheckType -->|HasAttr| HA_ForceAttrSet[强制求值 AttrSet]
|
||||||
|
HA_ForceAttrSet --> HA_ForceAttrPath[强制求值 AttrPath]
|
||||||
|
HA_ForceAttrPath --> HA_Result[求出结果]
|
||||||
|
HA_Result --> End
|
||||||
|
HA_ForceAttrSet -.->|eval| Eval
|
||||||
|
HA_ForceAttrPath -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|BinOp| B_ForceLeft[强制求值左操作数]
|
||||||
|
B_ForceLeft --> B_ForceRight[强制求值右操作数]
|
||||||
|
B_ForceRight --> B_Apply[应用操作符]
|
||||||
|
B_Apply --> End
|
||||||
|
B_ForceLeft -.->|eval| Eval
|
||||||
|
B_ForceRight -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|UnOp| U_ForceOperand[强制求值操作数]
|
||||||
|
U_ForceOperand --> U_Apply[应用操作符]
|
||||||
|
U_Apply --> End
|
||||||
|
U_ForceOperand -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|Select| S_ForceAttrSet[强制求值 AttrSet]
|
||||||
|
S_ForceAttrSet --> S_ForceAttrPath[强制求值 AttrPath]
|
||||||
|
S_ForceAttrPath --> S_Result[获取结果]
|
||||||
|
S_Result --> End
|
||||||
|
S_ForceAttrSet -.->|eval| Eval
|
||||||
|
S_ForceAttrPath -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|If| I_ForceCond[强制求值条件]
|
||||||
|
I_ForceCond --> I_Cond{判断条件}
|
||||||
|
I_Cond -->|true| I_Consq[返回 then]
|
||||||
|
I_Cond -->|false| I_Alter[返回 else]
|
||||||
|
I_Consq --> End
|
||||||
|
I_Alter --> End
|
||||||
|
I_ForceCond -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|Call| C_RegArg[注册函数参数]
|
||||||
|
C_RegArg --> C_ForceBody[强制求值函数体]
|
||||||
|
C_ForceBody --> End
|
||||||
|
C_ForceBody -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|With| W_ForceNameSpace[强制求值命名空间]
|
||||||
|
W_ForceNameSpace --> W_EnterWith[进入命名空间]
|
||||||
|
W_EnterWith --> W_ForceBody[强制求值表达式]
|
||||||
|
W_ForceBody --> W_ExitWith[退出命名空间]
|
||||||
|
W_ExitWith --> End
|
||||||
|
W_ForceNameSpace -.->|eval| Eval
|
||||||
|
W_ForceBody -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|Assert| As_ForceCond[强制求值断言]
|
||||||
|
As_ForceCond --> As_Cond{判断断言}
|
||||||
|
As_Cond -->|true| As_ForceBody[强制求值表达式]
|
||||||
|
As_Cond -->|false| As_Throw[抛出 Catchable]
|
||||||
|
As_ForceBody --> End
|
||||||
|
As_ForceCond -.->|eval| Eval
|
||||||
|
As_ForceBody -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|ConcatStrings| CS_ForceParts[强制求值所有字符串片段]
|
||||||
|
CS_ForceParts --> CS_Construct[构造字符串]
|
||||||
|
CS_Construct --> End
|
||||||
|
CS_ForceParts -.->|eval| Eval
|
||||||
|
|
||||||
|
CheckType -->|Const| Co_Construct[构造常量]
|
||||||
|
Co_Construct --> End
|
||||||
|
|
||||||
|
CheckType -->|Str| St_Construct[构造字符串]
|
||||||
|
St_Construct --> End
|
||||||
|
|
||||||
|
CheckType -->|Var| V_Lookup[动态查找变量]
|
||||||
|
V_Lookup --> End
|
||||||
|
|
||||||
|
CheckType -->|Arg| Ar_Lookup[查找参数]
|
||||||
|
Ar_Lookup --> End
|
||||||
|
|
||||||
|
CheckType -->|Func| F_Construct[构造函数]
|
||||||
|
F_Construct --> End
|
||||||
|
|
||||||
|
CheckType -->|StrictRef| SR_Eval[求值被引用表达式]
|
||||||
|
SR_Eval --> Eval
|
||||||
|
|
||||||
|
CheckType -->|LazyRef| LR_Resolve[解析动态变量引用]
|
||||||
|
LR_Resolve --> LR_Contruct[构造惰性引用]
|
||||||
|
LR_Contruct --> End
|
||||||
|
|
||||||
|
End([返回结果])
|
||||||
92
docs/resolve/resolve.mmd
Normal file
92
docs/resolve/resolve.mmd
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
flowchart TD
|
||||||
|
Start([ResolveCtx::resolve_root]) --> Resolve[ResolveContext::resolve]
|
||||||
|
Resolve --> CheckType{检查表达式类型}
|
||||||
|
|
||||||
|
CheckType -->|AttrSet| A_ResolveKeys[解析所有键]
|
||||||
|
A_ResolveKeys --> A_ResolveValues[解析所有值]
|
||||||
|
A_ResolveValues --> End
|
||||||
|
A_ResolveKeys -.->|resolve| Resolve
|
||||||
|
A_ResolveValues -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|List| L_ResolveElements[解析所有元素]
|
||||||
|
L_ResolveElements --> End
|
||||||
|
L_ResolveElements -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|HasAttr| HA_ResolveAttrSet[解析 AttrSet]
|
||||||
|
HA_ResolveAttrSet --> HA_ResolveAttrPath[解析 AttrPath]
|
||||||
|
HA_ResolveAttrPath --> End
|
||||||
|
HA_ResolveAttrSet -.->|resolve| Resolve
|
||||||
|
HA_ResolveAttrPath -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|BinOp| B_ResolveLeft[解析左操作数]
|
||||||
|
B_ResolveLeft --> B_ResolveRight[解析右操作数]
|
||||||
|
B_ResolveRight --> End
|
||||||
|
B_ResolveLeft -.->|resolve| Resolve
|
||||||
|
B_ResolveRight -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|UnOp| U_ResolveOperand[解析操作数]
|
||||||
|
U_ResolveOperand --> End
|
||||||
|
U_ResolveOperand -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|Select| S_ResolveAttrSet[解析 AttrSet]
|
||||||
|
S_ResolveAttrSet --> S_ResolveAttrPath[解析 AttrPath]
|
||||||
|
S_ResolveAttrPath --> End
|
||||||
|
S_ResolveAttrSet -.->|resolve| Resolve
|
||||||
|
S_ResolveAttrPath -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|If| I_ResolveCond[解析条件]
|
||||||
|
I_ResolveCond --> I_ResolveConsq[解析 then 分支]
|
||||||
|
I_ResolveConsq --> I_ResolveAlter[解析 else 分支]
|
||||||
|
I_ResolveAlter --> End
|
||||||
|
I_ResolveCond -.->|resolve| Resolve
|
||||||
|
I_ResolveConsq -.->|resolve| Resolve
|
||||||
|
I_ResolveAlter -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|Call| C_ResolveFunc[解析函数]
|
||||||
|
C_ResolveFunc --> C_ResolveArg[解析参数]
|
||||||
|
C_ResolveArg --> End
|
||||||
|
C_ResolveFunc -.->|resolve| Resolve
|
||||||
|
C_ResolveArg -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|Let| Le_EnterLet[进入 Let 环境]
|
||||||
|
Le_EnterLet --> Le_ResolveValues[解析所有键]
|
||||||
|
Le_ResolveValues --> Le_ResolveBody[解析表达式]
|
||||||
|
Le_ResolveBody --> Le_ExitLet[退出 Let 环境]
|
||||||
|
Le_ExitLet --> End
|
||||||
|
Le_ResolveValues -.->|resolve| Resolve
|
||||||
|
Le_ResolveBody -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|With| W_ResolveNameSpace[解析命名空间]
|
||||||
|
W_ResolveNameSpace --> W_EnterWith[进入命名空间]
|
||||||
|
W_EnterWith --> W_ResolveBody[解析表达式]
|
||||||
|
W_ResolveBody --> W_ExitWith[退出命名空间]
|
||||||
|
W_ExitWith --> End
|
||||||
|
W_ResolveNameSpace -.->|resolve| Resolve
|
||||||
|
W_ResolveBody -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|Assert| As_ResolveCond[解析断言]
|
||||||
|
As_ResolveCond --> As_ResolveBody[解析表达式]
|
||||||
|
As_ResolveBody --> End
|
||||||
|
As_ResolveCond -.->|resolve| Resolve
|
||||||
|
As_ResolveBody -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|ConcatStrings| CS_ResolveParts[解析所有字符串片段]
|
||||||
|
CS_ResolveParts --> End
|
||||||
|
CS_ResolveParts -.->|resolve| Resolve
|
||||||
|
|
||||||
|
CheckType -->|Const| End
|
||||||
|
|
||||||
|
CheckType -->|Str| End
|
||||||
|
|
||||||
|
CheckType -->|Var| V_Lookup[查找变量]
|
||||||
|
V_Lookup --> End
|
||||||
|
|
||||||
|
CheckType -->|Arg| Ar_Lookup[查找参数]
|
||||||
|
Ar_Lookup --> End
|
||||||
|
|
||||||
|
CheckType -->|Func| F_EnterEnv[进入函数环境]
|
||||||
|
F_EnterEnv --> F_ResolveBody[解析函数体]
|
||||||
|
F_ResolveBody --> F_ExitEnv[退出函数环境]
|
||||||
|
F_ExitEnv --> End
|
||||||
|
|
||||||
|
End([返回结果])
|
||||||
15
evaluator/nixjit/Cargo.toml
Normal file
15
evaluator/nixjit/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mimalloc = "0.1"
|
||||||
|
|
||||||
|
anyhow = "1.0"
|
||||||
|
bumpalo = "3.19"
|
||||||
|
regex = "1.11"
|
||||||
|
rustyline = "14.0"
|
||||||
|
|
||||||
|
nixjit_context = { path = "../nixjit_context" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
16
evaluator/nixjit/src/lib.rs
Normal file
16
evaluator/nixjit/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//! The main library crate for the nixjit interpreter and JIT compiler.
|
||||||
|
//!
|
||||||
|
//! This crate orchestrates the entire process of parsing, analyzing,
|
||||||
|
//! and evaluating Nix expressions. It integrates all the other `nixjit_*`
|
||||||
|
//! components to provide a complete Nix evaluation environment.
|
||||||
|
|
||||||
|
use mimalloc::MiMalloc;
|
||||||
|
|
||||||
|
pub use nixjit_context as context;
|
||||||
|
pub use nixjit_value as value;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static GLOBAL: MiMalloc = MiMalloc;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
53
evaluator/nixjit/src/main.rs
Normal file
53
evaluator/nixjit/src/main.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use regex::Regex;
|
||||||
|
use rustyline::DefaultEditor;
|
||||||
|
use rustyline::error::ReadlineError;
|
||||||
|
|
||||||
|
use nixjit::context::Context;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut rl = DefaultEditor::new()?;
|
||||||
|
let bump = Bump::new();
|
||||||
|
let mut context = Context::new(&bump);
|
||||||
|
let re = Regex::new(r"^\s*([a-zA-Z_][a-zA-Z0-9_'-]*)\s*=(.*)$").unwrap();
|
||||||
|
loop {
|
||||||
|
let readline = rl.readline("nixjit-repl> ");
|
||||||
|
match readline {
|
||||||
|
Ok(line) => {
|
||||||
|
if line.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let _ = rl.add_history_entry(line.as_str());
|
||||||
|
if let Some(caps) = re.captures(&line) {
|
||||||
|
let ident = caps.get(1).unwrap().as_str();
|
||||||
|
let expr = caps.get(2).unwrap().as_str().trim();
|
||||||
|
if expr.is_empty() {
|
||||||
|
eprintln!("Error: missing expression after '='");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Err(err) = context.add_binding(ident, expr) {
|
||||||
|
eprintln!("Error: {}", err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match context.eval(&line) {
|
||||||
|
Ok(value) => println!("{}", value),
|
||||||
|
Err(err) => eprintln!("Error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ReadlineError::Interrupted) => {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
Err(ReadlineError::Eof) => {
|
||||||
|
println!("CTRL-D");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,40 +1,21 @@
|
|||||||
extern crate test;
|
#![allow(unused_macros)]
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use inkwell::context::Context;
|
use bumpalo::Bump;
|
||||||
use test::{Bencher, black_box};
|
use nixjit_context::Context;
|
||||||
|
use nixjit_value::{AttrSet, Const, List, Symbol, Value};
|
||||||
use ecow::EcoString;
|
|
||||||
use rpds::vector_sync;
|
|
||||||
|
|
||||||
use crate::compile::compile;
|
|
||||||
use crate::ir::downgrade;
|
|
||||||
use crate::ty::public::Symbol;
|
|
||||||
use crate::ty::public::*;
|
|
||||||
use crate::vm::JITContext;
|
|
||||||
|
|
||||||
use super::run;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn test_expr(expr: &str, expected: Value) {
|
fn test_expr(expr: &str, expected: Value) {
|
||||||
let downgraded = downgrade(rnix::Root::parse(expr).tree().expr().unwrap()).unwrap();
|
println!("{expr}");
|
||||||
let prog = compile(downgraded);
|
assert_eq!(Context::new(&Bump::new()).eval(expr).unwrap(), expected);
|
||||||
dbg!(&prog);
|
|
||||||
let ctx = Context::create();
|
|
||||||
let jit = JITContext::new(&ctx);
|
|
||||||
assert_eq!(run(prog, jit).unwrap(), expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! map {
|
macro_rules! map {
|
||||||
($($k:expr => $v:expr),*) => {
|
($($k:expr => $v:expr),*) => {
|
||||||
{
|
{
|
||||||
#[allow(unused_mut)]
|
BTreeMap::from([$(($k, $v),)*])
|
||||||
let mut m = HashMap::new();
|
|
||||||
$(
|
|
||||||
m.insert($k, $v);
|
|
||||||
)*
|
|
||||||
m
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -65,19 +46,19 @@ macro_rules! boolean {
|
|||||||
|
|
||||||
macro_rules! string {
|
macro_rules! string {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
Value::Const(Const::String(EcoString::from($e)))
|
Value::String(String::from($e))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! symbol {
|
macro_rules! symbol {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
Symbol::from($e.to_string())
|
Symbol::from($e)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! list {
|
macro_rules! list {
|
||||||
($($x:tt)*) => (
|
($($x:tt)*) => (
|
||||||
Value::List(List::new(vector_sync![$($x)*]))
|
Value::List(List::new(vec![$($x)*]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +140,13 @@ fn test_attrs() {
|
|||||||
symbol!("a") => int!(1)
|
symbol!("a") => int!(1)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
test_expr(
|
||||||
|
"rec { a = 1; b = a; }",
|
||||||
|
attrs! {
|
||||||
|
symbol!("a") => int!(1),
|
||||||
|
symbol!("b") => int!(1)
|
||||||
|
},
|
||||||
|
);
|
||||||
test_expr("{ a = 1; }.a", int!(1));
|
test_expr("{ a = 1; }.a", int!(1));
|
||||||
test_expr("{ a = 1; }.b or 1", int!(1));
|
test_expr("{ a = 1; }.b or 1", int!(1));
|
||||||
test_expr(
|
test_expr(
|
||||||
@@ -184,11 +172,6 @@ fn test_if() {
|
|||||||
test_expr("if true || false then 1 else 2", int!(1));
|
test_expr("if true || false then 1 else 2", int!(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_with() {
|
|
||||||
test_expr(r#"with { a = 1; }; a"#, int!(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_let() {
|
fn test_let() {
|
||||||
test_expr(r#"let a = 1; in a"#, int!(1));
|
test_expr(r#"let a = 1; in a"#, int!(1));
|
||||||
@@ -198,19 +181,31 @@ fn test_let() {
|
|||||||
r#"let b = "c"; in { a.b = 1; } // { a."a${b}" = 2; }"#,
|
r#"let b = "c"; in { a.b = 1; } // { a."a${b}" = 2; }"#,
|
||||||
attrs! { symbol!("a") => attrs!{ symbol!("ac") => int!(2) } },
|
attrs! { symbol!("a") => attrs!{ symbol!("ac") => int!(2) } },
|
||||||
);
|
);
|
||||||
|
test_expr(
|
||||||
|
"let f = n: let a = n; f = x: a + x; in f; in f 0 1",
|
||||||
|
int!(1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_func() {
|
fn test_func() {
|
||||||
test_expr("(x: x) 1", int!(1));
|
test_expr("(x: x) 1", int!(1));
|
||||||
test_expr("(x: x) (x: x) 1", int!(1));
|
test_expr("(x: x) (x: x) 1", int!(1));
|
||||||
test_expr("(x: y: x + y) 1 1", int!(2));
|
test_expr("(x: y: x / y) 1 2", int!(0));
|
||||||
test_expr("({ x, y }: x + y) { x = 1; y = 2; }", int!(3));
|
test_expr("({ x, y }: x + y) { x = 1; y = 2; }", int!(3));
|
||||||
test_expr("({ x, y, ... }: x + y) { x = 1; y = 2; z = 3; }", int!(3));
|
test_expr("({ x, y, ... }: x + y) { x = 1; y = 2; z = 3; }", int!(3));
|
||||||
test_expr(
|
test_expr(
|
||||||
"(inputs@{ x, y, ... }: x + inputs.y) { x = 1; y = 2; z = 3; }",
|
"(inputs@{ x, y, ... }: x + inputs.y) { x = 1; y = 2; z = 3; }",
|
||||||
int!(3),
|
int!(3),
|
||||||
);
|
);
|
||||||
|
test_expr(
|
||||||
|
"((f: let x = f x; in x) (self: { x = 1; y = self.x + 1; })).y",
|
||||||
|
int!(2),
|
||||||
|
);
|
||||||
|
test_expr(
|
||||||
|
"let fix = f: let x = f x; in x; in (fix (self: { x = 1; y = self.x + 1; })).y",
|
||||||
|
int!(2),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -219,15 +214,5 @@ fn test_fib() {
|
|||||||
test_expr(
|
test_expr(
|
||||||
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30",
|
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30",
|
||||||
int!(832040),
|
int!(832040),
|
||||||
)
|
);
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_fib(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(test_expr(
|
|
||||||
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 20",
|
|
||||||
int!(6765),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
10
evaluator/nixjit_builtins/Cargo.toml
Normal file
10
evaluator/nixjit_builtins/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_builtins"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nixjit_error = { path = "../nixjit_error" }
|
||||||
|
nixjit_eval = { path = "../nixjit_eval" }
|
||||||
|
nixjit_macros = { path = "../nixjit_macros" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
69
evaluator/nixjit_builtins/src/lib.rs
Normal file
69
evaluator/nixjit_builtins/src/lib.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
use nixjit_error::Result;
|
||||||
|
use nixjit_eval::{Args, Value};
|
||||||
|
use nixjit_macros::builtins;
|
||||||
|
|
||||||
|
pub trait BuiltinsContext {}
|
||||||
|
|
||||||
|
pub type BuiltinFn<Ctx> = fn(&mut Ctx, Args) -> Result<Value>;
|
||||||
|
|
||||||
|
#[builtins]
|
||||||
|
pub mod builtins {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_eval::{List, Value};
|
||||||
|
use nixjit_value::Const;
|
||||||
|
|
||||||
|
use super::BuiltinsContext;
|
||||||
|
|
||||||
|
const TRUE: Const = Const::Bool(true);
|
||||||
|
const FALSE: Const = Const::Bool(false);
|
||||||
|
const NULL: Const = Const::Null;
|
||||||
|
|
||||||
|
fn add(a: Value, b: Value) -> Result<Value> {
|
||||||
|
use Value::*;
|
||||||
|
Ok(match (a, b) {
|
||||||
|
(Int(a), Int(b)) => Int(a + b),
|
||||||
|
(Int(a), Float(b)) => Float(a as f64 + b),
|
||||||
|
(Float(a), Int(b)) => Float(a + b as f64),
|
||||||
|
(Float(a), Float(b)) => Float(a + b),
|
||||||
|
(Int(_), b) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"expected an integer but found {}",
|
||||||
|
b.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
(Float(_), b) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"expected an float but found {}",
|
||||||
|
b.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
(a, _) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"expected an integer but found {}",
|
||||||
|
a.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import(ctx: &mut impl BuiltinsContext, path: Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elem_at(list: Rc<List>, idx: i64) -> Result<Value> {
|
||||||
|
list.get(idx as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::eval_error(format!(
|
||||||
|
"'builtins.elemAt' called with index {idx} on a list of size {}",
|
||||||
|
list.len()
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elem(elem: Value, list: Rc<List>) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
27
evaluator/nixjit_context/Cargo.toml
Normal file
27
evaluator/nixjit_context/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_context"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bumpalo = { version = "3.19", features = ["boxed", "collections"] }
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
hashbrown = "0.15"
|
||||||
|
itertools = "0.14"
|
||||||
|
petgraph = "0.8"
|
||||||
|
replace_with = "0.1"
|
||||||
|
rnix = "0.12"
|
||||||
|
|
||||||
|
cranelift = "0.122"
|
||||||
|
cranelift-module = "0.122"
|
||||||
|
cranelift-jit = "0.122"
|
||||||
|
cranelift-native = "0.122"
|
||||||
|
|
||||||
|
nixjit_builtins = { path = "../nixjit_builtins" }
|
||||||
|
nixjit_error = { path = "../nixjit_error" }
|
||||||
|
nixjit_eval = { path = "../nixjit_eval" }
|
||||||
|
nixjit_hir = { path = "../nixjit_hir" }
|
||||||
|
nixjit_ir = { path = "../nixjit_ir" }
|
||||||
|
nixjit_jit = { path = "../nixjit_jit" }
|
||||||
|
nixjit_lir = { path = "../nixjit_lir" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
76
evaluator/nixjit_context/src/downgrade.rs
Normal file
76
evaluator/nixjit_context/src/downgrade.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use nixjit_error::Result;
|
||||||
|
use nixjit_hir::{Downgrade, DowngradeContext, Hir};
|
||||||
|
use nixjit_ir::ExprId;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
|
||||||
|
pub struct DowngradeCtx<'ctx, 'bump> {
|
||||||
|
ctx: &'ctx mut Context<'bump>,
|
||||||
|
irs: Vec<RefCell<Hir>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx, 'bump> DowngradeCtx<'ctx, 'bump> {
|
||||||
|
pub fn new(ctx: &'ctx mut Context<'bump>) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
irs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DowngradeCtx<'_, '_> {
|
||||||
|
fn get_ir(&self, id: ExprId) -> &RefCell<Hir> {
|
||||||
|
// SAFETY: The `ExprId` is guaranteed to be valid and correspond to an expression
|
||||||
|
// allocated within this context, making the raw index access safe.
|
||||||
|
let idx = unsafe { id.raw() } - self.ctx.lirs.len() - self.ctx.hirs.len();
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
self.irs.get(idx).unwrap()
|
||||||
|
} else {
|
||||||
|
// SAFETY: The index calculation is guarded by the logic that creates `ExprId`s,
|
||||||
|
// ensuring it's always within the bounds of the `irs` vector in release builds.
|
||||||
|
// The debug build's `unwrap()` serves as a runtime check for this invariant.
|
||||||
|
unsafe { self.irs.get_unchecked(idx) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DowngradeContext for DowngradeCtx<'_, '_> {
|
||||||
|
fn new_expr(&mut self, expr: Hir) -> ExprId {
|
||||||
|
self.irs.push(expr.into());
|
||||||
|
self.ctx.alloc_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T {
|
||||||
|
// SAFETY: This is a common pattern to temporarily bypass the borrow checker.
|
||||||
|
// We are creating a mutable reference to `self` from a raw pointer. This is safe
|
||||||
|
// because `self_mut` is only used within the closure `f`, and we are careful
|
||||||
|
// not to create aliasing mutable references. The `RefCell`'s runtime borrow
|
||||||
|
// checking further ensures that we don't have multiple mutable borrows of the
|
||||||
|
// same `Hir` expression simultaneously.
|
||||||
|
unsafe {
|
||||||
|
let self_mut = &mut *(self as *mut Self);
|
||||||
|
f(&mut self.get_ir(id).borrow_mut(), self_mut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade_root(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||||
|
let id = root.downgrade(&mut self)?;
|
||||||
|
self.ctx
|
||||||
|
.hirs
|
||||||
|
.extend(self.irs.into_iter().map(RefCell::into_inner));
|
||||||
|
for (idx, ir) in self.ctx.hirs.iter().enumerate() {
|
||||||
|
println!(
|
||||||
|
"{:?} {:#?}",
|
||||||
|
// SAFETY: The index `idx` is obtained from iterating over `self.ctx.hirs`,
|
||||||
|
// so it is guaranteed to be a valid index. The length of `lirs` is added
|
||||||
|
// as an offset to ensure the `ExprId` correctly corresponds to its position
|
||||||
|
// in the combined IR storage.
|
||||||
|
unsafe { ExprId::from_raw(idx + self.ctx.lirs.len()) },
|
||||||
|
&ir
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
136
evaluator/nixjit_context/src/eval.rs
Normal file
136
evaluator/nixjit_context/src/eval.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use petgraph::prelude::DiGraph;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_eval::{Args, EvalContext, Evaluate, PrimOpApp, Value, ValueId};
|
||||||
|
use nixjit_ir::{ExprId, PrimOpId};
|
||||||
|
use nixjit_jit::JITContext;
|
||||||
|
use nixjit_lir::Lir;
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
|
||||||
|
enum ValueCache {
|
||||||
|
Expr(ExprId),
|
||||||
|
BlackHole,
|
||||||
|
Value(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueCache {
|
||||||
|
fn get_or_eval(&mut self, eval: impl FnOnce(ExprId) -> Result<Value>) -> Result<&Value> {
|
||||||
|
match self {
|
||||||
|
&mut Self::Expr(id) => {
|
||||||
|
*self = Self::BlackHole;
|
||||||
|
match eval(id) {
|
||||||
|
Ok(value) => {
|
||||||
|
*self = Self::Value(value);
|
||||||
|
let Self::Value(value) = self else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Value(value) => Ok(value),
|
||||||
|
Self::BlackHole => Err(Error::eval_error(format!("infinite recursion encountered"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EvalCtx<'ctx, 'bump> {
|
||||||
|
ctx: &'ctx mut Context<'bump>,
|
||||||
|
graph: DiGraph<Vec<ValueId>, ()>,
|
||||||
|
caches: Vec<ValueCache>,
|
||||||
|
with_scopes: Vec<Rc<HashMap<String, Value>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx, 'bump> EvalCtx<'ctx, 'bump> {
|
||||||
|
pub fn new(ctx: &'ctx mut Context<'bump>) -> Self {
|
||||||
|
Self {
|
||||||
|
graph: DiGraph::with_capacity(ctx.graph.node_count(), ctx.graph.edge_count()),
|
||||||
|
caches: Vec::new(),
|
||||||
|
with_scopes: Vec::new(),
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EvalContext for EvalCtx<'_, '_> {
|
||||||
|
fn eval(&mut self, expr: ExprId) -> Result<Value> {
|
||||||
|
// SAFETY: The `expr` `ExprId` is guaranteed to be a valid index into the `lirs`
|
||||||
|
// vector by the `downgrade` and `resolve` stages, which are responsible for
|
||||||
|
// creating and managing these IDs. The `get_unchecked` is safe under this invariant.
|
||||||
|
// The subsequent raw pointer operations are to safely extend the lifetime of the
|
||||||
|
// `Lir` reference. This is sound because the `lirs` vector is allocated within a
|
||||||
|
// `Bump` arena, ensuring that the `Lir` objects have a stable memory location
|
||||||
|
// and will not be deallocated or moved for the lifetime of the context.
|
||||||
|
let idx = unsafe { expr.raw() };
|
||||||
|
let lir = unsafe { &*(&**self.ctx.lirs.get_unchecked(idx) as *const Lir) };
|
||||||
|
lir.eval(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(&mut self, id: ExprId) -> Result<ValueId> {
|
||||||
|
let mut deps = Vec::new();
|
||||||
|
|
||||||
|
self.caches.push(ValueCache::Expr(id));
|
||||||
|
let id = self.graph.add_node(deps);
|
||||||
|
|
||||||
|
// SAFETY: The `id.index()` is guaranteed to be a valid raw ID for a `ValueId`
|
||||||
|
// because it is generated by the `petgraph::DiGraph`, which manages its own
|
||||||
|
// internal indices. This ensures that the raw value is unique and corresponds
|
||||||
|
// to a valid node in the graph.
|
||||||
|
Ok(unsafe { ValueId::from_raw(id.index()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, func: ValueId, arg: Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force(&mut self, id: ValueId) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value> {
|
||||||
|
for scope in self.with_scopes.iter().rev() {
|
||||||
|
if let Some(val) = scope.get(ident) {
|
||||||
|
return Some(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value> {
|
||||||
|
// SAFETY: The `PrimOpId` is created and managed by the `Context` and is
|
||||||
|
// guaranteed to be a valid index into the `primops` array. The `get_unchecked`
|
||||||
|
// is safe under this invariant, avoiding a bounds check for performance.
|
||||||
|
let &(arity, primop) = unsafe { self.ctx.primops.get_unchecked(id.raw()) };
|
||||||
|
if args.len() == arity {
|
||||||
|
primop(self.ctx, args)
|
||||||
|
} else {
|
||||||
|
Ok(Value::PrimOpApp(PrimOpApp::new(id, args).into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_with_env<T>(
|
||||||
|
&mut self,
|
||||||
|
namespace: Rc<HashMap<String, Value>>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> T {
|
||||||
|
self.with_scopes.push(namespace);
|
||||||
|
let res = f(self);
|
||||||
|
self.with_scopes.pop();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JITContext for EvalCtx<'_, '_> {
|
||||||
|
fn enter_with(&mut self, namespace: Rc<HashMap<String, Value>>) {
|
||||||
|
self.with_scopes.push(namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_with(&mut self) {
|
||||||
|
self.with_scopes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
216
evaluator/nixjit_context/src/lib.rs
Normal file
216
evaluator/nixjit_context/src/lib.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
use bumpalo::{Bump, boxed::Box};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use petgraph::graphmap::DiGraphMap;
|
||||||
|
|
||||||
|
use nixjit_builtins::{
|
||||||
|
builtins::{GLOBAL_LEN, SCOPED_LEN}, BuiltinFn, Builtins, BuiltinsContext
|
||||||
|
};
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_eval::{Args, EvalContext, Value};
|
||||||
|
use nixjit_hir::{DowngradeContext, Hir};
|
||||||
|
use nixjit_ir::{AttrSet, ExprId, Param, PrimOpId};
|
||||||
|
use nixjit_lir::Lir;
|
||||||
|
|
||||||
|
use crate::downgrade::DowngradeCtx;
|
||||||
|
use crate::eval::EvalCtx;
|
||||||
|
use crate::resolve::ResolveCtx;
|
||||||
|
|
||||||
|
mod downgrade;
|
||||||
|
mod eval;
|
||||||
|
mod resolve;
|
||||||
|
|
||||||
|
/// The main evaluation context.
|
||||||
|
///
|
||||||
|
/// This struct orchestrates the entire Nix expression evaluation process,
|
||||||
|
/// from parsing and semantic analysis to interpretation and JIT compilation.
|
||||||
|
pub struct Context<'bump> {
|
||||||
|
ir_count: usize,
|
||||||
|
hirs: Vec<Hir>,
|
||||||
|
lirs: Vec<Box<'bump, Lir>>,
|
||||||
|
/// Maps a function's body `ExprId` to its parameter definition.
|
||||||
|
funcs: HashMap<ExprId, Param>,
|
||||||
|
|
||||||
|
repl_scope: NonNull<HashMap<String, ExprId>>,
|
||||||
|
global_scope: NonNull<HashMap<&'static str, ExprId>>,
|
||||||
|
|
||||||
|
/// A dependency graph between expressions.
|
||||||
|
graph: DiGraphMap<ExprId, ()>,
|
||||||
|
|
||||||
|
/// A table of primitive operation implementations.
|
||||||
|
primops: [(usize, BuiltinFn<Self>); GLOBAL_LEN + SCOPED_LEN],
|
||||||
|
|
||||||
|
bump: &'bump Bump,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Context<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: `repl_scope` and `global_scope` are `NonNull` pointers to `HashMap`s
|
||||||
|
// allocated within the `bump` arena. Because `NonNull` does not convey ownership,
|
||||||
|
// Rust's drop checker will not automatically drop the pointed-to `HashMap`s when
|
||||||
|
// the `Context` is dropped. We must manually call `drop_in_place` to ensure
|
||||||
|
// their destructors are run. This is safe because these pointers are guaranteed
|
||||||
|
// to be valid and non-null for the lifetime of the `Context`, as they are
|
||||||
|
// initialized in `new()` and never deallocated or changed.
|
||||||
|
unsafe {
|
||||||
|
self.repl_scope.drop_in_place();
|
||||||
|
self.global_scope.drop_in_place();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'bump> Context<'bump> {
|
||||||
|
pub fn new(bump: &'bump Bump) -> Self {
|
||||||
|
let Builtins { global, scoped } = Builtins::new();
|
||||||
|
let global_scope = global
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, (k, _, _))| {
|
||||||
|
// SAFETY: The index `idx` comes from `enumerate()` on the `global` array,
|
||||||
|
// so it is guaranteed to be a valid, unique index for a primop LIR.
|
||||||
|
(*k, unsafe { ExprId::from_raw(idx) })
|
||||||
|
})
|
||||||
|
.chain(core::iter::once((
|
||||||
|
"builtins",
|
||||||
|
// SAFETY: This ID corresponds to the `builtins` attrset LIR, which is
|
||||||
|
// constructed and placed after all the global and scoped primop LIRs.
|
||||||
|
// The index is calculated to be exactly at that position.
|
||||||
|
unsafe { ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN) },
|
||||||
|
)))
|
||||||
|
.collect();
|
||||||
|
let primops = global
|
||||||
|
.iter()
|
||||||
|
.map(|&(_, arity, f)| (arity, f))
|
||||||
|
.chain(scoped.iter().map(|&(_, arity, f)| (arity, f)))
|
||||||
|
.collect_array()
|
||||||
|
.unwrap();
|
||||||
|
let lirs = (0..global.len())
|
||||||
|
.map(|idx| {
|
||||||
|
// SAFETY: The index `idx` is guaranteed to be within the bounds of the
|
||||||
|
// `global` primops array, making it a valid raw ID for a `PrimOpId`.
|
||||||
|
Lir::PrimOp(unsafe { PrimOpId::from_raw(idx) })
|
||||||
|
})
|
||||||
|
.chain((0..scoped.len()).map(|idx| {
|
||||||
|
// SAFETY: The index `idx` is within the bounds of the `scoped` primops
|
||||||
|
// array. Adding `GLOBAL_LEN` correctly offsets it to its position in
|
||||||
|
// the combined `primops` table.
|
||||||
|
Lir::PrimOp(unsafe { PrimOpId::from_raw(idx + GLOBAL_LEN) })
|
||||||
|
}))
|
||||||
|
.chain(core::iter::once(Lir::AttrSet(AttrSet {
|
||||||
|
stcs: global
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, (name, ..))| {
|
||||||
|
// SAFETY: `idx` from `enumerate` is a valid index for the LIR
|
||||||
|
// corresponding to this global primop.
|
||||||
|
(name.to_string(), unsafe { ExprId::from_raw(idx) })
|
||||||
|
})
|
||||||
|
.chain(scoped.into_iter().enumerate().map(|(idx, (name, ..))| {
|
||||||
|
// SAFETY: `idx + GLOBAL_LEN` is a valid index for the LIR
|
||||||
|
// corresponding to this scoped primop.
|
||||||
|
(name.to_string(), unsafe {
|
||||||
|
ExprId::from_raw(idx + GLOBAL_LEN)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.chain(core::iter::once((
|
||||||
|
"builtins".to_string(),
|
||||||
|
// SAFETY: This ID points to the `Thunk` that wraps this very
|
||||||
|
// `AttrSet`. The index is calculated to be one position after
|
||||||
|
// the `AttrSet` itself.
|
||||||
|
unsafe { ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN + 1) },
|
||||||
|
)))
|
||||||
|
.collect(),
|
||||||
|
..AttrSet::default()
|
||||||
|
})))
|
||||||
|
.chain(core::iter::once(Lir::Thunk(
|
||||||
|
// SAFETY: This ID points to the `builtins` `AttrSet` defined just above.
|
||||||
|
// Its index is calculated to be at that exact position.
|
||||||
|
unsafe { ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN) },
|
||||||
|
)))
|
||||||
|
.map(|lir| Box::new_in(lir, bump))
|
||||||
|
.collect_vec();
|
||||||
|
Self {
|
||||||
|
ir_count: lirs.len(),
|
||||||
|
hirs: Vec::new(),
|
||||||
|
lirs,
|
||||||
|
funcs: HashMap::new(),
|
||||||
|
|
||||||
|
global_scope: NonNull::from(bump.alloc(global_scope)),
|
||||||
|
repl_scope: NonNull::from(bump.alloc(HashMap::new())),
|
||||||
|
graph: DiGraphMap::new(),
|
||||||
|
|
||||||
|
primops,
|
||||||
|
|
||||||
|
bump,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downgrade_ctx<'a>(&'a mut self) -> DowngradeCtx<'a, 'bump> {
|
||||||
|
DowngradeCtx::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_ctx<'a>(&'a mut self, root: ExprId) -> ResolveCtx<'a, 'bump> {
|
||||||
|
ResolveCtx::new(self, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_ctx<'a>(&'a mut self) -> EvalCtx<'a, 'bump> {
|
||||||
|
EvalCtx::new(self)
|
||||||
|
}
|
||||||
|
/// The main entry point for evaluating a Nix expression string.
|
||||||
|
///
|
||||||
|
/// This function performs the following steps:
|
||||||
|
/// 1. Parses the expression string into an `rnix` AST.
|
||||||
|
/// 2. Downgrades the AST to the High-Level IR (HIR).
|
||||||
|
/// 3. Resolves the HIR to the Low-Level IR (LIR).
|
||||||
|
/// 4. Evaluates the LIR to produce a final `Value`.
|
||||||
|
pub fn eval(&mut self, expr: &str) -> Result<nixjit_value::Value> {
|
||||||
|
let root = rnix::Root::parse(expr);
|
||||||
|
if !root.errors().is_empty() {
|
||||||
|
return Err(Error::parse_error(
|
||||||
|
root.errors().iter().map(|err| err.to_string()).join("; "),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let root = self
|
||||||
|
.downgrade_ctx()
|
||||||
|
.downgrade_root(root.tree().expr().unwrap())?;
|
||||||
|
let ctx = self.resolve_ctx(root);
|
||||||
|
ctx.resolve_root()?;
|
||||||
|
Ok(self.eval_ctx().eval(root)?.to_public())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_binding(&mut self, ident: &str, expr: &str) -> Result<()> {
|
||||||
|
let root = rnix::Root::parse(expr);
|
||||||
|
let root_expr = root
|
||||||
|
.ok()
|
||||||
|
.map_err(|err| Error::parse_error(err.to_string()))?
|
||||||
|
.expr()
|
||||||
|
.unwrap();
|
||||||
|
let expr_id = self.downgrade_ctx().downgrade_root(root_expr)?;
|
||||||
|
self.resolve_ctx(expr_id).resolve_root()?;
|
||||||
|
// SAFETY: `repl_scope` is a `NonNull` pointer that is guaranteed to be valid
|
||||||
|
// for the lifetime of `Context`. It is initialized in `new()` and the memory
|
||||||
|
// it points to is managed by the `bump` arena. Therefore, it is safe to
|
||||||
|
// dereference it to a mutable reference here.
|
||||||
|
unsafe { self.repl_scope.as_mut() }.insert(ident.to_string(), expr_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context<'_> {
|
||||||
|
fn alloc_id(&mut self) -> ExprId {
|
||||||
|
self.ir_count += 1;
|
||||||
|
// SAFETY: This function is the sole source of new `ExprId`s during the
|
||||||
|
// downgrade and resolve phases. By monotonically incrementing `ir_count`,
|
||||||
|
// we guarantee that each ID is unique and corresponds to a valid, soon-to-be-
|
||||||
|
// allocated slot in the IR vectors.
|
||||||
|
unsafe { ExprId::from_raw(self.ir_count - 1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_dep(&mut self, from: ExprId, to: ExprId) {
|
||||||
|
self.graph.add_edge(from, to, ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltinsContext for Context<'_> {}
|
||||||
218
evaluator/nixjit_context/src/resolve.rs
Normal file
218
evaluator/nixjit_context/src/resolve.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
use std::{cell::RefCell, ptr::NonNull};
|
||||||
|
|
||||||
|
use bumpalo::{boxed::Box, collections::Vec};
|
||||||
|
use derive_more::{Constructor, Unwrap};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use replace_with::replace_with_and_return;
|
||||||
|
|
||||||
|
use nixjit_error::Result;
|
||||||
|
use nixjit_hir::Hir;
|
||||||
|
use nixjit_ir::{Const, ExprId, Param, StackIdx};
|
||||||
|
use nixjit_lir::{Lir, LookupResult, Resolve, ResolveContext};
|
||||||
|
|
||||||
|
use super::Context;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Scope {
|
||||||
|
/// A `let` binding scope, mapping variable names to their expression IDs.
|
||||||
|
Let(HashMap<String, ExprId>),
|
||||||
|
/// A function argument scope. `Some` holds the name of the argument set if present.
|
||||||
|
Arg(Option<String>),
|
||||||
|
// Not using &'ctx HashMap<_, _> because bumpalo's Vec<'bump, T> is invariant over T.
|
||||||
|
Builtins(NonNull<HashMap<&'static str, ExprId>>),
|
||||||
|
Repl(NonNull<HashMap<String, ExprId>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an expression at different stages of compilation.
|
||||||
|
#[derive(Debug, Unwrap)]
|
||||||
|
enum Ir {
|
||||||
|
/// An expression in the High-Level Intermediate Representation (HIR).
|
||||||
|
Hir(Hir),
|
||||||
|
/// An expression in the Low-Level Intermediate Representation (LIR).
|
||||||
|
Lir(Lir),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Constructor)]
|
||||||
|
struct Closure {
|
||||||
|
id: ExprId,
|
||||||
|
arg: ExprId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ResolveCtx<'ctx, 'bump> {
|
||||||
|
ctx: &'ctx mut Context<'bump>,
|
||||||
|
irs: Vec<'bump, RefCell<Ir>>,
|
||||||
|
root: ExprId,
|
||||||
|
closures: Vec<'bump, Closure>,
|
||||||
|
scopes: Vec<'bump, Scope>,
|
||||||
|
has_with: bool,
|
||||||
|
with_used: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx, 'bump> ResolveCtx<'ctx, 'bump> {
|
||||||
|
pub fn new(ctx: &'ctx mut Context<'bump>, root: ExprId) -> Self {
|
||||||
|
Self {
|
||||||
|
scopes: {
|
||||||
|
let mut vec = Vec::new_in(ctx.bump);
|
||||||
|
vec.push(Scope::Builtins(ctx.global_scope));
|
||||||
|
vec.push(Scope::Repl(ctx.repl_scope));
|
||||||
|
vec
|
||||||
|
},
|
||||||
|
has_with: false,
|
||||||
|
with_used: false,
|
||||||
|
irs: Vec::from_iter_in(
|
||||||
|
core::mem::take(&mut ctx.hirs)
|
||||||
|
.into_iter()
|
||||||
|
.map(Ir::Hir)
|
||||||
|
.map(RefCell::new),
|
||||||
|
ctx.bump,
|
||||||
|
),
|
||||||
|
closures: Vec::new_in(ctx.bump),
|
||||||
|
ctx,
|
||||||
|
root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_root(mut self) -> Result<()> {
|
||||||
|
let ret = self.resolve(self.root);
|
||||||
|
ret.map(|_| {
|
||||||
|
self.ctx.lirs.extend(
|
||||||
|
self.irs
|
||||||
|
.into_iter()
|
||||||
|
.map(RefCell::into_inner)
|
||||||
|
.map(Ir::unwrap_lir)
|
||||||
|
.map(|lir| Box::new_in(lir, self.ctx.bump)),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ir(&self, id: ExprId) -> &RefCell<Ir> {
|
||||||
|
let idx = unsafe { id.raw() } - self.ctx.lirs.len();
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
self.irs.get(idx).unwrap()
|
||||||
|
} else {
|
||||||
|
unsafe { self.irs.get_unchecked(idx) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolveContext for ResolveCtx<'_, '_> {
|
||||||
|
fn resolve(&mut self, expr: ExprId) -> Result<ExprId> {
|
||||||
|
unsafe {
|
||||||
|
let ctx = &mut *(self as *mut Self);
|
||||||
|
let ir = self.get_ir(expr);
|
||||||
|
if !matches!(ir.try_borrow().as_deref(), Ok(Ir::Hir(_))) {
|
||||||
|
return Ok(expr);
|
||||||
|
}
|
||||||
|
replace_with_and_return(
|
||||||
|
&mut *ir.borrow_mut(),
|
||||||
|
|| {
|
||||||
|
Ir::Hir(Hir::Const(Const {
|
||||||
|
val: nixjit_value::Const::Null,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|ir| match ir.unwrap_hir().resolve(ctx) {
|
||||||
|
Ok(lir @ Lir::ExprRef(expr)) => (Ok(expr), Ir::Lir(lir)),
|
||||||
|
Ok(lir) => (Ok(expr), Ir::Lir(lir)),
|
||||||
|
Err(err) => (
|
||||||
|
Err(err),
|
||||||
|
Ir::Hir(Hir::Const(Const {
|
||||||
|
val: nixjit_value::Const::Null,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(&mut self, name: &str) -> LookupResult {
|
||||||
|
let mut closure_depth = 0;
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
match scope {
|
||||||
|
Scope::Builtins(scope) => {
|
||||||
|
if let Some(&primop) = unsafe { scope.as_ref() }.get(&name) {
|
||||||
|
return LookupResult::Expr(primop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scope::Let(scope) => {
|
||||||
|
if let Some(&dep) = scope.get(name) {
|
||||||
|
let expr = self
|
||||||
|
.closures
|
||||||
|
.last()
|
||||||
|
.map_or_else(|| self.root, |closure| closure.id);
|
||||||
|
self.ctx.add_dep(expr, dep);
|
||||||
|
return LookupResult::Expr(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Scope::Repl(scope) => {
|
||||||
|
if let Some(&dep) = unsafe { scope.as_ref() }.get(name) {
|
||||||
|
let expr = self
|
||||||
|
.closures
|
||||||
|
.last()
|
||||||
|
.map_or_else(|| self.root, |closure| closure.id);
|
||||||
|
self.ctx.add_dep(expr, dep);
|
||||||
|
return LookupResult::Expr(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scope::Arg(ident) => {
|
||||||
|
if ident.as_deref() == Some(name) {
|
||||||
|
let &Closure { id: func, arg } =
|
||||||
|
self.closures.iter().nth_back(closure_depth).unwrap();
|
||||||
|
self.ctx.add_dep(func, arg);
|
||||||
|
return LookupResult::Expr(arg);
|
||||||
|
}
|
||||||
|
closure_depth += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.has_with {
|
||||||
|
self.with_used = true;
|
||||||
|
LookupResult::Unknown
|
||||||
|
} else {
|
||||||
|
LookupResult::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_arg(&mut self) -> ExprId {
|
||||||
|
self.closures.last().unwrap().arg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_func(&mut self, body: ExprId, param: Param) {
|
||||||
|
self.ctx.funcs.insert(body, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_let_env<T>(
|
||||||
|
&mut self,
|
||||||
|
bindings: HashMap<String, ExprId>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> T {
|
||||||
|
self.scopes.push(Scope::Let(bindings));
|
||||||
|
let res = f(self);
|
||||||
|
self.scopes.pop();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_with_env(&mut self, f: impl FnOnce(&mut Self) -> Result<()>) -> Result<bool> {
|
||||||
|
let has_with = self.has_with;
|
||||||
|
let with_used = self.with_used;
|
||||||
|
self.has_with = true;
|
||||||
|
self.with_used = false;
|
||||||
|
let res = f(self);
|
||||||
|
self.has_with = has_with;
|
||||||
|
res.map(|_| core::mem::replace(&mut self.with_used, with_used))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_closure_env<T>(
|
||||||
|
&mut self,
|
||||||
|
func: ExprId,
|
||||||
|
arg: ExprId,
|
||||||
|
ident: Option<String>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> T {
|
||||||
|
self.closures.push(Closure::new(func, arg));
|
||||||
|
self.scopes.push(Scope::Arg(ident));
|
||||||
|
let res = f(self);
|
||||||
|
self.scopes.pop();
|
||||||
|
self.closures.pop();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
8
evaluator/nixjit_error/Cargo.toml
Normal file
8
evaluator/nixjit_error/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_error"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "2.0"
|
||||||
|
rnix = "0.12"
|
||||||
159
evaluator/nixjit_error/src/lib.rs
Normal file
159
evaluator/nixjit_error/src/lib.rs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
//! This crate defines the centralized error types and `Result` alias used
|
||||||
|
//! throughout the entire `nixjit` evaluation pipeline. By consolidating error
|
||||||
|
//! handling here, we ensure a consistent approach to reporting failures across
|
||||||
|
//! different stages of processing, from parsing to final evaluation.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// A specialized `Result` type used for all fallible operations within the
|
||||||
|
/// `nixjit` crates. It defaults to the crate's `Error` type.
|
||||||
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// The primary error enum, encompassing all potential failures that can occur
|
||||||
|
/// during the lifecycle of a Nix expression's evaluation.
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
/// An error occurred during the initial parsing phase. This typically
|
||||||
|
/// indicates a syntax error in the Nix source code, as detected by the
|
||||||
|
/// `rnix` parser.
|
||||||
|
#[error("error occurred during parse stage: {0}")]
|
||||||
|
ParseError(String),
|
||||||
|
|
||||||
|
/// An error occurred while "downgrading" the `rnix` AST to the
|
||||||
|
/// High-Level IR (HIR). This can happen if the AST has a structure that is
|
||||||
|
/// syntactically valid but semantically incorrect for our IR.
|
||||||
|
#[error("error occurred during downgrade stage: {0}")]
|
||||||
|
DowngradeError(String),
|
||||||
|
|
||||||
|
/// An error occurred during the variable resolution phase, where the HIR is
|
||||||
|
/// converted to the Low-Level IR (LIR). This is most commonly caused by
|
||||||
|
/// an unbound or undefined variable.
|
||||||
|
#[error("error occurred during variable resolve stage: {0}")]
|
||||||
|
ResolutionError(String),
|
||||||
|
|
||||||
|
/// An error occurred during the final evaluation of the LIR. This covers
|
||||||
|
/// all unrecoverable runtime errors, such as type mismatches (e.g., adding
|
||||||
|
/// a string to an integer), division by zero, or primitive operation
|
||||||
|
/// argument arity mismatch
|
||||||
|
#[error("error occurred during evaluation stage: {0}")]
|
||||||
|
EvalError(String),
|
||||||
|
|
||||||
|
/// Represents an error that can be generated by `throw` or `assert`, and
|
||||||
|
/// can be caught by `builtins.tryEval`.
|
||||||
|
#[error("{0}")]
|
||||||
|
Catchable(String),
|
||||||
|
|
||||||
|
/// A catch-all for any error that does not fit into the other categories.
|
||||||
|
#[error("an unknown or unexpected error occurred")]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
pub kind: ErrorKind,
|
||||||
|
pub span: Option<rnix::TextRange>,
|
||||||
|
pub source: Option<Rc<str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Basic display
|
||||||
|
write!(f, "{}", self.kind)?;
|
||||||
|
|
||||||
|
// If we have source and span, print context
|
||||||
|
if let (Some(source), Some(span)) = (&self.source, self.span) {
|
||||||
|
let start_byte = usize::from(span.start());
|
||||||
|
let end_byte = usize::from(span.end());
|
||||||
|
|
||||||
|
if start_byte > source.len() || end_byte > source.len() {
|
||||||
|
return Ok(()); // Span is out of bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut start_line = 1;
|
||||||
|
let mut start_col = 1usize;
|
||||||
|
let mut line_start_byte = 0;
|
||||||
|
for (i, c) in source.char_indices() {
|
||||||
|
if i >= start_byte {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if c == '\n' {
|
||||||
|
start_line += 1;
|
||||||
|
start_col = 1;
|
||||||
|
line_start_byte = i + 1;
|
||||||
|
} else {
|
||||||
|
start_col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let line_end_byte = source[line_start_byte..]
|
||||||
|
.find('\n')
|
||||||
|
.map(|i| line_start_byte + i)
|
||||||
|
.unwrap_or(source.len());
|
||||||
|
|
||||||
|
let line_str = &source[line_start_byte..line_end_byte];
|
||||||
|
|
||||||
|
let underline_len = if end_byte > start_byte {
|
||||||
|
end_byte - start_byte
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "\n --> {}:{}", start_line, start_col)?;
|
||||||
|
write!(f, "\n |\n")?;
|
||||||
|
writeln!(f, "{:4} | {}", start_line, line_str)?;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" | {}{}",
|
||||||
|
" ".repeat(start_col.saturating_sub(1)),
|
||||||
|
"^".repeat(underline_len)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
Some(&self.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn new(kind: ErrorKind) -> Self {
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
span: None,
|
||||||
|
source: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_span(mut self, span: rnix::TextRange) -> Self {
|
||||||
|
self.span = Some(span);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_source(mut self, source: Rc<str>) -> Self {
|
||||||
|
self.source = Some(source);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_error(msg: String) -> Self {
|
||||||
|
Self::new(ErrorKind::ParseError(msg))
|
||||||
|
}
|
||||||
|
pub fn downgrade_error(msg: String) -> Self {
|
||||||
|
Self::new(ErrorKind::DowngradeError(msg))
|
||||||
|
}
|
||||||
|
pub fn resolution_error(msg: String) -> Self {
|
||||||
|
Self::new(ErrorKind::ResolutionError(msg))
|
||||||
|
}
|
||||||
|
pub fn eval_error(msg: String) -> Self {
|
||||||
|
Self::new(ErrorKind::EvalError(msg))
|
||||||
|
}
|
||||||
|
pub fn catchable(msg: String) -> Self {
|
||||||
|
Self::new(ErrorKind::Catchable(msg))
|
||||||
|
}
|
||||||
|
pub fn unknown() -> Self {
|
||||||
|
Self::new(ErrorKind::Unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
evaluator/nixjit_eval/Cargo.toml
Normal file
16
evaluator/nixjit_eval/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_eval"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
hashbrown = "0.15"
|
||||||
|
itertools = "0.14"
|
||||||
|
replace_with = "0.1"
|
||||||
|
smallvec = { version = "1.15", features = ["union"] }
|
||||||
|
|
||||||
|
nixjit_error = { path = "../nixjit_error" }
|
||||||
|
nixjit_ir = { path = "../nixjit_ir" }
|
||||||
|
nixjit_lir = { path = "../nixjit_lir" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
349
evaluator/nixjit_eval/src/lib.rs
Normal file
349
evaluator/nixjit_eval/src/lib.rs
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
//! This module defines the core traits and logic for evaluating the LIR.
|
||||||
|
//!
|
||||||
|
//! The central components are:
|
||||||
|
//! - `EvalContext`: A trait that defines the environment and operations needed for evaluation.
|
||||||
|
//! It manages the evaluation stack, scopes, and primop calls.
|
||||||
|
//! - `Evaluate`: A trait implemented by LIR nodes to define how they are evaluated.
|
||||||
|
//! - `Value`: An enum representing all possible values during evaluation. This is an
|
||||||
|
//! internal representation, distinct from the public-facing `nixjit_value::Value`.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_ir::{self as ir, ExprId, PrimOpId, SymId};
|
||||||
|
use nixjit_lir as lir;
|
||||||
|
use nixjit_value::format_symbol;
|
||||||
|
|
||||||
|
pub use crate::value::*;
|
||||||
|
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
/// A trait defining the context in which LIR expressions are evaluated.
|
||||||
|
pub trait EvalContext {
|
||||||
|
fn eval(&mut self, id: ExprId) -> Result<Value>;
|
||||||
|
|
||||||
|
fn resolve(&mut self, id: ExprId) -> Result<ValueId>;
|
||||||
|
|
||||||
|
fn force(&mut self, id: ValueId) -> Result<Value>;
|
||||||
|
|
||||||
|
fn call(&mut self, func: ValueId, arg: Value) -> Result<Value>;
|
||||||
|
|
||||||
|
/// Enters a `with` scope for the duration of a closure's execution.
|
||||||
|
fn with_with_env<T>(
|
||||||
|
&mut self,
|
||||||
|
namespace: Rc<HashMap<String, Value>>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> T;
|
||||||
|
|
||||||
|
/// Looks up an identifier in the current `with` scope chain.
|
||||||
|
fn lookup_with<'a>(&'a self, ident: SymId) -> Option<&'a Value>;
|
||||||
|
|
||||||
|
/// Calls a primitive operation (builtin) by its ID.
|
||||||
|
fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value>;
|
||||||
|
|
||||||
|
fn new_sym(&mut self, sym: String) -> SymId;
|
||||||
|
fn get_sym(&self, id: SymId) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for types that can be evaluated within an `EvalContext`.
|
||||||
|
pub trait Evaluate<Ctx: EvalContext> {
|
||||||
|
/// Performs the evaluation.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ExprId {
|
||||||
|
/// Evaluating an `ExprId` simply delegates to the context.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
ctx.eval(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
|
||||||
|
/// Evaluates an LIR node by dispatching to the specific implementation for its variant.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
use lir::Lir::*;
|
||||||
|
match self {
|
||||||
|
AttrSet(x) => x.eval(ctx),
|
||||||
|
List(x) => x.eval(ctx),
|
||||||
|
HasAttr(x) => x.eval(ctx),
|
||||||
|
BinOp(x) => x.eval(ctx),
|
||||||
|
UnOp(x) => x.eval(ctx),
|
||||||
|
Select(x) => x.eval(ctx),
|
||||||
|
If(x) => x.eval(ctx),
|
||||||
|
Call(x) => x.eval(ctx),
|
||||||
|
With(x) => x.eval(ctx),
|
||||||
|
Assert(x) => x.eval(ctx),
|
||||||
|
ConcatStrings(x) => x.eval(ctx),
|
||||||
|
Const(x) => x.eval(ctx),
|
||||||
|
Str(x) => x.eval(ctx),
|
||||||
|
Var(x) => x.eval(ctx),
|
||||||
|
Path(x) => x.eval(ctx),
|
||||||
|
&ExprRef(expr) => ctx.eval(expr),
|
||||||
|
&FuncRef(body) => ctx.resolve(body).map(Value::Closure),
|
||||||
|
&Arg(_) => unreachable!(),
|
||||||
|
&PrimOp(primop) => Ok(Value::PrimOp(primop)),
|
||||||
|
&Thunk(id) => ctx.resolve(id).map(Value::Thunk),
|
||||||
|
&StackRef(idx) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
|
||||||
|
/// Evaluates an `AttrSet` by evaluating all its static and dynamic attributes.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let mut attrs = AttrSet::new(
|
||||||
|
self.stcs
|
||||||
|
.iter()
|
||||||
|
.map(|(&k, &v)| {
|
||||||
|
let eval_result = v.eval(ctx);
|
||||||
|
Ok((k, eval_result?))
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
);
|
||||||
|
for (k, v) in self.dyns.iter() {
|
||||||
|
let v = v.eval(ctx)?;
|
||||||
|
let sym = k.eval(ctx)?.force_string_no_ctx()?;
|
||||||
|
let sym = ctx.new_sym(sym);
|
||||||
|
attrs.push_attr(sym, v, ctx)?;
|
||||||
|
}
|
||||||
|
let result = Value::AttrSet(attrs.into());
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::List {
|
||||||
|
/// Evaluates a `List` by evaluating all its items.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let items = self
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|val| val.eval(ctx))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
let result = Value::List(List::from(items).into());
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
|
||||||
|
/// Evaluates a `HasAttr` by evaluating the LHS and the attribute path, then performing the check.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
use ir::Attr::*;
|
||||||
|
let mut val = self.lhs.eval(ctx)?;
|
||||||
|
val.has_attr(self.rhs.iter().map(|attr| {
|
||||||
|
match attr {
|
||||||
|
&Str(ident) => Ok(ident),
|
||||||
|
Dynamic(expr) => expr
|
||||||
|
.eval(ctx)?
|
||||||
|
.force_string_no_ctx()
|
||||||
|
.map(|sym| ctx.new_sym(sym)),
|
||||||
|
}
|
||||||
|
}))?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
|
||||||
|
/// Evaluates a `BinOp` by evaluating the LHS and RHS, then performing the operation.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
use ir::BinOpKind::*;
|
||||||
|
let mut lhs = self.lhs.eval(ctx)?;
|
||||||
|
if matches!((&self.kind, &lhs), (And, Value::Bool(false))) {
|
||||||
|
return Ok(Value::Bool(false));
|
||||||
|
} else if matches!((&self.kind, &lhs), (Or, Value::Bool(true))) {
|
||||||
|
return Ok(Value::Bool(true));
|
||||||
|
}
|
||||||
|
let mut rhs = self.rhs.eval(ctx)?;
|
||||||
|
match self.kind {
|
||||||
|
Add => lhs.add(rhs)?,
|
||||||
|
Sub => {
|
||||||
|
rhs.neg()?;
|
||||||
|
lhs.add(rhs)?;
|
||||||
|
}
|
||||||
|
Mul => lhs.mul(rhs)?,
|
||||||
|
Div => lhs.div(rhs)?,
|
||||||
|
Eq => lhs.eq(rhs),
|
||||||
|
Neq => {
|
||||||
|
lhs.eq(rhs);
|
||||||
|
let _ = lhs.not();
|
||||||
|
}
|
||||||
|
Lt => lhs.lt(rhs)?,
|
||||||
|
Gt => {
|
||||||
|
rhs.lt(lhs)?;
|
||||||
|
lhs = rhs;
|
||||||
|
}
|
||||||
|
Leq => {
|
||||||
|
rhs.lt(lhs)?;
|
||||||
|
let _ = rhs.not();
|
||||||
|
lhs = rhs;
|
||||||
|
}
|
||||||
|
Geq => {
|
||||||
|
lhs.lt(rhs)?;
|
||||||
|
let _ = lhs.not();
|
||||||
|
}
|
||||||
|
And => lhs.and(rhs)?,
|
||||||
|
Or => lhs.or(rhs)?,
|
||||||
|
Impl => {
|
||||||
|
lhs.not()?;
|
||||||
|
lhs.or(rhs)?;
|
||||||
|
}
|
||||||
|
Con => lhs.concat(rhs)?,
|
||||||
|
Upd => lhs.update(rhs)?,
|
||||||
|
PipeL => lhs.call(rhs, ctx)?,
|
||||||
|
PipeR => {
|
||||||
|
rhs.call(lhs, ctx)?;
|
||||||
|
lhs = rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(lhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::UnOp {
|
||||||
|
/// Evaluates a `UnOp` by evaluating the RHS and performing the operation.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
use ir::UnOpKind::*;
|
||||||
|
let mut rhs = self.rhs.eval(ctx)?;
|
||||||
|
match self.kind {
|
||||||
|
Neg => {
|
||||||
|
rhs.neg()?;
|
||||||
|
}
|
||||||
|
Not => {
|
||||||
|
rhs.not()?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
|
||||||
|
/// Evaluates a `Select` by evaluating the expression, the path, and the default value (if any),
|
||||||
|
/// then performing the selection.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
use ir::Attr::*;
|
||||||
|
let mut val = self.expr.eval(ctx)?;
|
||||||
|
for attr in self.attrpath.iter() {
|
||||||
|
let name = match attr {
|
||||||
|
&Str(name) => name,
|
||||||
|
Dynamic(expr) => {
|
||||||
|
let sym = expr.eval(ctx)?.force_string_no_ctx()?;
|
||||||
|
ctx.new_sym(sym)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(default) = self.default {
|
||||||
|
val.select_or(name, default, ctx)
|
||||||
|
} else {
|
||||||
|
val.select(name, ctx)
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::If {
|
||||||
|
/// Evaluates an `If` by evaluating the condition and then either the consequence or the alternative.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let cond = &self.cond.eval(ctx)?;
|
||||||
|
let &cond = cond.try_into().map_err(|_| {
|
||||||
|
Error::eval_error(format!(
|
||||||
|
"if-condition must be a boolean, but got {}",
|
||||||
|
cond.typename()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if cond {
|
||||||
|
self.consq.eval(ctx)
|
||||||
|
} else {
|
||||||
|
self.alter.eval(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Call {
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let mut func = self.func.eval(ctx)?;
|
||||||
|
func.call(self.arg.eval(ctx)?, ctx)?;
|
||||||
|
Ok(func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::With {
|
||||||
|
/// Evaluates a `With` by evaluating the namespace, entering a `with` scope,
|
||||||
|
/// and then evaluating the body.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let namespace = self.namespace.eval(ctx)?;
|
||||||
|
let typename = namespace.typename();
|
||||||
|
ctx.with_with_env(
|
||||||
|
namespace
|
||||||
|
.try_unwrap_attr_set()
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::eval_error(format!("'with' expects a set, but got {}", typename))
|
||||||
|
})?
|
||||||
|
.into_inner(),
|
||||||
|
|ctx| self.expr.eval(ctx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Assert {
|
||||||
|
/// Evaluates an `Assert` by evaluating the condition. If true, it evaluates and
|
||||||
|
/// returns the body; otherwise, it returns an error.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let cond = &self.assertion.eval(ctx)?;
|
||||||
|
let &cond = cond.try_into().map_err(|_| {
|
||||||
|
Error::eval_error(format!(
|
||||||
|
"assertion condition must be a boolean, but got {}",
|
||||||
|
cond.typename()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if cond {
|
||||||
|
self.expr.eval(ctx)
|
||||||
|
} else {
|
||||||
|
Err(Error::catchable("assertion failed".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::ConcatStrings {
|
||||||
|
/// Evaluates a `ConcatStrings` by evaluating each part, coercing it to a string,
|
||||||
|
/// and then concatenating the results.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
for part in self.parts.iter() {
|
||||||
|
buf.push_str(&part.eval(ctx)?.force_string_no_ctx()?);
|
||||||
|
}
|
||||||
|
Ok(Value::String(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Str {
|
||||||
|
/// Evaluates a `Str` literal into a `Value::String`.
|
||||||
|
fn eval(&self, _: &mut Ctx) -> Result<Value> {
|
||||||
|
Ok(Value::String(self.val.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Const {
|
||||||
|
/// Evaluates a `Const` literal into its corresponding `Value` variant.
|
||||||
|
fn eval(&self, _: &mut Ctx) -> Result<Value> {
|
||||||
|
Ok(self.val.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Var {
|
||||||
|
/// Evaluates a `Var` by looking it up in the `with` scope chain.
|
||||||
|
/// This is for variables that could not be resolved statically.
|
||||||
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
ctx.lookup_with(self.sym).cloned().ok_or_else(|| {
|
||||||
|
Error::eval_error(format!("undefined variable '{}'", format_symbol(ctx.get_sym(self.sym))))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Path {
|
||||||
|
/// Evaluates a `Path`. (Currently a TODO).
|
||||||
|
fn eval(&self, _ctx: &mut Ctx) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
176
evaluator/nixjit_eval/src/value/attrset.rs
Normal file
176
evaluator/nixjit_eval/src/value/attrset.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//! Defines the runtime representation of an attribute set (a map).
|
||||||
|
|
||||||
|
use core::ops::Deref;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use derive_more::Constructor;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use hashbrown::hash_map::Entry;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_ir::{ExprId, SymId};
|
||||||
|
use nixjit_value::{self as p, format_symbol};
|
||||||
|
|
||||||
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
use super::Value;
|
||||||
|
|
||||||
|
/// A wrapper around a `HashMap` representing a Nix attribute set.
|
||||||
|
///
|
||||||
|
/// It uses `#[repr(transparent)]` to ensure it has the same memory layout
|
||||||
|
/// as `HashMap<String, Value>`.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone, Constructor)]
|
||||||
|
pub struct AttrSet {
|
||||||
|
data: HashMap<SymId, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for AttrSet {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use Value::*;
|
||||||
|
write!(f, "{{ ")?;
|
||||||
|
for (k, v) in self.data.iter() {
|
||||||
|
match v {
|
||||||
|
List(_) => write!(f, "{:?} = [ ... ]; ", k)?,
|
||||||
|
AttrSet(_) => write!(f, "{:?} = {{ ... }}; ", k)?,
|
||||||
|
v => write!(f, "{:?} = {v:?}; ", k)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HashMap<SymId, Value>> for AttrSet {
|
||||||
|
fn from(data: HashMap<SymId, Value>) -> Self {
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AttrSet {
|
||||||
|
type Target = HashMap<SymId, Value>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttrSet {
|
||||||
|
/// Creates a new `AttrSet` with a specified initial capacity.
|
||||||
|
pub fn with_capacity(cap: usize) -> Self {
|
||||||
|
AttrSet {
|
||||||
|
data: HashMap::with_capacity(cap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts an attribute, overwriting any existing attribute with the same name.
|
||||||
|
pub fn push_attr_force(&mut self, sym: SymId, val: Value) {
|
||||||
|
self.data.insert(sym, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts an attribute, returns an error if the attribute is already defined.
|
||||||
|
pub fn push_attr(&mut self, sym: SymId, val: Value, ctx: &mut impl EvalContext) -> Result<()> {
|
||||||
|
match self.data.entry(sym) {
|
||||||
|
Entry::Occupied(occupied) => Err(Error::eval_error(format!(
|
||||||
|
"attribute '{}' already defined",
|
||||||
|
format_symbol(ctx.get_sym(*occupied.key()))
|
||||||
|
))),
|
||||||
|
Entry::Vacant(vacant) => {
|
||||||
|
vacant.insert(val);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select(&self, name: SymId, ctx: &mut impl EvalContext) -> Result<Value> {
|
||||||
|
self.data
|
||||||
|
.get(&name)
|
||||||
|
.cloned()
|
||||||
|
.map(|attr| match attr {
|
||||||
|
Value::Thunk(id) => ctx.force(id),
|
||||||
|
val => Ok(val),
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::eval_error(format!("attribute '{}' not found", format_symbol(ctx.get_sym(name))))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_or(
|
||||||
|
&self,
|
||||||
|
name: SymId,
|
||||||
|
default: ExprId,
|
||||||
|
ctx: &mut impl EvalContext,
|
||||||
|
) -> Result<Value> {
|
||||||
|
self.data
|
||||||
|
.get(&name)
|
||||||
|
.map(|attr| match attr {
|
||||||
|
&Value::Thunk(id) => ctx.force(id),
|
||||||
|
val => Ok(val.clone()),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| ctx.eval(default))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if an attribute path exists within the set.
|
||||||
|
pub fn has_attr(
|
||||||
|
&self,
|
||||||
|
mut path: impl DoubleEndedIterator<Item = Result<SymId>>,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let mut data = &self.data;
|
||||||
|
let last = path.nth_back(0).unwrap();
|
||||||
|
for item in path {
|
||||||
|
let Some(Value::AttrSet(attrs)) = data.get(&item?)
|
||||||
|
else {
|
||||||
|
return Ok(Value::Bool(false));
|
||||||
|
};
|
||||||
|
data = attrs.as_inner();
|
||||||
|
}
|
||||||
|
Ok(Value::Bool(
|
||||||
|
data.get(&last?).is_some(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges another `AttrSet` into this one, with attributes from `other`
|
||||||
|
/// overwriting existing ones. This corresponds to the `//` operator in Nix.
|
||||||
|
pub fn update(&mut self, other: &Self) {
|
||||||
|
for (k, v) in other.data.iter() {
|
||||||
|
self.push_attr_force(k.clone(), v.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner `HashMap`.
|
||||||
|
pub fn as_inner(&self) -> &HashMap<SymId, Value> {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an `Rc<AttrSet>` to an `Rc<HashMap<String, Value>>` without allocation.
|
||||||
|
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<String, Value>> {
|
||||||
|
// SAFETY: This is safe because `AttrSet` is `#[repr(transparent)]` over
|
||||||
|
// `HashMap<String, Value>`, so `Rc<Self>` has the same layout as
|
||||||
|
// `Rc<HashMap<String, Value>>`.
|
||||||
|
unsafe { core::mem::transmute(self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a deep equality comparison between two `AttrSet`s.
|
||||||
|
///
|
||||||
|
/// It recursively compares the contents of both sets, ensuring that both keys
|
||||||
|
/// and values are identical. The attributes are sorted before comparison to
|
||||||
|
/// ensure a consistent result.
|
||||||
|
pub fn eq_impl(&self, other: &Self) -> bool {
|
||||||
|
self.data.iter().len() == other.data.iter().len()
|
||||||
|
&& std::iter::zip(
|
||||||
|
self.data.iter().sorted_by(|(a, _), (b, _)| a.cmp(b)),
|
||||||
|
other.data.iter().sorted_by(|(a, _), (b, _)| a.cmp(b)),
|
||||||
|
)
|
||||||
|
.all(|((k1, v1), (k2, v2))| k1 == k2 && v1.eq_impl(v2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the `AttrSet` to its public-facing representation.
|
||||||
|
pub fn to_public(self, ctx: &mut impl EvalContext) -> p::Value {
|
||||||
|
p::Value::AttrSet(p::AttrSet::new(
|
||||||
|
self.data
|
||||||
|
.into_iter()
|
||||||
|
.map(|(sym, value)| (ctx.get_sym(sym).into(), value.to_public(ctx)))
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
104
evaluator/nixjit_eval/src/value/list.rs
Normal file
104
evaluator/nixjit_eval/src/value/list.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
//! Defines the runtime representation of a list.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_value::List as PubList;
|
||||||
|
use nixjit_value::Value as PubValue;
|
||||||
|
|
||||||
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
use super::Value;
|
||||||
|
|
||||||
|
/// A wrapper around a `Vec<Value>` representing a Nix list.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct List {
|
||||||
|
data: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for List {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "[ ")?;
|
||||||
|
for v in self.data.iter() {
|
||||||
|
write!(f, "{v:?} ")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Vec<Value>>> From<T> for List {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self { data: value.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for List {
|
||||||
|
type Target = [Value];
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
/// Creates a new, empty `List`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
List { data: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `List` with a specified initial capacity.
|
||||||
|
pub fn with_capacity(cap: usize) -> Self {
|
||||||
|
List {
|
||||||
|
data: Vec::with_capacity(cap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends an element to the back of the list.
|
||||||
|
pub fn push(&mut self, elem: Value) {
|
||||||
|
self.data.push(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends all elements from another `List` to this one.
|
||||||
|
/// This corresponds to the `++` operator in Nix.
|
||||||
|
pub fn concat(&mut self, other: &Self) {
|
||||||
|
for elem in other.data.iter() {
|
||||||
|
self.data.push(elem.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn elem_at(&self, idx: usize, ctx: &mut impl EvalContext) -> Result<Value> {
|
||||||
|
self.data
|
||||||
|
.get(idx)
|
||||||
|
.map(|elem| match elem {
|
||||||
|
&Value::Thunk(id) => ctx.force(id),
|
||||||
|
val => Ok(val.clone()),
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::eval_error(format!(
|
||||||
|
"'builtins.elemAt' called with index {idx} on a list of size {}",
|
||||||
|
self.len()
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the `List` and returns the inner `Vec<Value>`.
|
||||||
|
pub fn into_inner(self) -> Vec<Value> {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a deep equality comparison between two `List`s.
|
||||||
|
pub fn eq_impl(&self, other: &Self) -> bool {
|
||||||
|
self.len() == other.len()
|
||||||
|
&& core::iter::zip(self.iter(), other.iter()).all(|(a, b)| a.eq_impl(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the `List` to its public-facing representation.
|
||||||
|
pub fn to_public(&self, ctx: &mut impl EvalContext) -> PubValue {
|
||||||
|
PubValue::List(PubList::new(
|
||||||
|
self.data
|
||||||
|
.iter()
|
||||||
|
.map(|value| value.clone().to_public(ctx))
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
456
evaluator/nixjit_eval/src/value/mod.rs
Normal file
456
evaluator/nixjit_eval/src/value/mod.rs
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
//! Defines the internal representation of values during evaluation.
|
||||||
|
//!
|
||||||
|
//! This module introduces the `Value` enum, which is the cornerstone of the
|
||||||
|
//! interpreter's runtime. It represents all possible data types that can exist
|
||||||
|
//! during the evaluation of a Nix expression. This is an internal, mutable
|
||||||
|
//! representation, distinct from the public-facing `nixjit_value::Value`.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use derive_more::{IsVariant, TryInto, TryUnwrap, Unwrap};
|
||||||
|
use nixjit_ir::ExprId;
|
||||||
|
use nixjit_ir::PrimOpId;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_ir::SymId;
|
||||||
|
use nixjit_value::Const;
|
||||||
|
use nixjit_value::Value as PubValue;
|
||||||
|
use replace_with::replace_with_and_return;
|
||||||
|
use smallvec::smallvec;
|
||||||
|
|
||||||
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
mod attrset;
|
||||||
|
mod list;
|
||||||
|
mod primop;
|
||||||
|
mod string;
|
||||||
|
|
||||||
|
pub use attrset::AttrSet;
|
||||||
|
pub use list::List;
|
||||||
|
pub use primop::*;
|
||||||
|
|
||||||
|
/// The internal, C-compatible representation of a Nix value during evaluation.
|
||||||
|
///
|
||||||
|
/// This enum is designed for efficient manipulation within the interpreter and
|
||||||
|
/// JIT-compiled code. It uses `#[repr(C, u64)]` to ensure a predictable layout,
|
||||||
|
/// with the discriminant serving as a type tag.
|
||||||
|
#[repr(C, u64)]
|
||||||
|
#[derive(IsVariant, Clone, Unwrap, TryUnwrap, TryInto)]
|
||||||
|
#[try_into(owned, ref, ref_mut)]
|
||||||
|
pub enum Value {
|
||||||
|
Int(i64) = Self::INT,
|
||||||
|
Float(f64) = Self::FLOAT,
|
||||||
|
Bool(bool) = Self::BOOL,
|
||||||
|
String(String) = Self::STRING,
|
||||||
|
Null = Self::NULL,
|
||||||
|
Thunk(ValueId) = Self::THUNK,
|
||||||
|
AttrSet(Rc<AttrSet>) = Self::ATTRSET,
|
||||||
|
List(Rc<List>) = Self::LIST,
|
||||||
|
PrimOp(PrimOpId) = Self::PRIMOP,
|
||||||
|
PrimOpApp(Rc<PrimOpApp>) = Self::PRIMOP_APP,
|
||||||
|
Closure(ValueId) = Self::CLOSURE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Value {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use Value::*;
|
||||||
|
match self {
|
||||||
|
Int(x) => write!(f, "{x}"),
|
||||||
|
Float(x) => write!(f, "{x}"),
|
||||||
|
Bool(x) => write!(f, "{x}"),
|
||||||
|
Null => write!(f, "null"),
|
||||||
|
String(x) => write!(f, "{x:?}"),
|
||||||
|
AttrSet(x) => write!(f, "{x:?}"),
|
||||||
|
List(x) => write!(f, "{x:?}"),
|
||||||
|
Thunk(thunk) => write!(f, "<THUNK {thunk:?}>"),
|
||||||
|
Closure(func) => write!(f, "<LAMBDA-APP {:?}>", func),
|
||||||
|
PrimOp(_) => write!(f, "<PRIMOP>"),
|
||||||
|
PrimOpApp(_) => write!(f, "<PRIMOP-APP>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Const> for Value {
|
||||||
|
fn from(value: nixjit_value::Const) -> Self {
|
||||||
|
match value {
|
||||||
|
Const::Null => Value::Null,
|
||||||
|
Const::Int(x) => Value::Int(x),
|
||||||
|
Const::Float(x) => Value::Float(x),
|
||||||
|
Const::Bool(x) => Value::Bool(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub const INT: u64 = 0;
|
||||||
|
pub const FLOAT: u64 = 1;
|
||||||
|
pub const BOOL: u64 = 2;
|
||||||
|
pub const STRING: u64 = 3;
|
||||||
|
pub const NULL: u64 = 4;
|
||||||
|
pub const THUNK: u64 = 5;
|
||||||
|
pub const CLOSURE_THUNK: u64 = 6;
|
||||||
|
pub const ATTRSET: u64 = 7;
|
||||||
|
pub const LIST: u64 = 8;
|
||||||
|
pub const PRIMOP: u64 = 9;
|
||||||
|
pub const PRIMOP_APP: u64 = 10;
|
||||||
|
pub const CLOSURE: u64 = 11;
|
||||||
|
|
||||||
|
fn eq_impl(&self, other: &Self) -> bool {
|
||||||
|
use Value::*;
|
||||||
|
match (self, other) {
|
||||||
|
(Bool(a), Bool(b)) => a == b,
|
||||||
|
(Int(a), Int(b)) => a == b,
|
||||||
|
(Float(a), Float(b)) => a == b,
|
||||||
|
(Int(a), Float(b)) => *a as f64 == *b,
|
||||||
|
(Float(a), Int(b)) => *b as f64 == *a,
|
||||||
|
(String(a), String(b)) => a.as_str().eq(b.as_str()),
|
||||||
|
(Null, Null) => true,
|
||||||
|
(AttrSet(a), AttrSet(b)) => a.eq_impl(b),
|
||||||
|
(List(a), List(b)) => a.eq_impl(b),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
/// Returns the name of the value's type.
|
||||||
|
pub fn typename(&self) -> &'static str {
|
||||||
|
use Value::*;
|
||||||
|
match self {
|
||||||
|
Int(_) => "int",
|
||||||
|
Float(_) => "float",
|
||||||
|
Bool(_) => "bool",
|
||||||
|
String(_) => "string",
|
||||||
|
Null => "null",
|
||||||
|
Thunk(_) => "thunk",
|
||||||
|
AttrSet(_) => "set",
|
||||||
|
List(_) => "list",
|
||||||
|
PrimOp(_) => "lambda",
|
||||||
|
PrimOpApp(_) => "lambda",
|
||||||
|
Closure(..) => "lambda",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force(&mut self, ctx: &mut impl EvalContext) -> Result<()> {
|
||||||
|
let map = |result| match result {
|
||||||
|
Ok(ok) => (Ok(()), ok),
|
||||||
|
Err(err) => (Err(err), Value::Null),
|
||||||
|
};
|
||||||
|
replace_with_and_return(
|
||||||
|
self,
|
||||||
|
|| Value::Null,
|
||||||
|
|val| match val {
|
||||||
|
Value::Thunk(id) => map(ctx.force(id)),
|
||||||
|
val => (Ok(()), val),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a function call on the `Value`.
|
||||||
|
///
|
||||||
|
/// This method handles calling functions, primops, and their partially
|
||||||
|
/// applied variants. It manages argument application and delegates to the
|
||||||
|
/// `EvalContext` for the actual execution.
|
||||||
|
pub fn call<Ctx: EvalContext>(&mut self, arg: Value, ctx: &mut Ctx) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
let map = |result| match result {
|
||||||
|
Ok(ok) => (Ok(()), ok),
|
||||||
|
Err(err) => (Err(err), Null),
|
||||||
|
};
|
||||||
|
replace_with_and_return(
|
||||||
|
self,
|
||||||
|
|| Null,
|
||||||
|
|func| match func {
|
||||||
|
PrimOp(id) => map(ctx.call_primop(id, smallvec![arg])),
|
||||||
|
PrimOpApp(primop) => map(primop.call(arg, ctx)),
|
||||||
|
Closure(func) => map(ctx.call(func, arg)),
|
||||||
|
_ => (
|
||||||
|
Err(Error::eval_error(
|
||||||
|
"attempt to call something which is not a function but ...".to_string(),
|
||||||
|
)),
|
||||||
|
Null,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not(&mut self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
match &*self {
|
||||||
|
Bool(bool) => {
|
||||||
|
*self = Bool(!bool);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(Error::eval_error(format!(
|
||||||
|
"expected a boolean but found {}",
|
||||||
|
self.typename()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and(&mut self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
match (&*self, other) {
|
||||||
|
(&Bool(a), Bool(b)) => {
|
||||||
|
*self = Bool(a && b);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(Error::eval_error(format!(
|
||||||
|
"expected a boolean but found {}",
|
||||||
|
self.typename()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn or(&mut self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
match (&*self, other) {
|
||||||
|
(&Bool(a), Bool(b)) => {
|
||||||
|
*self = Bool(a || b);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(Error::eval_error(format!(
|
||||||
|
"expected a boolean but found {}",
|
||||||
|
self.typename()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eq(&mut self, other: Self) {
|
||||||
|
*self = Value::Bool(self.eq_impl(&other));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lt(&mut self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
*self = Bool(match (&*self, other) {
|
||||||
|
(Int(a), Int(b)) => *a < b,
|
||||||
|
(Int(a), Float(b)) => (*a as f64) < b,
|
||||||
|
(Float(a), Int(b)) => *a < b as f64,
|
||||||
|
(Float(a), Float(b)) => *a < b,
|
||||||
|
(String(a), String(b)) => a.as_str() < b.as_str(),
|
||||||
|
(a, b) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"cannot compare {} with {}",
|
||||||
|
a.typename(),
|
||||||
|
b.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn neg(&mut self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
*self = match &*self {
|
||||||
|
Int(int) => Int(-int),
|
||||||
|
Float(float) => Float(-float),
|
||||||
|
_ => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"expected an integer but found {}",
|
||||||
|
self.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(mut self: &mut Self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
if let (String(a), String(b)) = (&mut self, &other) {
|
||||||
|
a.push_str(b.as_str());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
*self = match (&mut self, other) {
|
||||||
|
(Int(a), Int(b)) => Int(*a + b),
|
||||||
|
(&mut Int(a), Float(b)) => Float(*a as f64 + b),
|
||||||
|
(&mut Float(a), Int(b)) => Float(*a + b as f64),
|
||||||
|
(&mut Float(a), Float(b)) => Float(*a + b),
|
||||||
|
(a, b) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"cannot add {} to {}",
|
||||||
|
a.typename(),
|
||||||
|
b.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul(&mut self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
*self = match (&*self, other) {
|
||||||
|
(Int(a), Int(b)) => Int(a * b),
|
||||||
|
(Int(a), Float(b)) => Float(*a as f64 * b),
|
||||||
|
(Float(a), Int(b)) => Float(a * b as f64),
|
||||||
|
(Float(a), Float(b)) => Float(a * b),
|
||||||
|
(a, b) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"cannot multiply {} with {}",
|
||||||
|
a.typename(),
|
||||||
|
b.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div(&mut self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
*self = match (&*self, other) {
|
||||||
|
(_, Int(0)) => return Err(Error::eval_error("division by zero".to_string())),
|
||||||
|
(_, Float(0.)) => {
|
||||||
|
return Err(Error::eval_error("division by zero".to_string()));
|
||||||
|
}
|
||||||
|
(Int(a), Int(b)) => Int(a / b),
|
||||||
|
(Int(a), Float(b)) => Float(*a as f64 / b),
|
||||||
|
(Float(a), Int(b)) => Float(a / b as f64),
|
||||||
|
(Float(a), Float(b)) => Float(a / b),
|
||||||
|
(a, b) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"cannot divide {} with {}",
|
||||||
|
a.typename(),
|
||||||
|
b.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn concat(mut self: &mut Self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
match (&mut self, other) {
|
||||||
|
(List(a), List(b)) => {
|
||||||
|
Rc::make_mut(a).concat(&b);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(List(_), b) => Err(Error::eval_error(format!(
|
||||||
|
"expected a list but found {}",
|
||||||
|
b.typename()
|
||||||
|
))),
|
||||||
|
(a, _) => Err(Error::eval_error(format!(
|
||||||
|
"expected a list but found {}",
|
||||||
|
a.typename()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(mut self: &mut Self, other: Self) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
match (&mut self, other) {
|
||||||
|
(AttrSet(a), AttrSet(b)) => {
|
||||||
|
Rc::make_mut(a).update(&b);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(AttrSet(_), other) => Err(Error::eval_error(format!(
|
||||||
|
"expected a set but found {}",
|
||||||
|
other.typename()
|
||||||
|
))),
|
||||||
|
_ => Err(Error::eval_error(format!(
|
||||||
|
"expected a set but found {}",
|
||||||
|
self.typename()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select(&mut self, name: SymId, ctx: &mut impl EvalContext) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
let val = match self {
|
||||||
|
AttrSet(attrs) => attrs.select(name, ctx),
|
||||||
|
_ => Err(Error::eval_error(format!(
|
||||||
|
"expected a set but found {}",
|
||||||
|
self.typename()
|
||||||
|
))),
|
||||||
|
}?;
|
||||||
|
*self = val;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_or<Ctx: EvalContext>(
|
||||||
|
&mut self,
|
||||||
|
name: SymId,
|
||||||
|
default: ExprId,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
let val = match self {
|
||||||
|
AttrSet(attrs) => attrs.select_or(name, default, ctx)?,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"expected a set but found {}",
|
||||||
|
self.typename()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*self = val;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_attr(&mut self, path: impl DoubleEndedIterator<Item = Result<SymId>>) -> Result<()> {
|
||||||
|
use Value::*;
|
||||||
|
if let AttrSet(attrs) = self {
|
||||||
|
let val = attrs.has_attr(path)?;
|
||||||
|
*self = val;
|
||||||
|
} else {
|
||||||
|
*self = Bool(false);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_string_no_ctx(self) -> Result<String> {
|
||||||
|
use Value::*;
|
||||||
|
if let String(string) = self {
|
||||||
|
Ok(string)
|
||||||
|
} else {
|
||||||
|
Err(Error::eval_error(format!(
|
||||||
|
"cannot coerce {} to string",
|
||||||
|
self.typename()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the internal `Value` to its public-facing, serializable
|
||||||
|
/// representation from the `nixjit_value` crate.
|
||||||
|
pub fn to_public(self, ctx: &mut impl EvalContext) -> PubValue {
|
||||||
|
use Value::*;
|
||||||
|
match self {
|
||||||
|
AttrSet(attrs) => Rc::unwrap_or_clone(attrs).to_public(ctx),
|
||||||
|
List(list) => Rc::unwrap_or_clone(list.clone()).to_public(ctx),
|
||||||
|
Int(x) => PubValue::Const(Const::Int(x)),
|
||||||
|
Float(x) => PubValue::Const(Const::Float(x)),
|
||||||
|
Bool(x) => PubValue::Const(Const::Bool(x)),
|
||||||
|
String(x) => PubValue::String(x),
|
||||||
|
Null => PubValue::Const(Const::Null),
|
||||||
|
Thunk(_) => PubValue::Thunk,
|
||||||
|
PrimOp(_) => PubValue::PrimOp,
|
||||||
|
PrimOpApp(_) => PubValue::PrimOpApp,
|
||||||
|
Closure(..) => PubValue::Func,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||||
|
pub struct ValueId(usize);
|
||||||
|
|
||||||
|
impl ValueId {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn raw(self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an `ExprId` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the provided index is valid for the expression table.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn from_raw(id: usize) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
evaluator/nixjit_eval/src/value/primop.rs
Normal file
33
evaluator/nixjit_eval/src/value/primop.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//! Defines the runtime representation of a partially applied primitive operation.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use derive_more::Constructor;
|
||||||
|
|
||||||
|
use nixjit_error::Result;
|
||||||
|
use nixjit_ir::PrimOpId;
|
||||||
|
|
||||||
|
use super::Value;
|
||||||
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
pub type Args = smallvec::SmallVec<[Value; 2]>;
|
||||||
|
|
||||||
|
/// Represents a partially applied primitive operation (builtin function).
|
||||||
|
///
|
||||||
|
/// This struct holds the state of a primop that has received some, but not
|
||||||
|
/// all, of its required arguments.
|
||||||
|
#[derive(Debug, Clone, Constructor)]
|
||||||
|
pub struct PrimOpApp {
|
||||||
|
/// The unique ID of the primop.
|
||||||
|
id: PrimOpId,
|
||||||
|
/// The arguments that have already been applied.
|
||||||
|
args: Args,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimOpApp {
|
||||||
|
pub fn call(self: Rc<Self>, arg: Value, ctx: &mut impl EvalContext) -> Result<Value> {
|
||||||
|
let PrimOpApp { id, mut args } = Rc::unwrap_or_clone(self);
|
||||||
|
args.push(arg);
|
||||||
|
ctx.call_primop(id, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
evaluator/nixjit_eval/src/value/string.rs
Normal file
38
evaluator/nixjit_eval/src/value/string.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//! Defines a placeholder for Nix's contextful strings.
|
||||||
|
//!
|
||||||
|
//! In Nix, strings can carry a "context" which affects how they are
|
||||||
|
//! handled, particularly with regards to path resolution. This module
|
||||||
|
//! provides the basic structures for this feature, although it is
|
||||||
|
//! currently a work in progress.
|
||||||
|
|
||||||
|
// TODO: Contextful String
|
||||||
|
|
||||||
|
/// Represents the context associated with a string.
|
||||||
|
pub struct StringContext {
|
||||||
|
context: Vec<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringContext {
|
||||||
|
/// Creates a new, empty `StringContext`.
|
||||||
|
pub fn new() -> StringContext {
|
||||||
|
StringContext {
|
||||||
|
context: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A string that carries an associated context.
|
||||||
|
pub struct ContextfulString {
|
||||||
|
string: String,
|
||||||
|
context: StringContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextfulString {
|
||||||
|
/// Creates a new `ContextfulString` from a standard `String`.
|
||||||
|
pub fn new(string: String) -> ContextfulString {
|
||||||
|
ContextfulString {
|
||||||
|
string,
|
||||||
|
context: StringContext::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
evaluator/nixjit_hir/Cargo.toml
Normal file
15
evaluator/nixjit_hir/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_hir"
|
||||||
|
description = "The high-level intermediate representation (HIR) for nixjit."
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
hashbrown = "0.15"
|
||||||
|
rnix = "0.12"
|
||||||
|
|
||||||
|
nixjit_error = { path = "../nixjit_error" }
|
||||||
|
nixjit_ir = { path = "../nixjit_ir" }
|
||||||
|
nixjit_macros = { path = "../nixjit_macros" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
363
evaluator/nixjit_hir/src/downgrade.rs
Normal file
363
evaluator/nixjit_hir/src/downgrade.rs
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
//! This module handles the "downgrading" of the `rnix` Abstract Syntax Tree (AST)
|
||||||
|
//! into the High-level Intermediate Representation (HIR). The term "downgrade" is used
|
||||||
|
//! because the process moves from a concrete syntax tree, which is very detailed about
|
||||||
|
//! source code structure (like parentheses and whitespace), to a more abstract,
|
||||||
|
//! semantically-focused representation.
|
||||||
|
//!
|
||||||
|
//! The core of this module is the `Downgrade` trait, which defines a standard way to
|
||||||
|
//! convert different AST node types into their corresponding HIR representations.
|
||||||
|
|
||||||
|
use rnix::ast::{self, Expr};
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A trait for converting (downgrading) an `rnix` AST node into an HIR expression.
|
||||||
|
pub trait Downgrade<Ctx: DowngradeContext> {
|
||||||
|
/// Performs the downgrade conversion.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `self` - The `rnix` AST node to convert.
|
||||||
|
/// * `ctx` - The context for the conversion, used for allocating new HIR expressions.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A `Result` containing the `ExprId` of the newly created HIR expression, or an error.
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main entry point for downgrading any `rnix` expression.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
use Expr::*;
|
||||||
|
match self {
|
||||||
|
// Dispatch to the specific implementation for each expression type.
|
||||||
|
Apply(apply) => apply.downgrade(ctx),
|
||||||
|
Assert(assert) => assert.downgrade(ctx),
|
||||||
|
Error(error) => Err(self::Error::downgrade_error(error.to_string())),
|
||||||
|
IfElse(ifelse) => ifelse.downgrade(ctx),
|
||||||
|
Select(select) => select.downgrade(ctx),
|
||||||
|
Str(str) => str.downgrade(ctx),
|
||||||
|
Path(path) => path.downgrade(ctx),
|
||||||
|
Literal(lit) => lit.downgrade(ctx),
|
||||||
|
Lambda(lambda) => lambda.downgrade(ctx),
|
||||||
|
LegacyLet(let_) => let_.downgrade(ctx),
|
||||||
|
LetIn(letin) => letin.downgrade(ctx),
|
||||||
|
List(list) => list.downgrade(ctx),
|
||||||
|
BinOp(op) => op.downgrade(ctx),
|
||||||
|
AttrSet(attrs) => attrs.downgrade(ctx),
|
||||||
|
UnaryOp(op) => op.downgrade(ctx),
|
||||||
|
Ident(ident) => ident.downgrade(ctx),
|
||||||
|
With(with) => with.downgrade(ctx),
|
||||||
|
HasAttr(has) => has.downgrade(ctx),
|
||||||
|
// Parentheses and the root node are transparent; we just downgrade their contents.
|
||||||
|
Paren(paren) => paren.expr().unwrap().downgrade(ctx),
|
||||||
|
Root(root) => root.expr().unwrap().downgrade(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an `assert` expression.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let assertion = self.condition().unwrap().downgrade(ctx)?;
|
||||||
|
let expr = self.body().unwrap().downgrade(ctx)?;
|
||||||
|
Ok(ctx.new_expr(Assert { assertion, expr }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an `if-then-else` expression.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let cond = self.condition().unwrap().downgrade(ctx)?;
|
||||||
|
let consq = self.body().unwrap().downgrade(ctx)?;
|
||||||
|
let alter = self.else_body().unwrap().downgrade(ctx)?;
|
||||||
|
Ok(ctx.new_expr(If { cond, consq, alter }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a path expression.
|
||||||
|
/// A path can be a simple literal or contain interpolated expressions.
|
||||||
|
/// If it contains interpolations, it's converted into a `ConcatStrings` HIR node
|
||||||
|
/// which is then wrapped in a `Path` node.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let parts = self
|
||||||
|
.parts()
|
||||||
|
.map(|part| match part {
|
||||||
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
||||||
|
Str {
|
||||||
|
val: lit.to_string(),
|
||||||
|
}
|
||||||
|
.to_hir(),
|
||||||
|
)),
|
||||||
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
|
interpol.expr().unwrap().downgrade(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
let expr = if parts.len() == 1 {
|
||||||
|
// If there's only one part, it's a simple string, no concatenation needed.
|
||||||
|
parts.into_iter().next().unwrap()
|
||||||
|
} else {
|
||||||
|
// Multiple parts (e.g., `./${name}.txt`) require string concatenation.
|
||||||
|
ctx.new_expr(ConcatStrings { parts }.to_hir())
|
||||||
|
};
|
||||||
|
Ok(ctx.new_expr(Path { expr }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a string expression.
|
||||||
|
/// A string can be a simple literal or contain interpolated expressions.
|
||||||
|
/// If it contains interpolations, it's converted into a `ConcatStrings` HIR node.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let parts = self
|
||||||
|
.normalized_parts()
|
||||||
|
.into_iter()
|
||||||
|
.map(|part| match part {
|
||||||
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit }.to_hir())),
|
||||||
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
|
interpol.expr().unwrap().downgrade(ctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
Ok(if parts.len() == 1 {
|
||||||
|
// If there's only one part, it's a simple string, no concatenation needed.
|
||||||
|
parts.into_iter().next().unwrap()
|
||||||
|
} else {
|
||||||
|
// Multiple parts (e.g., "hello ${name}") require string concatenation.
|
||||||
|
ctx.new_expr(ConcatStrings { parts }.to_hir())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a literal value (integer, float, or URI).
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
Ok(ctx.new_expr(match self.kind() {
|
||||||
|
ast::LiteralKind::Integer(int) => Const::from(int.value().unwrap()).to_hir(),
|
||||||
|
ast::LiteralKind::Float(float) => Const::from(float.value().unwrap()).to_hir(),
|
||||||
|
ast::LiteralKind::Uri(uri) => Str {
|
||||||
|
val: uri.to_string(),
|
||||||
|
}
|
||||||
|
.to_hir(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an identifier to a variable lookup.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let sym = self.ident_token().unwrap().to_string();
|
||||||
|
let sym = ctx.new_sym(sym);
|
||||||
|
Ok(ctx.new_expr(Var { sym }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an attribute set.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let rec = self.rec_token().is_some();
|
||||||
|
let attrs = downgrade_attrs(self, ctx)?;
|
||||||
|
let bindings = attrs.stcs.clone();
|
||||||
|
let body = ctx.new_expr(attrs.to_hir());
|
||||||
|
if rec {
|
||||||
|
Ok(ctx.new_expr(Let { bindings, body }.to_hir()))
|
||||||
|
} else {
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a list.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let items = self
|
||||||
|
.items()
|
||||||
|
.map(|item| maybe_thunk(item, ctx))
|
||||||
|
.collect::<Result<_>>()?;
|
||||||
|
Ok(ctx.new_expr(List { items }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a binary operation.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
||||||
|
let rhs = self.rhs().unwrap().downgrade(ctx)?;
|
||||||
|
let kind = self.operator().unwrap().into();
|
||||||
|
Ok(ctx.new_expr(BinOp { lhs, rhs, kind }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a "has attribute" (`?`) expression.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
||||||
|
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||||
|
Ok(ctx.new_expr(HasAttr { lhs, rhs }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a unary operation.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
||||||
|
let kind = self.operator().unwrap().into();
|
||||||
|
Ok(ctx.new_expr(UnOp { rhs, kind }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an attribute selection (`.`).
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let expr = self.expr().unwrap().downgrade(ctx)?;
|
||||||
|
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||||
|
let default = if let Some(default) = self.default_expr() {
|
||||||
|
Some(default.downgrade(ctx)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(ctx.new_expr(
|
||||||
|
Select {
|
||||||
|
expr,
|
||||||
|
attrpath,
|
||||||
|
default,
|
||||||
|
}
|
||||||
|
.to_hir(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a `legacy let`, which is essentially a recursive attribute set.
|
||||||
|
/// The body of the `let` is accessed via `let.body`.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let attrs = downgrade_attrs(self, ctx)?;
|
||||||
|
let bindings = attrs.stcs.clone();
|
||||||
|
let body = ctx.new_expr(attrs.to_hir());
|
||||||
|
let expr = ctx.new_expr(Let { bindings, body }.to_hir());
|
||||||
|
let sym = ctx.new_sym("body".into());
|
||||||
|
// The result of a `legacy let` is the `body` attribute of the resulting set.
|
||||||
|
let attrpath = vec![Attr::Str(sym)];
|
||||||
|
Ok(ctx.new_expr(
|
||||||
|
Select {
|
||||||
|
expr,
|
||||||
|
attrpath,
|
||||||
|
default: None,
|
||||||
|
}
|
||||||
|
.to_hir(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a `let ... in ...` expression.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let body = self.body().unwrap().downgrade(ctx)?;
|
||||||
|
let bindings = downgrade_static_attrs(self, ctx)?;
|
||||||
|
Ok(ctx.new_expr(Let { bindings, body }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a `with` expression.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
||||||
|
let expr = self.body().unwrap().downgrade(ctx)?;
|
||||||
|
Ok(ctx.new_expr(With { namespace, expr }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a lambda (function) expression.
|
||||||
|
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let param = downgrade_param(self.param().unwrap(), ctx)?;
|
||||||
|
let mut body = self.body().unwrap().downgrade(ctx)?;
|
||||||
|
let arg = ctx.new_expr(Hir::Arg(()));
|
||||||
|
|
||||||
|
let ident;
|
||||||
|
let required;
|
||||||
|
let allowed;
|
||||||
|
match param {
|
||||||
|
Param::Ident(id) => {
|
||||||
|
// Simple case: `x: body`
|
||||||
|
ident = Some(ctx.new_sym(id));
|
||||||
|
required = None;
|
||||||
|
allowed = None;
|
||||||
|
}
|
||||||
|
Param::Formals {
|
||||||
|
formals,
|
||||||
|
ellipsis,
|
||||||
|
alias,
|
||||||
|
} => {
|
||||||
|
// Complex case: `{ a, b ? 2, ... }@args: body`
|
||||||
|
let alias = alias.map(|sym| ctx.new_sym(sym));
|
||||||
|
ident = alias;
|
||||||
|
required = Some(
|
||||||
|
formals
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, default)| default.is_none())
|
||||||
|
.map(|(k, _)| ctx.new_sym(k.clone()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
allowed = if ellipsis {
|
||||||
|
None // `...` means any attribute is allowed.
|
||||||
|
} else {
|
||||||
|
Some(formals.iter().map(|(k, _)| ctx.new_sym(k.clone())).collect())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Desugar pattern matching in function arguments into a `let` expression.
|
||||||
|
// For example, `({ a, b ? 2 }): a + b` is desugared into:
|
||||||
|
// `arg: let a = arg.a; b = arg.b or 2; in a + b`
|
||||||
|
let mut bindings: HashMap<_, _> = formals
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, default)| {
|
||||||
|
// For each formal parameter, create a `Select` expression to extract it from the argument set.
|
||||||
|
// `Arg` represents the raw argument (the attribute set) passed to the function.
|
||||||
|
let k = ctx.new_sym(k);
|
||||||
|
(
|
||||||
|
k,
|
||||||
|
ctx.new_expr(
|
||||||
|
Select {
|
||||||
|
expr: arg,
|
||||||
|
attrpath: vec![Attr::Str(k)],
|
||||||
|
default,
|
||||||
|
}
|
||||||
|
.to_hir(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// If there's an alias (`... }@alias`), bind the alias name to the raw argument set.
|
||||||
|
if let Some(alias) = alias {
|
||||||
|
bindings.insert(alias, arg);
|
||||||
|
}
|
||||||
|
// Wrap the original function body in the new `let` expression.
|
||||||
|
let let_ = Let { bindings, body };
|
||||||
|
body = ctx.new_expr(let_.to_hir());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let param = IrParam {
|
||||||
|
ident,
|
||||||
|
required,
|
||||||
|
allowed,
|
||||||
|
};
|
||||||
|
// The function's body and parameters are now stored directly in the `Func` node.
|
||||||
|
Ok(ctx.new_expr(Func { body, param, arg }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a function application.
|
||||||
|
/// In Nix, function application is left-associative, so `f a b` should be parsed as `((f a) b)`.
|
||||||
|
/// Each Apply node represents a single function call with one argument.
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
||||||
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
|
let func = self.lambda().unwrap().downgrade(ctx)?;
|
||||||
|
let arg = maybe_thunk(self.argument().unwrap(), ctx)?;
|
||||||
|
Ok(ctx.new_expr(Call { func, arg }.to_hir()))
|
||||||
|
}
|
||||||
|
}
|
||||||
207
evaluator/nixjit_hir/src/lib.rs
Normal file
207
evaluator/nixjit_hir/src/lib.rs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
//! The high-level intermediate representation (HIR) for nixjit.
|
||||||
|
//!
|
||||||
|
//! This module defines the data structures for the HIR, which is a more abstract and
|
||||||
|
//! semantically rich representation of the original Nix code compared to the raw AST from `rnix`.
|
||||||
|
//! It's designed to be easily translatable from the AST and serves as a stepping stone
|
||||||
|
//! towards the lower-level IR (`nixjit_lir`).
|
||||||
|
//!
|
||||||
|
//! The key components are:
|
||||||
|
//! - `Hir`: An enum representing all possible expression types in the HIR. This is
|
||||||
|
//! generated by the `ir!` macro.
|
||||||
|
//! - `Downgrade`: A trait for converting `rnix::ast` nodes into HIR expressions.
|
||||||
|
//! - `DowngradeContext`: A trait that provides the necessary context for the conversion,
|
||||||
|
//! such as allocating new expressions.
|
||||||
|
|
||||||
|
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_ir::{
|
||||||
|
Assert, Attr, AttrSet, BinOp, Call, ConcatStrings, Const, ExprId, Func, HasAttr, If, List, Param as IrParam, Path, Select, Str, SymId, UnOp, Var, With
|
||||||
|
};
|
||||||
|
use nixjit_macros::ir;
|
||||||
|
use nixjit_value::format_symbol;
|
||||||
|
|
||||||
|
mod downgrade;
|
||||||
|
mod utils;
|
||||||
|
use utils::*;
|
||||||
|
|
||||||
|
pub use downgrade::Downgrade;
|
||||||
|
|
||||||
|
/// A context for the AST-to-HIR downgrading process.
|
||||||
|
///
|
||||||
|
/// This trait abstracts the storage of HIR expressions, allowing the
|
||||||
|
/// `downgrade` implementations to be generic over the specific context implementation.
|
||||||
|
pub trait DowngradeContext {
|
||||||
|
/// Allocates a new HIR expression in the context and returns its ID.
|
||||||
|
fn new_expr(&mut self, expr: Hir) -> ExprId;
|
||||||
|
|
||||||
|
fn new_sym(&mut self, sym: String) -> SymId;
|
||||||
|
|
||||||
|
fn get_sym(&self, id: SymId) -> &str;
|
||||||
|
|
||||||
|
/// Provides temporary mutable access to an expression.
|
||||||
|
fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T;
|
||||||
|
|
||||||
|
fn downgrade_root(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `ir!` macro generates the `Hir` enum and related structs and traits.
|
||||||
|
// This reduces boilerplate for defining the IR structure.
|
||||||
|
ir! {
|
||||||
|
Hir,
|
||||||
|
|
||||||
|
// Represents an attribute set, e.g., `{ a = 1; b = 2; }`.
|
||||||
|
AttrSet,
|
||||||
|
// Represents a list, e.g., `[1 2 3]`.
|
||||||
|
List,
|
||||||
|
// Represents a "has attribute" check, e.g., `attrs ? a`.
|
||||||
|
HasAttr,
|
||||||
|
// Represents a binary operation, e.g., `a + b`.
|
||||||
|
BinOp,
|
||||||
|
// Represents a unary operation, e.g., `-a`.
|
||||||
|
UnOp,
|
||||||
|
// Represents an attribute selection, e.g., `attrs.a` or `attrs.a or defaultValue`.
|
||||||
|
Select,
|
||||||
|
// Represents an if-then-else expression.
|
||||||
|
If,
|
||||||
|
// Represents a function definition (lambda).
|
||||||
|
Func,
|
||||||
|
// Represents a function call.
|
||||||
|
Call,
|
||||||
|
// Represents a `with` expression, e.g., `with pkgs; stdenv.mkDerivation { ... }`.
|
||||||
|
With,
|
||||||
|
// Represents an `assert` expression.
|
||||||
|
Assert,
|
||||||
|
// Represents the concatenation of strings, often from interpolated strings.
|
||||||
|
ConcatStrings,
|
||||||
|
// Represents a constant value (integer, float, boolean, null).
|
||||||
|
Const,
|
||||||
|
// Represents a simple string literal.
|
||||||
|
Str,
|
||||||
|
// Represents a variable lookup by its symbol/name.
|
||||||
|
Var,
|
||||||
|
// Represents a path expression.
|
||||||
|
Path,
|
||||||
|
// Represents a `let ... in ...` binding.
|
||||||
|
Let { pub bindings: HashMap<SymId, ExprId>, pub body: ExprId },
|
||||||
|
// Represents a function argument lookup within the body of a function.
|
||||||
|
Arg(()),
|
||||||
|
Thunk(ExprId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait defining operations on attribute sets within the HIR.
|
||||||
|
trait Attrs {
|
||||||
|
/// Inserts a value into the attribute set at a given path.
|
||||||
|
///
|
||||||
|
/// This method handles the creation of nested attribute sets as needed.
|
||||||
|
/// For example, `insert([a, b], value)` on an empty set results in `{ a = { b = value; }; }`.
|
||||||
|
fn insert(
|
||||||
|
&mut self,
|
||||||
|
path: Vec<Attr>,
|
||||||
|
value: ExprId,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Internal helper for recursively inserting an attribute.
|
||||||
|
fn _insert(
|
||||||
|
&mut self,
|
||||||
|
path: impl Iterator<Item = Attr>,
|
||||||
|
name: Attr,
|
||||||
|
value: ExprId,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attrs for AttrSet {
|
||||||
|
fn _insert(
|
||||||
|
&mut self,
|
||||||
|
mut path: impl Iterator<Item = Attr>,
|
||||||
|
name: Attr,
|
||||||
|
value: ExprId,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some(attr) = path.next() {
|
||||||
|
// If the path is not yet exhausted, we need to recurse deeper.
|
||||||
|
match attr {
|
||||||
|
Attr::Str(ident) => {
|
||||||
|
// If the next attribute is a static string.
|
||||||
|
if let Some(&id) = self.stcs.get(&ident) {
|
||||||
|
// If a sub-attrset already exists, recurse into it.
|
||||||
|
ctx.with_expr_mut(id, |expr, ctx| {
|
||||||
|
expr.as_mut()
|
||||||
|
.try_unwrap_attr_set()
|
||||||
|
.map_err(|_| {
|
||||||
|
// This path segment exists but is not an attrset, which is an error.
|
||||||
|
Error::downgrade_error(format!(
|
||||||
|
"attribute '{}' already defined but is not an attribute set",
|
||||||
|
format_symbol(ctx.get_sym(ident))
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.and_then(|attrs| attrs._insert(path, name, value, ctx))
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
// Create a new sub-attrset because this path doesn't exist yet.
|
||||||
|
let mut attrs = AttrSet::default();
|
||||||
|
attrs._insert(path, name, value, ctx)?;
|
||||||
|
let attrs = ctx.new_expr(attrs.to_hir());
|
||||||
|
self.stcs.insert(ident, attrs);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Attr::Dynamic(dynamic) => {
|
||||||
|
// If the next attribute is a dynamic expression, we must create a new sub-attrset.
|
||||||
|
// We cannot merge with existing dynamic attributes at this stage.
|
||||||
|
let mut attrs = AttrSet::default();
|
||||||
|
attrs._insert(path, name, value, ctx)?;
|
||||||
|
self.dyns.push((dynamic, ctx.new_expr(attrs.to_hir())));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is the final attribute in the path, so insert the value here.
|
||||||
|
match name {
|
||||||
|
Attr::Str(ident) => {
|
||||||
|
if self.stcs.insert(ident, value).is_some() {
|
||||||
|
return Err(Error::downgrade_error(format!(
|
||||||
|
"attribute '{}' already defined",
|
||||||
|
format_symbol(ctx.get_sym(ident))
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Dynamic(dynamic) => {
|
||||||
|
self.dyns.push((dynamic, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
&mut self,
|
||||||
|
path: Vec<Attr>,
|
||||||
|
value: ExprId,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut path = path.into_iter();
|
||||||
|
// The last part of the path is the name of the attribute to be inserted.
|
||||||
|
let name = path.next_back().unwrap();
|
||||||
|
self._insert(path, name, value, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the different kinds of parameters a function can have in the HIR stage.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Param {
|
||||||
|
/// A simple parameter, e.g., `x: ...`.
|
||||||
|
Ident(String),
|
||||||
|
/// A pattern-matching parameter (formals), e.g., `{ a, b ? 2, ... }@args: ...`.
|
||||||
|
Formals {
|
||||||
|
/// The individual formal parameters, with optional default values.
|
||||||
|
formals: Vec<(String, Option<ExprId>)>,
|
||||||
|
/// Whether an ellipsis (`...`) is present, allowing extra arguments.
|
||||||
|
ellipsis: bool,
|
||||||
|
/// An optional alias for the entire argument set, e.g., `args @ { ... }`.
|
||||||
|
alias: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
267
evaluator/nixjit_hir/src/utils.rs
Normal file
267
evaluator/nixjit_hir/src/utils.rs
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
//! This module provides utility functions for the AST-to-HIR downgrade process.
|
||||||
|
//! These functions handle common, often complex, patterns in the `rnix` AST,
|
||||||
|
//! such as parsing parameters, attribute sets, and `inherit` statements.
|
||||||
|
//! They are helpers to the main `Downgrade` trait implementations.
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use hashbrown::hash_map::Entry;
|
||||||
|
|
||||||
|
use nixjit_value::format_symbol;
|
||||||
|
use rnix::ast;
|
||||||
|
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, SymId, Var};
|
||||||
|
|
||||||
|
use crate::Hir;
|
||||||
|
|
||||||
|
use super::downgrade::Downgrade;
|
||||||
|
use super::{Attrs, DowngradeContext, Param, ToHir};
|
||||||
|
|
||||||
|
pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Result<ExprId> {
|
||||||
|
use ast::Expr::*;
|
||||||
|
let expr = loop {
|
||||||
|
expr = match expr {
|
||||||
|
Paren(paren) => paren.expr().unwrap(),
|
||||||
|
Root(root) => root.expr().unwrap(),
|
||||||
|
expr => break expr,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match expr {
|
||||||
|
Error(error) => return Err(self::Error::downgrade_error(error.to_string())),
|
||||||
|
Ident(ident) => return ident.downgrade(ctx),
|
||||||
|
Literal(lit) => return lit.downgrade(ctx),
|
||||||
|
Str(str) => return str.downgrade(ctx),
|
||||||
|
Path(path) => return path.downgrade(ctx),
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
let id = match expr {
|
||||||
|
Apply(apply) => apply.downgrade(ctx),
|
||||||
|
Assert(assert) => assert.downgrade(ctx),
|
||||||
|
IfElse(ifelse) => ifelse.downgrade(ctx),
|
||||||
|
Select(select) => select.downgrade(ctx),
|
||||||
|
Lambda(lambda) => lambda.downgrade(ctx),
|
||||||
|
LegacyLet(let_) => let_.downgrade(ctx),
|
||||||
|
LetIn(letin) => letin.downgrade(ctx),
|
||||||
|
List(list) => list.downgrade(ctx),
|
||||||
|
BinOp(op) => op.downgrade(ctx),
|
||||||
|
AttrSet(attrs) => attrs.downgrade(ctx),
|
||||||
|
UnaryOp(op) => op.downgrade(ctx),
|
||||||
|
With(with) => with.downgrade(ctx),
|
||||||
|
HasAttr(has) => has.downgrade(ctx),
|
||||||
|
|
||||||
|
_ => unreachable!(),
|
||||||
|
}?;
|
||||||
|
Ok(ctx.new_expr(Hir::Thunk(id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a function parameter from the AST.
|
||||||
|
pub fn downgrade_param(param: ast::Param, ctx: &mut impl DowngradeContext) -> Result<Param> {
|
||||||
|
match param {
|
||||||
|
ast::Param::IdentParam(ident) => Ok(Param::Ident(ident.to_string())),
|
||||||
|
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a parameter pattern (formals) from the AST.
|
||||||
|
/// This handles `{ a, b ? 2, ... }@args` style parameters.
|
||||||
|
pub fn downgrade_pattern(pattern: ast::Pattern, ctx: &mut impl DowngradeContext) -> Result<Param> {
|
||||||
|
// Extract each formal parameter, downgrading its default value if it exists.
|
||||||
|
let formals = pattern
|
||||||
|
.pat_entries()
|
||||||
|
.map(|entry| {
|
||||||
|
let ident = entry.ident().unwrap().to_string();
|
||||||
|
if entry.default().is_none() {
|
||||||
|
Ok((ident, None))
|
||||||
|
} else {
|
||||||
|
entry
|
||||||
|
.default()
|
||||||
|
.unwrap()
|
||||||
|
.downgrade(ctx)
|
||||||
|
.map(|ok| (ident, Some(ok)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
let ellipsis = pattern.ellipsis_token().is_some();
|
||||||
|
let alias = pattern
|
||||||
|
.pat_bind()
|
||||||
|
.map(|alias| alias.ident().unwrap().to_string());
|
||||||
|
Ok(Param::Formals {
|
||||||
|
formals,
|
||||||
|
ellipsis,
|
||||||
|
alias,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades the entries of an attribute set.
|
||||||
|
/// This handles `inherit` and `attrpath = value;` entries.
|
||||||
|
pub fn downgrade_attrs(
|
||||||
|
attrs: impl ast::HasEntry,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<AttrSet> {
|
||||||
|
let entries = attrs.entries();
|
||||||
|
let mut attrs = AttrSet {
|
||||||
|
stcs: HashMap::new(),
|
||||||
|
dyns: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
match entry {
|
||||||
|
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
||||||
|
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades attribute set entries for a `let...in` expression.
|
||||||
|
/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes,
|
||||||
|
/// as `let` bindings must be statically known.
|
||||||
|
pub fn downgrade_static_attrs(
|
||||||
|
attrs: impl ast::HasEntry,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<HashMap<SymId, ExprId>> {
|
||||||
|
let entries = attrs.entries();
|
||||||
|
let mut attrs = AttrSet {
|
||||||
|
stcs: HashMap::new(),
|
||||||
|
dyns: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
match entry {
|
||||||
|
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
||||||
|
ast::Entry::AttrpathValue(value) => {
|
||||||
|
downgrade_static_attrpathvalue(value, &mut attrs, ctx)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(attrs.stcs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an `inherit` statement.
|
||||||
|
/// `inherit (from) a b;` is translated into `a = from.a; b = from.b;`.
|
||||||
|
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
|
||||||
|
pub fn downgrade_inherit(
|
||||||
|
inherit: ast::Inherit,
|
||||||
|
stcs: &mut HashMap<SymId, ExprId>,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Downgrade the `from` expression if it exists.
|
||||||
|
let from = if let Some(from) = inherit.from() {
|
||||||
|
Some(from.expr().unwrap().downgrade(ctx)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
for attr in inherit.attrs() {
|
||||||
|
let ident = match downgrade_attr(attr, ctx)? {
|
||||||
|
Attr::Str(ident) => ident,
|
||||||
|
_ => {
|
||||||
|
// `inherit` does not allow dynamic attributes.
|
||||||
|
return Err(Error::downgrade_error(
|
||||||
|
"dynamic attributes not allowed in inherit".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let expr = from.as_ref().map_or_else(
|
||||||
|
// If `from` is None, `inherit foo;` becomes `foo = foo;`.
|
||||||
|
|| Var { sym: ident.clone() }.to_hir(),
|
||||||
|
// If `from` is Some, `inherit (from) foo;` becomes `foo = from.foo;`.
|
||||||
|
|&expr| {
|
||||||
|
Select {
|
||||||
|
expr,
|
||||||
|
attrpath: vec![Attr::Str(ident.clone())],
|
||||||
|
default: None,
|
||||||
|
}
|
||||||
|
.to_hir()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
match stcs.entry(ident) {
|
||||||
|
Entry::Occupied(occupied) => {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"attribute '{}' already defined",
|
||||||
|
format_symbol(ctx.get_sym(*occupied.key()))
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Entry::Vacant(vacant) => vacant.insert(ctx.new_expr(expr)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades a single attribute key (part of an attribute path).
|
||||||
|
/// An attribute can be a static identifier, an interpolated string, or a dynamic expression.
|
||||||
|
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
||||||
|
use ast::Attr::*;
|
||||||
|
use ast::InterpolPart::*;
|
||||||
|
match attr {
|
||||||
|
Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))),
|
||||||
|
Str(string) => {
|
||||||
|
let parts = string.normalized_parts();
|
||||||
|
if parts.is_empty() {
|
||||||
|
Ok(Attr::Str(ctx.new_sym("".into())))
|
||||||
|
} else if parts.len() == 1 {
|
||||||
|
// If the string has only one part, it's either a literal or a single interpolation.
|
||||||
|
match parts.into_iter().next().unwrap() {
|
||||||
|
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident))),
|
||||||
|
Interpolation(interpol) => {
|
||||||
|
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the string has multiple parts, it's an interpolated string that must be concatenated.
|
||||||
|
let parts = parts
|
||||||
|
.into_iter()
|
||||||
|
.map(|part| match part {
|
||||||
|
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit }.to_hir())),
|
||||||
|
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
Ok(Attr::Dynamic(
|
||||||
|
ctx.new_expr(ConcatStrings { parts }.to_hir()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec<Attr>`.
|
||||||
|
pub fn downgrade_attrpath(
|
||||||
|
attrpath: ast::Attrpath,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<Vec<Attr>> {
|
||||||
|
attrpath
|
||||||
|
.attrs()
|
||||||
|
.map(|attr| downgrade_attr(attr, ctx))
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades an `attrpath = value;` expression and inserts it into an `AttrSet`.
|
||||||
|
pub fn downgrade_attrpathvalue(
|
||||||
|
value: ast::AttrpathValue,
|
||||||
|
attrs: &mut AttrSet,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
||||||
|
let value = maybe_thunk(value.value().unwrap(), ctx)?;
|
||||||
|
attrs.insert(path, value, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stricter version of `downgrade_attrpathvalue` for `let...in` bindings.
|
||||||
|
/// It ensures that the attribute path contains no dynamic parts.
|
||||||
|
pub fn downgrade_static_attrpathvalue(
|
||||||
|
value: ast::AttrpathValue,
|
||||||
|
attrs: &mut AttrSet,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
||||||
|
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
|
||||||
|
return Err(Error::downgrade_error(
|
||||||
|
"dynamic attributes not allowed in let bindings".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let value = value.value().unwrap().downgrade(ctx)?;
|
||||||
|
attrs.insert(path, value, ctx)
|
||||||
|
}
|
||||||
12
evaluator/nixjit_ir/Cargo.toml
Normal file
12
evaluator/nixjit_ir/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_ir"
|
||||||
|
description = "The core data structures for the nixjit intermediate representation (IR)."
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
hashbrown = "0.15"
|
||||||
|
rnix = "0.12"
|
||||||
|
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
362
evaluator/nixjit_ir/src/lib.rs
Normal file
362
evaluator/nixjit_ir/src/lib.rs
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
//! This crate defines the core data structures for the nixjit Intermediate Representation (IR).
|
||||||
|
//!
|
||||||
|
//! The IR provides a simplified, language-agnostic representation of Nix expressions,
|
||||||
|
//! serving as a bridge between the high-level representation (HIR) and the low-level
|
||||||
|
//! representation (LIR). It defines the fundamental building blocks like expression IDs,
|
||||||
|
//! argument indices, and structures for various expression types (e.g., binary operations,
|
||||||
|
//! attribute sets, function calls).
|
||||||
|
//!
|
||||||
|
//! These structures are designed to be generic and reusable across different stages of
|
||||||
|
//! the compiler.
|
||||||
|
|
||||||
|
use rnix::ast;
|
||||||
|
|
||||||
|
use derive_more::TryUnwrap;
|
||||||
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use nixjit_value::Const as PubConst;
|
||||||
|
|
||||||
|
/// A type-safe wrapper for an index into an expression table.
|
||||||
|
///
|
||||||
|
/// Using a newtype wrapper to prevent accidentally mixing up different kinds of indices.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct ExprId(usize);
|
||||||
|
|
||||||
|
impl ExprId {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn raw(self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an `ExprId` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the provided index is valid for the expression table.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn from_raw(id: usize) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type-safe wrapper for an index into an symbol table.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct SymId(usize);
|
||||||
|
|
||||||
|
impl SymId {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn raw(self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an `SymId` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the provided index is valid for the symbol table.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn from_raw(id: usize) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type-safe wrapper for an index into a primop (builtin function) table.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct PrimOpId(usize);
|
||||||
|
|
||||||
|
impl PrimOpId {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller is responsible for using this index correctly.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn raw(self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a `PrimOpId` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the provided index is valid.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn from_raw(id: usize) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type-safe wrapper for an index into a function's dependency stack.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct StackIdx(usize);
|
||||||
|
|
||||||
|
impl StackIdx {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller is responsible for using this index correctly.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn raw(self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an `StackIdx` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the provided index is valid.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn from_raw(idx: usize) -> Self {
|
||||||
|
Self(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Arg;
|
||||||
|
|
||||||
|
/// Represents a Nix attribute set.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AttrSet {
|
||||||
|
/// Statically known attributes (key is a string).
|
||||||
|
pub stcs: HashMap<SymId, ExprId>,
|
||||||
|
/// Dynamically computed attributes, where both the key and value are expressions.
|
||||||
|
pub dyns: Vec<(ExprId, ExprId)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a key in an attribute path.
|
||||||
|
#[derive(Debug, TryUnwrap)]
|
||||||
|
pub enum Attr {
|
||||||
|
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||||
|
/// Example: `attrs.${key}`
|
||||||
|
Dynamic(ExprId),
|
||||||
|
/// A static attribute key.
|
||||||
|
/// Example: `attrs.key`
|
||||||
|
Str(SymId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix list.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct List {
|
||||||
|
/// The expressions that are elements of the list.
|
||||||
|
pub items: Vec<ExprId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a "has attribute" check (`?` operator).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HasAttr {
|
||||||
|
/// The expression to check for the attribute (the left-hand side).
|
||||||
|
pub lhs: ExprId,
|
||||||
|
/// The attribute path to look for (the right-hand side).
|
||||||
|
pub rhs: Vec<Attr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a binary operation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BinOp {
|
||||||
|
pub lhs: ExprId,
|
||||||
|
pub rhs: ExprId,
|
||||||
|
pub kind: BinOpKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kinds of binary operations supported in Nix.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum BinOpKind {
|
||||||
|
// Arithmetic
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Div,
|
||||||
|
Mul,
|
||||||
|
|
||||||
|
// Comparison
|
||||||
|
Eq,
|
||||||
|
Neq,
|
||||||
|
Lt,
|
||||||
|
Gt,
|
||||||
|
Leq,
|
||||||
|
Geq,
|
||||||
|
|
||||||
|
// Logical
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Impl,
|
||||||
|
|
||||||
|
// Set/String/Path operations
|
||||||
|
Con, // List concatenation (`++`)
|
||||||
|
Upd, // AttrSet update (`//`)
|
||||||
|
|
||||||
|
// Not standard, but part of rnix AST
|
||||||
|
PipeL,
|
||||||
|
PipeR,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ast::BinOpKind> for BinOpKind {
|
||||||
|
fn from(op: ast::BinOpKind) -> Self {
|
||||||
|
use BinOpKind::*;
|
||||||
|
use ast::BinOpKind as kind;
|
||||||
|
match op {
|
||||||
|
kind::Concat => Con,
|
||||||
|
kind::Update => Upd,
|
||||||
|
kind::Add => Add,
|
||||||
|
kind::Sub => Sub,
|
||||||
|
kind::Mul => Mul,
|
||||||
|
kind::Div => Div,
|
||||||
|
kind::And => And,
|
||||||
|
kind::Equal => Eq,
|
||||||
|
kind::Implication => Impl,
|
||||||
|
kind::Less => Lt,
|
||||||
|
kind::LessOrEq => Leq,
|
||||||
|
kind::More => Gt,
|
||||||
|
kind::MoreOrEq => Geq,
|
||||||
|
kind::NotEqual => Neq,
|
||||||
|
kind::Or => Or,
|
||||||
|
kind::PipeLeft => PipeL,
|
||||||
|
kind::PipeRight => PipeR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a unary operation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UnOp {
|
||||||
|
pub rhs: ExprId,
|
||||||
|
pub kind: UnOpKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kinds of unary operations.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum UnOpKind {
|
||||||
|
Neg, // Negation (`-`)
|
||||||
|
Not, // Logical not (`!`)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ast::UnaryOpKind> for UnOpKind {
|
||||||
|
fn from(value: ast::UnaryOpKind) -> Self {
|
||||||
|
match value {
|
||||||
|
ast::UnaryOpKind::Invert => UnOpKind::Not,
|
||||||
|
ast::UnaryOpKind::Negate => UnOpKind::Neg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an attribute selection from an attribute set.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Select {
|
||||||
|
/// The expression that should evaluate to an attribute set.
|
||||||
|
pub expr: ExprId,
|
||||||
|
/// The path of attributes to select.
|
||||||
|
pub attrpath: Vec<Attr>,
|
||||||
|
/// An optional default value to return if the selection fails.
|
||||||
|
pub default: Option<ExprId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an `if-then-else` expression.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct If {
|
||||||
|
pub cond: ExprId,
|
||||||
|
pub consq: ExprId, // Consequence (then branch)
|
||||||
|
pub alter: ExprId, // Alternative (else branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a function value (a lambda).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Func {
|
||||||
|
/// The body of the function
|
||||||
|
pub body: ExprId,
|
||||||
|
/// The parameter specification for the function.
|
||||||
|
pub param: Param,
|
||||||
|
|
||||||
|
pub arg: ExprId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the parameters of a function.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Param {
|
||||||
|
/// The name of the argument if it's a simple identifier (e.g., `x: ...`).
|
||||||
|
/// Also used for the alias in a pattern (e.g., `args @ { ... }`).
|
||||||
|
pub ident: Option<SymId>,
|
||||||
|
/// The set of required parameter names for a pattern-matching function.
|
||||||
|
pub required: Option<Vec<SymId>>,
|
||||||
|
/// The set of all allowed parameter names for a non-ellipsis pattern-matching function.
|
||||||
|
/// If `None`, any attribute is allowed (ellipsis `...` is present).
|
||||||
|
pub allowed: Option<HashSet<SymId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a function call.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Call {
|
||||||
|
/// The expression that evaluates to the function to be called.
|
||||||
|
pub func: ExprId,
|
||||||
|
pub arg: ExprId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a `with` expression.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct With {
|
||||||
|
/// The namespace to bring into scope.
|
||||||
|
pub namespace: ExprId,
|
||||||
|
/// The expression to be evaluated within the new scope.
|
||||||
|
pub expr: ExprId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an `assert` expression.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Assert {
|
||||||
|
/// The condition to assert.
|
||||||
|
pub assertion: ExprId,
|
||||||
|
/// The expression to return if the assertion is true.
|
||||||
|
pub expr: ExprId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the concatenation of multiple string expressions.
|
||||||
|
/// This is typically the result of downgrading an interpolated string.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConcatStrings {
|
||||||
|
pub parts: Vec<ExprId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a constant value (e.g., integer, float, boolean, null).
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Const {
|
||||||
|
pub val: PubConst,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<PubConst>> From<T> for Const {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self { val: value.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a simple, non-interpolated string literal.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Str {
|
||||||
|
pub val: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a variable lookup by its name.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Var {
|
||||||
|
pub sym: SymId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a path literal.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Path {
|
||||||
|
/// The expression that evaluates to the string content of the path.
|
||||||
|
/// This can be a simple `Str` or a `ConcatStrings` for interpolated paths.
|
||||||
|
pub expr: ExprId,
|
||||||
|
}
|
||||||
19
evaluator/nixjit_jit/Cargo.toml
Normal file
19
evaluator/nixjit_jit/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_jit"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hashbrown = "0.15"
|
||||||
|
|
||||||
|
cranelift = "0.122"
|
||||||
|
cranelift-module = "0.122"
|
||||||
|
cranelift-jit = "0.122"
|
||||||
|
cranelift-native = "0.122"
|
||||||
|
|
||||||
|
nixjit_error = { path = "../nixjit_error" }
|
||||||
|
nixjit_eval = { path = "../nixjit_eval" }
|
||||||
|
nixjit_hir = { path = "../nixjit_hir" }
|
||||||
|
nixjit_ir = { path = "../nixjit_ir" }
|
||||||
|
nixjit_lir = { path = "../nixjit_lir" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
559
evaluator/nixjit_jit/src/compile.rs
Normal file
559
evaluator/nixjit_jit/src/compile.rs
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
//! This module defines the `JITCompile` trait and its implementations for
|
||||||
|
//! various IR types. It provides the translation from LIR to Cranelift IR.
|
||||||
|
|
||||||
|
use cranelift::codegen::ir::{self, StackSlot};
|
||||||
|
use cranelift::prelude::*;
|
||||||
|
|
||||||
|
use nixjit_eval::Value;
|
||||||
|
use nixjit_ir::*;
|
||||||
|
use nixjit_lir::Lir;
|
||||||
|
|
||||||
|
use super::{Context, JITContext};
|
||||||
|
|
||||||
|
/// A trait for compiling IR nodes to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This trait defines how different IR nodes should be compiled to
|
||||||
|
/// Cranelift IR instructions that can be executed by the JIT compiler.
|
||||||
|
pub trait JITCompile<Ctx: JITContext> {
|
||||||
|
/// Compiles the IR node to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `ctx` - The compilation context
|
||||||
|
/// * `rt_ctx` - The evaluation context value
|
||||||
|
/// * `env` - The environment value
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A stack slot containing the compiled result
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for ExprId {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Lir {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for AttrSet {
|
||||||
|
/// Compiles an attribute set to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This creates a new attribute set and compiles all static attributes into it.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
let attrs = ctx.create_attrs();
|
||||||
|
for (&k, v) in self.stcs.iter() {
|
||||||
|
let v = v.compile(ctx, rt_ctx);
|
||||||
|
ctx.push_attr(attrs, k, v);
|
||||||
|
}
|
||||||
|
ctx.finalize_attrs(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for List {
|
||||||
|
/// Compiles a list to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This creates a new list by compiling all items and storing them in an array.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
let array = ctx.alloc_array(self.items.len());
|
||||||
|
for (i, item) in self.items.iter().enumerate() {
|
||||||
|
let item = item.compile(ctx, rt_ctx);
|
||||||
|
let tag = ctx.builder.ins().stack_load(types::I64, item, 0);
|
||||||
|
let val0 = ctx.builder.ins().stack_load(types::I64, item, 8);
|
||||||
|
let val1 = ctx.builder.ins().stack_load(types::I64, item, 16);
|
||||||
|
let val2 = ctx.builder.ins().stack_load(types::I64, item, 24);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), tag, array, i as i32 * 32);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), val0, array, i as i32 * 32 + 8);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), val1, array, i as i32 * 32 + 16);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), val2, array, i as i32 * 32 + 24);
|
||||||
|
}
|
||||||
|
ctx.create_list(array, self.items.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for HasAttr {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for BinOp {
|
||||||
|
/// Compiles a binary operation to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This implementation handles various binary operations like addition, subtraction,
|
||||||
|
/// division, logical AND/OR, and equality checks. It generates code that checks
|
||||||
|
/// the types of operands and performs the appropriate operation.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
use BinOpKind::*;
|
||||||
|
let lhs = self.lhs.compile(ctx, rt_ctx);
|
||||||
|
let rhs = self.rhs.compile(ctx, rt_ctx);
|
||||||
|
let lhs_tag = ctx.get_tag(lhs);
|
||||||
|
let rhs_tag = ctx.get_tag(rhs);
|
||||||
|
let eq = ctx.builder.ins().icmp(IntCC::Equal, lhs_tag, rhs_tag);
|
||||||
|
|
||||||
|
let eq_block = ctx.builder.create_block();
|
||||||
|
let neq_block = ctx.builder.create_block();
|
||||||
|
let exit_block = ctx.builder.create_block();
|
||||||
|
ctx.builder.ins().brif(eq, eq_block, [], neq_block, []);
|
||||||
|
|
||||||
|
match self.kind {
|
||||||
|
Add => {
|
||||||
|
ctx.builder.switch_to_block(eq_block);
|
||||||
|
let default_block = ctx.builder.create_block();
|
||||||
|
let int_block = ctx.builder.create_block();
|
||||||
|
let float_block = ctx.builder.create_block();
|
||||||
|
let float_check_block = ctx.builder.create_block();
|
||||||
|
|
||||||
|
let is_int = ctx
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::INT as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_int, int_block, [], float_check_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(int_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||||
|
let result = ctx.builder.ins().iadd(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, &[]);
|
||||||
|
|
||||||
|
// FIXME: Non-float
|
||||||
|
ctx.builder.switch_to_block(float_check_block);
|
||||||
|
let is_float =
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::FLOAT as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_float, float_block, [], default_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(float_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::F64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::F64, rhs);
|
||||||
|
let result = ctx.builder.ins().fadd(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, &[]);
|
||||||
|
|
||||||
|
// FIXME: finish this
|
||||||
|
ctx.builder.switch_to_block(default_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(neq_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.seal_block(default_block);
|
||||||
|
ctx.builder.seal_block(int_block);
|
||||||
|
ctx.builder.seal_block(float_check_block);
|
||||||
|
ctx.builder.seal_block(float_block);
|
||||||
|
}
|
||||||
|
Sub => {
|
||||||
|
ctx.builder.switch_to_block(eq_block);
|
||||||
|
let default_block = ctx.builder.create_block();
|
||||||
|
let int_block = ctx.builder.create_block();
|
||||||
|
let float_block = ctx.builder.create_block();
|
||||||
|
let float_check_block = ctx.builder.create_block();
|
||||||
|
|
||||||
|
let is_int = ctx
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::INT as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_int, int_block, [], float_check_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(int_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||||
|
let result = ctx.builder.ins().isub(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, &[]);
|
||||||
|
|
||||||
|
// FIXME: Non-float
|
||||||
|
ctx.builder.switch_to_block(float_check_block);
|
||||||
|
let is_float =
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::FLOAT as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_float, float_block, [], default_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(float_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::F64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::F64, rhs);
|
||||||
|
let result = ctx.builder.ins().fsub(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, &[]);
|
||||||
|
|
||||||
|
// FIXME: finish this
|
||||||
|
ctx.builder.switch_to_block(default_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(neq_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.seal_block(default_block);
|
||||||
|
ctx.builder.seal_block(int_block);
|
||||||
|
ctx.builder.seal_block(float_check_block);
|
||||||
|
ctx.builder.seal_block(float_block);
|
||||||
|
}
|
||||||
|
Div => {
|
||||||
|
ctx.builder.switch_to_block(eq_block);
|
||||||
|
let default_block = ctx.builder.create_block();
|
||||||
|
let int_block = ctx.builder.create_block();
|
||||||
|
let float_block = ctx.builder.create_block();
|
||||||
|
let float_check_block = ctx.builder.create_block();
|
||||||
|
|
||||||
|
let is_int = ctx
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::INT as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_int, int_block, [], float_check_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(int_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||||
|
let result = ctx.builder.ins().sdiv(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, &[]);
|
||||||
|
|
||||||
|
// FIXME: Non-float
|
||||||
|
ctx.builder.switch_to_block(float_check_block);
|
||||||
|
let is_float =
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::FLOAT as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_float, float_block, [], default_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(float_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::F64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::F64, rhs);
|
||||||
|
let result = ctx.builder.ins().fdiv(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, &[]);
|
||||||
|
|
||||||
|
// FIXME: finish this
|
||||||
|
ctx.builder.switch_to_block(default_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(neq_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.seal_block(default_block);
|
||||||
|
ctx.builder.seal_block(int_block);
|
||||||
|
ctx.builder.seal_block(float_check_block);
|
||||||
|
ctx.builder.seal_block(float_block);
|
||||||
|
}
|
||||||
|
And => {
|
||||||
|
ctx.builder.switch_to_block(eq_block);
|
||||||
|
let bool_block = ctx.builder.create_block();
|
||||||
|
let non_bool_block = ctx.builder.create_block();
|
||||||
|
|
||||||
|
let is_bool = ctx
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::BOOL as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_bool, bool_block, [], non_bool_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(bool_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||||
|
let result = ctx.builder.ins().band(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(non_bool_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(neq_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.seal_block(bool_block);
|
||||||
|
ctx.builder.seal_block(non_bool_block);
|
||||||
|
}
|
||||||
|
Or => {
|
||||||
|
ctx.builder.switch_to_block(eq_block);
|
||||||
|
let bool_block = ctx.builder.create_block();
|
||||||
|
let non_bool_block = ctx.builder.create_block();
|
||||||
|
|
||||||
|
let is_bool = ctx
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, lhs_tag, Value::BOOL as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_bool, bool_block, [], non_bool_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(bool_block);
|
||||||
|
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||||
|
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||||
|
let result = ctx.builder.ins().bor(lhs_value, rhs_value);
|
||||||
|
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||||
|
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||||
|
ctx.builder.ins().jump(exit_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(non_bool_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(neq_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.seal_block(bool_block);
|
||||||
|
ctx.builder.seal_block(non_bool_block);
|
||||||
|
}
|
||||||
|
Eq => {
|
||||||
|
ctx.builder.switch_to_block(eq_block);
|
||||||
|
ctx.eq(lhs, rhs);
|
||||||
|
ctx.builder.ins().jump(exit_block, []);
|
||||||
|
ctx.builder.switch_to_block(neq_block);
|
||||||
|
ctx.eq(lhs, rhs);
|
||||||
|
ctx.builder.ins().jump(exit_block, []);
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.builder.seal_block(exit_block);
|
||||||
|
ctx.builder.seal_block(eq_block);
|
||||||
|
ctx.builder.seal_block(neq_block);
|
||||||
|
ctx.builder.switch_to_block(exit_block);
|
||||||
|
ctx.free_slot(rhs);
|
||||||
|
|
||||||
|
lhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for UnOp {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Attr {
|
||||||
|
/// Compiles an attribute key to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// An attribute can be either a static string or a dynamic expression.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
use Attr::*;
|
||||||
|
match self {
|
||||||
|
Str(string) => ctx.create_string(string),
|
||||||
|
Dynamic(ir) => ir.compile(ctx, rt_ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Select {
|
||||||
|
/// Compiles an attribute selection to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This compiles the expression to select from, builds the attribute path,
|
||||||
|
/// and calls the select helper function.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
let val = self.expr.compile(ctx, rt_ctx);
|
||||||
|
let attrpath = ctx.alloc_array(self.attrpath.len());
|
||||||
|
for (i, attr) in self.attrpath.iter().enumerate() {
|
||||||
|
let arg = attr.compile(ctx, rt_ctx);
|
||||||
|
let tag = ctx.builder.ins().stack_load(types::I64, arg, 0);
|
||||||
|
let val0 = ctx.builder.ins().stack_load(types::I64, arg, 8);
|
||||||
|
let val1 = ctx.builder.ins().stack_load(types::I64, arg, 16);
|
||||||
|
let val2 = ctx.builder.ins().stack_load(types::I64, arg, 24);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), tag, attrpath, i as i32 * 32);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), val0, attrpath, i as i32 * 32 + 8);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), val1, attrpath, i as i32 * 32 + 16);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.store(MemFlags::new(), val2, attrpath, i as i32 * 32 + 24);
|
||||||
|
}
|
||||||
|
ctx.select(val, attrpath, self.attrpath.len(), rt_ctx);
|
||||||
|
val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for If {
|
||||||
|
/// Compiles an if-expression to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This generates code that evaluates the condition, checks that it's a boolean,
|
||||||
|
/// and then jumps to the appropriate branch (true or false).
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
let cond = self.cond.compile(ctx, rt_ctx);
|
||||||
|
let cond_type = ctx.builder.ins().stack_load(types::I64, cond, 0);
|
||||||
|
let cond_value = ctx.builder.ins().stack_load(types::I64, cond, 8);
|
||||||
|
|
||||||
|
let true_block = ctx.builder.create_block();
|
||||||
|
let false_block = ctx.builder.create_block();
|
||||||
|
let exit_block = ctx.builder.create_block();
|
||||||
|
let error_block = ctx.builder.create_block();
|
||||||
|
let judge_block = ctx.builder.create_block();
|
||||||
|
let slot = ctx.alloca();
|
||||||
|
|
||||||
|
let is_bool = ctx
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.icmp_imm(IntCC::Equal, cond_type, Value::BOOL as i64);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(is_bool, judge_block, [], error_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(judge_block);
|
||||||
|
ctx.builder
|
||||||
|
.ins()
|
||||||
|
.brif(cond_value, true_block, [], false_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(true_block);
|
||||||
|
let ret = self.consq.compile(ctx, rt_ctx);
|
||||||
|
let tag = ctx.builder.ins().stack_load(types::I64, ret, 0);
|
||||||
|
let val0 = ctx.builder.ins().stack_load(types::I64, ret, 8);
|
||||||
|
let val1 = ctx.builder.ins().stack_load(types::I64, ret, 16);
|
||||||
|
let val2 = ctx.builder.ins().stack_load(types::I64, ret, 24);
|
||||||
|
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||||
|
ctx.builder.ins().stack_store(val0, slot, 8);
|
||||||
|
ctx.builder.ins().stack_store(val1, slot, 16);
|
||||||
|
ctx.builder.ins().stack_store(val2, slot, 24);
|
||||||
|
ctx.builder.ins().jump(exit_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(false_block);
|
||||||
|
let ret = self.alter.compile(ctx, rt_ctx);
|
||||||
|
let tag = ctx.builder.ins().stack_load(types::I64, ret, 0);
|
||||||
|
let val0 = ctx.builder.ins().stack_load(types::I64, ret, 8);
|
||||||
|
let val1 = ctx.builder.ins().stack_load(types::I64, ret, 16);
|
||||||
|
let val2 = ctx.builder.ins().stack_load(types::I64, ret, 24);
|
||||||
|
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||||
|
ctx.builder.ins().stack_store(val0, slot, 8);
|
||||||
|
ctx.builder.ins().stack_store(val1, slot, 16);
|
||||||
|
ctx.builder.ins().stack_store(val2, slot, 24);
|
||||||
|
ctx.builder.ins().jump(exit_block, []);
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(error_block);
|
||||||
|
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||||
|
|
||||||
|
ctx.builder.switch_to_block(exit_block);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Call {
|
||||||
|
/// Compiles a function call to Cranelift IR.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
let func = self.func.compile(ctx, rt_ctx);
|
||||||
|
let arg = self.arg.compile(ctx, rt_ctx);
|
||||||
|
ctx.call(func, arg, rt_ctx);
|
||||||
|
func
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for With {
|
||||||
|
/// Compiles a `with` expression to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This enters a new `with` scope with the compiled namespace, compiles the body expression,
|
||||||
|
/// and then exits the `with` scope.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
let namespace = self.namespace.compile(ctx, rt_ctx);
|
||||||
|
ctx.enter_with(rt_ctx, namespace);
|
||||||
|
let ret = self.expr.compile(ctx, rt_ctx);
|
||||||
|
ctx.exit_with(rt_ctx);
|
||||||
|
ctx.free_slot(namespace);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Assert {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for ConcatStrings {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Const {
|
||||||
|
/// Compiles a constant value to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This handles boolean, integer, float, and null constants by storing
|
||||||
|
/// their values and type tags in a stack slot.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
use nixjit_value::Const::*;
|
||||||
|
let slot = ctx.alloca();
|
||||||
|
match self.val {
|
||||||
|
Bool(x) => {
|
||||||
|
let tag = ctx.builder.ins().iconst(types::I64, Value::BOOL as i64);
|
||||||
|
let val = ctx.builder.ins().iconst(types::I64, x as i64);
|
||||||
|
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||||
|
ctx.builder.ins().stack_store(val, slot, 8);
|
||||||
|
}
|
||||||
|
Int(x) => {
|
||||||
|
let tag = ctx.builder.ins().iconst(types::I64, Value::INT as i64);
|
||||||
|
let val = ctx.builder.ins().iconst(types::I64, x);
|
||||||
|
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||||
|
ctx.builder.ins().stack_store(val, slot, 8);
|
||||||
|
}
|
||||||
|
Float(x) => {
|
||||||
|
let tag = ctx.builder.ins().iconst(types::I64, Value::FLOAT as i64);
|
||||||
|
let val = ctx.builder.ins().f64const(x);
|
||||||
|
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||||
|
ctx.builder.ins().stack_store(val, slot, 8);
|
||||||
|
}
|
||||||
|
Null => {
|
||||||
|
let tag = ctx.builder.ins().iconst(types::I64, Value::NULL as i64);
|
||||||
|
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Str {
|
||||||
|
/// Compiles a string literal to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This creates a string value from the string literal.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
ctx.create_string(&self.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Var {
|
||||||
|
/// Compiles a variable lookup to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This looks up a variable by its symbol in the current environment.
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
ctx.lookup(rt_ctx, &self.sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompile<Ctx> for Path {
|
||||||
|
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
237
evaluator/nixjit_jit/src/helpers.rs
Normal file
237
evaluator/nixjit_jit/src/helpers.rs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
//! Helper functions for the JIT compiler.
|
||||||
|
//!
|
||||||
|
//! These functions are called from JIT-compiled code to perform operations
|
||||||
|
//! that are difficult or unsafe to do directly in the generated IR.
|
||||||
|
|
||||||
|
use core::{slice, str};
|
||||||
|
use std::alloc::Layout;
|
||||||
|
use std::alloc::alloc;
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use nixjit_eval::{AttrSet, EvalContext, List, Value};
|
||||||
|
use nixjit_ir::ExprId;
|
||||||
|
use nixjit_ir::SymId;
|
||||||
|
|
||||||
|
use super::JITContext;
|
||||||
|
|
||||||
|
/// Helper function to call a function with arguments.
|
||||||
|
pub extern "C" fn helper_call<Ctx: JITContext>(
|
||||||
|
func: &mut Value,
|
||||||
|
arg: NonNull<Value>,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) {
|
||||||
|
// SAFETY: The `arg` pointer is guaranteed to be valid and non-null by the JIT compiler,
|
||||||
|
// which allocates it on the stack. The JIT code ensures that the pointer points to a
|
||||||
|
// valid `Value` and that its lifetime is managed correctly within the compiled function.
|
||||||
|
func.call(unsafe { arg.read() }, ctx).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to look up a function argument.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to access function arguments.
|
||||||
|
pub extern "C" fn helper_lookup_arg<Ctx: EvalContext>(ctx: &mut Ctx, ret: &mut MaybeUninit<Value>) {
|
||||||
|
todo!()
|
||||||
|
// ret.write(ctx.lookup_arg().unwrap().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to look up a variable by name.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to perform variable lookups
|
||||||
|
/// in the current scope and `with` expression scopes.
|
||||||
|
pub extern "C" fn helper_lookup<Ctx: JITContext>(
|
||||||
|
ctx: &Ctx,
|
||||||
|
sym_ptr: *const u8,
|
||||||
|
sym_len: usize,
|
||||||
|
ret: &mut MaybeUninit<Value>,
|
||||||
|
) {
|
||||||
|
// TODO: Error Handling
|
||||||
|
// SAFETY: The `sym_ptr` and `sym_len` are provided by the JIT compiler and are
|
||||||
|
// guaranteed to form a valid UTF-8 string slice. The string data is embedded
|
||||||
|
// in the compiled code and has a static lifetime, ensuring the pointer is always valid.
|
||||||
|
unsafe {
|
||||||
|
ret.write(
|
||||||
|
ctx.lookup_with(str::from_utf8_unchecked(slice::from_raw_parts(
|
||||||
|
sym_ptr, sym_len,
|
||||||
|
)))
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to perform attribute selection.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to select attributes from
|
||||||
|
/// an attribute set using a path of attribute names.
|
||||||
|
pub extern "C" fn helper_select<Ctx: JITContext>(
|
||||||
|
val: &mut Value,
|
||||||
|
path_ptr: *mut Value,
|
||||||
|
path_len: usize,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) {
|
||||||
|
let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len);
|
||||||
|
// SAFETY: The `path_ptr` is allocated by the JIT compiler using `helper_alloc_array`
|
||||||
|
// and is guaranteed to be valid for the given length. The `Box::from_raw` call
|
||||||
|
// correctly takes ownership of the allocated slice, ensuring it is properly deallocated.
|
||||||
|
let path = unsafe { Box::from_raw(path) };
|
||||||
|
for attr in path {
|
||||||
|
val.select(&attr.force_string_no_ctx().unwrap(), ctx)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to perform attribute selection with a default value.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to select attributes from
|
||||||
|
/// an attribute set, returning a default value if the selection fails.
|
||||||
|
pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
|
||||||
|
val: &mut Value,
|
||||||
|
path_ptr: *mut Value,
|
||||||
|
path_len: usize,
|
||||||
|
default: ExprId,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) {
|
||||||
|
let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len);
|
||||||
|
// SAFETY: The `path_ptr` is allocated by the JIT compiler using `helper_alloc_array`
|
||||||
|
// and is guaranteed to be valid for the given length. The `Box::from_raw` call
|
||||||
|
// correctly takes ownership of the allocated slice, ensuring it is properly deallocated.
|
||||||
|
let path = unsafe { Box::from_raw(path) };
|
||||||
|
for attr in path {
|
||||||
|
val.select_or(&attr.force_string_no_ctx().unwrap(), default, ctx)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to check equality between two values.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to perform equality comparisons.
|
||||||
|
pub extern "C" fn helper_eq(lhs: &mut Value, rhs: NonNull<Value>) {
|
||||||
|
// SAFETY: The `rhs` pointer is guaranteed to be valid and non-null by the JIT compiler,
|
||||||
|
// which allocates it on the stack. The JIT code ensures that the pointer points to a
|
||||||
|
// valid `Value` and that its lifetime is managed correctly within the compiled function.
|
||||||
|
lhs.eq(unsafe { rhs.read() });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a string value.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to create string values
|
||||||
|
/// from raw byte arrays.
|
||||||
|
pub unsafe extern "C" fn helper_create_string(
|
||||||
|
ptr: *const u8,
|
||||||
|
len: usize,
|
||||||
|
ret: &mut MaybeUninit<Value>,
|
||||||
|
) {
|
||||||
|
// SAFETY: The `ptr` and `len` are provided by the JIT compiler and are guaranteed
|
||||||
|
// to form a valid UTF-8 string slice. The string data is embedded in the compiled
|
||||||
|
// code and has a static lifetime, ensuring the pointer is always valid.
|
||||||
|
unsafe {
|
||||||
|
ret.write(Value::String(
|
||||||
|
str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)).to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a list value.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to create list values
|
||||||
|
/// from arrays of values.
|
||||||
|
pub unsafe extern "C" fn helper_create_list(
|
||||||
|
ptr: *mut Value,
|
||||||
|
len: usize,
|
||||||
|
ret: &mut MaybeUninit<Value>,
|
||||||
|
) {
|
||||||
|
// SAFETY: The `ptr` is allocated by the JIT compiler using `helper_alloc_array` and
|
||||||
|
// is guaranteed to be valid for `len` elements. The `Vec::from_raw_parts` call
|
||||||
|
// correctly takes ownership of the allocated memory, ensuring it is properly managed.
|
||||||
|
unsafe {
|
||||||
|
ret.write(Value::List(
|
||||||
|
List::from(Vec::from_raw_parts(ptr, len, len)).into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create an attribute set.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to create a new, empty attribute set.
|
||||||
|
pub unsafe extern "C" fn helper_create_attrs(
|
||||||
|
ret: &mut MaybeUninit<HashMap<String, Value>>,
|
||||||
|
) {
|
||||||
|
ret.write(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to add an attribute to an attribute set.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to insert a key-value pair
|
||||||
|
/// into an attribute set.
|
||||||
|
pub unsafe extern "C" fn helper_push_attr(
|
||||||
|
attrs: &mut HashMap<SymId, Value>,
|
||||||
|
sym: SymId,
|
||||||
|
val: NonNull<Value>,
|
||||||
|
) {
|
||||||
|
// SAFETY: The `sym_ptr` and `sym_len` are provided by the JIT compiler and are
|
||||||
|
// guaranteed to form a valid UTF-8 string slice. The `val` pointer is also
|
||||||
|
// guaranteed to be valid and non-null by the JIT compiler.
|
||||||
|
unsafe {
|
||||||
|
attrs.insert(
|
||||||
|
sym,
|
||||||
|
val.read(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to finalize an attribute set.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to convert a HashMap into
|
||||||
|
/// a proper attribute set value.
|
||||||
|
pub unsafe extern "C" fn helper_finalize_attrs(
|
||||||
|
attrs: NonNull<HashMap<String, Value>>,
|
||||||
|
ret: &mut MaybeUninit<Value>,
|
||||||
|
) {
|
||||||
|
// SAFETY: The `attrs` pointer is guaranteed to be valid and non-null by the JIT
|
||||||
|
// compiler, which allocates it on the stack. The `read` operation correctly
|
||||||
|
// takes ownership of the HashMap.
|
||||||
|
ret.write(Value::AttrSet(
|
||||||
|
AttrSet::from(unsafe { attrs.read() }).into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to enter a `with` expression scope.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to enter a new `with` scope
|
||||||
|
/// with the given namespace.
|
||||||
|
pub unsafe extern "C" fn helper_enter_with<Ctx: JITContext>(
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
namespace: NonNull<Value>,
|
||||||
|
) {
|
||||||
|
// SAFETY: The `namespace` pointer is guaranteed to be valid and non-null by the JIT
|
||||||
|
// compiler. The `read` operation correctly takes ownership of the `Value`.
|
||||||
|
ctx.enter_with(unsafe { namespace.read() }.unwrap_attr_set().into_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to exit a `with` expression scope.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to exit the current `with` scope.
|
||||||
|
pub unsafe extern "C" fn helper_exit_with<Ctx: JITContext>(ctx: &mut Ctx) {
|
||||||
|
ctx.exit_with();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to allocate an array of values.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to allocate memory for
|
||||||
|
/// arrays of values, such as function arguments or list elements.
|
||||||
|
pub unsafe extern "C" fn helper_alloc_array(len: usize) -> *mut u8 {
|
||||||
|
// SAFETY: The `Layout` is guaranteed to be valid for non-zero `len`. The caller
|
||||||
|
// is responsible for deallocating the memory, which is typically done by
|
||||||
|
// `Vec::from_raw_parts` or `Box::from_raw` in other helpers.
|
||||||
|
unsafe { alloc(Layout::array::<Value>(len).unwrap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for debugging.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to print a value for debugging purposes.
|
||||||
|
pub extern "C" fn helper_dbg(value: &Value) {
|
||||||
|
println!("{value:?}")
|
||||||
|
}
|
||||||
740
evaluator/nixjit_jit/src/lib.rs
Normal file
740
evaluator/nixjit_jit/src/lib.rs
Normal file
@@ -0,0 +1,740 @@
|
|||||||
|
//! The Just-In-Time (JIT) compilation module for nixjit.
|
||||||
|
//!
|
||||||
|
//! This module provides functionality to compile Low-Level IR (LIR) expressions
|
||||||
|
//! into optimized machine code using Cranelift. The JIT compiler translates
|
||||||
|
//! Nix expressions into efficient native code for faster evaluation.
|
||||||
|
//!
|
||||||
|
//! The main components are:
|
||||||
|
//! - `JITCompiler`: The core compiler that manages the compilation process
|
||||||
|
//! - `JITContext`: A trait that provides the execution context for JIT-compiled code
|
||||||
|
//! - `Context`: An internal compilation context used during code generation
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use cranelift::codegen::ir::Function;
|
||||||
|
use cranelift::codegen::ir::{self, ArgumentExtension, ArgumentPurpose, StackSlot};
|
||||||
|
use cranelift::prelude::*;
|
||||||
|
use cranelift_jit::{JITBuilder, JITModule};
|
||||||
|
use cranelift_module::{FuncId, Linkage, Module};
|
||||||
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use nixjit_eval::{EvalContext, Value};
|
||||||
|
use nixjit_ir::SymId;
|
||||||
|
use nixjit_lir::Lir;
|
||||||
|
|
||||||
|
mod compile;
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
pub use compile::JITCompile;
|
||||||
|
use helpers::*;
|
||||||
|
|
||||||
|
/// A trait that provides the execution context for JIT-compiled code.
|
||||||
|
///
|
||||||
|
/// This trait extends `EvalContext` with additional methods needed
|
||||||
|
/// for JIT compilation, such as managing `with` expression scopes directly.
|
||||||
|
pub trait JITContext: EvalContext {
|
||||||
|
/// Enters a `with` expression scope with the given namespace.
|
||||||
|
fn enter_with(&mut self, namespace: Rc<HashMap<String, Value>>);
|
||||||
|
/// Exits the current `with` expression scope.
|
||||||
|
fn exit_with(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type alias for a JIT-compiled function.
|
||||||
|
///
|
||||||
|
/// This represents a function pointer to JIT-compiled code that takes
|
||||||
|
/// a context pointer and a mutable value pointer as arguments.
|
||||||
|
type F<Ctx> = unsafe extern "C" fn(*const Ctx, *mut Value);
|
||||||
|
|
||||||
|
/// A JIT-compiled function.
|
||||||
|
///
|
||||||
|
/// This struct holds a function pointer to the compiled code and
|
||||||
|
/// a set of strings that were used during compilation, which need
|
||||||
|
/// to be kept alive for the function to work correctly.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct JITFunc<Ctx: JITContext> {
|
||||||
|
func: F<Ctx>,
|
||||||
|
strings: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> Deref for JITFunc<Ctx> {
|
||||||
|
type Target = F<Ctx>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.func
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The internal compilation context used during code generation.
|
||||||
|
///
|
||||||
|
/// This context holds references to the compiler, the Cranelift function builder,
|
||||||
|
/// and manages resources like stack slots and string literals during compilation.
|
||||||
|
struct Context<'comp, 'ctx, Ctx: JITContext> {
|
||||||
|
/// Reference to the JIT compiler.
|
||||||
|
pub compiler: &'comp mut JITCompiler<Ctx>,
|
||||||
|
/// The Cranelift function builder used to generate IR.
|
||||||
|
pub builder: FunctionBuilder<'ctx>,
|
||||||
|
/// Stack slots available for reuse.
|
||||||
|
free_slots: Vec<StackSlot>,
|
||||||
|
/// String literals used during compilation.
|
||||||
|
strings: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'comp, 'ctx, Ctx: JITContext> Context<'comp, 'ctx, Ctx> {
|
||||||
|
fn new(compiler: &'comp mut JITCompiler<Ctx>, builder: FunctionBuilder<'ctx>) -> Self {
|
||||||
|
Self {
|
||||||
|
compiler,
|
||||||
|
builder,
|
||||||
|
free_slots: Vec::new(),
|
||||||
|
strings: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloca(&mut self) -> StackSlot {
|
||||||
|
self.free_slots.pop().map_or_else(
|
||||||
|
|| {
|
||||||
|
let slot = StackSlotData::new(StackSlotKind::ExplicitSlot, 32, 3);
|
||||||
|
self.builder.create_sized_stack_slot(slot)
|
||||||
|
},
|
||||||
|
|x| x,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_slot(&mut self, slot: StackSlot) {
|
||||||
|
self.free_slots.push(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_array(&mut self, len: usize) -> ir::Value {
|
||||||
|
let len = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, len as i64);
|
||||||
|
let alloc_array = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.alloc_array, self.builder.func);
|
||||||
|
let inst = self.builder.ins().call(alloc_array, &[len]);
|
||||||
|
self.builder.inst_results(inst)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_string(&mut self, string: &str) -> StackSlot {
|
||||||
|
let string = self
|
||||||
|
.strings
|
||||||
|
.get_or_insert_with(string, |_| string.to_owned());
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, string.as_ptr() as i64);
|
||||||
|
let len = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, string.len() as i64);
|
||||||
|
let create_string = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.create_string, self.builder.func);
|
||||||
|
let slot = self.alloca();
|
||||||
|
let ret = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder.ins().call(create_string, &[ptr, len, ret]);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_list(&mut self, ptr: ir::Value, len: usize) -> StackSlot {
|
||||||
|
let len = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, len as i64);
|
||||||
|
let create_list = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.create_list, self.builder.func);
|
||||||
|
let slot = self.alloca();
|
||||||
|
let ret = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder.ins().call(create_list, &[ptr, len, ret]);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_attrs(&mut self) -> StackSlot {
|
||||||
|
let create_attrs = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.create_attrs, self.builder.func);
|
||||||
|
let slot = StackSlotData::new(StackSlotKind::ExplicitSlot, 40, 3);
|
||||||
|
let slot = self.builder.create_sized_stack_slot(slot);
|
||||||
|
let ret = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder.ins().call(create_attrs, &[ret]);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_attr(&mut self, attrs: StackSlot, sym: SymId, val: StackSlot) {
|
||||||
|
self.free_slot(attrs);
|
||||||
|
self.free_slot(val);
|
||||||
|
let attrs = self.builder.ins().stack_addr(types::I64, attrs, 0);
|
||||||
|
let val = self.builder.ins().stack_addr(types::I64, val, 0);
|
||||||
|
let sym = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, unsafe { sym.raw() } as i64);
|
||||||
|
let push_attr = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.push_attr, self.builder.func);
|
||||||
|
self.builder.ins().call(push_attr, &[attrs, sym, val]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize_attrs(&mut self, attrs: StackSlot) -> StackSlot {
|
||||||
|
let attrs = self.builder.ins().stack_addr(types::I64, attrs, 0);
|
||||||
|
let finalize_attrs = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.finalize_attrs, self.builder.func);
|
||||||
|
let slot = self.alloca();
|
||||||
|
let ret = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder.ins().call(finalize_attrs, &[attrs, ret]);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_with(&mut self, rt_ctx: ir::Value, namespace: StackSlot) {
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, namespace, 0);
|
||||||
|
let enter_with = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.enter_with, self.builder.func);
|
||||||
|
self.builder.ins().call(enter_with, &[rt_ctx, ptr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_with(&mut self, rt_ctx: ir::Value) {
|
||||||
|
let exit_with = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.exit_with, self.builder.func);
|
||||||
|
self.builder.ins().call(exit_with, &[rt_ctx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbg(&mut self, slot: StackSlot) {
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
let dbg = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.dbg, self.builder.func);
|
||||||
|
self.builder.ins().call(dbg, &[ptr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, func: StackSlot, arg: StackSlot, call_ctx: ir::Value) {
|
||||||
|
let call = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.call, self.builder.func);
|
||||||
|
let func = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, func, 0);
|
||||||
|
let arg = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, arg, 0);
|
||||||
|
self.builder.ins().call(call, &[func, arg, call_ctx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(&mut self, rt_ctx: ir::Value, sym: &str) -> StackSlot {
|
||||||
|
let sym = self.strings.get_or_insert_with(sym, |_| sym.to_owned());
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, sym.as_ptr() as i64);
|
||||||
|
let len = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, sym.len() as i64);
|
||||||
|
let lookup = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.lookup, self.builder.func);
|
||||||
|
let slot = self.alloca();
|
||||||
|
let ret = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder.ins().call(lookup, &[rt_ctx, ptr, len, ret]);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_arg(&mut self, ctx: ir::Value, idx: usize) -> StackSlot {
|
||||||
|
let slot = self.alloca();
|
||||||
|
let lookup_arg = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.lookup_arg, self.builder.func);
|
||||||
|
let idx = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, idx as i64);
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder.ins().call(lookup_arg, &[ctx, idx, ptr]);
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select(&mut self, slot: StackSlot, path_ptr: ir::Value, path_len: usize, ctx: ir::Value) {
|
||||||
|
let select = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.select, self.builder.func);
|
||||||
|
let path_len = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, path_len as i64);
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
self.builder
|
||||||
|
.ins()
|
||||||
|
.call(select, &[ptr, path_ptr, path_len, ctx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_with_default(
|
||||||
|
&mut self,
|
||||||
|
slot: StackSlot,
|
||||||
|
path_ptr: ir::Value,
|
||||||
|
path_len: usize,
|
||||||
|
default: StackSlot,
|
||||||
|
engine: ir::Value,
|
||||||
|
rt_ctx: ir::Value,
|
||||||
|
) {
|
||||||
|
let select_with_default = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.select_with_default, self.builder.func);
|
||||||
|
let path_len = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.iconst(self.compiler.ptr_type, path_len as i64);
|
||||||
|
let ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||||
|
let default_ptr = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, default, 0);
|
||||||
|
self.builder.ins().call(
|
||||||
|
select_with_default,
|
||||||
|
&[ptr, path_ptr, path_len, default_ptr, engine, rt_ctx],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eq(&mut self, lhs: StackSlot, rhs: StackSlot) {
|
||||||
|
let lhs = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, lhs, 0);
|
||||||
|
let rhs = self
|
||||||
|
.builder
|
||||||
|
.ins()
|
||||||
|
.stack_addr(self.compiler.ptr_type, rhs, 0);
|
||||||
|
let eq = self
|
||||||
|
.compiler
|
||||||
|
.module
|
||||||
|
.declare_func_in_func(self.compiler.eq, self.builder.func);
|
||||||
|
self.builder.ins().call(eq, &[lhs, rhs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tag(&mut self, slot: StackSlot) -> ir::Value {
|
||||||
|
self.builder.ins().stack_load(types::I64, slot, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_small_value(&mut self, ty: Type, slot: StackSlot) -> ir::Value {
|
||||||
|
self.builder.ins().stack_load(ty, slot, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main JIT compiler that manages the compilation process.
|
||||||
|
pub struct JITCompiler<Ctx: JITContext> {
|
||||||
|
ctx: codegen::Context,
|
||||||
|
module: JITModule,
|
||||||
|
builder_ctx: Option<FunctionBuilderContext>,
|
||||||
|
_marker: PhantomData<Ctx>,
|
||||||
|
|
||||||
|
int_type: Type,
|
||||||
|
float_type: Type,
|
||||||
|
bool_type: Type,
|
||||||
|
ptr_type: Type,
|
||||||
|
value_type: Type,
|
||||||
|
func_sig: Signature,
|
||||||
|
|
||||||
|
call: FuncId,
|
||||||
|
lookup_arg: FuncId,
|
||||||
|
lookup: FuncId,
|
||||||
|
select: FuncId,
|
||||||
|
select_with_default: FuncId,
|
||||||
|
|
||||||
|
eq: FuncId,
|
||||||
|
|
||||||
|
alloc_array: FuncId,
|
||||||
|
create_string: FuncId,
|
||||||
|
create_list: FuncId,
|
||||||
|
create_attrs: FuncId,
|
||||||
|
push_attr: FuncId,
|
||||||
|
finalize_attrs: FuncId,
|
||||||
|
enter_with: FuncId,
|
||||||
|
exit_with: FuncId,
|
||||||
|
dbg: FuncId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> Default for JITCompiler<Ctx> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: JITContext> JITCompiler<Ctx> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut flag_builder = settings::builder();
|
||||||
|
flag_builder.set("use_colocated_libcalls", "false").unwrap();
|
||||||
|
flag_builder.set("is_pic", "false").unwrap();
|
||||||
|
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
|
||||||
|
panic!("host machine is not supported: {msg}");
|
||||||
|
});
|
||||||
|
let isa = isa_builder
|
||||||
|
.finish(settings::Flags::new(flag_builder))
|
||||||
|
.unwrap();
|
||||||
|
let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
|
||||||
|
|
||||||
|
builder.symbol("helper_call", helper_call::<Ctx> as _);
|
||||||
|
builder.symbol("helper_lookup_arg", helper_lookup_arg::<Ctx> as _);
|
||||||
|
builder.symbol("helper_lookup", helper_lookup::<Ctx> as _);
|
||||||
|
builder.symbol("helper_select", helper_select::<Ctx> as _);
|
||||||
|
builder.symbol(
|
||||||
|
"helper_select_with_default",
|
||||||
|
helper_select_with_default::<Ctx> as _,
|
||||||
|
);
|
||||||
|
builder.symbol("helper_eq", helper_eq as _);
|
||||||
|
|
||||||
|
builder.symbol("helper_alloc_array", helper_alloc_array as _);
|
||||||
|
builder.symbol("helper_create_string", helper_create_string as _);
|
||||||
|
builder.symbol("helper_create_list", helper_create_list as _);
|
||||||
|
builder.symbol("helper_create_attrs", helper_create_attrs as _);
|
||||||
|
builder.symbol("helper_push_attr", helper_push_attr as _);
|
||||||
|
builder.symbol("helper_finalize_attrs", helper_finalize_attrs as _);
|
||||||
|
builder.symbol("helper_enter_with", helper_enter_with::<Ctx> as _);
|
||||||
|
builder.symbol("helper_exit_with", helper_exit_with::<Ctx> as _);
|
||||||
|
builder.symbol("helper_dbg", helper_dbg as _);
|
||||||
|
|
||||||
|
let mut module = JITModule::new(builder);
|
||||||
|
let ctx = module.make_context();
|
||||||
|
|
||||||
|
let int_type = types::I64;
|
||||||
|
let float_type = types::F64;
|
||||||
|
let bool_type = types::I8;
|
||||||
|
let ptr_type = module.target_config().pointer_type();
|
||||||
|
let value_type = types::I128;
|
||||||
|
|
||||||
|
let mut func_sig = module.make_signature();
|
||||||
|
func_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 2],
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut call_sig = module.make_signature();
|
||||||
|
call_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 3],
|
||||||
|
);
|
||||||
|
let call = module
|
||||||
|
.declare_function("helper_call", Linkage::Import, &call_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut lookup_arg_sig = module.make_signature();
|
||||||
|
lookup_arg_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 3],
|
||||||
|
);
|
||||||
|
let lookup_arg = module
|
||||||
|
.declare_function("helper_lookup_arg", Linkage::Import, &lookup_arg_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut lookup_sig = module.make_signature();
|
||||||
|
lookup_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 4],
|
||||||
|
);
|
||||||
|
let lookup = module
|
||||||
|
.declare_function("helper_lookup", Linkage::Import, &lookup_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut select_sig = module.make_signature();
|
||||||
|
select_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 4],
|
||||||
|
);
|
||||||
|
let select = module
|
||||||
|
.declare_function("helper_select", Linkage::Import, &select_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut select_with_default_sig = module.make_signature();
|
||||||
|
select_with_default_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 6],
|
||||||
|
);
|
||||||
|
let select_with_default = module
|
||||||
|
.declare_function(
|
||||||
|
"helper_select_with_default",
|
||||||
|
Linkage::Import,
|
||||||
|
&select_with_default_sig,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut eq_sig = module.make_signature();
|
||||||
|
eq_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 2],
|
||||||
|
);
|
||||||
|
let eq = module
|
||||||
|
.declare_function("helper_eq", Linkage::Import, &eq_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut alloc_array_sig = module.make_signature();
|
||||||
|
alloc_array_sig.params.push(AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
});
|
||||||
|
alloc_array_sig.returns.push(AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
});
|
||||||
|
let alloc_array = module
|
||||||
|
.declare_function("helper_alloc_array", Linkage::Import, &alloc_array_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut create_string_sig = module.make_signature();
|
||||||
|
create_string_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 3],
|
||||||
|
);
|
||||||
|
let create_string = module
|
||||||
|
.declare_function("helper_create_string", Linkage::Import, &create_string_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut create_list_sig = module.make_signature();
|
||||||
|
create_list_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 3],
|
||||||
|
);
|
||||||
|
let create_list = module
|
||||||
|
.declare_function("helper_create_list", Linkage::Import, &create_list_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut create_attrs_sig = module.make_signature();
|
||||||
|
create_attrs_sig.params.push(AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
});
|
||||||
|
let create_attrs = module
|
||||||
|
.declare_function("helper_create_attrs", Linkage::Import, &create_attrs_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut push_attr_sig = module.make_signature();
|
||||||
|
push_attr_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 3],
|
||||||
|
);
|
||||||
|
let push_attr = module
|
||||||
|
.declare_function("helper_push_attr", Linkage::Import, &push_attr_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut finalize_attrs_sig = module.make_signature();
|
||||||
|
finalize_attrs_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 2],
|
||||||
|
);
|
||||||
|
let finalize_attrs = module
|
||||||
|
.declare_function(
|
||||||
|
"helper_finalize_attrs",
|
||||||
|
Linkage::Import,
|
||||||
|
&finalize_attrs_sig,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut enter_with_sig = module.make_signature();
|
||||||
|
enter_with_sig.params.extend(
|
||||||
|
[AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
}; 2],
|
||||||
|
);
|
||||||
|
let enter_with = module
|
||||||
|
.declare_function("helper_enter_with", Linkage::Import, &enter_with_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut exit_with_sig = module.make_signature();
|
||||||
|
exit_with_sig.params.push(AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
});
|
||||||
|
let exit_with = module
|
||||||
|
.declare_function("helper_exit_with", Linkage::Import, &exit_with_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut dbg_sig = module.make_signature();
|
||||||
|
dbg_sig.params.push(AbiParam {
|
||||||
|
value_type: ptr_type,
|
||||||
|
purpose: ArgumentPurpose::Normal,
|
||||||
|
extension: ArgumentExtension::None,
|
||||||
|
});
|
||||||
|
let dbg = module
|
||||||
|
.declare_function("helper_dbg", Linkage::Import, &dbg_sig)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builder_ctx: None,
|
||||||
|
_marker: PhantomData,
|
||||||
|
ctx,
|
||||||
|
module,
|
||||||
|
|
||||||
|
int_type,
|
||||||
|
float_type,
|
||||||
|
bool_type,
|
||||||
|
ptr_type,
|
||||||
|
value_type,
|
||||||
|
func_sig,
|
||||||
|
|
||||||
|
call,
|
||||||
|
lookup_arg,
|
||||||
|
lookup,
|
||||||
|
select,
|
||||||
|
select_with_default,
|
||||||
|
|
||||||
|
eq,
|
||||||
|
|
||||||
|
alloc_array,
|
||||||
|
create_string,
|
||||||
|
create_list,
|
||||||
|
create_attrs,
|
||||||
|
push_attr,
|
||||||
|
finalize_attrs,
|
||||||
|
enter_with,
|
||||||
|
exit_with,
|
||||||
|
dbg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile(&mut self, ir: &Lir, id: usize) -> JITFunc<Ctx> {
|
||||||
|
let func_id = self
|
||||||
|
.module
|
||||||
|
.declare_function(
|
||||||
|
format!("nixjit_thunk{id}").as_str(),
|
||||||
|
Linkage::Local,
|
||||||
|
&self.func_sig,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut func = Function::new();
|
||||||
|
func.signature = self.func_sig.clone();
|
||||||
|
let mut builder_ctx = self.builder_ctx.take().unwrap_or_default();
|
||||||
|
let mut ctx = Context::new(self, FunctionBuilder::new(&mut func, &mut builder_ctx));
|
||||||
|
|
||||||
|
let entry = ctx.builder.create_block();
|
||||||
|
ctx.builder.append_block_params_for_function_params(entry);
|
||||||
|
ctx.builder.switch_to_block(entry);
|
||||||
|
|
||||||
|
let params = ctx.builder.block_params(entry);
|
||||||
|
let rt_ctx = params[0];
|
||||||
|
let ret = params[1];
|
||||||
|
let res = ir.compile(&mut ctx, rt_ctx);
|
||||||
|
|
||||||
|
let tag = ctx.builder.ins().stack_load(types::I64, res, 0);
|
||||||
|
let val0 = ctx.builder.ins().stack_load(types::I64, res, 8);
|
||||||
|
let val1 = ctx.builder.ins().stack_load(types::I64, res, 16);
|
||||||
|
let val2 = ctx.builder.ins().stack_load(types::I64, res, 24);
|
||||||
|
ctx.builder.ins().store(MemFlags::new(), tag, ret, 0);
|
||||||
|
ctx.builder.ins().store(MemFlags::new(), val0, ret, 8);
|
||||||
|
ctx.builder.ins().store(MemFlags::new(), val1, ret, 16);
|
||||||
|
ctx.builder.ins().store(MemFlags::new(), val2, ret, 24);
|
||||||
|
ctx.builder.ins().return_(&[]);
|
||||||
|
ctx.builder.seal_all_blocks();
|
||||||
|
ctx.builder.finalize();
|
||||||
|
|
||||||
|
let strings = ctx.strings;
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!("{ir:#?}");
|
||||||
|
println!("{}", func.display());
|
||||||
|
}
|
||||||
|
self.ctx.func = func;
|
||||||
|
self.module.define_function(func_id, &mut self.ctx).unwrap();
|
||||||
|
self.module.finalize_definitions().unwrap();
|
||||||
|
self.ctx.clear();
|
||||||
|
|
||||||
|
let _ = self.builder_ctx.insert(builder_ctx);
|
||||||
|
// SAFETY: The `get_finalized_function` method returns a raw pointer to the
|
||||||
|
// compiled machine code. We transmute it to the correct function pointer type `F<Ctx>`.
|
||||||
|
// This is safe because the function was compiled with the signature defined in `self.func_sig`,
|
||||||
|
// which matches the signature of `F<Ctx>`. The lifetime of the compiled code is managed
|
||||||
|
// by the `JITModule`, ensuring the pointer remains valid.
|
||||||
|
unsafe {
|
||||||
|
JITFunc {
|
||||||
|
func: std::mem::transmute::<*const u8, F<Ctx>>(
|
||||||
|
self.module.get_finalized_function(func_id),
|
||||||
|
),
|
||||||
|
strings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
evaluator/nixjit_lir/Cargo.toml
Normal file
16
evaluator/nixjit_lir/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_lir"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
hashbrown = "0.15"
|
||||||
|
itertools = "0.14"
|
||||||
|
rnix = "0.12"
|
||||||
|
|
||||||
|
nixjit_error = { path = "../nixjit_error" }
|
||||||
|
nixjit_ir = { path = "../nixjit_ir" }
|
||||||
|
nixjit_hir = { path = "../nixjit_hir" }
|
||||||
|
nixjit_macros = { path = "../nixjit_macros" }
|
||||||
|
nixjit_value = { path = "../nixjit_value" }
|
||||||
315
evaluator/nixjit_lir/src/lib.rs
Normal file
315
evaluator/nixjit_lir/src/lib.rs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
//! The Low-level Intermediate Representation (LIR) for nixjit.
|
||||||
|
//!
|
||||||
|
//! This module defines the LIR, which is a more resolved and explicit representation
|
||||||
|
//! than the HIR. The key transformation from HIR to LIR is the resolution of variable
|
||||||
|
//! lookups. In the LIR, variable references are either resolved to a specific expression,
|
||||||
|
//! a function argument, or are left as-is for dynamic lookup in a `with` environment.
|
||||||
|
//!
|
||||||
|
//! Key components:
|
||||||
|
//! - `Lir`: An enum representing all LIR expression types, generated by the `ir!` macro.
|
||||||
|
//! - `Resolve`: A trait for converting HIR nodes into LIR expressions.
|
||||||
|
//! - `ResolveContext`: A trait providing the context for resolution, including scope
|
||||||
|
//! management and dependency tracking.
|
||||||
|
|
||||||
|
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use nixjit_error::{Error, Result};
|
||||||
|
use nixjit_hir as hir;
|
||||||
|
use nixjit_ir::*;
|
||||||
|
use nixjit_macros::ir;
|
||||||
|
use nixjit_value::format_symbol;
|
||||||
|
|
||||||
|
// The `ir!` macro generates the `Lir` enum and related structs and traits.
|
||||||
|
ir! {
|
||||||
|
Lir,
|
||||||
|
|
||||||
|
AttrSet,
|
||||||
|
List,
|
||||||
|
HasAttr,
|
||||||
|
BinOp,
|
||||||
|
UnOp,
|
||||||
|
Select,
|
||||||
|
If,
|
||||||
|
Call,
|
||||||
|
With,
|
||||||
|
Assert,
|
||||||
|
ConcatStrings,
|
||||||
|
Const,
|
||||||
|
Str,
|
||||||
|
Var,
|
||||||
|
Path,
|
||||||
|
Arg(()),
|
||||||
|
PrimOp(PrimOpId),
|
||||||
|
ExprRef(ExprId),
|
||||||
|
StackRef(StackIdx),
|
||||||
|
FuncRef(ExprId),
|
||||||
|
Thunk(ExprId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the result of a variable lookup within the `ResolveContext`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LookupResult {
|
||||||
|
Stack(StackIdx),
|
||||||
|
/// The variable was found and resolved to a specific expression.
|
||||||
|
Expr(ExprId),
|
||||||
|
/// The variable could not be resolved statically, likely due to a `with` expression.
|
||||||
|
/// The lookup must be performed dynamically at evaluation time.
|
||||||
|
Unknown,
|
||||||
|
/// The variable was not found in any scope.
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A context for the HIR-to-LIR resolution process.
|
||||||
|
///
|
||||||
|
/// This trait abstracts the environment in which expressions are resolved, managing
|
||||||
|
/// scopes, dependencies, and the resolution of expressions themselves.
|
||||||
|
pub trait ResolveContext {
|
||||||
|
/// Creates a new function, associating a parameter specification with a body expression.
|
||||||
|
fn new_func(&mut self, body: ExprId, param: Param);
|
||||||
|
|
||||||
|
/// Triggers the resolution of a given expression.
|
||||||
|
fn resolve(&mut self, expr: ExprId) -> Result<ExprId>;
|
||||||
|
|
||||||
|
/// Looks up a variable by name in the current scope.
|
||||||
|
fn lookup(&mut self, name: SymId) -> LookupResult;
|
||||||
|
|
||||||
|
fn get_sym(&self, id: SymId) -> &str;
|
||||||
|
|
||||||
|
fn lookup_arg(&mut self) -> ExprId;
|
||||||
|
|
||||||
|
/// Enters a `with` scope for the duration of a closure.
|
||||||
|
fn with_with_env(&mut self, f: impl FnOnce(&mut Self) -> Result<()>) -> Result<bool>;
|
||||||
|
|
||||||
|
/// Enters a `let` scope with a given set of bindings for the duration of a closure.
|
||||||
|
fn with_let_env<T>(
|
||||||
|
&mut self,
|
||||||
|
bindings: HashMap<SymId, ExprId>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> T;
|
||||||
|
|
||||||
|
/// Enters a function parameter scope for the duration of a closure.
|
||||||
|
fn with_closure_env<T>(
|
||||||
|
&mut self,
|
||||||
|
func: ExprId,
|
||||||
|
arg: ExprId,
|
||||||
|
ident: Option<SymId>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for converting (resolving) an HIR node into an LIR expression.
|
||||||
|
pub trait Resolve<Ctx: ResolveContext> {
|
||||||
|
/// Performs the resolution.
|
||||||
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main entry point for resolving any HIR expression.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
|
||||||
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
use hir::Hir::*;
|
||||||
|
match self {
|
||||||
|
AttrSet(x) => x.resolve(ctx),
|
||||||
|
List(x) => x.resolve(ctx),
|
||||||
|
HasAttr(x) => x.resolve(ctx),
|
||||||
|
BinOp(x) => x.resolve(ctx),
|
||||||
|
UnOp(x) => x.resolve(ctx),
|
||||||
|
Select(x) => x.resolve(ctx),
|
||||||
|
If(x) => x.resolve(ctx),
|
||||||
|
Func(x) => x.resolve(ctx),
|
||||||
|
Call(x) => x.resolve(ctx),
|
||||||
|
With(x) => x.resolve(ctx),
|
||||||
|
Assert(x) => x.resolve(ctx),
|
||||||
|
ConcatStrings(x) => x.resolve(ctx),
|
||||||
|
Const(x) => Ok(Lir::Const(x)),
|
||||||
|
Str(x) => Ok(Lir::Str(x)),
|
||||||
|
Var(x) => x.resolve(ctx),
|
||||||
|
Path(x) => x.resolve(ctx),
|
||||||
|
Let(x) => x.resolve(ctx),
|
||||||
|
Thunk(x) => ctx.resolve(x).map(Lir::Thunk),
|
||||||
|
Arg(_) => Ok(Lir::ExprRef(ctx.lookup_arg())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves an `AttrSet` by resolving all key and value expressions.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
for (_, v) in self.stcs.iter_mut() {
|
||||||
|
*v = ctx.resolve(*v)?;
|
||||||
|
}
|
||||||
|
for (k, _) in self.dyns.iter_mut() {
|
||||||
|
*k = ctx.resolve(*k)?;
|
||||||
|
}
|
||||||
|
for (_, v) in self.dyns.iter_mut() {
|
||||||
|
*v = ctx.resolve(*v)?;
|
||||||
|
}
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `List` by resolving each of its items.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for List {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
for item in self.items.iter_mut() {
|
||||||
|
*item = ctx.resolve(*item)?;
|
||||||
|
}
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `HasAttr` expression by resolving the LHS and any dynamic attributes in the path.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.lhs = ctx.resolve(self.lhs)?;
|
||||||
|
for attr in self.rhs.iter_mut() {
|
||||||
|
if let &mut Attr::Dynamic(expr) = attr {
|
||||||
|
*attr = ctx.resolve(expr).map(Attr::Dynamic)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `BinOp` by resolving its left and right hand sides.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.lhs = ctx.resolve(self.lhs)?;
|
||||||
|
self.rhs = ctx.resolve(self.rhs)?;
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `UnOp` by resolving its right hand side.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.rhs = ctx.resolve(self.rhs)?;
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Select` by resolving the expression being selected from, any dynamic
|
||||||
|
/// attributes in the path, and the default value if it exists.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.expr = ctx.resolve(self.expr)?;
|
||||||
|
for attr in self.attrpath.iter_mut() {
|
||||||
|
if let &mut Attr::Dynamic(expr) = attr {
|
||||||
|
*attr = ctx.resolve(expr).map(Attr::Dynamic)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(expr) = &mut self.default {
|
||||||
|
*expr = ctx.resolve(*expr)?;
|
||||||
|
}
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves an `If` expression by resolving the condition, consequence, and alternative.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for If {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.cond = ctx.resolve(self.cond)?;
|
||||||
|
self.consq = ctx.resolve(self.consq)?;
|
||||||
|
self.alter = ctx.resolve(self.alter)?;
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Func` by resolving its body within a new parameter scope.
|
||||||
|
/// It then registers the function with the context.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
ctx.with_closure_env(self.body, self.arg, self.param.ident, |ctx| {
|
||||||
|
self.body = ctx.resolve(self.body)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
ctx.new_func(self.body, self.param);
|
||||||
|
Ok(Lir::FuncRef(self.body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.func = ctx.resolve(self.func)?;
|
||||||
|
self.func = ctx.resolve(self.arg)?;
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `With` expression by resolving the namespace and the body.
|
||||||
|
/// The body is resolved within a special "with" scope.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for With {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.namespace = ctx.resolve(self.namespace)?;
|
||||||
|
let env_used = ctx.with_with_env(|ctx| {
|
||||||
|
self.expr = ctx.resolve(self.expr)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
// Optimization: if the `with` environment was not actually used by any variable
|
||||||
|
// lookup in the body, we can elide the `With` node entirely.
|
||||||
|
if env_used {
|
||||||
|
Ok(self.to_lir())
|
||||||
|
} else {
|
||||||
|
Ok(Lir::ExprRef(self.expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves an `Assert` by resolving the assertion condition and the body.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.assertion = ctx.resolve(self.assertion)?;
|
||||||
|
self.expr = ctx.resolve(self.expr)?;
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `ConcatStrings` by resolving each part.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
for part in self.parts.iter_mut() {
|
||||||
|
*part = ctx.resolve(*part)?;
|
||||||
|
}
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Var` by looking it up in the current context.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
|
||||||
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
use LookupResult::*;
|
||||||
|
match ctx.lookup(self.sym) {
|
||||||
|
Expr(id) => Ok(Lir::ExprRef(id)),
|
||||||
|
Stack(idx) => Ok(Lir::StackRef(idx)),
|
||||||
|
Unknown => Ok(self.to_lir()),
|
||||||
|
NotFound => Err(Error::resolution_error(format!(
|
||||||
|
"undefined variable '{}'",
|
||||||
|
format_symbol(ctx.get_sym(self.sym))
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Path` by resolving the underlying expression that defines the path's content.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
self.expr = ctx.resolve(self.expr)?;
|
||||||
|
Ok(self.to_lir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Let` expression by creating a new scope for the bindings, resolving
|
||||||
|
/// the bindings and the body, and then returning a reference to the body.
|
||||||
|
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
|
||||||
|
fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
|
ctx.with_let_env(self.bindings.clone(), |ctx| {
|
||||||
|
for id in self.bindings.values_mut() {
|
||||||
|
*id = ctx.resolve(*id)?;
|
||||||
|
}
|
||||||
|
self.body = ctx.resolve(self.body)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
// The `let` expression itself evaluates to its body.
|
||||||
|
Ok(Lir::ExprRef(self.body))
|
||||||
|
}
|
||||||
|
}
|
||||||
13
evaluator/nixjit_macros/Cargo.toml
Normal file
13
evaluator/nixjit_macros/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
convert_case = "0.8"
|
||||||
|
quote = "1.0"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
syn = { version = "2.0", features = ["full"] }
|
||||||
261
evaluator/nixjit_macros/src/builtins.rs
Normal file
261
evaluator/nixjit_macros/src/builtins.rs
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
//! Implements the `#[builtins]` procedural macro attribute.
|
||||||
|
//!
|
||||||
|
//! This macro simplifies the process of defining built-in functions (primops)
|
||||||
|
//! for the Nix interpreter. It inspects the functions inside a `mod` block
|
||||||
|
//! and generates the necessary boilerplate to make them callable from Nix code.
|
||||||
|
//!
|
||||||
|
//! Specifically, it generates:
|
||||||
|
//! 1. A `Builtins` struct containing arrays of constant values and function pointers.
|
||||||
|
//! 2. A wrapper function for each user-defined function. This wrapper handles:
|
||||||
|
//! - Arity (argument count) checking.
|
||||||
|
//! - Type conversion from the generic `nixjit_eval::Value` into the
|
||||||
|
//! specific types expected by the user's function.
|
||||||
|
//! - Calling the user's function with the converted arguments.
|
||||||
|
//! - Wrapping the return value back into a `Result<nixjit_eval::Value>`.
|
||||||
|
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use quote::{ToTokens, format_ident, quote};
|
||||||
|
use syn::{
|
||||||
|
FnArg, Item, ItemConst, ItemFn, ItemMod, Pat, PatIdent, PatType, Type, Visibility,
|
||||||
|
parse_macro_input,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The implementation of the `#[builtins]` macro.
|
||||||
|
pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||||
|
let item_mod = parse_macro_input!(input as ItemMod);
|
||||||
|
let mod_name = &item_mod.ident;
|
||||||
|
let visibility = &item_mod.vis;
|
||||||
|
|
||||||
|
let (_brace, items) = match item_mod.content {
|
||||||
|
Some(content) => content,
|
||||||
|
None => {
|
||||||
|
return syn::Error::new_spanned(
|
||||||
|
item_mod,
|
||||||
|
"`#[builtins]` macro can only be used on an inline module: `mod name { ... }`",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pub_item_mod = Vec::new();
|
||||||
|
let mut global = Vec::new();
|
||||||
|
let mut scoped = Vec::new();
|
||||||
|
let mut wrappers = Vec::new();
|
||||||
|
|
||||||
|
// Iterate over the items (functions, consts) in the user's module.
|
||||||
|
for item in &items {
|
||||||
|
match item {
|
||||||
|
Item::Const(item_const) => {
|
||||||
|
let (primop, wrapper) = match generate_const_wrapper(item_const) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => return e.to_compile_error().into(),
|
||||||
|
};
|
||||||
|
// Public functions are added to the global scope, private ones to a scoped set.
|
||||||
|
if matches!(item_const.vis, Visibility::Public(_)) {
|
||||||
|
global.push(primop);
|
||||||
|
} else {
|
||||||
|
scoped.push(primop);
|
||||||
|
}
|
||||||
|
wrappers.push(wrapper);
|
||||||
|
}
|
||||||
|
Item::Fn(item_fn) => {
|
||||||
|
// Handle function definitions. These become primops.
|
||||||
|
let (primop, wrapper) = match generate_primop_wrapper(item_fn) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => return e.to_compile_error().into(),
|
||||||
|
};
|
||||||
|
// Public functions are added to the global scope, private ones to a scoped set.
|
||||||
|
if matches!(item_fn.vis, Visibility::Public(_)) {
|
||||||
|
global.push(primop);
|
||||||
|
pub_item_mod.push(quote! { #item_fn });
|
||||||
|
} else {
|
||||||
|
scoped.push(primop);
|
||||||
|
pub_item_mod.push(
|
||||||
|
quote! {
|
||||||
|
pub #item_fn
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
wrappers.push(wrapper);
|
||||||
|
}
|
||||||
|
// Other items are passed through unchanged.
|
||||||
|
item => pub_item_mod.push(item.to_token_stream()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let global_len = global.len();
|
||||||
|
let scoped_len = scoped.len();
|
||||||
|
|
||||||
|
// Assemble the final generated code.
|
||||||
|
let output = quote! {
|
||||||
|
// Re-create the user's module, now with generated wrappers.
|
||||||
|
#visibility mod #mod_name {
|
||||||
|
#(#pub_item_mod)*
|
||||||
|
#(#wrappers)*
|
||||||
|
pub const GLOBAL_LEN: usize = #global_len;
|
||||||
|
pub const SCOPED_LEN: usize = #scoped_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct containing all the built-in constants and functions.
|
||||||
|
pub struct Builtins<Ctx: BuiltinsContext> {
|
||||||
|
/// Global functions available in the global scope.
|
||||||
|
pub global: [(&'static str, usize, fn(&mut Ctx, ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::GLOBAL_LEN],
|
||||||
|
/// Scoped functions, typically available under the `builtins` attribute set.
|
||||||
|
pub scoped: [(&'static str, usize, fn(&mut Ctx, ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::SCOPED_LEN],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: BuiltinsContext> Builtins<Ctx> {
|
||||||
|
/// Creates a new instance of the `Builtins` struct.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
global: [#(#global,)*],
|
||||||
|
scoped: [#(#scoped,)*],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
output.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_const_wrapper(
|
||||||
|
item_const: &ItemConst,
|
||||||
|
) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
|
||||||
|
let const_name = &item_const.ident;
|
||||||
|
let const_val = &item_const.expr;
|
||||||
|
let name_str = const_name
|
||||||
|
.to_string()
|
||||||
|
.from_case(Case::UpperSnake)
|
||||||
|
.to_case(Case::Camel);
|
||||||
|
let const_name = format_ident!("{name_str}");
|
||||||
|
let wrapper_name = format_ident!("wrapper_{}", const_name);
|
||||||
|
let mod_name = format_ident!("builtins");
|
||||||
|
|
||||||
|
let fn_type = quote! { fn(&mut Ctx, ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value> };
|
||||||
|
|
||||||
|
// The primop metadata tuple: (name, arity, wrapper_function_pointer)
|
||||||
|
let primop = quote! { (#name_str, 0, #mod_name::#wrapper_name as #fn_type) };
|
||||||
|
|
||||||
|
// The generated wrapper function.
|
||||||
|
let wrapper = quote! {
|
||||||
|
pub fn #wrapper_name<Ctx: BuiltinsContext>(ctx: &mut Ctx, mut args: ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value> {
|
||||||
|
Ok(#const_val.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((primop, wrapper))
|
||||||
|
}
|
||||||
|
/// Generates the primop metadata and the wrapper function for a single user-defined function.
|
||||||
|
fn generate_primop_wrapper(
|
||||||
|
item_fn: &ItemFn,
|
||||||
|
) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
|
||||||
|
let fn_name = &item_fn.sig.ident;
|
||||||
|
let name_str = fn_name
|
||||||
|
.to_string()
|
||||||
|
.from_case(Case::Snake)
|
||||||
|
.to_case(Case::Camel);
|
||||||
|
let wrapper_name = format_ident!("wrapper_{}", fn_name);
|
||||||
|
let mod_name = format_ident!("builtins");
|
||||||
|
|
||||||
|
let mut user_args = item_fn.sig.inputs.iter().peekable();
|
||||||
|
|
||||||
|
// Check if the first argument is a context `&mut Ctx`.
|
||||||
|
let has_ctx = if let Some(FnArg::Typed(first_arg)) = user_args.peek() {
|
||||||
|
if let (Type::Reference(_), Pat::Ident(PatIdent { ident, .. })) =
|
||||||
|
(&*first_arg.ty, &*first_arg.pat)
|
||||||
|
{
|
||||||
|
if ident == "ctx" {
|
||||||
|
user_args.next();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
fn_name,
|
||||||
|
"A builtin function must not have a receiver argument",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect the remaining arguments.
|
||||||
|
let arg_pats: Vec<_> = user_args.collect();
|
||||||
|
let arg_count = arg_pats.len();
|
||||||
|
|
||||||
|
let arg_unpacks = arg_pats.iter().enumerate().map(|(i, arg)| {
|
||||||
|
let arg_name = format_ident!("_arg{}", i, span = Span::call_site());
|
||||||
|
let arg_ty = match &arg {
|
||||||
|
FnArg::Typed(PatType { ty, .. }) => ty,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
let #arg_name: #arg_ty = args.next().ok_or_else(|| ::nixjit_error::Error::eval_error("Not enough arguments provided".to_string()))?
|
||||||
|
.try_into().map_err(|e| ::nixjit_error::Error::eval_error(format!("Argument type conversion failed: {}", e)))?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the names of the arguments to pass to the user's function.
|
||||||
|
let arg_names: Vec<_> = arg_pats
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, arg)| match &arg {
|
||||||
|
FnArg::Typed(PatType { .. }) => {
|
||||||
|
format_ident!("_arg{}", i, span = Span::call_site())
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Construct the argument list for the final call.
|
||||||
|
let mut call_args = quote! { #(#arg_names),* };
|
||||||
|
if has_ctx {
|
||||||
|
call_args = quote! { ctx, #(#arg_names),* };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user's function already returns a `Result`.
|
||||||
|
let returns_result = match &item_fn.sig.output {
|
||||||
|
syn::ReturnType::Type(_, ty) => {
|
||||||
|
if let Type::Path(type_path) = &**ty {
|
||||||
|
type_path.path.segments.iter().any(|s| s.ident == "Result")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap the call expression in `Ok(...)` if it doesn't return a `Result`.
|
||||||
|
let call_expr = if returns_result {
|
||||||
|
quote! { #fn_name(#call_args) }
|
||||||
|
} else {
|
||||||
|
quote! { Ok(#fn_name(#call_args).into()) }
|
||||||
|
};
|
||||||
|
|
||||||
|
let arity = arg_names.len();
|
||||||
|
let fn_type = quote! { fn(&mut Ctx, ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value> };
|
||||||
|
|
||||||
|
// The primop metadata tuple: (name, arity, wrapper_function_pointer)
|
||||||
|
let primop = quote! { (#name_str, #arity, #mod_name::#wrapper_name as #fn_type) };
|
||||||
|
|
||||||
|
// The generated wrapper function.
|
||||||
|
let wrapper = quote! {
|
||||||
|
pub fn #wrapper_name<Ctx: BuiltinsContext>(ctx: &mut Ctx, mut args: ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value> {
|
||||||
|
if args.len() != #arg_count {
|
||||||
|
return Err(::nixjit_error::Error::eval_error(format!("Function '{}' expects {} arguments, but received {}", #name_str, #arg_count, args.len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
#(#arg_unpacks)*
|
||||||
|
|
||||||
|
#call_expr
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((primop, wrapper))
|
||||||
|
}
|
||||||
203
evaluator/nixjit_macros/src/ir.rs
Normal file
203
evaluator/nixjit_macros/src/ir.rs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
//! Implements the `ir!` procedural macro.
|
||||||
|
//!
|
||||||
|
//! This macro is designed to reduce the boilerplate associated with defining
|
||||||
|
//! an Intermediate Representation (IR) that follows a specific pattern. It generates:
|
||||||
|
//! 1. An enum representing the different kinds of IR nodes (e.g., `Hir`, `Lir`).
|
||||||
|
//! 2. Structs for each of the variants that have fields.
|
||||||
|
//! 3. `Ref` and `Mut` versions of the main enum for ergonomic pattern matching on references.
|
||||||
|
//! 4. `From` implementations to easily convert from a struct variant (e.g., `BinOp`) to the main enum (`Hir::BinOp`).
|
||||||
|
//! 5. A `To[IrName]` trait to provide a convenient `.to_hir()` or `.to_lir()` method on the variant structs.
|
||||||
|
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::{
|
||||||
|
FieldsNamed, Ident, Token, Type, parenthesized,
|
||||||
|
parse::{Parse, ParseStream, Result},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents one of the variants passed to the `ir!` macro.
|
||||||
|
pub enum VariantInput {
|
||||||
|
/// A unit-like variant, e.g., `Arg`.
|
||||||
|
Unit(Ident),
|
||||||
|
/// A tuple-like variant with one unnamed field, e.g., `ExprRef(ExprId)`.
|
||||||
|
Tuple(Ident, Type),
|
||||||
|
/// A struct-like variant with named fields, e.g., `BinOp { lhs: ExprId, rhs: ExprId, kind: BinOpKind }`.
|
||||||
|
Struct(Ident, FieldsNamed),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The top-level input for the `ir!` macro.
|
||||||
|
pub struct MacroInput {
|
||||||
|
/// The name of the main IR enum to be generated (e.g., `Hir`).
|
||||||
|
pub base_name: Ident,
|
||||||
|
/// The list of variants for the enum.
|
||||||
|
pub variants: Punctuated<VariantInput, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for VariantInput {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let name: Ident = input.parse()?;
|
||||||
|
|
||||||
|
if input.peek(token::Paren) {
|
||||||
|
// Parse a tuple-like variant: `Variant(Type)`
|
||||||
|
let content;
|
||||||
|
parenthesized!(content in input);
|
||||||
|
let ty: Type = content.parse()?;
|
||||||
|
|
||||||
|
if !content.is_empty() {
|
||||||
|
return Err(content.error("Expected a single type inside parentheses"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(VariantInput::Tuple(name, ty))
|
||||||
|
} else if input.peek(token::Brace) {
|
||||||
|
// Parse a struct-like variant: `Variant { field: Type, ... }`
|
||||||
|
let fields: FieldsNamed = input.parse()?;
|
||||||
|
Ok(VariantInput::Struct(name, fields))
|
||||||
|
} else {
|
||||||
|
// Parse a unit-like variant: `Variant`
|
||||||
|
Ok(VariantInput::Unit(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for MacroInput {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
// The macro input is expected to be: `IrName, Variant1, Variant2, ...`
|
||||||
|
let base_name = input.parse()?;
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
let variants = Punctuated::parse_terminated(input)?;
|
||||||
|
|
||||||
|
Ok(MacroInput {
|
||||||
|
base_name,
|
||||||
|
variants,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The implementation of the `ir!` macro.
|
||||||
|
pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||||
|
let parsed_input = syn::parse_macro_input!(input as MacroInput);
|
||||||
|
|
||||||
|
let base_name = &parsed_input.base_name;
|
||||||
|
let ref_name = format_ident!("{}Ref", base_name);
|
||||||
|
let mut_name = format_ident!("{}Mut", base_name);
|
||||||
|
let to_trait_name = format_ident!("To{}", base_name);
|
||||||
|
let to_trait_fn_name = format_ident!("to_{}", base_name.to_string().to_case(Case::Snake));
|
||||||
|
|
||||||
|
let mut enum_variants = Vec::new();
|
||||||
|
let mut struct_defs = Vec::new();
|
||||||
|
let mut ref_variants = Vec::new();
|
||||||
|
let mut mut_variants = Vec::new();
|
||||||
|
let mut as_ref_arms = Vec::new();
|
||||||
|
let mut as_mut_arms = Vec::new();
|
||||||
|
let mut from_impls = Vec::new();
|
||||||
|
let mut to_trait_impls = Vec::new();
|
||||||
|
|
||||||
|
for variant in parsed_input.variants {
|
||||||
|
match variant {
|
||||||
|
VariantInput::Unit(name) => {
|
||||||
|
let inner_type = name.clone();
|
||||||
|
enum_variants.push(quote! { #name(#inner_type) });
|
||||||
|
ref_variants.push(quote! { #name(&'a #inner_type) });
|
||||||
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
from_impls.push(quote! {
|
||||||
|
impl From<#inner_type> for #base_name {
|
||||||
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
to_trait_impls.push(quote! {
|
||||||
|
impl #to_trait_name for #name {
|
||||||
|
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
VariantInput::Tuple(name, ty) => {
|
||||||
|
enum_variants.push(quote! { #name(#ty) });
|
||||||
|
ref_variants.push(quote! { #name(&'a #ty) });
|
||||||
|
mut_variants.push(quote! { #name(&'a mut #ty) });
|
||||||
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
}
|
||||||
|
VariantInput::Struct(name, fields) => {
|
||||||
|
let inner_type = name.clone();
|
||||||
|
struct_defs.push(quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct #name #fields
|
||||||
|
});
|
||||||
|
enum_variants.push(quote! { #name(#inner_type) });
|
||||||
|
ref_variants.push(quote! { #name(&'a #inner_type) });
|
||||||
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
from_impls.push(quote! {
|
||||||
|
impl From<#inner_type> for #base_name {
|
||||||
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
to_trait_impls.push(quote! {
|
||||||
|
impl #to_trait_name for #name {
|
||||||
|
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the final generated code.
|
||||||
|
let expanded = quote! {
|
||||||
|
/// The main IR enum, generated by the `ir!` macro.
|
||||||
|
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||||
|
pub enum #base_name {
|
||||||
|
#( #enum_variants ),*
|
||||||
|
}
|
||||||
|
|
||||||
|
// The struct definitions for the enum variants.
|
||||||
|
#( #struct_defs )*
|
||||||
|
|
||||||
|
/// An immutable reference version of the IR enum.
|
||||||
|
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||||
|
pub enum #ref_name<'a> {
|
||||||
|
#( #ref_variants ),*
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mutable reference version of the IR enum.
|
||||||
|
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||||
|
pub enum #mut_name<'a> {
|
||||||
|
#( #mut_variants ),*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #base_name {
|
||||||
|
/// Converts a `&Ir` into a `IrRef`.
|
||||||
|
pub fn as_ref(&self) -> #ref_name<'_> {
|
||||||
|
match self {
|
||||||
|
#( #as_ref_arms ),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a `&mut Ir` into a `IrMut`.
|
||||||
|
pub fn as_mut(&mut self) -> #mut_name<'_> {
|
||||||
|
match self {
|
||||||
|
#( #as_mut_arms ),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `From` implementations for converting variant structs into the main enum.
|
||||||
|
#( #from_impls )*
|
||||||
|
|
||||||
|
/// A trait for converting a variant struct into the main IR enum.
|
||||||
|
pub trait #to_trait_name {
|
||||||
|
/// Performs the conversion.
|
||||||
|
fn #to_trait_fn_name(self) -> #base_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the `ToIr` trait for each variant struct.
|
||||||
|
#( #to_trait_impls )*
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
||||||
23
evaluator/nixjit_macros/src/lib.rs
Normal file
23
evaluator/nixjit_macros/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//! This crate provides procedural macros for the nixjit project.
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
|
mod builtins;
|
||||||
|
mod ir;
|
||||||
|
|
||||||
|
/// A procedural macro to reduce boilerplate when defining an Intermediate Representation (IR).
|
||||||
|
///
|
||||||
|
/// It generates an enum for the IR, along with `Ref` and `Mut` variants,
|
||||||
|
/// `From` implementations, and a `ToHir` or `ToLir` trait.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn ir(input: TokenStream) -> TokenStream {
|
||||||
|
ir::ir_impl(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A procedural macro attribute to simplify the definition of built-in functions.
|
||||||
|
///
|
||||||
|
/// It generates the necessary boilerplate to wrap functions and expose them
|
||||||
|
/// to the evaluation engine, handling argument type conversions and arity checking.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn builtins(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
builtins::builtins_impl(input)
|
||||||
|
}
|
||||||
8
evaluator/nixjit_value/Cargo.toml
Normal file
8
evaluator/nixjit_value/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "nixjit_value"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_more = { version = "2.0", features = ["full"] }
|
||||||
|
regex = "1.11"
|
||||||
217
evaluator/nixjit_value/src/lib.rs
Normal file
217
evaluator/nixjit_value/src/lib.rs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
//! Defines the public-facing data structures for Nix values.
|
||||||
|
//!
|
||||||
|
//! These types are used to represent the final result of an evaluation and are
|
||||||
|
//! designed to be user-friendly and serializable. They are distinct from the
|
||||||
|
//! internal `Value` types used during evaluation in `nixjit_eval`.
|
||||||
|
|
||||||
|
use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
||||||
|
use core::hash::Hash;
|
||||||
|
use core::ops::Deref;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use derive_more::{Constructor, IsVariant, Unwrap};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
/// Represents a constant, primitive value in Nix.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap)]
|
||||||
|
pub enum Const {
|
||||||
|
/// A boolean value (`true` or `false`).
|
||||||
|
Bool(bool),
|
||||||
|
/// A 64-bit signed integer.
|
||||||
|
Int(i64),
|
||||||
|
/// A 64-bit floating-point number.
|
||||||
|
Float(f64),
|
||||||
|
/// The `null` value.
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Const {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
use Const::*;
|
||||||
|
match self {
|
||||||
|
Int(x) => write!(f, "{x}"),
|
||||||
|
Float(x) => write!(f, "{x}"),
|
||||||
|
Bool(x) => write!(f, "{x}"),
|
||||||
|
Null => write!(f, "null"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Const {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
Const::Bool(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for Const {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Const::Int(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for Const {
|
||||||
|
fn from(value: f64) -> Self {
|
||||||
|
Const::Float(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix symbol, which is used as a key in attribute sets.
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
||||||
|
pub struct Symbol(String);
|
||||||
|
|
||||||
|
impl<T: Into<String>> From<T> for Symbol {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Symbol(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a string slice as a Nix symbol, quoting it if necessary.
|
||||||
|
pub fn format_symbol<'a>(sym: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
|
||||||
|
let sym = sym.into();
|
||||||
|
if REGEX.is_match(&sym) {
|
||||||
|
sym
|
||||||
|
} else {
|
||||||
|
Cow::Owned(format!(r#""{sym}""#))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Symbol {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
if self.normal() {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
} else {
|
||||||
|
write!(f, r#""{}""#, self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static REGEX: LazyLock<Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_'-]*$").unwrap());
|
||||||
|
impl Symbol {
|
||||||
|
/// Checks if the symbol is a "normal" identifier that doesn't require quotes.
|
||||||
|
fn normal(&self) -> bool {
|
||||||
|
REGEX.is_match(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Symbol {
|
||||||
|
type Target = str;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Symbol {
|
||||||
|
/// Consumes the `Symbol`, returning its inner `String`.
|
||||||
|
pub fn into_inner(self) -> String {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner `String`.
|
||||||
|
pub fn as_inner(&self) -> &String {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix attribute set, which is a map from symbols to values.
|
||||||
|
#[derive(Constructor, Clone, PartialEq)]
|
||||||
|
pub struct AttrSet {
|
||||||
|
data: BTreeMap<Symbol, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for AttrSet {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
use Value::*;
|
||||||
|
write!(f, "{{")?;
|
||||||
|
for (k, v) in self.data.iter() {
|
||||||
|
write!(f, " {k:?} = ")?;
|
||||||
|
match v {
|
||||||
|
List(_) => write!(f, "[ ... ];")?,
|
||||||
|
AttrSet(_) => write!(f, "{{ ... }};")?,
|
||||||
|
v => write!(f, "{v:?};")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, " }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AttrSet {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
use Value::*;
|
||||||
|
write!(f, "{{ ")?;
|
||||||
|
let mut first = true;
|
||||||
|
for (k, v) in self.data.iter() {
|
||||||
|
if !first {
|
||||||
|
write!(f, "; ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{k} = ")?;
|
||||||
|
match v {
|
||||||
|
AttrSet(_) => write!(f, "{{ ... }}"),
|
||||||
|
List(_) => write!(f, "[ ... ]"),
|
||||||
|
v => write!(f, "{v}"),
|
||||||
|
}?;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
write!(f, " }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix list, which is a vector of values.
|
||||||
|
#[derive(Constructor, Clone, Debug, PartialEq)]
|
||||||
|
pub struct List {
|
||||||
|
data: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for List {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
write!(f, "[ ")?;
|
||||||
|
for v in self.data.iter() {
|
||||||
|
write!(f, "{v} ")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents any possible Nix value that can be returned from an evaluation.
|
||||||
|
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
||||||
|
pub enum Value {
|
||||||
|
/// A constant value (int, float, bool, null).
|
||||||
|
Const(Const),
|
||||||
|
/// A string value.
|
||||||
|
String(String),
|
||||||
|
/// An attribute set.
|
||||||
|
AttrSet(AttrSet),
|
||||||
|
/// A list.
|
||||||
|
List(List),
|
||||||
|
/// A thunk, representing a delayed computation.
|
||||||
|
Thunk,
|
||||||
|
/// A function (lambda).
|
||||||
|
Func,
|
||||||
|
/// A primitive (built-in) operation.
|
||||||
|
PrimOp,
|
||||||
|
/// A partially applied primitive operation.
|
||||||
|
PrimOpApp,
|
||||||
|
/// A marker for a value that has been seen before during serialization, to break cycles.
|
||||||
|
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
||||||
|
Repeated,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
use Value::*;
|
||||||
|
match self {
|
||||||
|
Const(x) => write!(f, "{x}"),
|
||||||
|
String(x) => write!(f, r#""{x}""#),
|
||||||
|
AttrSet(x) => write!(f, "{x}"),
|
||||||
|
List(x) => write!(f, "{x}"),
|
||||||
|
Thunk => write!(f, "<CODE>"),
|
||||||
|
Func => write!(f, "<LAMBDA>"),
|
||||||
|
PrimOp => write!(f, "<PRIMOP>"),
|
||||||
|
PrimOpApp => write!(f, "<PRIMOP-APP>"),
|
||||||
|
Repeated => write!(f, "<REPEATED>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
flake.lock
generated
18
flake.lock
generated
@@ -8,11 +8,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1746167999,
|
"lastModified": 1754894611,
|
||||||
"narHash": "sha256-18XGHsjk/5H8F0OGUCG56CeeW1u6qQ7tAfQK3azlwWg=",
|
"narHash": "sha256-TEyTVDhzFyfvPahhi1iAmkopt6fMiTlmn6f278lTdDs=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "bcbc23a4f3391c1c3657f1847cb693aaea3aed76",
|
"rev": "a01861ebeb4d9c504845e7fb81509b82333ca0aa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1746141548,
|
"lastModified": 1754725699,
|
||||||
"narHash": "sha256-IgBWhX7A2oJmZFIrpRuMnw5RAufVnfvOgHWgIdds+hc=",
|
"narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "f02fddb8acef29a8b32f10a335d44828d7825b78",
|
"rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -46,11 +46,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1746093169,
|
"lastModified": 1754834452,
|
||||||
"narHash": "sha256-3gmUmzIzfzlgF/b4HXvtoBIP4bKofVeEubX7LcPBYLo=",
|
"narHash": "sha256-otzv/l7c1rL+eH1cuJnUZVp4DR2dMdEIfhtLxTelIBY=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "298fa81aacda7b06de4db55c377b1aa081906bc9",
|
"rev": "4e147e787987fdb1baf081bd5c60bedfb0aabe16",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
24
flake.nix
24
flake.nix
@@ -10,11 +10,10 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells = forAllSystems (system:
|
devShells = forAllSystems (system:
|
||||||
let pkgs = import nixpkgs { inherit system; }; in
|
let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; in
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
zsh
|
|
||||||
(fenix.packages.${system}.complete.withComponents [
|
(fenix.packages.${system}.complete.withComponents [
|
||||||
"cargo"
|
"cargo"
|
||||||
"clippy"
|
"clippy"
|
||||||
@@ -24,24 +23,11 @@
|
|||||||
"rust-analyzer"
|
"rust-analyzer"
|
||||||
"miri"
|
"miri"
|
||||||
])
|
])
|
||||||
llvm_18
|
lldb
|
||||||
libffi
|
valgrind
|
||||||
libxml2
|
gemini-cli
|
||||||
ncurses
|
claude-code
|
||||||
];
|
];
|
||||||
LLVM_SYS_181_PREFIX = toString pkgs.llvm_18.dev;
|
|
||||||
LD_LIBRARY_PATH = let
|
|
||||||
libs = with pkgs; [
|
|
||||||
llvm_18.lib
|
|
||||||
stdenv.cc.cc.lib
|
|
||||||
libffi
|
|
||||||
libxml2
|
|
||||||
ncurses
|
|
||||||
libz
|
|
||||||
];
|
|
||||||
in
|
|
||||||
builtins.concatStringsSep ":" (map (lib: "${lib}/lib") libs)
|
|
||||||
;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
use inkwell::context::Context;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use rustyline::error::ReadlineError;
|
|
||||||
use rustyline::{DefaultEditor, Result};
|
|
||||||
|
|
||||||
use nixjit::compile::compile;
|
|
||||||
use nixjit::error::Error;
|
|
||||||
use nixjit::ir::downgrade;
|
|
||||||
use nixjit::vm::{JITContext, run};
|
|
||||||
|
|
||||||
macro_rules! unwrap {
|
|
||||||
($e:expr) => {
|
|
||||||
match $e {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(err) => {
|
|
||||||
println!("{err}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let mut rl = DefaultEditor::new()?;
|
|
||||||
loop {
|
|
||||||
let readline = rl.readline("nixjit-repl> ");
|
|
||||||
match readline {
|
|
||||||
Ok(expr) => {
|
|
||||||
if expr.trim().is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
rl.add_history_entry(expr.as_str())?;
|
|
||||||
let root = rnix::Root::parse(&expr);
|
|
||||||
if !root.errors().is_empty() {
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
Error::ParseError(
|
|
||||||
root.errors().iter().map(|err| err.to_string()).join(";")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let expr = root.tree().expr().unwrap();
|
|
||||||
let downgraded = unwrap!(downgrade(expr));
|
|
||||||
let prog = compile(downgraded);
|
|
||||||
let ctx = Context::create();
|
|
||||||
let jit = JITContext::new(&ctx);
|
|
||||||
println!("{}", unwrap!(run(prog, jit)));
|
|
||||||
}
|
|
||||||
Err(ReadlineError::Interrupted) => {
|
|
||||||
println!("CTRL-C");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(ReadlineError::Eof) => {
|
|
||||||
println!("CTRL-D");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
println!("Error: {err:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::ty::internal::{AttrSet, Const, PrimOp, Value};
|
|
||||||
use crate::vm::{Env, VM};
|
|
||||||
|
|
||||||
pub fn env<'vm>(vm: &'vm VM) -> Env<'vm> {
|
|
||||||
let mut env = Env::empty();
|
|
||||||
env.insert(vm.new_sym("true"), Value::Const(Const::Bool(true)));
|
|
||||||
env.insert(vm.new_sym("false"), Value::Const(Const::Bool(false)));
|
|
||||||
|
|
||||||
let primops = [
|
|
||||||
PrimOp::new("add", 2, |_, args| {
|
|
||||||
let [first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.add(second).ok()
|
|
||||||
}),
|
|
||||||
PrimOp::new("sub", 2, |_, args| {
|
|
||||||
let [first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.add(second.neg()).ok()
|
|
||||||
}),
|
|
||||||
PrimOp::new("mul", 2, |_, args| {
|
|
||||||
let [first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.mul(second).ok()
|
|
||||||
}),
|
|
||||||
PrimOp::new("div", 2, |_, args| {
|
|
||||||
let [first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.div(second)
|
|
||||||
}),
|
|
||||||
PrimOp::new("lessThan", 2, |_, args| {
|
|
||||||
let [first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.lt(second).ok()
|
|
||||||
}),
|
|
||||||
PrimOp::new("seq", 2, |vm, args| {
|
|
||||||
let [mut first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.force(vm).unwrap();
|
|
||||||
second.ok()
|
|
||||||
}),
|
|
||||||
PrimOp::new("deepSeq", 2, |vm, args| {
|
|
||||||
let [mut first, second]: [Value; 2] = args.try_into().unwrap();
|
|
||||||
first.force_deep(vm).unwrap();
|
|
||||||
second.ok()
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
for primop in primops {
|
|
||||||
let primop = Rc::new(primop);
|
|
||||||
env.insert(
|
|
||||||
vm.new_sym(format!("__{}", primop.name)),
|
|
||||||
Value::PrimOp(primop.clone()),
|
|
||||||
);
|
|
||||||
map.insert(vm.new_sym(primop.name), Value::PrimOp(primop));
|
|
||||||
}
|
|
||||||
let attrs: Rc<_> = AttrSet::from_inner(map).into();
|
|
||||||
let mut builtins = Value::AttrSet(attrs);
|
|
||||||
builtins.push_attr(vm.new_sym("builtins"), Value::Builtins);
|
|
||||||
|
|
||||||
env.insert(vm.new_sym("builtins"), builtins);
|
|
||||||
env
|
|
||||||
}
|
|
||||||
118
src/bytecode.rs
118
src/bytecode.rs
@@ -1,118 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
|
|
||||||
use ecow::EcoString;
|
|
||||||
|
|
||||||
use crate::ty::internal::{Const, Param};
|
|
||||||
|
|
||||||
type Slice<T> = Box<[T]>;
|
|
||||||
|
|
||||||
pub type OpCodes = Slice<OpCode>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum OpCode {
|
|
||||||
/// load a constant onto stack
|
|
||||||
Const { idx: usize },
|
|
||||||
/// load a dynamic var onto stack
|
|
||||||
LookUp { sym: usize },
|
|
||||||
/// load a thunk lazily onto stack
|
|
||||||
LoadThunk { idx: usize },
|
|
||||||
/// let TOS capture current environment
|
|
||||||
CaptureEnv,
|
|
||||||
/// force TOS to value
|
|
||||||
ForceValue,
|
|
||||||
|
|
||||||
/// [ .. func args @ .. ] consume (`arity` + 1) elements, call `func` with args` of length `arity`
|
|
||||||
/// Example: __add 1 2 => [ LookUp("__add") Const(1) Const(2) Call(2) ]
|
|
||||||
Call { arity: usize },
|
|
||||||
/// make a function
|
|
||||||
Func { idx: usize },
|
|
||||||
|
|
||||||
/// consume 1 element, assert TOS is true
|
|
||||||
Assert,
|
|
||||||
|
|
||||||
/// jump forward
|
|
||||||
Jmp { step: usize },
|
|
||||||
/// [ .. cond ] consume 1 element, if `cond`` is true, then jump forward
|
|
||||||
JmpIfTrue { step: usize },
|
|
||||||
/// [ .. cond ] consume 1 element, if `cond` is false, then jump forward
|
|
||||||
JmpIfFalse { step: usize },
|
|
||||||
|
|
||||||
/// push an empty attribute set onto stack
|
|
||||||
AttrSet { cap: usize },
|
|
||||||
/// finalize the recursive attribute set at TOS
|
|
||||||
FinalizeRec,
|
|
||||||
/// [ .. set value ] consume 1 element, push a static kv pair (`name`, `value`) into `set`
|
|
||||||
PushStaticAttr { name: usize },
|
|
||||||
/// [ .. set name value ] consume 2 elements, push a dynamic kv pair (`name`, `value`) in to `set`
|
|
||||||
PushDynamicAttr,
|
|
||||||
|
|
||||||
/// push an empty list onto stack
|
|
||||||
List,
|
|
||||||
/// [ .. list elem ] consume 1 element, push `elem` into `list`
|
|
||||||
PushElem,
|
|
||||||
|
|
||||||
/// convert the string as TOS to a path
|
|
||||||
Path,
|
|
||||||
|
|
||||||
/// [ .. a b ] consume 2 elements, perform a string concatenation `a` + `b`
|
|
||||||
ConcatString,
|
|
||||||
/// [ .. a b ] consume 2 elements, perform a binary operation `a` `op` `b`
|
|
||||||
BinOp { op: BinOp },
|
|
||||||
/// [ .. a ] consume 1 element, perform a unary operation `op` `a`
|
|
||||||
UnOp { op: UnOp },
|
|
||||||
/// set TOS to the bool value of whether TOS contains `sym`
|
|
||||||
HasAttr { sym: usize },
|
|
||||||
/// [ .. set sym ] consume 2 elements, set TOS to the bool value of whether `set` contains `sym`
|
|
||||||
HasDynamicAttr,
|
|
||||||
/// [ .. set ] select `sym` from `set`
|
|
||||||
Select { sym: usize },
|
|
||||||
/// [ .. set default ] select `sym` from `set` or `default`
|
|
||||||
SelectOrDefault { sym: usize },
|
|
||||||
/// [ .. set sym ] select `sym` from `set`
|
|
||||||
SelectDynamic,
|
|
||||||
/// [ .. set sym default ] select `sym` from `set` or `default`
|
|
||||||
SelectDynamicOrDefault,
|
|
||||||
/// enter the environment of the attribute set at TOS
|
|
||||||
EnterEnv,
|
|
||||||
/// exit current envrironment
|
|
||||||
LeaveEnv,
|
|
||||||
|
|
||||||
/// illegal operation, used as termporary placeholder
|
|
||||||
Illegal,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum BinOp {
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
Eq,
|
|
||||||
Lt,
|
|
||||||
Con,
|
|
||||||
Upd,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum UnOp {
|
|
||||||
Neg,
|
|
||||||
Not,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Func {
|
|
||||||
pub param: Param,
|
|
||||||
pub opcodes: OpCodes,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Program {
|
|
||||||
pub top_level: OpCodes,
|
|
||||||
pub thunks: Slice<OpCodes>,
|
|
||||||
pub funcs: Slice<Func>,
|
|
||||||
pub symbols: Vec<EcoString>,
|
|
||||||
pub symmap: HashMap<EcoString, usize>,
|
|
||||||
pub consts: Box<[Const]>,
|
|
||||||
}
|
|
||||||
410
src/compile.rs
410
src/compile.rs
@@ -1,410 +0,0 @@
|
|||||||
use crate::bytecode::*;
|
|
||||||
use crate::ir;
|
|
||||||
|
|
||||||
pub struct Compiler {
|
|
||||||
opcodes: Vec<OpCode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile(downgraded: ir::Downgraded) -> Program {
|
|
||||||
Program {
|
|
||||||
top_level: Compiler::new().compile(downgraded.top_level),
|
|
||||||
thunks: downgraded
|
|
||||||
.thunks
|
|
||||||
.into_iter()
|
|
||||||
.map(|thunk| Compiler::new().compile(thunk))
|
|
||||||
.collect(),
|
|
||||||
funcs: downgraded
|
|
||||||
.funcs
|
|
||||||
.into_iter()
|
|
||||||
.map(|func| Func {
|
|
||||||
param: func.param.into(),
|
|
||||||
opcodes: Compiler::new().compile(*func.body),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
symbols: downgraded.symbols,
|
|
||||||
symmap: downgraded.symmap,
|
|
||||||
consts: downgraded.consts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compiler {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
opcodes: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile(mut self, ir: ir::Ir) -> OpCodes {
|
|
||||||
ir.compile(&mut self);
|
|
||||||
self.opcodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, code: OpCode) {
|
|
||||||
self.opcodes.push(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn idx(&self) -> usize {
|
|
||||||
self.opcodes.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modify(&mut self, idx: usize, code: OpCode) {
|
|
||||||
self.opcodes[idx] = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(&mut self) -> Option<OpCode> {
|
|
||||||
self.opcodes.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn opcodes(self) -> OpCodes {
|
|
||||||
self.opcodes.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Compile {
|
|
||||||
fn compile(self, comp: &mut Compiler);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CompileWithLength {
|
|
||||||
fn compile_with_length(self, comp: &mut Compiler) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Compile> CompileWithLength for T {
|
|
||||||
fn compile_with_length(self, comp: &mut Compiler) -> usize {
|
|
||||||
let start = comp.idx();
|
|
||||||
self.compile(comp);
|
|
||||||
let end = comp.idx();
|
|
||||||
end - start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Const {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
comp.push(OpCode::Const { idx: self.idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Var {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
comp.push(OpCode::LookUp { sym: self.sym });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Thunk {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
comp.push(OpCode::LoadThunk { idx: self.idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Attrs {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
comp.push(OpCode::AttrSet {
|
|
||||||
cap: self.stcs.len() + self.dyns.len(),
|
|
||||||
});
|
|
||||||
for stc in self.stcs {
|
|
||||||
stc.1.compile(comp);
|
|
||||||
if !self.rec {
|
|
||||||
comp.push(OpCode::CaptureEnv);
|
|
||||||
}
|
|
||||||
comp.push(OpCode::PushStaticAttr { name: stc.0 });
|
|
||||||
}
|
|
||||||
for dynamic in self.dyns {
|
|
||||||
dynamic.0.compile(comp);
|
|
||||||
dynamic.1.compile(comp);
|
|
||||||
if !self.rec {
|
|
||||||
comp.push(OpCode::CaptureEnv);
|
|
||||||
}
|
|
||||||
comp.push(OpCode::PushDynamicAttr)
|
|
||||||
}
|
|
||||||
if self.rec {
|
|
||||||
comp.push(OpCode::FinalizeRec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::List {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
comp.push(OpCode::List);
|
|
||||||
for item in self.items {
|
|
||||||
item.compile(comp);
|
|
||||||
comp.push(OpCode::PushElem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::UnOp {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
use ir::UnOpKind::*;
|
|
||||||
match self.kind {
|
|
||||||
Neg => {
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::UnOp { op: UnOp::Neg });
|
|
||||||
}
|
|
||||||
Not => {
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::UnOp { op: UnOp::Not });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::BinOp {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
use ir::BinOpKind::*;
|
|
||||||
match self.kind {
|
|
||||||
Add => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Add });
|
|
||||||
}
|
|
||||||
Mul => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Mul });
|
|
||||||
}
|
|
||||||
Div => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Div });
|
|
||||||
}
|
|
||||||
And => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::And });
|
|
||||||
}
|
|
||||||
Or => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Or });
|
|
||||||
}
|
|
||||||
Eq => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Eq });
|
|
||||||
}
|
|
||||||
Lt => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Lt });
|
|
||||||
}
|
|
||||||
Con => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Con });
|
|
||||||
}
|
|
||||||
Upd => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Upd });
|
|
||||||
}
|
|
||||||
|
|
||||||
Sub => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Sub });
|
|
||||||
}
|
|
||||||
Impl => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
comp.push(OpCode::UnOp { op: UnOp::Not });
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Or });
|
|
||||||
}
|
|
||||||
Neq => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Eq });
|
|
||||||
comp.push(OpCode::UnOp { op: UnOp::Not });
|
|
||||||
}
|
|
||||||
Gt => {
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Lt });
|
|
||||||
}
|
|
||||||
Leq => {
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Lt });
|
|
||||||
comp.push(OpCode::UnOp { op: UnOp::Not });
|
|
||||||
}
|
|
||||||
Geq => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::BinOp { op: BinOp::Lt });
|
|
||||||
comp.push(OpCode::UnOp { op: UnOp::Not });
|
|
||||||
}
|
|
||||||
|
|
||||||
PipeL => {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
comp.push(OpCode::Call { arity: 1 });
|
|
||||||
}
|
|
||||||
PipeR => {
|
|
||||||
self.rhs.compile(comp);
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
comp.push(OpCode::Call { arity: 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::HasAttr {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.lhs.compile(comp);
|
|
||||||
for attr in self.rhs {
|
|
||||||
match attr {
|
|
||||||
ir::Attr::Str(sym) => {
|
|
||||||
comp.push(OpCode::AttrSet { cap: 0 });
|
|
||||||
comp.push(OpCode::SelectOrDefault { sym })
|
|
||||||
}
|
|
||||||
ir::Attr::Dynamic(dynamic) => {
|
|
||||||
dynamic.compile(comp);
|
|
||||||
comp.push(OpCode::AttrSet { cap: 0 });
|
|
||||||
comp.push(OpCode::SelectDynamicOrDefault);
|
|
||||||
}
|
|
||||||
ir::Attr::Strs(string) => {
|
|
||||||
string.compile(comp);
|
|
||||||
comp.push(OpCode::AttrSet { cap: 0 });
|
|
||||||
comp.push(OpCode::SelectDynamicOrDefault);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let last = comp.pop().unwrap();
|
|
||||||
let _ = comp.pop();
|
|
||||||
match last {
|
|
||||||
OpCode::SelectOrDefault { sym } => comp.push(OpCode::HasAttr { sym }),
|
|
||||||
OpCode::SelectDynamicOrDefault => comp.push(OpCode::HasDynamicAttr),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Select {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.expr.compile(comp);
|
|
||||||
for attr in self.attrpath {
|
|
||||||
match attr {
|
|
||||||
ir::Attr::Str(sym) => {
|
|
||||||
comp.push(OpCode::AttrSet { cap: 0 });
|
|
||||||
comp.push(OpCode::SelectOrDefault { sym })
|
|
||||||
}
|
|
||||||
ir::Attr::Dynamic(dynamic) => {
|
|
||||||
dynamic.compile(comp);
|
|
||||||
comp.push(OpCode::AttrSet { cap: 0 });
|
|
||||||
comp.push(OpCode::SelectDynamicOrDefault);
|
|
||||||
}
|
|
||||||
ir::Attr::Strs(string) => {
|
|
||||||
string.compile(comp);
|
|
||||||
comp.push(OpCode::AttrSet { cap: 0 });
|
|
||||||
comp.push(OpCode::SelectDynamicOrDefault);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match self.default {
|
|
||||||
Some(default) => {
|
|
||||||
let last = comp.pop().unwrap();
|
|
||||||
let _ = comp.pop();
|
|
||||||
default.compile(comp);
|
|
||||||
match last {
|
|
||||||
OpCode::SelectOrDefault { sym } => comp.push(OpCode::SelectOrDefault { sym }),
|
|
||||||
OpCode::SelectDynamicOrDefault => comp.push(OpCode::SelectDynamicOrDefault),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let last = comp.pop().unwrap();
|
|
||||||
let _ = comp.pop();
|
|
||||||
match last {
|
|
||||||
OpCode::SelectOrDefault { sym } => comp.push(OpCode::Select { sym }),
|
|
||||||
OpCode::SelectDynamicOrDefault => comp.push(OpCode::SelectDynamic),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::ConcatStrings {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
let mut iter = self.parts.into_iter();
|
|
||||||
iter.next().unwrap().compile(comp);
|
|
||||||
for item in iter {
|
|
||||||
item.compile(comp);
|
|
||||||
comp.push(OpCode::ConcatString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::If {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.cond.compile(comp);
|
|
||||||
|
|
||||||
let idx_jmp_if_false = comp.idx();
|
|
||||||
// place holder
|
|
||||||
comp.push(OpCode::Illegal);
|
|
||||||
|
|
||||||
let consq_length = self.consq.compile_with_length(comp);
|
|
||||||
|
|
||||||
let idx_jmp = comp.idx();
|
|
||||||
// place holder
|
|
||||||
comp.push(OpCode::Illegal);
|
|
||||||
|
|
||||||
let alter_length = self.alter.compile_with_length(comp);
|
|
||||||
|
|
||||||
comp.modify(
|
|
||||||
idx_jmp_if_false,
|
|
||||||
OpCode::JmpIfFalse {
|
|
||||||
step: consq_length + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
comp.modify(idx_jmp, OpCode::Jmp { step: alter_length });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Let {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.attrs.compile(comp);
|
|
||||||
comp.push(OpCode::EnterEnv);
|
|
||||||
self.expr.compile(comp);
|
|
||||||
comp.push(OpCode::LeaveEnv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::With {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.namespace.compile(comp);
|
|
||||||
comp.push(OpCode::EnterEnv);
|
|
||||||
self.expr.compile(comp);
|
|
||||||
comp.push(OpCode::LeaveEnv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Assert {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.assertion.compile(comp);
|
|
||||||
comp.push(OpCode::Assert);
|
|
||||||
self.expr.compile(comp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::LoadFunc {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
comp.push(OpCode::Func { idx: self.idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Call {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
let arity = self.args.len();
|
|
||||||
self.func.compile(comp);
|
|
||||||
self.args.into_iter().for_each(|arg| {
|
|
||||||
arg.compile(comp);
|
|
||||||
});
|
|
||||||
comp.push(OpCode::Call { arity });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for ir::Path {
|
|
||||||
fn compile(self, comp: &mut Compiler) {
|
|
||||||
self.expr.compile(comp);
|
|
||||||
comp.push(OpCode::Path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
src/error.rs
15
src/error.rs
@@ -1,15 +0,0 @@
|
|||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("error occurred during parse stage: {0}")]
|
|
||||||
ParseError(String),
|
|
||||||
#[error("error occurred during downgrade stage: {0}")]
|
|
||||||
DowngradeError(String),
|
|
||||||
#[error("error occurred during evaluation stage: {0}")]
|
|
||||||
EvalError(String),
|
|
||||||
#[error("unknown error")]
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
761
src/ir.rs
761
src/ir.rs
@@ -1,761 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
|
|
||||||
use ecow::EcoString;
|
|
||||||
use rnix::ast::{self, Expr};
|
|
||||||
|
|
||||||
use crate::compile::*;
|
|
||||||
use crate::error::*;
|
|
||||||
use crate::ty::internal as i;
|
|
||||||
|
|
||||||
pub fn downgrade(expr: Expr) -> Result<Downgraded> {
|
|
||||||
let mut ctx = DowngradeContext::new();
|
|
||||||
let ir = expr.downgrade(&mut ctx)?;
|
|
||||||
Ok(Downgraded {
|
|
||||||
top_level: ir,
|
|
||||||
consts: ctx.consts.into(),
|
|
||||||
symbols: ctx.symbols,
|
|
||||||
symmap: ctx.symmap,
|
|
||||||
thunks: ctx.thunks.into(),
|
|
||||||
funcs: ctx.funcs.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Downcast<T: Sized>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
fn downcast_ref(&self) -> Option<&T>;
|
|
||||||
fn downcast_mut(&mut self) -> Option<&mut T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! ir {
|
|
||||||
(
|
|
||||||
$(
|
|
||||||
$(#[$($x:tt)*])*
|
|
||||||
$ty:ident
|
|
||||||
=>
|
|
||||||
{$($name:ident : $elemtype:ty),*$(,)?}
|
|
||||||
)
|
|
||||||
,*$(,)?
|
|
||||||
) => {
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Ir {
|
|
||||||
$(
|
|
||||||
$ty($ty),
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ir {
|
|
||||||
fn boxed(self) -> Box<Self> {
|
|
||||||
Box::new(self)
|
|
||||||
}
|
|
||||||
fn ok(self) -> Result<Self> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compile for Ir {
|
|
||||||
fn compile(self, ctx: &mut Compiler) {
|
|
||||||
match self {
|
|
||||||
$(Ir::$ty(ir) => ir.compile(ctx),)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(
|
|
||||||
$(
|
|
||||||
#[$($x)*]
|
|
||||||
)*
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct $ty {
|
|
||||||
$(
|
|
||||||
pub $name : $elemtype,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $ty {
|
|
||||||
pub fn ir(self) -> Ir {
|
|
||||||
Ir::$ty(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downcast<$ty> for Ir {
|
|
||||||
fn downcast_ref(&self) -> Option<&$ty> {
|
|
||||||
match self {
|
|
||||||
Ir::$ty(value) => Some(value),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn downcast_mut(&mut self) -> Option<&mut $ty> {
|
|
||||||
match self {
|
|
||||||
Ir::$ty(value) => Some(value),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ir! {
|
|
||||||
Attrs => { stcs: HashMap<usize, Ir>, dyns: Vec<DynamicAttrPair>, rec: bool },
|
|
||||||
List => { items: Vec<Ir> },
|
|
||||||
HasAttr => { lhs: Box<Ir>, rhs: Vec<Attr> },
|
|
||||||
BinOp => { lhs: Box<Ir>, rhs: Box<Ir>, kind: BinOpKind },
|
|
||||||
UnOp => { rhs: Box<Ir>, kind: UnOpKind },
|
|
||||||
Select => { expr: Box<Ir>, attrpath: Vec<Attr>, default: Option<Box<Ir>> },
|
|
||||||
If => { cond: Box<Ir>, consq: Box<Ir>, alter: Box<Ir> },
|
|
||||||
LoadFunc => { idx: usize },
|
|
||||||
Call => { func: Box<Ir>, args: Vec<Ir> },
|
|
||||||
|
|
||||||
Let => { attrs: Attrs, expr: Box<Ir> },
|
|
||||||
With => { namespace: Box<Ir>, expr: Box<Ir> },
|
|
||||||
Assert => { assertion: Box<Ir>, expr: Box<Ir> },
|
|
||||||
ConcatStrings => { parts: Vec<Ir> },
|
|
||||||
Const => { idx: usize },
|
|
||||||
Var => { sym: usize },
|
|
||||||
#[derive(Copy)]
|
|
||||||
Thunk => { idx: usize },
|
|
||||||
Path => { expr: Box<Ir> },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct DynamicAttrPair(pub Ir, pub Ir);
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DowngradeContext {
|
|
||||||
thunks: Vec<Ir>,
|
|
||||||
funcs: Vec<Func>,
|
|
||||||
consts: Vec<i::Const>,
|
|
||||||
constmap: HashMap<i::Const, usize>,
|
|
||||||
symbols: Vec<EcoString>,
|
|
||||||
symmap: HashMap<EcoString, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Downgraded {
|
|
||||||
pub top_level: Ir,
|
|
||||||
pub consts: Box<[i::Const]>,
|
|
||||||
pub symbols: Vec<EcoString>,
|
|
||||||
pub symmap: HashMap<EcoString, usize>,
|
|
||||||
pub thunks: Box<[Ir]>,
|
|
||||||
pub funcs: Box<[Func]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DowngradeContext {
|
|
||||||
fn new() -> DowngradeContext {
|
|
||||||
DowngradeContext::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_thunk(&mut self, thunk: Ir) -> Thunk {
|
|
||||||
let idx = self.thunks.len();
|
|
||||||
self.thunks.push(thunk);
|
|
||||||
Thunk { idx }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_func(&mut self, func: Func) -> LoadFunc {
|
|
||||||
let idx = self.funcs.len();
|
|
||||||
self.funcs.push(func);
|
|
||||||
LoadFunc { idx }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_const(&mut self, cnst: i::Const) -> Const {
|
|
||||||
if let Some(&idx) = self.constmap.get(&cnst) {
|
|
||||||
Const { idx }
|
|
||||||
} else {
|
|
||||||
self.constmap.insert(cnst.clone(), self.consts.len());
|
|
||||||
self.consts.push(cnst);
|
|
||||||
Const {
|
|
||||||
idx: self.consts.len() - 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_sym(&mut self, sym: impl Into<EcoString>) -> usize {
|
|
||||||
let sym = sym.into();
|
|
||||||
if let Some(&idx) = self.symmap.get(&sym) {
|
|
||||||
idx
|
|
||||||
} else {
|
|
||||||
self.symmap.insert(sym.clone(), self.symbols.len());
|
|
||||||
self.symbols.push(sym);
|
|
||||||
self.symbols.len() - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Attrs {
|
|
||||||
fn _insert(&mut self, mut path: std::vec::IntoIter<Attr>, name: Attr, value: Ir) -> Result<()> {
|
|
||||||
if let Some(attr) = path.next() {
|
|
||||||
match attr {
|
|
||||||
Attr::Str(ident) => {
|
|
||||||
if self.stcs.get(&ident).is_some() {
|
|
||||||
self.stcs
|
|
||||||
.get_mut(&ident)
|
|
||||||
.unwrap()
|
|
||||||
.downcast_mut()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::DowngradeError(format!(
|
|
||||||
r#""{ident}" already exsists in this set"#
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.and_then(|attrs: &mut Attrs| attrs._insert(path, name, value))
|
|
||||||
} else {
|
|
||||||
let mut attrs = Attrs {
|
|
||||||
rec: false,
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
};
|
|
||||||
attrs._insert(path, name, value)?;
|
|
||||||
assert!(self.stcs.insert(ident, attrs.ir()).is_none());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Attr::Strs(string) => {
|
|
||||||
let mut attrs = Attrs {
|
|
||||||
rec: false,
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
};
|
|
||||||
attrs._insert(path, name, value)?;
|
|
||||||
self.dyns.push(DynamicAttrPair(string.ir(), attrs.ir()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Attr::Dynamic(dynamic) => {
|
|
||||||
let mut attrs = Attrs {
|
|
||||||
rec: false,
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
};
|
|
||||||
attrs._insert(path, name, value)?;
|
|
||||||
self.dyns.push(DynamicAttrPair(dynamic, attrs.ir()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match name {
|
|
||||||
Attr::Str(ident) => {
|
|
||||||
if self.stcs.get(&ident).is_some() {
|
|
||||||
return Err(Error::DowngradeError(format!(
|
|
||||||
r#""{ident}" already exsists in this set"#
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
self.stcs.insert(ident, value);
|
|
||||||
}
|
|
||||||
Attr::Strs(string) => {
|
|
||||||
self.dyns.push(DynamicAttrPair(string.ir(), value));
|
|
||||||
}
|
|
||||||
Attr::Dynamic(dynamic) => {
|
|
||||||
self.dyns.push(DynamicAttrPair(dynamic, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, path: Vec<Attr>, value: Ir) -> Result<()> {
|
|
||||||
let mut path = path.into_iter();
|
|
||||||
let name = path.next_back().unwrap();
|
|
||||||
self._insert(path, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _has_attr(&self, mut path: std::slice::Iter<Attr>, name: Attr) -> Option<bool> {
|
|
||||||
match path.next() {
|
|
||||||
Some(Attr::Str(ident)) => self
|
|
||||||
.stcs
|
|
||||||
.get(ident)
|
|
||||||
.and_then(|attrs| attrs.downcast_ref())
|
|
||||||
.map_or(Some(false), |attrs: &Attrs| attrs._has_attr(path, name)),
|
|
||||||
None => match name {
|
|
||||||
Attr::Str(ident) => Some(self.stcs.get(&ident).is_some()),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_attr(&self, path: &[Attr]) -> Option<bool> {
|
|
||||||
let mut path = path.iter();
|
|
||||||
let name = path.next_back().unwrap().clone();
|
|
||||||
self._has_attr(path, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Attr {
|
|
||||||
Dynamic(Ir),
|
|
||||||
Strs(ConcatStrings),
|
|
||||||
Str(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum BinOpKind {
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Div,
|
|
||||||
Mul,
|
|
||||||
Eq,
|
|
||||||
Neq,
|
|
||||||
Lt,
|
|
||||||
Gt,
|
|
||||||
Leq,
|
|
||||||
Geq,
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
Impl,
|
|
||||||
|
|
||||||
Con,
|
|
||||||
Upd,
|
|
||||||
|
|
||||||
PipeL,
|
|
||||||
PipeR,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ast::BinOpKind> for BinOpKind {
|
|
||||||
fn from(op: ast::BinOpKind) -> Self {
|
|
||||||
use BinOpKind::*;
|
|
||||||
use ast::BinOpKind as astkind;
|
|
||||||
match op {
|
|
||||||
astkind::Concat => Con,
|
|
||||||
astkind::Update => Upd,
|
|
||||||
astkind::Add => Add,
|
|
||||||
astkind::Sub => Sub,
|
|
||||||
astkind::Mul => Mul,
|
|
||||||
astkind::Div => Div,
|
|
||||||
astkind::And => And,
|
|
||||||
astkind::Equal => Eq,
|
|
||||||
astkind::Implication => Impl,
|
|
||||||
astkind::Less => Lt,
|
|
||||||
astkind::LessOrEq => Leq,
|
|
||||||
astkind::More => Gt,
|
|
||||||
astkind::MoreOrEq => Geq,
|
|
||||||
astkind::NotEqual => Neq,
|
|
||||||
astkind::Or => Or,
|
|
||||||
astkind::PipeLeft => PipeL,
|
|
||||||
astkind::PipeRight => PipeR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum UnOpKind {
|
|
||||||
Neg,
|
|
||||||
Not,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ast::UnaryOpKind> for UnOpKind {
|
|
||||||
fn from(value: ast::UnaryOpKind) -> Self {
|
|
||||||
match value {
|
|
||||||
ast::UnaryOpKind::Invert => UnOpKind::Not,
|
|
||||||
ast::UnaryOpKind::Negate => UnOpKind::Neg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Func {
|
|
||||||
pub param: Param,
|
|
||||||
pub body: Box<Ir>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Param {
|
|
||||||
Ident(usize),
|
|
||||||
Formals {
|
|
||||||
formals: Vec<(usize, Option<Thunk>)>,
|
|
||||||
ellipsis: bool,
|
|
||||||
alias: Option<usize>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Downgrade
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for Expr {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
match self {
|
|
||||||
Expr::Apply(apply) => apply.downgrade(ctx),
|
|
||||||
Expr::Assert(assert) => assert.downgrade(ctx),
|
|
||||||
Expr::Error(error) => Err(Error::DowngradeError(error.to_string())),
|
|
||||||
Expr::IfElse(ifelse) => ifelse.downgrade(ctx),
|
|
||||||
Expr::Select(select) => select.downgrade(ctx),
|
|
||||||
Expr::Str(str) => str.downgrade(ctx),
|
|
||||||
Expr::Path(path) => path.downgrade(ctx),
|
|
||||||
Expr::Literal(lit) => lit.downgrade(ctx),
|
|
||||||
Expr::Lambda(lambda) => lambda.downgrade(ctx),
|
|
||||||
Expr::LegacyLet(let_) => let_.downgrade(ctx),
|
|
||||||
Expr::LetIn(letin) => letin.downgrade(ctx),
|
|
||||||
Expr::List(list) => list.downgrade(ctx),
|
|
||||||
Expr::BinOp(op) => op.downgrade(ctx),
|
|
||||||
Expr::Paren(paren) => paren.expr().unwrap().downgrade(ctx),
|
|
||||||
Expr::Root(root) => root.expr().unwrap().downgrade(ctx),
|
|
||||||
Expr::AttrSet(attrs) => attrs.downgrade(ctx),
|
|
||||||
Expr::UnaryOp(op) => op.downgrade(ctx),
|
|
||||||
Expr::Ident(ident) => ident.downgrade(ctx),
|
|
||||||
Expr::With(with) => with.downgrade(ctx),
|
|
||||||
Expr::HasAttr(has) => has.downgrade(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Assert {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
Assert {
|
|
||||||
assertion: self.condition().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
expr: self.body().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::IfElse {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
If {
|
|
||||||
cond: self.condition().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
consq: self.body().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
alter: self.else_body().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Path {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let parts = self
|
|
||||||
.parts()
|
|
||||||
.map(|part| match part {
|
|
||||||
ast::InterpolPart::Literal(lit) => ctx.new_const(lit.to_string().into()).ir().ok(),
|
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
|
||||||
interpol.expr().unwrap().downgrade(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
if parts.len() == 1 {
|
|
||||||
Path {
|
|
||||||
expr: parts.into_iter().next().unwrap().boxed(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Path {
|
|
||||||
expr: ConcatStrings { parts }.ir().boxed(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Str {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let parts = self
|
|
||||||
.normalized_parts()
|
|
||||||
.into_iter()
|
|
||||||
.map(|part| match part {
|
|
||||||
ast::InterpolPart::Literal(lit) => ctx.new_const(lit.into()).ir().ok(),
|
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
|
||||||
interpol.expr().unwrap().downgrade(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
if parts.len() == 1 {
|
|
||||||
Ok(parts.into_iter().next().unwrap())
|
|
||||||
} else {
|
|
||||||
ConcatStrings { parts }.ir().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Literal {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
match self.kind() {
|
|
||||||
ast::LiteralKind::Integer(int) => ctx.new_const(int.value().unwrap().into()),
|
|
||||||
ast::LiteralKind::Float(float) => ctx.new_const(float.value().unwrap().into()),
|
|
||||||
ast::LiteralKind::Uri(uri) => ctx.new_const(uri.to_string().into()),
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Ident {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
Var {
|
|
||||||
sym: ctx.new_sym(self.to_string()),
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::AttrSet {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let rec = self.rec_token().is_some();
|
|
||||||
downgrade_has_entry(self, rec, ctx).map(|attrs| attrs.ir())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::List {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let mut items = Vec::with_capacity(self.items().size_hint().0);
|
|
||||||
for item in self.items() {
|
|
||||||
items.push(item.downgrade(ctx)?)
|
|
||||||
}
|
|
||||||
List { items }.ir().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::BinOp {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
BinOp {
|
|
||||||
lhs: self.lhs().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
rhs: self.rhs().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
kind: self.operator().unwrap().into(),
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::HasAttr {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let attrs = self.expr().unwrap().downgrade(ctx)?;
|
|
||||||
let path = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
|
||||||
HasAttr {
|
|
||||||
lhs: attrs.boxed(),
|
|
||||||
rhs: path,
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::UnaryOp {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
UnOp {
|
|
||||||
rhs: self.expr().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
kind: self.operator().unwrap().into(),
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Select {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
Select {
|
|
||||||
expr: self.expr().unwrap().downgrade(ctx)?.boxed(),
|
|
||||||
attrpath: downgrade_attrpath(self.attrpath().unwrap(), ctx)?,
|
|
||||||
default: match self.default_expr() {
|
|
||||||
Some(default) => Some(default.downgrade(ctx)?.boxed()),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::LegacyLet {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let attrs = downgrade_has_entry(self, true, ctx)?;
|
|
||||||
Select {
|
|
||||||
expr: attrs.ir().boxed(),
|
|
||||||
attrpath: vec![Attr::Str(ctx.new_sym("body".to_string()))],
|
|
||||||
default: None,
|
|
||||||
}
|
|
||||||
.ir()
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::LetIn {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let body = self.body().unwrap();
|
|
||||||
let attrs = downgrade_has_entry(self, true, ctx)?;
|
|
||||||
let expr = body.downgrade(ctx)?.boxed();
|
|
||||||
Let { attrs, expr }.ir().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::With {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
|
||||||
if let Ir::Attrs(attrs) = namespace {
|
|
||||||
let expr = self.body().unwrap().downgrade(ctx)?.boxed();
|
|
||||||
Let { attrs, expr }.ir().ok()
|
|
||||||
} else {
|
|
||||||
let namespace = namespace.boxed();
|
|
||||||
let expr = self.body().unwrap().downgrade(ctx)?.boxed();
|
|
||||||
With { namespace, expr }.ir().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Lambda {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let body = self.body().unwrap();
|
|
||||||
let param = downgrade_param(self.param().unwrap(), ctx)?;
|
|
||||||
let body = body.downgrade(ctx)?.boxed();
|
|
||||||
ctx.new_func(Func { param, body }).ir().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Downgrade for ast::Apply {
|
|
||||||
fn downgrade(self, ctx: &mut DowngradeContext) -> Result<Ir> {
|
|
||||||
let mut args = vec![self.argument().unwrap().downgrade(ctx)?];
|
|
||||||
let mut func = self.lambda().unwrap();
|
|
||||||
while let ast::Expr::Apply(call) = func {
|
|
||||||
func = call.lambda().unwrap();
|
|
||||||
args.push(call.argument().unwrap().downgrade(ctx)?);
|
|
||||||
}
|
|
||||||
let func = func.downgrade(ctx)?.boxed();
|
|
||||||
args.reverse();
|
|
||||||
Call { func, args }.ir().ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_param(param: ast::Param, ctx: &mut DowngradeContext) -> Result<Param> {
|
|
||||||
match param {
|
|
||||||
ast::Param::IdentParam(ident) => Ok(Param::Ident(ctx.new_sym(ident.to_string()))),
|
|
||||||
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_pattern(pattern: ast::Pattern, ctx: &mut DowngradeContext) -> Result<Param> {
|
|
||||||
let formals = pattern
|
|
||||||
.pat_entries()
|
|
||||||
.map(|entry| {
|
|
||||||
let ident = ctx.new_sym(entry.ident().unwrap().to_string());
|
|
||||||
if entry.default().is_none() {
|
|
||||||
Ok((ident, None))
|
|
||||||
} else {
|
|
||||||
entry
|
|
||||||
.default()
|
|
||||||
.unwrap()
|
|
||||||
.downgrade(ctx)
|
|
||||||
.map(|ok| (ident, Some(ctx.new_thunk(ok))))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
let ellipsis = pattern.ellipsis_token().is_some();
|
|
||||||
let alias = pattern
|
|
||||||
.pat_bind()
|
|
||||||
.map(|alias| ctx.new_sym(alias.ident().unwrap().to_string()));
|
|
||||||
Ok(Param::Formals {
|
|
||||||
formals,
|
|
||||||
ellipsis,
|
|
||||||
alias,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_has_entry(
|
|
||||||
has_entry: impl ast::HasEntry,
|
|
||||||
rec: bool,
|
|
||||||
ctx: &mut DowngradeContext,
|
|
||||||
) -> Result<Attrs> {
|
|
||||||
let entires = has_entry.entries();
|
|
||||||
let mut attrs = Attrs {
|
|
||||||
rec,
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entires {
|
|
||||||
match entry {
|
|
||||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
|
||||||
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_inherit(
|
|
||||||
inherit: ast::Inherit,
|
|
||||||
stcs: &mut HashMap<usize, Ir>,
|
|
||||||
ctx: &mut DowngradeContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let from = if let Some(from) = inherit.from() {
|
|
||||||
let from = from.expr().unwrap().downgrade(ctx)?;
|
|
||||||
Some(ctx.new_thunk(from))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
for attr in inherit.attrs() {
|
|
||||||
let ident = match downgrade_attr(attr, ctx)? {
|
|
||||||
Attr::Str(ident) => ident,
|
|
||||||
_ => {
|
|
||||||
return Err(Error::DowngradeError(
|
|
||||||
"dynamic attributes not allowed in inherit".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let expr = from.map_or_else(
|
|
||||||
|| Var { sym: ident }.ir().ok(),
|
|
||||||
|from| {
|
|
||||||
Ok(Select {
|
|
||||||
expr: from.ir().boxed(),
|
|
||||||
attrpath: vec![Attr::Str(ident)],
|
|
||||||
default: None,
|
|
||||||
}
|
|
||||||
.ir())
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
assert!(stcs.insert(ident, expr).is_none());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_attr(attr: ast::Attr, ctx: &mut DowngradeContext) -> Result<Attr> {
|
|
||||||
use ast::Attr::*;
|
|
||||||
use ast::InterpolPart::*;
|
|
||||||
match attr {
|
|
||||||
Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))),
|
|
||||||
Str(string) => {
|
|
||||||
let parts = string.normalized_parts();
|
|
||||||
if parts.len() == 0 {
|
|
||||||
Ok(Attr::Str(ctx.new_sym("")))
|
|
||||||
} else if parts.len() == 1 {
|
|
||||||
match parts.into_iter().next().unwrap() {
|
|
||||||
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident))),
|
|
||||||
Interpolation(interpol) => {
|
|
||||||
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let parts = parts
|
|
||||||
.into_iter()
|
|
||||||
.map(|part| match part {
|
|
||||||
Literal(lit) => ctx.new_const(lit.into()).ir().ok(),
|
|
||||||
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
Ok(Attr::Strs(ConcatStrings { parts }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_attrpath(attrpath: ast::Attrpath, ctx: &mut DowngradeContext) -> Result<Vec<Attr>> {
|
|
||||||
attrpath
|
|
||||||
.attrs()
|
|
||||||
.map(|attr| downgrade_attr(attr, ctx))
|
|
||||||
.collect::<Result<Vec<_>>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_attrpathvalue(
|
|
||||||
value: ast::AttrpathValue,
|
|
||||||
attrs: &mut Attrs,
|
|
||||||
ctx: &mut DowngradeContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
|
||||||
let value = value.value().unwrap().downgrade(ctx)?;
|
|
||||||
let value = match value {
|
|
||||||
x @ Ir::Const(_) => x,
|
|
||||||
x => ctx.new_thunk(x).ir(),
|
|
||||||
};
|
|
||||||
attrs.insert(path, value)
|
|
||||||
}
|
|
||||||
14
src/lib.rs
14
src/lib.rs
@@ -1,14 +0,0 @@
|
|||||||
#![cfg_attr(test, feature(test))]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
mod builtins;
|
|
||||||
mod bytecode;
|
|
||||||
mod stack;
|
|
||||||
mod ty;
|
|
||||||
|
|
||||||
pub mod compile;
|
|
||||||
pub mod error;
|
|
||||||
pub mod ir;
|
|
||||||
pub mod vm;
|
|
||||||
|
|
||||||
pub use ty::public::Value;
|
|
||||||
84
src/stack.rs
84
src/stack.rs
@@ -1,84 +0,0 @@
|
|||||||
use std::mem::{MaybeUninit, replace, transmute};
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::error::*;
|
|
||||||
|
|
||||||
pub struct Stack<T, const CAP: usize> {
|
|
||||||
items: [MaybeUninit<T>; CAP],
|
|
||||||
top: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! into {
|
|
||||||
($e:expr) => {
|
|
||||||
// SAFETY: This macro is used to transmute `MaybeUninit<Value<'vm>>` to `Value<'vm>`
|
|
||||||
// or `&MaybeUninit<Value<'vm>>` to `&Value<'vm>`.
|
|
||||||
// This is safe because the `Stack` ensures that only initialized values are accessed
|
|
||||||
// within the `0..top` range.
|
|
||||||
unsafe { transmute($e) }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const CAP: usize> Stack<T, CAP> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Stack {
|
|
||||||
items: [const { MaybeUninit::uninit() }; CAP],
|
|
||||||
top: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, item: T) -> Result<()> {
|
|
||||||
self.items
|
|
||||||
.get_mut(self.top)
|
|
||||||
.map_or_else(
|
|
||||||
|| Err(Error::EvalError("stack overflow".to_string())),
|
|
||||||
|ok| Ok(ok),
|
|
||||||
)?
|
|
||||||
.write(item);
|
|
||||||
self.top += 1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&mut self) -> T {
|
|
||||||
self.top -= 1;
|
|
||||||
let item = self.items.get_mut(self.top).unwrap();
|
|
||||||
|
|
||||||
// SAFETY: `item` at `self.top` was previously written and is initialized.
|
|
||||||
// We replace it with `MaybeUninit::uninit()` and then `assume_init`
|
|
||||||
// on the original value, which is safe as it was initialized.
|
|
||||||
unsafe { replace(item, MaybeUninit::uninit()).assume_init() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tos(&self) -> Result<&T> {
|
|
||||||
if self.top == 0 {
|
|
||||||
panic!("stack empty")
|
|
||||||
} else {
|
|
||||||
Ok(into!(&self.items[self.top - 1]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tos_mut(&mut self) -> Result<&mut T> {
|
|
||||||
if self.top == 0 {
|
|
||||||
panic!("stack empty")
|
|
||||||
} else {
|
|
||||||
Ok(into!(&mut self.items[self.top - 1]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const CAP: usize> Deref for Stack<T, CAP> {
|
|
||||||
type Target = [T];
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
into!(&self.items[0..self.top])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const CAP: usize> Drop for Stack<T, CAP> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.items.as_mut_slice()[0..self.top]
|
|
||||||
.iter_mut()
|
|
||||||
// SAFETY: Items in the range `0..self.top` are guaranteed to be initialized.
|
|
||||||
// `assume_init_drop` is called to correctly drop these initialized `Value`s.
|
|
||||||
.map(|item| unsafe { item.assume_init_drop() })
|
|
||||||
.for_each(drop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
|
||||||
|
|
||||||
use derive_more::Constructor;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Constructor, Hash)]
|
|
||||||
pub struct Catchable {
|
|
||||||
msg: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Catchable {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
write!(f, "<error: {}>", self.msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
use hashbrown::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use derive_more::Constructor;
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::vm::{Env, VM};
|
|
||||||
|
|
||||||
use super::super::public as p;
|
|
||||||
use super::Value;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Constructor, Clone, PartialEq)]
|
|
||||||
pub struct AttrSet<'vm> {
|
|
||||||
data: HashMap<usize, Value<'vm>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> AttrSet<'vm> {
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
AttrSet {
|
|
||||||
data: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_capacity(cap: usize) -> Self {
|
|
||||||
AttrSet {
|
|
||||||
data: HashMap::with_capacity(cap),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_attr_force(&mut self, sym: usize, val: Value<'vm>) {
|
|
||||||
self.data.insert(sym, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_attr(&mut self, sym: usize, val: Value<'vm>) {
|
|
||||||
if self.data.get(&sym).is_some() {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
self.data.insert(sym, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&self, sym: usize) -> Option<Value<'vm>> {
|
|
||||||
self.data.get(&sym).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_attr(&self, sym: usize) -> bool {
|
|
||||||
self.data.get(&sym).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capture(&mut self, env: &Env<'vm>) {
|
|
||||||
self.data.iter().for_each(|(_, v)| match v.clone() {
|
|
||||||
Value::Thunk(ref thunk) => {
|
|
||||||
thunk.capture(env.clone());
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, other: &AttrSet<'vm>) {
|
|
||||||
for (k, v) in other.data.iter() {
|
|
||||||
self.push_attr_force(k.clone(), v.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_inner(&self) -> &HashMap<usize, Value<'vm>> {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_inner(data: HashMap<usize, Value<'vm>>) -> Self {
|
|
||||||
Self { data }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force_deep(&mut self, vm: &'vm VM<'_>) -> Result<()> {
|
|
||||||
let mut map: Vec<_> = self
|
|
||||||
.data
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
|
||||||
.collect();
|
|
||||||
for (_, v) in map.iter_mut() {
|
|
||||||
v.force_deep(vm)?;
|
|
||||||
}
|
|
||||||
self.data = map.into_iter().collect();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eq_impl(&self, other: &AttrSet<'vm>, vm: &'vm VM<'_>) -> bool {
|
|
||||||
self.data.iter().len() == other.data.iter().len()
|
|
||||||
&& std::iter::zip(
|
|
||||||
self.data.iter().sorted_by_key(|(k, _)| **k),
|
|
||||||
self.data.iter().sorted_by_key(|(k, _)| **k),
|
|
||||||
)
|
|
||||||
.all(|((_, v1), (_, v2))| v1.eq_impl(v2, vm))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_public(&self, vm: &'vm VM, seen: &mut HashSet<Value<'vm>>) -> p::Value {
|
|
||||||
p::Value::AttrSet(p::AttrSet::new(
|
|
||||||
self.data
|
|
||||||
.iter()
|
|
||||||
.map(|(&sym, value)| (vm.get_sym(sym), value.to_public(vm, seen)))
|
|
||||||
.collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use derive_more::{IsVariant, Unwrap};
|
|
||||||
use ecow::EcoString;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, IsVariant, Unwrap)]
|
|
||||||
pub enum Const {
|
|
||||||
Bool(bool),
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
String(EcoString),
|
|
||||||
Null,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Const {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
use Const::*;
|
|
||||||
match self {
|
|
||||||
Int(x) => x.hash(state),
|
|
||||||
Float(x) => x.to_bits().hash(state),
|
|
||||||
Bool(x) => x.hash(state),
|
|
||||||
String(x) => x.hash(state),
|
|
||||||
x @ Null => x.hash(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for Const {
|
|
||||||
fn from(value: bool) -> Self {
|
|
||||||
Const::Bool(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i64> for Const {
|
|
||||||
fn from(value: i64) -> Self {
|
|
||||||
Const::Int(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for Const {
|
|
||||||
fn from(value: f64) -> Self {
|
|
||||||
Const::Float(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcoString> for Const {
|
|
||||||
fn from(value: EcoString) -> Self {
|
|
||||||
Const::String(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Const {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Const::String(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Const {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Const::String(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Const {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
use Const::*;
|
|
||||||
match (self, other) {
|
|
||||||
(Bool(a), Bool(b)) => a == b,
|
|
||||||
(Int(a), Int(b)) => a == b,
|
|
||||||
(Float(a), Float(b)) => a == b,
|
|
||||||
(Int(a), Float(b)) => *a as f64 == *b,
|
|
||||||
(Float(a), Int(b)) => *b as f64 == *a,
|
|
||||||
(String(a), String(b)) => a == b,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Const {}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
use derive_more::Constructor;
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::bytecode::Func as BFunc;
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::ir;
|
|
||||||
use crate::ty::internal::{Thunk, Value};
|
|
||||||
use crate::vm::{Env, VM};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Param {
|
|
||||||
Ident(usize),
|
|
||||||
Formals {
|
|
||||||
formals: Vec<(usize, Option<usize>)>,
|
|
||||||
ellipsis: bool,
|
|
||||||
alias: Option<usize>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ir::Param> for Param {
|
|
||||||
fn from(value: ir::Param) -> Self {
|
|
||||||
match value {
|
|
||||||
ir::Param::Ident(ident) => Param::Ident(ident),
|
|
||||||
ir::Param::Formals {
|
|
||||||
formals,
|
|
||||||
ellipsis,
|
|
||||||
alias,
|
|
||||||
} => Param::Formals {
|
|
||||||
formals: formals
|
|
||||||
.into_iter()
|
|
||||||
.map(|(sym, default)| (sym, default.map(|default| default.idx)))
|
|
||||||
.collect(),
|
|
||||||
ellipsis,
|
|
||||||
alias,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type JITFunc<'vm> =
|
|
||||||
unsafe extern "C" fn(vm: *mut VM<'_>, *mut Env<'vm>, *mut Value<'vm>) -> Value<'vm>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Constructor)]
|
|
||||||
pub struct Func<'vm> {
|
|
||||||
pub func: &'vm BFunc,
|
|
||||||
pub env: Env<'vm>,
|
|
||||||
pub compiled: Option<JITFunc<'vm>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> Func<'vm> {
|
|
||||||
pub fn call(&self, vm: &'vm VM<'_>, arg: Value<'vm>) -> Result<Value<'vm>> {
|
|
||||||
use Param::*;
|
|
||||||
|
|
||||||
let mut env = self.env.clone();
|
|
||||||
|
|
||||||
match self.func.param.clone() {
|
|
||||||
Ident(ident) => env = env.enter([(ident.into(), arg)].into_iter()),
|
|
||||||
Formals {
|
|
||||||
formals,
|
|
||||||
ellipsis,
|
|
||||||
alias,
|
|
||||||
} => {
|
|
||||||
let arg = arg.unwrap_attr_set();
|
|
||||||
let mut new = Vec::with_capacity(formals.len() + alias.iter().len());
|
|
||||||
if !ellipsis
|
|
||||||
&& arg
|
|
||||||
.as_inner()
|
|
||||||
.iter()
|
|
||||||
.map(|(k, _)| k)
|
|
||||||
.sorted()
|
|
||||||
.ne(formals.iter().map(|(k, _)| k).sorted())
|
|
||||||
{
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
for (formal, default) in formals {
|
|
||||||
let formal = formal.clone().into();
|
|
||||||
let arg = arg
|
|
||||||
.select(formal)
|
|
||||||
.or_else(|| {
|
|
||||||
default.map(|idx| Value::Thunk(Thunk::new(vm.get_thunk(idx)).into()))
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
new.push((formal, arg));
|
|
||||||
}
|
|
||||||
if let Some(alias) = alias {
|
|
||||||
new.push((alias.clone().into(), Value::AttrSet(arg)));
|
|
||||||
}
|
|
||||||
env = env.enter(new.into_iter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.eval(self.func.opcodes.iter().copied(), env)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Func<'_> {
|
|
||||||
fn eq(&self, _: &Self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
use hashbrown::HashSet;
|
|
||||||
|
|
||||||
use derive_more::Constructor;
|
|
||||||
use rpds::Vector;
|
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::ty::public as p;
|
|
||||||
use crate::vm::VM;
|
|
||||||
|
|
||||||
use super::Value;
|
|
||||||
|
|
||||||
#[derive(Debug, Constructor, Clone, PartialEq)]
|
|
||||||
pub struct List<'vm> {
|
|
||||||
data: Vector<Value<'vm>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> List<'vm> {
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
List {
|
|
||||||
data: Vector::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, elem: Value<'vm>) {
|
|
||||||
self.data.push_back_mut(elem);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn concat(&mut self, other: &List<'vm>) {
|
|
||||||
for elem in other.data.iter() {
|
|
||||||
self.data.push_back_mut(elem.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force_deep(&mut self, vm: &'vm VM<'_>) -> Result<()> {
|
|
||||||
let mut vec: Vec<_> = self.data.iter().cloned().collect();
|
|
||||||
for v in vec.iter_mut() {
|
|
||||||
v.force_deep(vm)?;
|
|
||||||
}
|
|
||||||
self.data = vec.into_iter().collect();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_public(&self, vm: &'vm VM, seen: &mut HashSet<Value<'vm>>) -> p::Value {
|
|
||||||
p::Value::List(p::List::new(
|
|
||||||
self.data
|
|
||||||
.iter()
|
|
||||||
.map(|value| value.clone().to_public(vm, seen))
|
|
||||||
.collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
use hashbrown::HashSet;
|
|
||||||
use std::cell::OnceCell;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use derive_more::{IsVariant, Unwrap};
|
|
||||||
|
|
||||||
use super::common as c;
|
|
||||||
use super::public as p;
|
|
||||||
|
|
||||||
use crate::bytecode::OpCodes;
|
|
||||||
use crate::error::*;
|
|
||||||
use crate::vm::{Env, VM};
|
|
||||||
|
|
||||||
mod attrset;
|
|
||||||
mod cnst;
|
|
||||||
mod func;
|
|
||||||
mod list;
|
|
||||||
mod primop;
|
|
||||||
mod string;
|
|
||||||
|
|
||||||
pub use attrset::*;
|
|
||||||
pub use cnst::Const;
|
|
||||||
pub use func::*;
|
|
||||||
pub use list::List;
|
|
||||||
pub use primop::*;
|
|
||||||
|
|
||||||
#[derive(Debug, IsVariant, Unwrap, Clone, PartialEq)]
|
|
||||||
pub enum Value<'vm> {
|
|
||||||
Const(Const),
|
|
||||||
Thunk(Rc<Thunk<'vm>>),
|
|
||||||
ThunkRef(&'vm Thunk<'vm>),
|
|
||||||
AttrSet(Rc<AttrSet<'vm>>),
|
|
||||||
List(Rc<List<'vm>>),
|
|
||||||
Catchable(c::Catchable),
|
|
||||||
PrimOp(Rc<PrimOp<'vm>>),
|
|
||||||
PartialPrimOp(Rc<PartialPrimOp<'vm>>),
|
|
||||||
Func(Rc<Func<'vm>>),
|
|
||||||
Builtins,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Value<'_> {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
use Value::*;
|
|
||||||
std::mem::discriminant(self).hash(state);
|
|
||||||
match self {
|
|
||||||
Const(x) => x.hash(state),
|
|
||||||
Thunk(x) => (x.as_ref() as *const self::Thunk).hash(state),
|
|
||||||
ThunkRef(x) => (*x as *const self::Thunk).hash(state),
|
|
||||||
AttrSet(x) => (x.as_ref() as *const self::AttrSet).hash(state),
|
|
||||||
List(x) => (x.as_ref() as *const self::List).hash(state),
|
|
||||||
Catchable(x) => x.hash(state),
|
|
||||||
PrimOp(x) => (x.as_ref() as *const self::PrimOp).hash(state),
|
|
||||||
PartialPrimOp(x) => (x.as_ref() as *const self::PartialPrimOp).hash(state),
|
|
||||||
Func(x) => (x.as_ref() as *const self::Func).hash(state),
|
|
||||||
Builtins => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> Value<'vm> {
|
|
||||||
fn eq_impl(&self, other: &Self, vm: &'vm VM<'_>) -> bool {
|
|
||||||
use Value::*;
|
|
||||||
match (self, other) {
|
|
||||||
(Const(a), Const(b)) => a.eq(b),
|
|
||||||
(AttrSet(a), AttrSet(b)) => a.eq_impl(b, vm),
|
|
||||||
(List(a), List(b)) => a.eq(b),
|
|
||||||
(Builtins, AttrSet(attrs)) => attrs.has_attr(vm.new_sym("builtins")),
|
|
||||||
(AttrSet(attrs), Builtins) => attrs.has_attr(vm.new_sym("builtins")),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Value<'_> {}
|
|
||||||
|
|
||||||
#[derive(Debug, IsVariant, Unwrap, Clone, PartialEq)]
|
|
||||||
pub enum ValueAsRef<'v, 'vm: 'v> {
|
|
||||||
Const(&'v Const),
|
|
||||||
Thunk(&'v Thunk<'vm>),
|
|
||||||
AttrSet(&'v AttrSet<'vm>),
|
|
||||||
List(&'v List<'vm>),
|
|
||||||
Catchable(&'v c::Catchable),
|
|
||||||
PrimOp(&'v PrimOp<'vm>),
|
|
||||||
PartialPrimOp(&'v PartialPrimOp<'vm>),
|
|
||||||
Func(&'v Func<'vm>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, IsVariant, Unwrap, PartialEq)]
|
|
||||||
pub enum ValueAsMut<'v, 'vm: 'v> {
|
|
||||||
Const(&'v mut Const),
|
|
||||||
Thunk(&'v Thunk<'vm>),
|
|
||||||
AttrSet(&'v mut AttrSet<'vm>),
|
|
||||||
List(&'v mut List<'vm>),
|
|
||||||
Catchable(&'v mut c::Catchable),
|
|
||||||
PrimOp(&'v mut PrimOp<'vm>),
|
|
||||||
PartialPrimOp(&'v mut PartialPrimOp<'vm>),
|
|
||||||
Func(&'v Func<'vm>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'v, 'vm: 'v> Value<'vm> {
|
|
||||||
pub fn as_ref(&'v self) -> ValueAsRef<'v, 'vm> {
|
|
||||||
use Value::*;
|
|
||||||
use ValueAsRef as R;
|
|
||||||
match self {
|
|
||||||
Const(x) => R::Const(x),
|
|
||||||
Thunk(x) => R::Thunk(x),
|
|
||||||
ThunkRef(x) => R::Thunk(x),
|
|
||||||
AttrSet(x) => R::AttrSet(x),
|
|
||||||
List(x) => R::List(x),
|
|
||||||
Catchable(x) => R::Catchable(x),
|
|
||||||
PrimOp(x) => R::PrimOp(x),
|
|
||||||
PartialPrimOp(x) => R::PartialPrimOp(x),
|
|
||||||
Func(x) => R::Func(x),
|
|
||||||
Builtins => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_mut(&'v mut self) -> ValueAsMut<'v, 'vm> {
|
|
||||||
use Value::*;
|
|
||||||
use ValueAsMut as M;
|
|
||||||
match self {
|
|
||||||
Const(x) => M::Const(x),
|
|
||||||
Thunk(x) => M::Thunk(x),
|
|
||||||
ThunkRef(x) => M::Thunk(x),
|
|
||||||
AttrSet(x) => M::AttrSet(Rc::make_mut(x)),
|
|
||||||
List(x) => M::List(Rc::make_mut(x)),
|
|
||||||
Catchable(x) => M::Catchable(x),
|
|
||||||
PrimOp(x) => M::PrimOp(Rc::make_mut(x)),
|
|
||||||
PartialPrimOp(x) => M::PartialPrimOp(Rc::make_mut(x)),
|
|
||||||
Func(x) => M::Func(x),
|
|
||||||
Builtins => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use Value::Const as VmConst;
|
|
||||||
impl<'vm> Value<'vm> {
|
|
||||||
pub fn ok(self) -> Result<Self> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn typename(&self) -> &'static str {
|
|
||||||
use Value::*;
|
|
||||||
match self {
|
|
||||||
Const(_) => unreachable!(),
|
|
||||||
Thunk(_) => "thunk",
|
|
||||||
ThunkRef(_) => "thunk",
|
|
||||||
AttrSet(_) => "set",
|
|
||||||
List(_) => "list",
|
|
||||||
Catchable(_) => unreachable!(),
|
|
||||||
PrimOp(_) => "lambda",
|
|
||||||
PartialPrimOp(_) => "lambda",
|
|
||||||
Func(_) => "lambda",
|
|
||||||
Builtins => "set",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn callable(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Value::PrimOp(_) | Value::PartialPrimOp(_) | Value::Func(_) => true,
|
|
||||||
Value::AttrSet(_) => todo!(),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call(&self, vm: &'vm VM<'_>, args: Vec<Self>) -> Result<Self> {
|
|
||||||
use Value::*;
|
|
||||||
match self {
|
|
||||||
PrimOp(func) => func.call(vm, args),
|
|
||||||
PartialPrimOp(func) => func.call(vm, args),
|
|
||||||
func @ Value::Func(_) => {
|
|
||||||
let mut iter = args.into_iter();
|
|
||||||
let mut func = func.clone();
|
|
||||||
while let Some(arg) = iter.next() {
|
|
||||||
func = match func {
|
|
||||||
PrimOp(func) => {
|
|
||||||
return func.call(vm, [arg].into_iter().chain(iter).collect());
|
|
||||||
}
|
|
||||||
PartialPrimOp(func) => {
|
|
||||||
return func.call(vm, [arg].into_iter().chain(iter).collect());
|
|
||||||
}
|
|
||||||
Func(func) => func.call(vm, arg)?,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func.ok()
|
|
||||||
}
|
|
||||||
x @ Catchable(_) => x.clone().ok(),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn not(self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
match self {
|
|
||||||
VmConst(Bool(bool)) => VmConst(Bool(!bool)),
|
|
||||||
x @ Value::Catchable(_) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn and(self, other: Self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
match (self, other) {
|
|
||||||
(VmConst(Bool(a)), VmConst(Bool(b))) => VmConst(Bool(a && b)),
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn or(self, other: Self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
match (self, other) {
|
|
||||||
(VmConst(Bool(a)), VmConst(Bool(b))) => VmConst(Bool(a || b)),
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eq(self, other: Self, vm: &'vm VM<'_>) -> Self {
|
|
||||||
use Const::Bool;
|
|
||||||
match (self, other) {
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
(s, other) => VmConst(Bool(s.eq_impl(&other, vm))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lt(self, other: Self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
VmConst(Bool(match (self, other) {
|
|
||||||
(VmConst(Int(a)), VmConst(Int(b))) => a < b,
|
|
||||||
(VmConst(Int(a)), VmConst(Float(b))) => (a as f64) < b,
|
|
||||||
(VmConst(Float(a)), VmConst(Int(b))) => a < b as f64,
|
|
||||||
(VmConst(Float(a)), VmConst(Float(b))) => a < b,
|
|
||||||
(VmConst(String(a)), VmConst(String(b))) => a < b,
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => return x,
|
|
||||||
_ => todo!(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn neg(self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
match self {
|
|
||||||
VmConst(Int(int)) => VmConst(Int(-int)),
|
|
||||||
VmConst(Float(float)) => VmConst(Float(-float)),
|
|
||||||
x @ Value::Catchable(_) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(self, other: Self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
match (self, other) {
|
|
||||||
(VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a + b)),
|
|
||||||
(VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 + b)),
|
|
||||||
(VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a + b as f64)),
|
|
||||||
(VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a + b)),
|
|
||||||
(VmConst(String(a)), VmConst(String(b))) => {
|
|
||||||
let mut string = a.clone();
|
|
||||||
string.push_str(b.as_str());
|
|
||||||
VmConst(String(string))
|
|
||||||
}
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mul(self, other: Self) -> Self {
|
|
||||||
use Const::*;
|
|
||||||
match (self, other) {
|
|
||||||
(VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a * b)),
|
|
||||||
(VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 * b)),
|
|
||||||
(VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a * b as f64)),
|
|
||||||
(VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a * b)),
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn div(self, other: Self) -> Result<Self> {
|
|
||||||
use Const::*;
|
|
||||||
Ok(match (self, other) {
|
|
||||||
(_, VmConst(Int(0))) => return Err(Error::EvalError("division by zero".to_string())),
|
|
||||||
(_, VmConst(Float(0.))) => {
|
|
||||||
return Err(Error::EvalError("division by zero".to_string()));
|
|
||||||
}
|
|
||||||
(VmConst(Int(a)), VmConst(Int(b))) => VmConst(Int(a / b)),
|
|
||||||
(VmConst(Int(a)), VmConst(Float(b))) => VmConst(Float(a as f64 / b)),
|
|
||||||
(VmConst(Float(a)), VmConst(Int(b))) => VmConst(Float(a / b as f64)),
|
|
||||||
(VmConst(Float(a)), VmConst(Float(b))) => VmConst(Float(a / b)),
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn concat_string(&mut self, mut other: Self) -> &mut Self {
|
|
||||||
match (self.coerce_to_string(), other.coerce_to_string()) {
|
|
||||||
(VmConst(Const::String(a)), VmConst(Const::String(b))) => a.push_str(b.as_str()),
|
|
||||||
(_, Value::Catchable(_)) => *self = other,
|
|
||||||
(Value::Catchable(_), _) => (),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, elem: Self) -> &mut Self {
|
|
||||||
if let Value::List(list) = self {
|
|
||||||
Rc::make_mut(list).push(elem);
|
|
||||||
} else if let Value::Catchable(_) = self {
|
|
||||||
} else if let Value::Catchable(_) = elem {
|
|
||||||
*self = elem;
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn concat(self, other: Self) -> Self {
|
|
||||||
match (self, other) {
|
|
||||||
(Value::List(mut a), Value::List(b)) => {
|
|
||||||
Rc::make_mut(&mut a).concat(b.as_ref());
|
|
||||||
Value::List(a)
|
|
||||||
}
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_attr(&mut self, sym: usize, val: Self) -> &mut Self {
|
|
||||||
if let Value::AttrSet(attrs) = self {
|
|
||||||
Rc::make_mut(attrs).push_attr(sym, val)
|
|
||||||
} else if let Value::Catchable(_) = self {
|
|
||||||
} else if let Value::Catchable(_) = val {
|
|
||||||
*self = val
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(self, other: Self) -> Self {
|
|
||||||
match (self, other) {
|
|
||||||
(Value::AttrSet(mut a), Value::AttrSet(b)) => {
|
|
||||||
Rc::make_mut(&mut a).update(b.as_ref());
|
|
||||||
Value::AttrSet(a)
|
|
||||||
}
|
|
||||||
(x @ Value::Catchable(_), _) | (_, x @ Value::Catchable(_)) => x,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&mut self, sym: usize, vm: &'vm VM<'_>) -> Result<&mut Self> {
|
|
||||||
let val = match self {
|
|
||||||
Value::AttrSet(attrs) => attrs
|
|
||||||
.select(sym)
|
|
||||||
.ok_or_else(|| Error::EvalError(format!("{} not found", vm.get_sym(sym)))),
|
|
||||||
Value::Catchable(_) => return Ok(self),
|
|
||||||
_ => Err(Error::EvalError(format!(
|
|
||||||
"cannot select from {:?}",
|
|
||||||
self.typename()
|
|
||||||
))),
|
|
||||||
}?;
|
|
||||||
if let Value::Builtins = val {
|
|
||||||
} else {
|
|
||||||
*self = val;
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select_with_default(&mut self, sym: usize, default: Self) -> Result<&mut Self> {
|
|
||||||
let val = match self {
|
|
||||||
Value::AttrSet(attrs) => attrs.select(sym).unwrap_or(default),
|
|
||||||
Value::Catchable(_) => return Ok(self),
|
|
||||||
_ => {
|
|
||||||
return Err(Error::EvalError(format!(
|
|
||||||
"cannot select from {:?}",
|
|
||||||
self.typename()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Value::Builtins = val {
|
|
||||||
} else {
|
|
||||||
*self = val;
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_attr(&mut self, sym: usize) -> &mut Self {
|
|
||||||
if let Value::AttrSet(attrs) = self {
|
|
||||||
let val = VmConst(Const::Bool(attrs.has_attr(sym)));
|
|
||||||
*self = val;
|
|
||||||
} else if let Value::Catchable(_) = self {
|
|
||||||
} else {
|
|
||||||
*self = VmConst(Const::Bool(false));
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn coerce_to_string(&mut self) -> &mut Self {
|
|
||||||
if let VmConst(Const::String(_)) = self {
|
|
||||||
} else if let Value::Catchable(_) = self {
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force(&mut self, vm: &'vm VM<'_>) -> Result<&mut Self> {
|
|
||||||
if let Value::Thunk(thunk) = self {
|
|
||||||
let value = thunk.force(vm)?;
|
|
||||||
*self = value
|
|
||||||
} else if let Value::ThunkRef(thunk) = self {
|
|
||||||
let value = thunk.force(vm)?;
|
|
||||||
*self = value
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force_deep(&mut self, vm: &'vm VM<'_>) -> Result<&mut Self> {
|
|
||||||
match self {
|
|
||||||
Value::Thunk(thunk) => {
|
|
||||||
let mut value = thunk.force(vm)?;
|
|
||||||
let _ = value.force_deep(vm)?;
|
|
||||||
*self = value;
|
|
||||||
}
|
|
||||||
Value::ThunkRef(thunk) => {
|
|
||||||
let mut value = thunk.force(vm)?;
|
|
||||||
let _ = value.force_deep(vm)?;
|
|
||||||
*self = value;
|
|
||||||
}
|
|
||||||
Value::List(list) => Rc::make_mut(list).force_deep(vm)?,
|
|
||||||
Value::AttrSet(attrs) => Rc::make_mut(attrs).force_deep(vm)?,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_public(&self, vm: &'vm VM, seen: &mut HashSet<Value<'vm>>) -> p::Value {
|
|
||||||
use self::Value::*;
|
|
||||||
use p::Value;
|
|
||||||
if seen.contains(self) {
|
|
||||||
return Value::Repeated;
|
|
||||||
}
|
|
||||||
seen.insert(self.clone());
|
|
||||||
match self {
|
|
||||||
AttrSet(attrs) => attrs.to_public(vm, seen),
|
|
||||||
List(list) => list.to_public(vm, seen),
|
|
||||||
Catchable(catchable) => Value::Catchable(catchable.clone()),
|
|
||||||
Const(cnst) => Value::Const(cnst.clone().into()),
|
|
||||||
Thunk(_) => Value::Thunk,
|
|
||||||
ThunkRef(_) => Value::Thunk,
|
|
||||||
PrimOp(primop) => Value::PrimOp(primop.name),
|
|
||||||
PartialPrimOp(primop) => Value::PartialPrimOp(primop.name),
|
|
||||||
Func(_) => Value::Func,
|
|
||||||
Builtins => Value::Repeated,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Thunk<'vm> {
|
|
||||||
pub thunk: RefCell<_Thunk<'vm>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, IsVariant, Unwrap, Clone)]
|
|
||||||
pub enum _Thunk<'vm> {
|
|
||||||
Code(&'vm OpCodes, OnceCell<Env<'vm>>),
|
|
||||||
SuspendedFrom(*const Thunk<'vm>),
|
|
||||||
Value(Value<'vm>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> Thunk<'vm> {
|
|
||||||
pub fn new(opcodes: &'vm OpCodes) -> Self {
|
|
||||||
Thunk {
|
|
||||||
thunk: RefCell::new(_Thunk::Code(opcodes, OnceCell::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn capture(&self, env: Env<'vm>) {
|
|
||||||
if let _Thunk::Code(_, envcell) = &*self.thunk.borrow() {
|
|
||||||
envcell.get_or_init(|| env);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force(&self, vm: &'vm VM<'_>) -> Result<Value<'vm>> {
|
|
||||||
match &*self.thunk.borrow() {
|
|
||||||
_Thunk::Value(value) => return Ok(value.clone()),
|
|
||||||
_Thunk::SuspendedFrom(from) => {
|
|
||||||
return Err(Error::EvalError(format!(
|
|
||||||
"thunk {:p} already suspended from {from:p} (infinite recursion encountered)",
|
|
||||||
self as *const Thunk
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
_Thunk::Code(..) => (),
|
|
||||||
}
|
|
||||||
let (opcodes, env) = std::mem::replace(
|
|
||||||
&mut *self.thunk.borrow_mut(),
|
|
||||||
_Thunk::SuspendedFrom(self as *const Thunk),
|
|
||||||
)
|
|
||||||
.unwrap_code();
|
|
||||||
let value = vm.eval(opcodes.iter().copied(), env.get().unwrap().clone())?;
|
|
||||||
let _ = std::mem::replace(
|
|
||||||
&mut *self.thunk.borrow_mut(),
|
|
||||||
_Thunk::Value(value.clone().into()),
|
|
||||||
);
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value(&'vm self) -> Option<Value<'vm>> {
|
|
||||||
match &*self.thunk.borrow() {
|
|
||||||
_Thunk::Value(value) => Some(value.clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Thunk<'_> {
|
|
||||||
fn eq(&self, _: &Self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use derive_more::Constructor;
|
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::vm::VM;
|
|
||||||
|
|
||||||
use super::Value;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Constructor)]
|
|
||||||
pub struct PrimOp<'vm> {
|
|
||||||
pub name: &'static str,
|
|
||||||
arity: usize,
|
|
||||||
func: fn(&'vm VM<'_>, Vec<Value<'vm>>) -> Result<Value<'vm>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for PrimOp<'_> {
|
|
||||||
fn eq(&self, _: &Self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> PrimOp<'vm> {
|
|
||||||
pub fn call(&self, vm: &'vm VM<'_>, args: Vec<Value<'vm>>) -> Result<Value<'vm>> {
|
|
||||||
if (args.len()) < self.arity {
|
|
||||||
Value::PartialPrimOp(
|
|
||||||
PartialPrimOp {
|
|
||||||
name: self.name,
|
|
||||||
arity: self.arity - args.len(),
|
|
||||||
args,
|
|
||||||
func: self.func,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
} else if args.len() == self.arity {
|
|
||||||
(self.func)(vm, args)
|
|
||||||
} else {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PartialPrimOp<'vm> {
|
|
||||||
pub name: &'static str,
|
|
||||||
arity: usize,
|
|
||||||
args: Vec<Value<'vm>>,
|
|
||||||
func: fn(&'vm VM<'_>, Vec<Value<'vm>>) -> Result<Value<'vm>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for PartialPrimOp<'_> {
|
|
||||||
fn eq(&self, _: &Self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> PartialPrimOp<'vm> {
|
|
||||||
pub fn call(self: &Rc<Self>, vm: &'vm VM<'_>, args: Vec<Value<'vm>>) -> Result<Value<'vm>> {
|
|
||||||
let len = args.len();
|
|
||||||
let mut self_clone = self.clone();
|
|
||||||
let self_mut = Rc::make_mut(&mut self_clone);
|
|
||||||
self_mut.args.extend(args);
|
|
||||||
self_mut.arity -= len;
|
|
||||||
if self_mut.arity > 0 {
|
|
||||||
Value::PartialPrimOp(self_clone).ok()
|
|
||||||
} else if self_mut.arity == 0 {
|
|
||||||
let args = std::mem::replace(&mut self_mut.args, Vec::new());
|
|
||||||
(self.func)(vm, args)
|
|
||||||
} else {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// TODO: Contextful String
|
|
||||||
|
|
||||||
use ecow::EcoString;
|
|
||||||
use rpds::List;
|
|
||||||
|
|
||||||
pub struct StringContext {
|
|
||||||
context: List<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StringContext {
|
|
||||||
pub fn new() -> StringContext {
|
|
||||||
StringContext {
|
|
||||||
context: List::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContextfulString {
|
|
||||||
string: EcoString,
|
|
||||||
context: StringContext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextfulString {
|
|
||||||
pub fn new(string: EcoString) -> ContextfulString {
|
|
||||||
ContextfulString {
|
|
||||||
string,
|
|
||||||
context: StringContext::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod common;
|
|
||||||
pub mod internal;
|
|
||||||
pub mod public;
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
|
||||||
|
|
||||||
use derive_more::{IsVariant, Unwrap};
|
|
||||||
use ecow::EcoString;
|
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
use super::super::internal as i;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, IsVariant, Unwrap)]
|
|
||||||
pub enum Const {
|
|
||||||
Bool(bool),
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
String(EcoString),
|
|
||||||
Null,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Const {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
use Const::*;
|
|
||||||
match self {
|
|
||||||
Bool(b) => write!(f, "{b}"),
|
|
||||||
Int(i) => write!(f, "{i}"),
|
|
||||||
Float(float) => write!(f, "{float}"),
|
|
||||||
String(s) => write!(f, "{s}"),
|
|
||||||
Null => write!(f, "null"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i::Const> for Const {
|
|
||||||
fn from(value: i::Const) -> Self {
|
|
||||||
use i::Const::*;
|
|
||||||
match value {
|
|
||||||
Bool(bool) => Const::Bool(bool),
|
|
||||||
Int(int) => Const::Int(int),
|
|
||||||
Float(float) => Const::Float(float),
|
|
||||||
String(string) => Const::String(string),
|
|
||||||
Null => Const::Null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for Const {
|
|
||||||
fn from(value: bool) -> Self {
|
|
||||||
Const::Bool(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i64> for Const {
|
|
||||||
fn from(value: i64) -> Self {
|
|
||||||
Const::Int(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for Const {
|
|
||||||
fn from(value: f64) -> Self {
|
|
||||||
Const::Float(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcoString> for Const {
|
|
||||||
fn from(value: EcoString) -> Self {
|
|
||||||
Const::String(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Const {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Const::String(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for Const {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Const::String(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a Const> for &'a bool {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Const::Bool(b) => Ok(b),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> TryFrom<&'a Const> for &'a i64 {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Const::Int(int) => Ok(int),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a Const> for &'a f64 {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Const::Float(float) => Ok(float),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a Const> for &'a str {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: &'a Const) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Const::String(string) => Ok(string),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Const {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
use Const::*;
|
|
||||||
match (self, other) {
|
|
||||||
(Bool(a), Bool(b)) => a == b,
|
|
||||||
(Int(a), Int(b)) => a == b,
|
|
||||||
(Float(a), Float(b)) => a == b,
|
|
||||||
(String(a), String(b)) => a == b,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Const {}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use derive_more::{Constructor, IsVariant, Unwrap};
|
|
||||||
use ecow::EcoString;
|
|
||||||
use regex::Regex;
|
|
||||||
use rpds::VectorSync;
|
|
||||||
|
|
||||||
use super::common::*;
|
|
||||||
|
|
||||||
mod cnst;
|
|
||||||
|
|
||||||
pub use cnst::Const;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
|
||||||
pub struct Symbol(EcoString);
|
|
||||||
|
|
||||||
impl<T: Into<EcoString>> From<T> for Symbol {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Symbol(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Symbol {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
if self.normal() {
|
|
||||||
write!(f, r#""{}""#, self.0)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static REGEX: LazyLock<Regex> =
|
|
||||||
LazyLock::new(|| Regex::new(r#"^[a-zA-Z\_][a-zA-Z0-9\_\'\-]*$"#).unwrap());
|
|
||||||
impl Symbol {
|
|
||||||
fn normal(&self) -> bool {
|
|
||||||
!REGEX.is_match(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Symbol {
|
|
||||||
type Target = str;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Symbol {
|
|
||||||
pub fn into_inner(self) -> EcoString {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_inner(&self) -> &EcoString {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Constructor, Clone, PartialEq)]
|
|
||||||
pub struct AttrSet {
|
|
||||||
data: HashMap<Symbol, Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for AttrSet {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
use Value::*;
|
|
||||||
write!(f, "{{ ")?;
|
|
||||||
for (k, v) in self.data.iter() {
|
|
||||||
match v {
|
|
||||||
List(_) => write!(f, "{k:?} = [ ... ]; ")?,
|
|
||||||
AttrSet(_) => write!(f, "{k:?} = {{ ... }}; ")?,
|
|
||||||
v => write!(f, "{k:?} = {v:?}; ")?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "}}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for AttrSet {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
write!(f, "{{ ")?;
|
|
||||||
for (k, v) in self.data.iter() {
|
|
||||||
write!(f, "{k} = {v}; ")?;
|
|
||||||
}
|
|
||||||
write!(f, "}}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Constructor, Clone, Debug, PartialEq)]
|
|
||||||
pub struct List {
|
|
||||||
data: VectorSync<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for List {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
write!(f, "[ ")?;
|
|
||||||
for v in self.data.iter() {
|
|
||||||
write!(f, "{v} ")?;
|
|
||||||
}
|
|
||||||
write!(f, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
|
||||||
pub enum Value {
|
|
||||||
Const(Const),
|
|
||||||
AttrSet(AttrSet),
|
|
||||||
List(List),
|
|
||||||
Catchable(Catchable),
|
|
||||||
Thunk,
|
|
||||||
Func,
|
|
||||||
PrimOp(&'static str),
|
|
||||||
PartialPrimOp(&'static str),
|
|
||||||
Repeated,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Value {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
use Value::*;
|
|
||||||
match self {
|
|
||||||
Const(x) => write!(f, "{x}"),
|
|
||||||
AttrSet(x) => write!(f, "{x}"),
|
|
||||||
List(x) => write!(f, "{x}"),
|
|
||||||
Catchable(x) => write!(f, "{x}"),
|
|
||||||
Thunk => write!(f, "<CODE>"),
|
|
||||||
Func => write!(f, "<LAMBDA>"),
|
|
||||||
PrimOp(x) => write!(f, "<PRIMOP {x}>"),
|
|
||||||
PartialPrimOp(x) => write!(f, "<PARTIAL PRIMOP {x}>"),
|
|
||||||
Repeated => write!(f, "<REPEATED>"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::ty::internal::{AttrSet, Value};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct Env<'vm> {
|
|
||||||
last: Option<Rc<Env<'vm>>>,
|
|
||||||
map: Rc<HashMap<usize, Value<'vm>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm> Env<'vm> {
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Env::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup(&self, symbol: usize) -> Option<Value<'vm>> {
|
|
||||||
if let Some(val) = self.map.get(&symbol).cloned() {
|
|
||||||
return Some(val);
|
|
||||||
}
|
|
||||||
self.last.as_ref().map(|env| env.lookup(symbol)).flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, symbol: usize, value: Value<'vm>) {
|
|
||||||
Rc::make_mut(&mut self.map).insert(symbol, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enter(self, new: impl Iterator<Item = (usize, Value<'vm>)>) -> Self {
|
|
||||||
let map = Rc::new(new.collect());
|
|
||||||
let last = Some(
|
|
||||||
Env {
|
|
||||||
last: self.last,
|
|
||||||
map: self.map,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
Env { last, map }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enter_with(self, new: Rc<AttrSet<'vm>>) -> Self {
|
|
||||||
let map = Rc::new(
|
|
||||||
new.as_inner()
|
|
||||||
.iter()
|
|
||||||
.map(|(&k, v)| {
|
|
||||||
(
|
|
||||||
k,
|
|
||||||
if let Value::Builtins = v {
|
|
||||||
Value::AttrSet(new.clone())
|
|
||||||
} else {
|
|
||||||
v.clone()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
let last = Some(
|
|
||||||
Env {
|
|
||||||
last: self.last.clone(),
|
|
||||||
map: self.map.clone(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
Env { last, map }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn leave(self) -> Self {
|
|
||||||
self.last.unwrap().as_ref().clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use inkwell::builder::Builder;
|
|
||||||
use inkwell::context::Context;
|
|
||||||
use inkwell::execution_engine::ExecutionEngine;
|
|
||||||
use inkwell::module::Module;
|
|
||||||
use inkwell::types::{BasicType, BasicTypeEnum, FunctionType, StructType};
|
|
||||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue};
|
|
||||||
use inkwell::{AddressSpace, OptimizationLevel};
|
|
||||||
|
|
||||||
use crate::stack::Stack;
|
|
||||||
|
|
||||||
use super::STACK_SIZE;
|
|
||||||
|
|
||||||
#[repr(usize)]
|
|
||||||
pub enum ValueTag {
|
|
||||||
Int,
|
|
||||||
String,
|
|
||||||
Bool,
|
|
||||||
AttrSet,
|
|
||||||
List,
|
|
||||||
Function,
|
|
||||||
Thunk,
|
|
||||||
Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct JITValue {
|
|
||||||
tag: ValueTag,
|
|
||||||
data: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct JITContext<'ctx> {
|
|
||||||
context: &'ctx Context,
|
|
||||||
module: Module<'ctx>,
|
|
||||||
builder: Builder<'ctx>,
|
|
||||||
execution_engine: ExecutionEngine<'ctx>,
|
|
||||||
stack: Stack<BasicValueEnum<'ctx>, STACK_SIZE>,
|
|
||||||
cur_func: Option<FunctionValue<'ctx>>,
|
|
||||||
|
|
||||||
value_type: StructType<'ctx>,
|
|
||||||
func_type: FunctionType<'ctx>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ctx> JITContext<'ctx> {
|
|
||||||
pub fn new(context: &'ctx Context) -> Pin<Box<Self>> {
|
|
||||||
let module = context.create_module("nixjit");
|
|
||||||
let stack = Stack::new();
|
|
||||||
|
|
||||||
let int_type = context.i64_type();
|
|
||||||
let pointer_type = context.ptr_type(AddressSpace::default());
|
|
||||||
let value_type = context.struct_type(&[int_type.into(), int_type.into()], false);
|
|
||||||
let func_type = value_type.fn_type(
|
|
||||||
&[pointer_type.into(), pointer_type.into(), value_type.into()],
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
Pin::new(Box::new(JITContext {
|
|
||||||
execution_engine: module
|
|
||||||
.create_jit_execution_engine(OptimizationLevel::Default)
|
|
||||||
.unwrap(),
|
|
||||||
builder: context.create_builder(),
|
|
||||||
context,
|
|
||||||
module,
|
|
||||||
stack,
|
|
||||||
cur_func: None,
|
|
||||||
|
|
||||||
value_type,
|
|
||||||
func_type,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_int(&self, int: i64) -> IntValue {
|
|
||||||
self.context.i64_type().const_int(int as u64, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_trace(&mut self) {}
|
|
||||||
}
|
|
||||||
267
src/vm/mod.rs
267
src/vm/mod.rs
@@ -1,267 +0,0 @@
|
|||||||
use hashbrown::{HashMap, HashSet};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use crate::builtins::env;
|
|
||||||
use crate::bytecode::{BinOp, Func as F, OpCode, OpCodes, Program, UnOp};
|
|
||||||
use crate::error::*;
|
|
||||||
use crate::ty::internal::*;
|
|
||||||
use crate::ty::public::{self as p, Symbol};
|
|
||||||
|
|
||||||
use crate::stack::Stack;
|
|
||||||
|
|
||||||
use derive_more::Constructor;
|
|
||||||
use ecow::EcoString;
|
|
||||||
pub use env::Env;
|
|
||||||
pub use jit::JITContext;
|
|
||||||
|
|
||||||
mod env;
|
|
||||||
mod jit;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test;
|
|
||||||
|
|
||||||
pub const STACK_SIZE: usize = 8 * 1024 / size_of::<Value>();
|
|
||||||
|
|
||||||
pub fn run(prog: Program, jit: Pin<Box<JITContext<'_>>>) -> Result<p::Value> {
|
|
||||||
let vm = VM::new(
|
|
||||||
prog.thunks,
|
|
||||||
prog.funcs,
|
|
||||||
RefCell::new(prog.symbols),
|
|
||||||
RefCell::new(prog.symmap),
|
|
||||||
prog.consts,
|
|
||||||
jit,
|
|
||||||
);
|
|
||||||
let env = env(&vm);
|
|
||||||
let mut seen = HashSet::new();
|
|
||||||
let value = vm
|
|
||||||
.eval(prog.top_level.into_iter(), env)?
|
|
||||||
.to_public(&vm, &mut seen);
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Constructor)]
|
|
||||||
pub struct VM<'jit> {
|
|
||||||
thunks: Box<[OpCodes]>,
|
|
||||||
funcs: Box<[F]>,
|
|
||||||
symbols: RefCell<Vec<EcoString>>,
|
|
||||||
symmap: RefCell<HashMap<EcoString, usize>>,
|
|
||||||
consts: Box<[Const]>,
|
|
||||||
jit: Pin<Box<JITContext<'jit>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'vm, 'jit: 'vm> VM<'jit> {
|
|
||||||
pub fn get_thunk(&self, idx: usize) -> &OpCodes {
|
|
||||||
&self.thunks[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_func(&self, idx: usize) -> &F {
|
|
||||||
&self.funcs[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_sym(&self, idx: usize) -> Symbol {
|
|
||||||
self.symbols.borrow()[idx].clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_sym(&self, sym: impl Into<EcoString>) -> usize {
|
|
||||||
let sym = sym.into();
|
|
||||||
if let Some(&idx) = self.symmap.borrow().get(&sym) {
|
|
||||||
idx
|
|
||||||
} else {
|
|
||||||
self.symmap
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(sym.clone(), self.symbols.borrow().len());
|
|
||||||
self.symbols.borrow_mut().push(sym);
|
|
||||||
self.symbols.borrow().len() - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval(
|
|
||||||
&'vm self,
|
|
||||||
opcodes: impl Iterator<Item = OpCode>,
|
|
||||||
mut env: Env<'vm>,
|
|
||||||
) -> Result<Value<'vm>> {
|
|
||||||
let mut stack = Stack::<_, STACK_SIZE>::new();
|
|
||||||
let mut iter = opcodes.into_iter();
|
|
||||||
while let Some(opcode) = iter.next() {
|
|
||||||
let jmp = self.single_op(opcode, &mut stack, &mut env)?;
|
|
||||||
for _ in 0..jmp {
|
|
||||||
iter.next().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(stack.len(), 1);
|
|
||||||
let mut ret = stack.pop();
|
|
||||||
ret.force(self)?;
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn single_op<'s, const CAP: usize>(
|
|
||||||
&'vm self,
|
|
||||||
opcode: OpCode,
|
|
||||||
stack: &'s mut Stack<Value<'vm>, CAP>,
|
|
||||||
env: &mut Env<'vm>,
|
|
||||||
) -> Result<usize> {
|
|
||||||
match opcode {
|
|
||||||
OpCode::Illegal => panic!("illegal opcode"),
|
|
||||||
OpCode::Const { idx } => stack.push(Value::Const(self.consts[idx].clone()))?,
|
|
||||||
OpCode::LoadThunk { idx } => {
|
|
||||||
stack.push(Value::Thunk(Thunk::new(self.get_thunk(idx)).into()))?
|
|
||||||
}
|
|
||||||
OpCode::CaptureEnv => match stack.tos()? {
|
|
||||||
Value::Thunk(thunk) => thunk.capture(env.clone()),
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
OpCode::ForceValue => {
|
|
||||||
stack.tos_mut()?.force(self)?;
|
|
||||||
}
|
|
||||||
OpCode::Jmp { step } => return Ok(step),
|
|
||||||
OpCode::JmpIfTrue { step } => {
|
|
||||||
if let Value::Const(Const::Bool(true)) = stack.pop() {
|
|
||||||
return Ok(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OpCode::JmpIfFalse { step } => {
|
|
||||||
if let Value::Const(Const::Bool(false)) = stack.pop() {
|
|
||||||
return Ok(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OpCode::Call { arity } => {
|
|
||||||
let mut args = Vec::with_capacity(arity);
|
|
||||||
for _ in 0..arity {
|
|
||||||
args.insert(0, stack.pop());
|
|
||||||
}
|
|
||||||
let mut func = stack.pop();
|
|
||||||
func.force(self)?;
|
|
||||||
stack.push(func.call(self, args)?)?;
|
|
||||||
}
|
|
||||||
OpCode::Func { idx } => {
|
|
||||||
let func = self.get_func(idx);
|
|
||||||
stack.push(Value::Func(Func::new(func, env.clone(), None).into()))?;
|
|
||||||
}
|
|
||||||
OpCode::UnOp { op } => {
|
|
||||||
use UnOp::*;
|
|
||||||
let mut value = stack.pop();
|
|
||||||
value.force(self)?;
|
|
||||||
stack.push(match op {
|
|
||||||
Neg => value.neg(),
|
|
||||||
Not => value.not(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
OpCode::BinOp { op } => {
|
|
||||||
use BinOp::*;
|
|
||||||
let mut rhs = stack.pop();
|
|
||||||
let mut lhs = stack.pop();
|
|
||||||
lhs.force(self)?;
|
|
||||||
rhs.force(self)?;
|
|
||||||
stack.push(match op {
|
|
||||||
Add => lhs.add(rhs),
|
|
||||||
Sub => lhs.add(rhs.neg()),
|
|
||||||
Mul => lhs.mul(rhs),
|
|
||||||
Div => lhs.div(rhs)?,
|
|
||||||
And => lhs.and(rhs),
|
|
||||||
Or => lhs.or(rhs),
|
|
||||||
Eq => lhs.eq(rhs, self),
|
|
||||||
Lt => lhs.lt(rhs),
|
|
||||||
Con => lhs.concat(rhs),
|
|
||||||
Upd => lhs.update(rhs),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
OpCode::ConcatString => {
|
|
||||||
let mut rhs = stack.pop();
|
|
||||||
rhs.force(self)?;
|
|
||||||
stack.tos_mut()?.concat_string(rhs);
|
|
||||||
}
|
|
||||||
OpCode::Path => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
OpCode::List => {
|
|
||||||
stack.push(Value::List(List::empty().into()))?;
|
|
||||||
}
|
|
||||||
OpCode::PushElem => {
|
|
||||||
let elem = stack.pop();
|
|
||||||
stack.tos_mut()?.push(elem);
|
|
||||||
}
|
|
||||||
OpCode::AttrSet { cap } => {
|
|
||||||
stack.push(Value::AttrSet(AttrSet::with_capacity(cap).into()))?;
|
|
||||||
}
|
|
||||||
OpCode::FinalizeRec => {
|
|
||||||
let env = env.clone().enter(
|
|
||||||
stack
|
|
||||||
.tos()?
|
|
||||||
.clone()
|
|
||||||
.unwrap_attr_set()
|
|
||||||
.as_inner()
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (k.clone(), v.clone())),
|
|
||||||
);
|
|
||||||
stack.tos_mut()?.as_mut().unwrap_attr_set().capture(&env);
|
|
||||||
}
|
|
||||||
OpCode::PushStaticAttr { name } => {
|
|
||||||
let val = stack.pop();
|
|
||||||
stack.tos_mut()?.push_attr(name, val);
|
|
||||||
}
|
|
||||||
OpCode::PushDynamicAttr => {
|
|
||||||
let val = stack.pop();
|
|
||||||
let mut sym = stack.pop();
|
|
||||||
sym.force(self)?.coerce_to_string();
|
|
||||||
let sym = self.new_sym(sym.unwrap_const().unwrap_string());
|
|
||||||
stack.tos_mut()?.push_attr(sym, val);
|
|
||||||
}
|
|
||||||
OpCode::Select { sym } => {
|
|
||||||
stack.tos_mut()?.force(self)?.select(sym, self)?;
|
|
||||||
}
|
|
||||||
OpCode::SelectOrDefault { sym } => {
|
|
||||||
let default = stack.pop();
|
|
||||||
stack
|
|
||||||
.tos_mut()?
|
|
||||||
.force(self)?
|
|
||||||
.select_with_default(sym, default)?;
|
|
||||||
}
|
|
||||||
OpCode::SelectDynamic => {
|
|
||||||
let mut val = stack.pop();
|
|
||||||
val.force(self)?;
|
|
||||||
val.coerce_to_string();
|
|
||||||
let sym = self.new_sym(val.unwrap_const().unwrap_string());
|
|
||||||
stack.tos_mut()?.force(self)?.select(sym, self)?;
|
|
||||||
}
|
|
||||||
OpCode::SelectDynamicOrDefault => {
|
|
||||||
let default = stack.pop();
|
|
||||||
let mut val = stack.pop();
|
|
||||||
val.force(self)?;
|
|
||||||
val.coerce_to_string();
|
|
||||||
let sym = self.new_sym(val.unwrap_const().unwrap_string());
|
|
||||||
stack
|
|
||||||
.tos_mut()?
|
|
||||||
.force(self)?
|
|
||||||
.select_with_default(sym, default)?;
|
|
||||||
}
|
|
||||||
OpCode::HasAttr { sym } => {
|
|
||||||
stack.tos_mut()?.force(self)?.has_attr(sym);
|
|
||||||
}
|
|
||||||
OpCode::HasDynamicAttr => {
|
|
||||||
let mut val = stack.pop();
|
|
||||||
val.coerce_to_string();
|
|
||||||
let sym = self.new_sym(val.unwrap_const().unwrap_string());
|
|
||||||
stack.tos_mut()?.force(self)?.has_attr(sym);
|
|
||||||
}
|
|
||||||
OpCode::LookUp { sym } => {
|
|
||||||
stack.push(env.lookup(sym).ok_or_else(|| {
|
|
||||||
Error::EvalError(format!("{} not found", self.get_sym(sym)))
|
|
||||||
})?)?;
|
|
||||||
}
|
|
||||||
OpCode::EnterEnv => match stack.pop() {
|
|
||||||
Value::AttrSet(attrs) => *env = env.clone().enter_with(attrs),
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
OpCode::LeaveEnv => *env = env.clone().leave(),
|
|
||||||
OpCode::Assert => {
|
|
||||||
if !stack.pop().unwrap_const().unwrap_bool() {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user