From 03556a2ada9b6231ba127227f2acbd4b16ef2fac Mon Sep 17 00:00:00 2001 From: Ara Adkins Date: Wed, 9 Oct 2024 13:58:39 -0600 Subject: [PATCH] Implement the module mapping pass This pass runs an analysis of LLVM modules to resolve all top-level entities. The results are written out as pass data, and are intended for use as part of a consistency check during the compilation to FLO. As part of implementing this pass, this commit also implements: - A proxy for the portions of the LLVM type system that we support, enabling us to do pre- and during-compilation consistency checking where necessary. - A parser for LLVM's data-layout specifications, allowing us to ensure that the provided modules are not making any assumptions that would be unsafe for our target machine. --- Cargo.lock | 1715 ++--------------- Cargo.toml | 8 - crates/cli/src/main.rs | 2 +- crates/compiler/Cargo.toml | 5 +- crates/compiler/src/compile/mod.rs | 193 -- .../src/compile/pass/analysis/module_map.rs | 81 - crates/compiler/src/compile/source/mod.rs | 113 -- crates/compiler/src/constant.rs | 4 + crates/compiler/src/lib.rs | 194 +- crates/compiler/src/llvm/data_layout.rs | 1415 ++++++++++++++ crates/compiler/src/llvm/mod.rs | 37 + crates/compiler/src/llvm/typesystem.rs | 544 ++++++ .../src/{compile => }/pass/analysis/mod.rs | 0 .../compiler/src/pass/analysis/module_map.rs | 455 +++++ .../compiler/src/{compile => }/pass/data.rs | 22 +- crates/compiler/src/{compile => }/pass/mod.rs | 33 +- crates/compiler/src/polyfill.rs | 3 +- crates/compiler/src/source/mod.rs | 115 ++ .../src/{compile => }/source/module.rs | 2 +- crates/driver/Cargo.toml | 3 - crates/error/src/compile.rs | 23 +- 21 files changed, 3003 insertions(+), 1964 deletions(-) delete mode 100644 crates/compiler/src/compile/mod.rs delete mode 100644 crates/compiler/src/compile/pass/analysis/module_map.rs delete mode 100644 crates/compiler/src/compile/source/mod.rs create mode 100644 crates/compiler/src/constant.rs create mode 100644 crates/compiler/src/llvm/data_layout.rs create mode 100644 crates/compiler/src/llvm/mod.rs create mode 100644 crates/compiler/src/llvm/typesystem.rs rename crates/compiler/src/{compile => }/pass/analysis/mod.rs (100%) create mode 100644 crates/compiler/src/pass/analysis/module_map.rs rename crates/compiler/src/{compile => }/pass/data.rs (87%) rename crates/compiler/src/{compile => }/pass/mod.rs (90%) create mode 100644 crates/compiler/src/source/mod.rs rename crates/compiler/src/{compile => }/source/module.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index d844c2f..6ca90f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,15 +14,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "aliasable" version = "0.1.3" @@ -71,7 +62,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -81,7 +72,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -100,386 +91,12 @@ dependencies = [ "yansi", ] -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - [[package]] name = "bimap" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bstr" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "cairo-lang-compiler" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5852668d1c6966b34d6e4fe249732769ab9cb2012c201e3889d8119f206760a0" -dependencies = [ - "anyhow", - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-lowering", - "cairo-lang-parser", - "cairo-lang-project", - "cairo-lang-semantic", - "cairo-lang-sierra", - "cairo-lang-sierra-generator", - "cairo-lang-syntax", - "cairo-lang-utils", - "indoc", - "rayon", - "rust-analyzer-salsa", - "semver", - "smol_str", - "thiserror", -] - -[[package]] -name = "cairo-lang-debug" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0644fab571f598547993936918c85f0e89b0bbc15140ca3ea723bff376be07d" -dependencies = [ - "cairo-lang-utils", -] - -[[package]] -name = "cairo-lang-defs" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f5f437d75ac25644880458effde562edcac45a888d27f2e497d30c6450fa97d" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-parser", - "cairo-lang-syntax", - "cairo-lang-utils", - "itertools 0.12.1", - "rust-analyzer-salsa", - "smol_str", -] - -[[package]] -name = "cairo-lang-diagnostics" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec5b44d3eaf50e28e068d163e56b9effcea6afe3625c32dd96418d2d4ebc34c" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-filesystem", - "cairo-lang-utils", - "itertools 0.12.1", -] - -[[package]] -name = "cairo-lang-filesystem" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323a2385e000589f7591f8a46599b4a462db6e36e5935bad3bceddcc1a1608e1" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-utils", - "path-clean", - "rust-analyzer-salsa", - "semver", - "serde", - "smol_str", -] - -[[package]] -name = "cairo-lang-formatter" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf9cf637e12d41260dc59f3d988c76a6347424913ac8b6b8449ff3e79b59750" -dependencies = [ - "anyhow", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-parser", - "cairo-lang-syntax", - "cairo-lang-utils", - "diffy", - "ignore", - "itertools 0.12.1", - "rust-analyzer-salsa", - "serde", - "smol_str", - "thiserror", -] - -[[package]] -name = "cairo-lang-lowering" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d71bc5b1f19a00eb662c2cac33259b16b9cdbf9c005047aca0d538c13936407" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-parser", - "cairo-lang-proc-macros", - "cairo-lang-semantic", - "cairo-lang-syntax", - "cairo-lang-utils", - "id-arena", - "itertools 0.12.1", - "log", - "num-bigint", - "num-traits", - "rust-analyzer-salsa", - "smol_str", -] - -[[package]] -name = "cairo-lang-parser" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d939d258e26ace0f3cb1e50338ae18981a7505e3c20eabd24a62d70ee862d6c" -dependencies = [ - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-syntax", - "cairo-lang-syntax-codegen", - "cairo-lang-utils", - "colored", - "itertools 0.12.1", - "num-bigint", - "num-traits", - "rust-analyzer-salsa", - "smol_str", - "unescaper", -] - -[[package]] -name = "cairo-lang-plugins" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67a553a6d2d2b54264e77e3c8cb5bc866b40b32d5e2144a58b74c559c7e289f" -dependencies = [ - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-parser", - "cairo-lang-syntax", - "cairo-lang-utils", - "indent", - "indoc", - "itertools 0.12.1", - "rust-analyzer-salsa", - "smol_str", -] - -[[package]] -name = "cairo-lang-proc-macros" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33b5f4502b7efde6ac07fd5468f6dae15d88760aeece3d57a7bc4c224ba693e" -dependencies = [ - "cairo-lang-debug", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "cairo-lang-project" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63d6a3cc86a79a29978acaaf6f94738c5487e265247fe06c7bf359645d8c200" -dependencies = [ - "cairo-lang-filesystem", - "cairo-lang-utils", - "serde", - "smol_str", - "thiserror", - "toml", -] - -[[package]] -name = "cairo-lang-semantic" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c284031fd14796dad91483c3039d7929f8440e1e9e334017744b1d22df5aa8" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-parser", - "cairo-lang-plugins", - "cairo-lang-proc-macros", - "cairo-lang-syntax", - "cairo-lang-test-utils", - "cairo-lang-utils", - "id-arena", - "indoc", - "itertools 0.12.1", - "num-bigint", - "num-traits", - "rust-analyzer-salsa", - "smol_str", - "toml", -] - -[[package]] -name = "cairo-lang-sierra" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891488c1a3184ce91679f5bdb63015a1d24769a48bd07e5d51a1779d0031dfbe" -dependencies = [ - "anyhow", - "cairo-lang-utils", - "const-fnv1a-hash", - "convert_case", - "derivative", - "itertools 0.12.1", - "lalrpop", - "lalrpop-util", - "num-bigint", - "num-integer", - "num-traits", - "regex", - "rust-analyzer-salsa", - "serde", - "serde_json", - "sha3", - "smol_str", - "starknet-types-core", - "thiserror", -] - -[[package]] -name = "cairo-lang-sierra-generator" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5cc616e8df44c4d685fe3c5f81f35ebbda57225098b35cea8602457c45c9e96" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-lowering", - "cairo-lang-parser", - "cairo-lang-semantic", - "cairo-lang-sierra", - "cairo-lang-syntax", - "cairo-lang-utils", - "itertools 0.12.1", - "num-traits", - "rust-analyzer-salsa", - "serde", - "serde_json", - "smol_str", -] - -[[package]] -name = "cairo-lang-syntax" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db0776c3d06cea65d7afe7a3c7685f6867eb6d951cf505caf35abfd1746773b" -dependencies = [ - "cairo-lang-debug", - "cairo-lang-filesystem", - "cairo-lang-utils", - "num-bigint", - "num-traits", - "rust-analyzer-salsa", - "smol_str", - "unescaper", -] - -[[package]] -name = "cairo-lang-syntax-codegen" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce0f7fa01c26cc731bc1d6350ac02fae91a68b5fdf60e684f991e861715adc4" -dependencies = [ - "genco", - "xshell", -] - -[[package]] -name = "cairo-lang-test-utils" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630a070a69c387eee9c0eda65e4f2508d129d4fbe081091077e661020ab95637" -dependencies = [ - "cairo-lang-formatter", - "cairo-lang-utils", - "colored", - "log", - "pretty_assertions", -] - -[[package]] -name = "cairo-lang-utils" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73104609a7d865e4cd1de9cbf4e750683d076b6d0233bf81be511df274a26916" -dependencies = [ - "hashbrown 0.14.5", - "indexmap 2.5.0", - "itertools 0.12.1", - "num-bigint", - "num-traits", - "schemars", - "serde", -] - [[package]] name = "cc" version = "1.1.15" @@ -495,6 +112,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown", + "stacker", +] + [[package]] name = "clap" version = "4.5.18" @@ -529,1000 +156,262 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] -name = "colored" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" -dependencies = [ - "lazy_static", - "windows-sys 0.48.0", -] - -[[package]] -name = "const-fnv1a-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" - -[[package]] -name = "convert_case" -version = "0.6.0" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "unicode-segmentation", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "cpufeatures" -version = "0.2.14" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" -dependencies = [ - "libc", -] +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "crossbeam-deque" -version = "0.8.5" +name = "either" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "crossbeam-utils", + "ahash", + "allocator-api2", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "crypto-common" -version = "0.1.6" +name = "inkwell" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "40fb405537710d51f6bdbc8471365ddd4cd6d3a3c3ad6e0c8291691031ba94b2" dependencies = [ - "generic-array", - "typenum", + "either", + "inkwell_internals", + "libc", + "llvm-sys", + "once_cell", + "thiserror", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "inkwell_internals" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "9dd28cfd4cfba665d47d31c08a6ba637eed16770abca2eccbbc3ca831fef1e44" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "diffy" -version = "0.3.0" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e616e59155c92257e84970156f506287853355f58cd4a6eb167385722c32b790" -dependencies = [ - "nu-ansi-term", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "digest" -version = "0.10.7" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ - "block-buffer", - "crypto-common", + "either", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "cfg-if", - "dirs-sys-next", + "either", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "downcast-rs" -version = "1.2.1" +name = "libc" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "dyn-clone" -version = "1.0.17" +name = "llvm-sys" +version = "180.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "genco" -version = "0.17.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afac3cbb14db69ac9fef9cdb60d8a87e39a7a527f85a81a923436efa40ad42c6" -dependencies = [ - "genco-macros", - "relative-path", - "smallvec", -] - -[[package]] -name = "genco-macros" -version = "0.17.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553630feadf7b76442b0849fd25fdf89b860d933623aec9693fed19af0400c78" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "globset" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", - "serde", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - -[[package]] -name = "ignore" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indent" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1a0777d972970f204fdf8ef319f1f4f8459131636d7e3c96c5d59570d0fa6" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", - "serde", -] - -[[package]] -name = "indoc" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" - -[[package]] -name = "inkwell" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fb405537710d51f6bdbc8471365ddd4cd6d3a3c3ad6e0c8291691031ba94b2" -dependencies = [ - "either", - "inkwell_internals", - "libc", - "llvm-sys", - "once_cell", - "thiserror", -] - -[[package]] -name = "inkwell_internals" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd28cfd4cfba665d47d31c08a6ba637eed16770abca2eccbbc3ca831fef1e44" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "lambdaworks-crypto" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" -dependencies = [ - "lambdaworks-math", - "serde", - "sha2", - "sha3", -] - -[[package]] -name = "lambdaworks-math" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] - -[[package]] -name = "llvm-sys" -version = "180.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778fa5fa02e32728e718f11eec147e6f134137399ab02fd2c13d32476337affa" -dependencies = [ - "anyhow", - "cc", - "lazy_static", - "libc", - "regex-lite", - "semver", -] - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "778fa5fa02e32728e718f11eec147e6f134137399ab02fd2c13d32476337affa" +dependencies = [ + "anyhow", + "cc", + "lazy_static", + "libc", + "regex-lite", + "semver", +] [[package]] name = "ltc-cli" -version = "0.1.0" -dependencies = [ - "ariadne", - "clap", - "itertools 0.13.0", - "ltc-compiler", - "tracing", -] - -[[package]] -name = "ltc-compiler" -version = "0.1.0" -dependencies = [ - "anyhow", - "bimap", - "cairo-lang-compiler", - "cairo-lang-debug", - "cairo-lang-lowering", - "cairo-lang-semantic", - "clap", - "derivative", - "downcast-rs", - "inkwell", - "itertools 0.13.0", - "ltc-errors", - "ouroboros", - "tracing", -] - -[[package]] -name = "ltc-driver" -version = "0.1.0" -dependencies = [ - "ariadne", - "cairo-lang-lowering", - "cairo-lang-sierra-generator", - "cairo-lang-utils", - "itertools 0.13.0", - "thiserror", - "tracing", -] - -[[package]] -name = "ltc-errors" -version = "0.1.0" -dependencies = [ - "ariadne", - "inkwell", - "thiserror", -] - -[[package]] -name = "ltc-flir" -version = "0.1.0" - -[[package]] -name = "ltc-rust-test-input" -version = "0.1.0" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "oorandom" -version = "11.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" - -[[package]] -name = "ouroboros" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" -dependencies = [ - "heck", - "itertools 0.12.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "path-clean" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap 2.5.0", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", - "version_check", - "yansi", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +version = "0.1.0" +dependencies = [ + "ariadne", + "clap", + "itertools 0.13.0", + "ltc-compiler", + "tracing", +] [[package]] -name = "rust-analyzer-salsa" -version = "0.17.0-pre.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719825638c59fd26a55412a24561c7c5bcf54364c88b9a7a04ba08a6eafaba8d" +name = "ltc-compiler" +version = "0.1.0" dependencies = [ - "indexmap 2.5.0", - "lock_api", - "oorandom", - "parking_lot", - "rust-analyzer-salsa-macros", - "rustc-hash", - "smallvec", + "anyhow", + "bimap", + "chumsky", + "clap", + "derivative", + "downcast-rs", + "inkwell", + "itertools 0.13.0", + "ltc-errors", + "ouroboros", "tracing", - "triomphe", ] [[package]] -name = "rust-analyzer-salsa-macros" -version = "0.17.0-pre.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d96498e9684848c6676c399032ebc37c52da95ecbefa83d71ccc53b9f8a4a8e" +name = "ltc-driver" +version = "0.1.0" dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.77", + "ariadne", + "itertools 0.13.0", + "thiserror", + "tracing", ] [[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +name = "ltc-errors" +version = "0.1.0" +dependencies = [ + "ariadne", + "inkwell", + "thiserror", +] [[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +name = "ltc-flir" +version = "0.1.0" [[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +name = "ltc-rust-test-input" +version = "0.1.0" [[package]] -name = "same-file" -version = "1.0.6" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "schemars" -version = "0.8.21" +name = "ouroboros" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", + "aliasable", + "ouroboros_macro", + "static_assertions", ] [[package]] -name = "schemars_derive" -version = "0.8.21" +name = "ouroboros_macro" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ + "heck", + "itertools 0.12.1", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "serde_derive_internals", "syn 2.0.77", ] [[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.210" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" -dependencies = [ - "serde_derive", -] +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "serde_derive" -version = "1.0.210" +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", + "unicode-ident", ] [[package]] -name = "serde_derive_internals" -version = "0.29.1" +name = "proc-macro2-diagnostics" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", "syn 2.0.77", + "version_check", + "yansi", ] [[package]] -name = "serde_json" -version = "1.0.128" +name = "psm" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "cc", ] [[package]] -name = "serde_spanned" -version = "0.6.7" +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "serde", + "proc-macro2", ] [[package]] -name = "sha2" -version = "0.10.8" +name = "regex-lite" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] -name = "sha3" -version = "0.10.8" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "shlex" @@ -1531,45 +420,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "starknet-types-core" -version = "0.1.5" +name = "stacker" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6bacf0ba19bc721e518bc4bf389ff13daa8a7c5db5fd320600473b8aa9fcbd" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ - "lambdaworks-crypto", - "lambdaworks-math", - "lazy_static", - "num-bigint", - "num-integer", - "num-traits", - "serde", + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", ] [[package]] @@ -1578,19 +438,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "strsim" version = "0.11.1" @@ -1619,17 +466,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "thiserror" version = "1.0.63" @@ -1650,49 +486,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" -dependencies = [ - "indexmap 2.5.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "tracing" version = "0.1.40" @@ -1724,55 +517,18 @@ dependencies = [ "once_cell", ] -[[package]] -name = "triomphe" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369" -dependencies = [ - "serde", - "stable_deref_trait", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unescaper" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" -dependencies = [ - "thiserror", -] - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" -[[package]] -name = "unicode-xid" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" - [[package]] name = "utf8parse" version = "0.2.2" @@ -1785,84 +541,13 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -1871,46 +556,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1923,78 +590,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" -dependencies = [ - "memchr", -] - -[[package]] -name = "xshell" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" -dependencies = [ - "xshell-macros", -] - -[[package]] -name = "xshell-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" - [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index d0e133a..01cd39e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,14 +30,6 @@ rust-version = "1.81.0" [workspace.dependencies] anyhow = "1.0.89" ariadne = "0.4.1" -cairo-lang-compiler = "2.8.2" -cairo-lang-debug = "2.8.2" -cairo-lang-diagnostics = "2.8.2" -cairo-lang-eq-solver = "2.8.2" -cairo-lang-lowering = "2.8.2" -cairo-lang-semantic = "2.8.2" -cairo-lang-sierra-generator = "2.8.2" -cairo-lang-utils = "2.8.2" clap = "4.5.16" inkwell = { version = "0.5.0", features = ["llvm18-0"] } itertools = "0.13.0" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 04c997f..725f119 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,5 +1,5 @@ //! This is the CLI driver for the compilation of LLVM IR to Cairo. For more -//! detail, please see the documentation for the crate [`ltc_compiler`]. +//! detail, please see the documentation for the [`ltc_compiler`] crate. #![warn(clippy::all, clippy::cargo, clippy::pedantic)] #![allow(clippy::module_name_repetitions)] // Allows for better API naming diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index fb6a45c..79b7e24 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -15,10 +15,7 @@ rust-version.workspace = true [dependencies] bimap = "0.6.3" -cairo-lang-compiler.workspace = true -cairo-lang-debug.workspace = true -cairo-lang-lowering.workspace = true -cairo-lang-semantic.workspace = true +chumsky = "0.9.3" clap.workspace = true derivative = "2.2.0" downcast-rs = "1.2.1" diff --git a/crates/compiler/src/compile/mod.rs b/crates/compiler/src/compile/mod.rs deleted file mode 100644 index adcc460..0000000 --- a/crates/compiler/src/compile/mod.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! Handles the compilation of LLVM IR to Cairo's internal `FlatLowered` IR. -//! -//! In the context of LLVM to Cairo, compilation refers to the process of -//! translating from [LLVM IR](https://llvm.org/docs/LangRef.html) to Cairo's -//! internal -//! [`FlatLowered`](https://github.com/starkware-libs/cairo/blob/main/crates/cairo-lang-lowering/src/objects.rs#L135) -//! structure. -//! -//! LLVM IR is designed around a virtual processor model that is expected to -//! have a multitude of operations common to real CPUs. As we are compiling to -//! target the Cairo VM, we have to work out how to take each of these -//! operations, and represent them in our extremely restricted instruction set. -//! -//! Doing this involves two major approaches: -//! -//! 1. **Translation:** Where there is a good match between the structure of the -//! LLVM IR and the structure of `FlatLowered`, we can translate one to the -//! other. This is useful both in terms of code structure—as LLVM IR is still -//! a structured IR—and in terms of basic operations that are common to both -//! representations. -//! 2. **Polyfills:** Where LLVM expects an operation that we do not have an -//! equivalent for, we instead emit a call to an _implementation of that -//! operation_ in Cairo. We term these implementations _polyfills_ as an -//! analogy to the term used on the web, and they are _software_ -//! implementations of features and capabilities that our hardware is -//! missing. For more information on polyfills, see the [`crate::polyfill`] -//! module. -//! -//! We aim for this compilation process to both achieve a 1:1 semantic match to -//! the original LLVM IR—through use of translation and polyfills as needed—and -//! to retain as much context information as possible so to ensure the -//! possibility of a good user experience in the future. -//! -//! # Targeting `FlatLowered` instead of `Sierra` -//! -//! It might seem strange to target `FlatLowered` instead of something like -//! [Sierra](https://docs.starknet.io/architecture-and-concepts/smart-contracts/cairo-and-sierra/#why_do_we_need_sierra) -//! which is _intended_ as a target for compilation. -//! -//! While we definitely want the benefits of Sierra—particularly model checking -//! for the underlying machine, and the gas monitoring—we do not want to perform -//! all the necessary bookkeeping to make Sierra work on our own. By targeting -//! `FlatLowered` instead, we gain the benefits of the _already existing_ -//! [`sierragen`](https://github.com/starkware-libs/cairo/blob/main/crates/cairo-lang-sierra-generator/src/lib.rs) -//! functionality, which ingests `FlatLowered` and handles the required Sierra -//! bookkeeping for us. -//! -//! While this does give us less control—as we rely on the existing -//! translation—the benefits of not having to manually perform this additional -//! work far outweighs that downside. If we _do_ need any additional control, we -//! can always modify this process at a later date. - -pub mod pass; -pub mod source; - -use ltc_errors::compile::Result; - -use crate::{ - compile::{ - pass::{data::DynPassDataMap, PassManager, PassManagerReturnData}, - source::SourceContext, - }, - polyfill::PolyfillMap, -}; - -/// The compiler is responsible for processing the LLVM IR input to generate a -/// `FlatLowered` output. -#[allow(dead_code)] -pub struct Compiler { - /// The source context, containing references to the LLVM module to be - /// compiled. - context: SourceContext, - - /// The passes that this compiler is configured to run. - passes: PassManager, - - /// The mapping between LLVM names and polyfill names for the compiler to - /// use during compilation. - polyfill_map: PolyfillMap, -} - -impl Compiler { - /// Constructs a new compiler instance, wrapping the provided `context` - /// describing the LLVM module to compile, the `passes` to run, and the - /// `polyfill_map` from LLVM names to polyfill names. - fn new(context: SourceContext, passes: PassManager, polyfill_map: PolyfillMap) -> Self { - Self { - context, - passes, - polyfill_map, - } - } - - /// Executes the compiler on the configured LLVM module. - /// - /// Note that this invokes a state transition that leaves the compiler in an - /// invalid state, and hence it consumes the compiler to prevent API misuse. - /// - /// # Errors - /// - /// - [`ltc_errors::compile::Error`] if the compilation process fails for - /// any reason. - pub fn run(mut self) -> Result { - let PassManagerReturnData { - context: _context, - data, - } = self.passes.run(self.context)?; - - // TODO (#24) Actually compile to FLIR. - - Ok(CompilationResult::new(data)) - } -} - -/// The result of compiling an LLVM IR module. -#[derive(Debug)] -pub struct CompilationResult { - /// The final state of the pass data after the compiler passes have been - /// executed. - pub pass_results: DynPassDataMap, - - /// The `FLIR` module that results from compilation. - pub result_module: (), -} - -impl CompilationResult { - /// Constructs a new compilation result wrapping the final `FLIR` module - /// and also containing the final output of any compiler passes. - #[must_use] - pub fn new(pass_results: DynPassDataMap) -> Self { - let result_module = (); - Self { - pass_results, - result_module, - } - } -} - -/// Allows for building a [`Compiler`] instance while retaining the defaults for -/// fields that do not need to be customized. -pub struct CompilerBuilder { - /// The source context, containing references to the LLVM module to be - /// compiled. - context: SourceContext, - - /// The passes that this compiler is configured to run. - passes: Option, - - /// The mapping between LLVM names and polyfill names for the compiler to - /// use during compilation. - polyfill_map: Option, -} - -impl CompilerBuilder { - /// Creates a new compiler builder wrapping the provided context. - /// - /// The compiler's passes configuration and polyfill configuration will be - /// left as default. - #[must_use] - pub fn new(context: SourceContext) -> Self { - let passes = None; - let polyfill_map = None; - Self { - context, - passes, - polyfill_map, - } - } - - /// Specifies the pass configuration for the compiler. - #[must_use] - pub fn with_passes(mut self, pass_manager: PassManager) -> Self { - self.passes = Some(pass_manager); - self - } - - /// Specifies the polyfill configuration for the compiler. - #[must_use] - pub fn with_polyfills(mut self, polyfill_map: PolyfillMap) -> Self { - self.polyfill_map = Some(polyfill_map); - self - } - - /// Builds a compiler from the specified configuration. - #[must_use] - pub fn build(self) -> Compiler { - Compiler::new( - self.context, - self.passes.unwrap_or_default(), - self.polyfill_map.unwrap_or_default(), - ) - } -} diff --git a/crates/compiler/src/compile/pass/analysis/module_map.rs b/crates/compiler/src/compile/pass/analysis/module_map.rs deleted file mode 100644 index 267dab5..0000000 --- a/crates/compiler/src/compile/pass/analysis/module_map.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! This pass is responsible for generating a map of the top-level structure of -//! an LLVM IR module as described by an `.ll` file. This is used for -//! consistency checking during the compilation step. - -use ltc_errors::compile::Result; - -use crate::compile::{ - pass::{ - data::{ConcretePassData, DynPassDataMap, PassDataOps}, - ConcretePass, - DynamicPassReturnData, - Pass, - PassKey, - PassOps, - }, - source::SourceContext, -}; - -/// Generates a map of the top-level structure of an LLVM module. -#[derive(Clone, Debug, PartialEq)] -pub struct ModuleMap { - depends: Vec, - invalidates: Vec, -} - -impl ModuleMap { - /// Creates a new instance of the module mapping pass. - #[must_use] - pub fn new() -> Box { - let depends = vec![]; - let invalidates = vec![]; - Box::new(Self { - depends, - invalidates, - }) - } -} - -impl PassOps for ModuleMap { - fn run( - &mut self, - context: SourceContext, - _pass_data: &DynPassDataMap, - ) -> Result { - Ok(DynamicPassReturnData::new(context, ModuleMapData::new())) - } - - fn depends(&self) -> &[PassKey] { - self.depends.as_slice() - } - - fn invalidates(&self) -> &[PassKey] { - self.invalidates.as_slice() - } - - fn dupe(&self) -> Pass { - Box::new(self.clone()) - } -} - -impl ConcretePass for ModuleMap { - type Data = ModuleMapData; -} - -/// The module map that results from executing this analysis pass on an LLVM IR -/// module. -#[derive(Clone, Debug, PartialEq)] -pub struct ModuleMapData {} - -impl ModuleMapData { - /// Creates a new instance of the output data for the module mapping pass. - #[must_use] - pub fn new() -> Box { - Box::new(Self {}) - } -} - -impl PassDataOps for ModuleMapData {} -impl ConcretePassData for ModuleMapData { - type Pass = ModuleMap; -} diff --git a/crates/compiler/src/compile/source/mod.rs b/crates/compiler/src/compile/source/mod.rs deleted file mode 100644 index 31ca1ab..0000000 --- a/crates/compiler/src/compile/source/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! Contains the source compilation context, which is a way of tracking the -//! compilation units being processed by the compiler. - -use inkwell::{context::Context as LLVMContext, module::Module}; -use ltc_errors::compile::{Error, Result}; -use ouroboros::self_referencing; - -pub mod module; - -use module::SourceModule; - -/// The source compilation context manages the LLVM state across compiler -/// operations. -/// -/// It is intended to exist only throughout the compilation process, after which -/// it may be safely discarded. -/// -/// # Self-Reference -/// -/// We use the [`ouroboros`] crate to create a struct that can contain a field -/// and references to that field at once. These references do not leak into the -/// crate boundary. -/// -/// Do note that _this requires unsafe code_, but that said unsafe code is -/// encapsulated within the library. -#[self_referencing] -#[derive(Debug)] -pub struct SourceContext { - /// The underlying context that contains the LLVM representation of the - /// input IR. - llvm_context: LLVMContext, - - /// The modules that have been added to the LLVM context. These are the - /// objects that are directly compiled here. - #[borrows(llvm_context)] - #[not_covariant] - modules: Vec>, -} - -impl SourceContext { - /// Creates a new, empty, source compilation context. - #[must_use] - pub fn create() -> Self { - let llvm_context = LLVMContext::create(); - - SourceContextBuilder { - llvm_context, - modules_builder: |_| Vec::new(), - } - .build() - } - - /// Adds the provided `module` to the compilation context. - /// - /// # Errors - /// - /// - [`Error::UnableToAddModuleToContext`] if the module cannot be added to - /// the context for some reason. - pub fn add_module( - &mut self, - module: impl TryInto, - ) -> Result<()> { - let module_source = module - .try_into() - .map_err(|e| Error::UnableToAddModuleToContext(e.to_string()))?; - - self.with_mut(|all_fields| -> Result<()> { - let ctx = &all_fields.llvm_context; - let module = ctx - .create_module_from_ir(module_source.into()) - .map_err(|e| Error::UnableToAddModuleToContext(e.to_string()))?; - all_fields.modules.push(module); - - Ok(()) - })?; - - Ok(()) - } - - /// Runs analysis on each module in the context using the provided function, - /// and returns the analysis results. - /// - /// It does not have the ability to modify the underlying modules at all. - /// - /// # Errors - /// - /// - [`Error`] if any of the passes fail - pub fn analyze_modules(&self, op: impl Fn(&Module) -> Result) -> Result> { - self.with_modules(|mods| mods.iter().map(op).collect()) - } - - /// Runs a transformation on all modules in the context using the provided - /// function, returning any results from the modification. - /// - /// # Errors - /// - /// - [`Error`] if any of the passes fail - pub fn modify_modules(&mut self, op: impl Fn(&mut Module) -> Result) -> Result> { - self.with_modules_mut(|mods| mods.iter_mut().map(op).collect()) - } - - /// Gets a reference to the underlying LLVM context. - #[must_use] - pub fn context_raw(&self) -> &LLVMContext { - self.borrow_llvm_context() - } -} - -impl From for LLVMContext { - fn from(value: SourceContext) -> Self { - value.into_heads().llvm_context - } -} diff --git a/crates/compiler/src/constant.rs b/crates/compiler/src/constant.rs new file mode 100644 index 0000000..5c95077 --- /dev/null +++ b/crates/compiler/src/constant.rs @@ -0,0 +1,4 @@ +//! Useful constants for use within the compiler. + +/// The size of a byte on our architecture. +pub const BYTE_SIZE: usize = 8; diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index aa6af64..b8d31f1 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -44,20 +44,206 @@ #![allow(clippy::module_name_repetitions)] // Allows for better API naming #![allow(clippy::multiple_crate_versions)] // Enforced by our dependencies -pub mod compile; +pub mod constant; +pub mod llvm; +pub mod pass; pub mod polyfill; +pub mod source; + +use ltc_errors::compile::Result; + +use crate::{ + pass::{data::DynPassDataMap, PassManager, PassManagerReturnData}, + polyfill::PolyfillMap, + source::SourceContext, +}; + +/// Handles the compilation of LLVM IR to Cairo's internal `FlatLowered` IR. +/// +/// In the context of LLVM to Cairo, compilation refers to the process of +/// translating from [LLVM IR](https://llvm.org/docs/LangRef.html) to our +/// internal `FLO` object file format. +/// +/// LLVM IR is designed around a virtual processor model that is expected to +/// have a multitude of operations common to real CPUs. As we are compiling to +/// target the Cairo VM, we have to work out how to take each of these +/// operations, and represent them in our extremely restricted instruction set. +/// +/// Doing this involves two major approaches: +/// +/// 1. **Translation:** Where there is a good match between the structure of the +/// LLVM IR and the structure of `FlatLowered`, we can translate one to the +/// other. This is useful both in terms of code structure—as LLVM IR is still +/// a structured IR—and in terms of basic operations that are common to both +/// representations. +/// 2. **Polyfills:** Where LLVM expects an operation that we do not have an +/// equivalent for, we instead emit a call to an _implementation of that +/// operation_ in Cairo. We term these implementations _polyfills_ as an +/// analogy to the term used on the web, and they are _software_ +/// implementations of features and capabilities that our hardware is +/// missing. For more information on polyfills, see the [`polyfill`] module. +/// +/// We aim for this compilation process to both achieve a 1:1 semantic match to +/// the original LLVM IR—through use of translation and polyfills as needed—and +/// to retain as much context information as possible so to ensure the +/// possibility of a good user experience in the future. +/// +/// # Targeting `FlatLowered` instead of `Sierra` +/// +/// It might seem strange to target `FlatLowered` instead of something like +/// [Sierra](https://docs.starknet.io/architecture-and-concepts/smart-contracts/cairo-and-sierra/#why_do_we_need_sierra) +/// which is _intended_ as a target for compilation. +/// +/// While we definitely want the benefits of Sierra—particularly model checking +/// for the underlying machine, and the gas monitoring—we do not want to perform +/// all the necessary bookkeeping to make Sierra work on our own. By targeting +/// `FlatLowered` instead, we gain the benefits of the _already existing_ +/// [`sierragen`](https://github.com/starkware-libs/cairo/blob/main/crates/cairo-lang-sierra-generator/src/lib.rs) +/// functionality, which ingests `FlatLowered` and handles the required Sierra +/// bookkeeping for us. +/// +/// While this does give us less control—as we rely on the existing +/// translation—the benefits of not having to manually perform this additional +/// work far outweighs that downside. If we _do_ need any additional control, we +/// can always modify this process at a later date. +#[allow(dead_code)] +pub struct Compiler { + /// The source context, containing references to the LLVM module to be + /// compiled. + pub context: SourceContext, + + /// The passes that this compiler is configured to run. + pub passes: PassManager, + + /// The mapping between LLVM names and polyfill names for the compiler to + /// use during compilation. + pub polyfill_map: PolyfillMap, +} + +impl Compiler { + /// Constructs a new compiler instance, wrapping the provided `context` + /// describing the LLVM module to compile, the `passes` to run, and the + /// `polyfill_map` from LLVM names to polyfill names. + #[must_use] + fn new(context: SourceContext, passes: PassManager, polyfill_map: PolyfillMap) -> Self { + Self { + context, + passes, + polyfill_map, + } + } + + /// Executes the compiler on the configured LLVM module. + /// + /// Note that this invokes a state transition that leaves the compiler in an + /// invalid state, and hence it consumes the compiler to prevent API misuse. + /// + /// # Errors + /// + /// - [`ltc_errors::compile::Error`] if the compilation process fails for + /// any reason. + pub fn run(mut self) -> Result { + let PassManagerReturnData { + context: _context, + data, + } = self.passes.run(self.context)?; + + // TODO (#24) Actually compile to FLIR. + + Ok(CompilationResult::new(data)) + } +} + +/// The result of compiling an LLVM IR module. +#[derive(Debug)] +pub struct CompilationResult { + /// The final state of the pass data after the compiler passes have been + /// executed. + pub pass_results: DynPassDataMap, + + /// The `FLO` module that results from compilation. + pub result_module: (), +} + +impl CompilationResult { + /// Constructs a new compilation result wrapping the final `FLO` module + /// and also containing the final output of any compiler passes. + #[must_use] + pub fn new(pass_results: DynPassDataMap) -> Self { + let result_module = (); + Self { + pass_results, + result_module, + } + } +} + +/// Allows for building a [`Compiler`] instance while retaining the defaults for +/// fields that do not need to be customized. +pub struct CompilerBuilder { + /// The source context, containing references to the LLVM module to be + /// compiled. + context: SourceContext, + + /// The passes that this compiler is configured to run. + passes: Option, + + /// The mapping between LLVM names and polyfill names for the compiler to + /// use during compilation. + polyfill_map: Option, +} + +impl CompilerBuilder { + /// Creates a new compiler builder wrapping the provided context. + /// + /// The compiler's passes configuration and polyfill configuration will be + /// left as default. + #[must_use] + pub fn new(context: SourceContext) -> Self { + let passes = None; + let polyfill_map = None; + Self { + context, + passes, + polyfill_map, + } + } + + /// Specifies the pass configuration for the compiler. + #[must_use] + pub fn with_passes(mut self, pass_manager: PassManager) -> Self { + self.passes = Some(pass_manager); + self + } + + /// Specifies the polyfill configuration for the compiler. + #[must_use] + pub fn with_polyfills(mut self, polyfill_map: PolyfillMap) -> Self { + self.polyfill_map = Some(polyfill_map); + self + } + + /// Builds a compiler from the specified configuration. + #[must_use] + pub fn build(self) -> Compiler { + Compiler::new( + self.context, + self.passes.unwrap_or_default(), + self.polyfill_map.unwrap_or_default(), + ) + } +} #[cfg(test)] mod test { use std::path::Path; - use crate::compile::{source::SourceContext, CompilerBuilder}; + use crate::{source::SourceContext, CompilerBuilder}; #[test] fn run() -> anyhow::Result<()> { let test_input = r"input/add.ll"; - let mut ctx = SourceContext::create(); - ctx.add_module(Path::new(test_input))?; + let ctx = SourceContext::create(Path::new(test_input))?; let compiler = CompilerBuilder::new(ctx).build(); let result = compiler.run()?; diff --git a/crates/compiler/src/llvm/data_layout.rs b/crates/compiler/src/llvm/data_layout.rs new file mode 100644 index 0000000..d5e4bd9 --- /dev/null +++ b/crates/compiler/src/llvm/data_layout.rs @@ -0,0 +1,1415 @@ +//! This module contains the definition of the [`DataLayout`] struct, as well as +//! utilities for querying and reasoning about said layouts. + +use chumsky::{ + error::Simple, + prelude::{choice, just}, + Parser, +}; +use ltc_errors::compile::{Error, Result}; + +use crate::constant::BYTE_SIZE; + +/// Information about the expected data-layout for this module. +/// +/// # Defaulting +/// +/// LLVM starts with a default specification of the data-layout that is possibly +/// overridden by the data-layout string. This struct implements this behavior, +/// so if you want a defaulted layout, either call [`DataLayout::new`] with an +/// empty string, or use the [`Default`] instance. +#[derive(Clone, Debug, PartialEq)] +pub struct DataLayout { + /// The endianness used in this data layout. + pub endianness: Endianness, + + /// The mangling scheme used by this data layout. + pub mangling: Mangling, + + /// The natural alignment of the stack in bits. + pub stack_alignment: usize, + + /// The index of the address space that corresponds to program memory. + pub program_address_space: usize, + + /// The index of the address space that corresponds to globals. + pub global_address_space: usize, + + /// The index of the address space for allocations. + pub alloc_address_space: usize, + + /// The layout of pointers. + pub pointer_layouts: Vec, + + /// The layout of the various integer types. + pub integer_layouts: Vec, + + /// The layout of the various vector types. + pub vector_layouts: Vec, + + /// The layout of the various floating-point types. + pub float_layouts: Vec, + + /// The layout of aggregate types. + pub aggregate_layout: AggregateLayout, + + /// The layout of function pointers. + pub function_pointer_layout: FunctionPointerLayout, + + /// The integer widths natively supported by the CPU in this layout. + pub native_integer_widths: NativeIntegerWidths, + + /// The address space numbers in which pointers should be treated as + /// non-integral. + pub nointptr_address_spaces: NonIntegralPointerAddressSpaces, +} + +impl DataLayout { + /// Constructs a new data layout description from the provided + /// `layout_string`. + /// + /// If any portion of the data layout specification is left unspecified, + /// then the default data layout specification is used in its place as + /// described [here](https://llvm.org/docs/LangRef.html#data-layout). In + /// addition, we: + /// + /// - Default to 32 and 64-bit native integer widths. + /// - Default to independent function pointers aligned to 64 bits. + /// - Default to the ELF mangling scheme if none is specified. + /// + /// # Errors + /// + /// - [`Error::InvalidDataLayoutSpecification`] if the provided + /// `layout_string` cannot be parsed as a data layout specification. + pub fn new(layout_string: &str) -> Result { + let parts = layout_string.split('-'); + + let mut endianness = None; + let mut mangling = None; + let mut stack_alignment = None; + let mut program_address_space = None; + let mut global_address_space = None; + let mut alloc_address_space = None; + let mut pointer_specs = vec![]; + let mut int_specs = vec![]; + let mut vector_specs = vec![]; + let mut float_specs = vec![]; + let mut aggregate_spec = None; + let mut function_pointer_spec = None; + let mut native_integer_widths = None; + let mut nointptr_addr_spaces = None; + + for part in parts { + if let Ok(e) = Endianness::parser().parse(part) { + endianness = Some(e); + } else if let Ok(m) = Mangling::parser().parse(part) { + mangling = Some(m); + } else if let Ok(align) = parsing::stack_alignment().parse(part) { + stack_alignment = Some(align); + } else if let Ok(p_addr) = parsing::program_address_space().parse(part) { + program_address_space = Some(p_addr); + } else if let Ok(g_addr) = parsing::global_address_space().parse(part) { + global_address_space = Some(g_addr); + } else if let Ok(a_addr) = parsing::alloc_address_space().parse(part) { + alloc_address_space = Some(a_addr); + } else if let Ok(ptr_spec) = PointerLayout::parser().parse(part) { + pointer_specs.push(ptr_spec); + } else if let Ok(int_spec) = IntegerLayout::parser().parse(part) { + int_specs.push(int_spec); + } else if let Ok(vec) = VectorLayout::parser().parse(part) { + vector_specs.push(vec); + } else if let Ok(float_spec) = FloatLayout::parser().parse(part) { + float_specs.push(float_spec); + } else if let Ok(agg) = AggregateLayout::parser().parse(part) { + aggregate_spec = Some(agg); + } else if let Ok(f_ptr) = FunctionPointerLayout::parser().parse(part) { + function_pointer_spec = Some(f_ptr); + } else if let Ok(iw) = NativeIntegerWidths::parser().parse(part) { + native_integer_widths = Some(iw); + } else if let Ok(npa) = NonIntegralPointerAddressSpaces::parser().parse(part) { + nointptr_addr_spaces = Some(npa); + } else if part.is_empty() { + continue; + } else { + Err(Error::InvalidDataLayoutSpecification( + layout_string.to_string(), + part.to_string(), + ))?; + } + } + + // Compute the defaults if we have not parsed anything for a given segment. + let endianness = endianness.unwrap_or(Endianness::Little); + let mangling = mangling.unwrap_or(Mangling::ELF); + let stack_alignment = stack_alignment.unwrap_or(0); + let program_address_space = program_address_space.unwrap_or(0); + let global_address_space = global_address_space.unwrap_or(0); + let alloc_address_space = alloc_address_space.unwrap_or(0); + let pointer_layouts = Self::pointer_specs_or_defaults(pointer_specs); + let integer_layouts = Self::int_specs_or_defaults(int_specs); + let vector_layouts = Self::vec_specs_or_defaults(vector_specs); + let float_layouts = Self::float_specs_or_defaults(float_specs); + let aggregate_layout = aggregate_spec.unwrap_or(AggregateLayout { + abi_alignment: 0, + preferred_alignment: 64, + }); + let function_pointer_layout = function_pointer_spec.unwrap_or(FunctionPointerLayout { + ptr_type: FunctionPointerType::Independent, + abi_alignment: 64, + }); + let native_integer_widths = native_integer_widths.unwrap_or(NativeIntegerWidths { + widths: vec![32, 64], + }); + let nointptr_address_spaces = + nointptr_addr_spaces.unwrap_or(NonIntegralPointerAddressSpaces { + address_spaces: Vec::new(), + }); + + // Finally we can build the data layout + let layout = DataLayout { + endianness, + mangling, + stack_alignment, + program_address_space, + global_address_space, + alloc_address_space, + pointer_layouts, + integer_layouts, + vector_layouts, + float_layouts, + aggregate_layout, + function_pointer_layout, + native_integer_widths, + nointptr_address_spaces, + }; + + Ok(layout) + } + + /// Augments the parsed floating-point layout specifications with any + /// missing information based on the defaults for LLVM's data layout. + fn float_specs_or_defaults(mut specs: Vec) -> Vec { + // Add the default for half-precision floats + if !specs.iter().any(|f| f.size == 16) { + specs.push(FloatLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 16, + }); + } + + // Add the default for full-precision floats + if !specs.iter().any(|f| f.size == 32) { + specs.push(FloatLayout { + size: 32, + abi_alignment: 32, + preferred_alignment: 32, + }); + } + + // Add the default for double-precision floats + if !specs.iter().any(|f| f.size == 64) { + specs.push(FloatLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + }); + } + + // Add the default for quad-precision floats + if !specs.iter().any(|f| f.size == 128) { + specs.push(FloatLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + }); + } + + specs.sort(); + specs + } + + /// Augments the parsed vector layout specifications with any missing + /// information based on the defaults for LLVM's data layout. + fn vec_specs_or_defaults(mut specs: Vec) -> Vec { + // Add the default for 64-bit vectors + if !specs.iter().any(|v| v.size == 64) { + specs.push(VectorLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + }); + } + + // Add the default for 128-bit vectors + if !specs.iter().any(|v| v.size == 128) { + specs.push(VectorLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + }); + } + + specs.sort(); + specs + } + + /// Augments the parsed integer specifications with any missing information + /// based on the defaults for LLVM's data layout. + fn int_specs_or_defaults(mut specs: Vec) -> Vec { + // Add the default for i1 (bool) + if !specs.iter().any(|s| s.size == 1) { + specs.push(IntegerLayout { + size: 1, + abi_alignment: 8, + preferred_alignment: 8, + }); + } + + // Add the default for i8 + if !specs.iter().any(|s| s.size == 8) { + specs.push(IntegerLayout { + size: 8, + abi_alignment: 8, + preferred_alignment: 8, + }); + } + + // Add the default for i16 + if !specs.iter().any(|s| s.size == 16) { + specs.push(IntegerLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 16, + }); + } + + // Add the default for i32 + if !specs.iter().any(|s| s.size == 32) { + specs.push(IntegerLayout { + size: 32, + abi_alignment: 32, + preferred_alignment: 32, + }); + } + + // Add the default for i64 + if !specs.iter().any(|s| s.size == 64) { + specs.push(IntegerLayout { + size: 64, + abi_alignment: 32, + preferred_alignment: 64, + }); + } + + specs.sort(); + specs + } + + /// Augments the parsed pointer specifications with any missing information + /// based on the defaults for LLVM's data layout. + fn pointer_specs_or_defaults(mut specs: Vec) -> Vec { + if !specs.iter().any(|l| l.address_space == 0) { + specs.push(PointerLayout { + address_space: 0, + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + index_size: 64, + }); + } + + specs.sort(); + specs + } +} + +impl Default for DataLayout { + fn default() -> Self { + Self::new("").expect("The empty string was not a valid data layout specification") + } +} + +impl TryFrom<&str> for DataLayout { + type Error = Error; + + fn try_from(value: &str) -> std::result::Result { + Self::new(value) + } +} + +impl TryFrom for DataLayout { + type Error = Error; + + fn try_from(value: String) -> std::result::Result { + Self::new(&value) + } +} + +/// A description of the endianness used when laying out data. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Endianness { + /// Little-endian (least-significant byte first). + Little, + + /// Big-endian (most-significant byte first). + Big, +} + +impl Endianness { + /// Parses the endianness specification part of the data-layout. + fn parser() -> impl parsing::DLParser { + choice(( + just("e").to(Endianness::Little), + just("E").to(Endianness::Big), + )) + } +} + +/// A description of the mangling scheme used by this module. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Mangling { + /// The Windows COFF mangling scheme. + /// Private symbols get the usual prefix. Functions with `__stdcall`, + /// `__fastcall`, and `__vectorcall` have custom mangling that appends + /// `@N` where `N` is the number of bytes used to pass parameters. C++ + /// symbols starting with `?` are not mangled in any way. + COFF, + + /// The Windows x86 COFF mangling scheme. + /// + /// Private symbols get the usual prefix. Regular C symbols get an `_` + /// prefix. Functions with `__stdcall`, `__fastcall`, and `__vectorcall` + /// have custom mangling that appends `@N` where `N` is the number of + /// bytes used to pass parameters. C++ symbols starting with `?` are not + /// mangled in any way. + COFF86, + + /// The ELF mangling scheme, where private symbols get a `.L` prefix. + ELF, + + /// The GOFF mangling scheme, where private symbols get an `@` prefix. + GOFF, + + /// The Mach-O mangling scheme, where private symbols get an `L` prefix and + /// other symbols get an `_` prefix. + MachO, + + /// The MIPS mangling scheme, where private symbols get a `$` prefix. + MIPS, + + /// The XCOFF mangling scheme, where private symbols get an `L..` prefix. + XCOFF, +} + +impl Mangling { + /// Parses the mangling specification part of the data-layout. + fn parser() -> impl parsing::DLParser { + just("m:").ignore_then(choice(( + just("a").to(Mangling::XCOFF), + just("e").to(Mangling::ELF), + just("l").to(Mangling::GOFF), + just("m").to(Mangling::MIPS), + just("o").to(Mangling::MachO), + just("w").to(Mangling::COFF), + just("x").to(Mangling::COFF86), + ))) + } +} + +/// A specification of the pointer layout for this data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PointerLayout { + /// The address space for which the pointer is being specified. + pub address_space: usize, + + /// The size of the pointer. + pub size: usize, + + /// The required ABI alignment for the pointer. + pub abi_alignment: usize, + + /// The preferred alignment for the pointer. + pub preferred_alignment: usize, + + /// The size of the index used for address calculation. + pub index_size: usize, +} + +impl PointerLayout { + /// Parses the pointer layout specification as part of the data layout + /// string. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("p") + .ignore_then(parsing::pos_int(10).delimited_by(just("["), just("]")).or_not()) + .then(parsing::field(parsing::pos_int(10))) + .then(parsing::field(parsing::pos_int(10))) + .then(parsing::field(parsing::pos_int(10)).or_not()) + .then(parsing::field(parsing::pos_int(10)).or_not()) + .try_map( + |((((address_space, size), abi_alignment), preferred_alignment), index_size), + span| { + let address_space = address_space.unwrap_or(0); + let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment); + let index_size = index_size.unwrap_or(size); + if index_size > size { + Err(Simple::custom( + span, + format!( + "The requested index size {index_size} is larger than the pointer \ + size {size}" + ), + ))?; + }; + + Ok(Self { + address_space, + size, + abi_alignment, + preferred_alignment, + index_size, + }) + }, + ) + } +} + +/// A specification of an integer layout for this data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct IntegerLayout { + /// The size of the integer. + pub size: usize, + + /// The required ABI alignment for the integer. + pub abi_alignment: usize, + + /// The preferred alignment for the integer. + pub preferred_alignment: usize, +} + +impl IntegerLayout { + /// Parses an integer layout specification as part of the data layout + /// string. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("i") + .ignore_then(parsing::pos_int(10)) + .then(parsing::field(parsing::pos_int(10))) + .then(parsing::field(parsing::pos_int(10)).or_not()) + .try_map(|((size, abi_alignment), preferred_alignment), span| { + let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment); + if size == BYTE_SIZE && abi_alignment != size { + Err(Simple::custom( + span, + "i8 was not aligned to a byte boundary", + ))?; + } + + Ok(Self { + size, + abi_alignment, + preferred_alignment, + }) + }) + } +} + +/// A specification of a vector layout for this data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct VectorLayout { + /// The size of the vector. + pub size: usize, + + /// The required ABI alignment for the vector. + pub abi_alignment: usize, + + /// The preferred alignment for the vector. + pub preferred_alignment: usize, +} + +impl VectorLayout { + /// Parses a vector layout specification as part of the data layout + /// string. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("v") + .ignore_then(parsing::pos_int(10)) + .then(parsing::field(parsing::pos_int(10))) + .then(parsing::field(parsing::pos_int(10)).or_not()) + .map(|((size, abi_alignment), preferred_alignment)| { + let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment); + + Self { + size, + abi_alignment, + preferred_alignment, + } + }) + } +} + +/// A specification of a floating-point layout for this data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct FloatLayout { + /// The size of the floating-point number. + pub size: usize, + + /// The required ABI alignment for the floating-point number. + pub abi_alignment: usize, + + /// The preferred alignment for the floating-point number. + pub preferred_alignment: usize, +} + +impl FloatLayout { + /// Parses a floating-point layout specification as part of the data layout + /// string. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("f") + .ignore_then(parsing::pos_int(10)) + .then(parsing::field(parsing::pos_int(10))) + .then(parsing::field(parsing::pos_int(10)).or_not()) + .try_map(|((size, abi_alignment), preferred_alignment), span| { + let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment); + if !&[16, 32, 64, 80, 128].contains(&size) { + Err(Simple::custom( + span, + format!("{size} is not a valid floating-point size"), + ))?; + } + + Ok(Self { + size, + abi_alignment, + preferred_alignment, + }) + }) + } +} + +/// A specification of the aggregate layout for this data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct AggregateLayout { + /// The required ABI alignment for an aggregate. + pub abi_alignment: usize, + + /// The preferred alignment for an aggregate. + pub preferred_alignment: usize, +} + +impl AggregateLayout { + /// Parses the aggregate layout specification as part of the data layout + /// string. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("a") + .ignore_then(parsing::pos_int(10)) + .then(parsing::field(parsing::pos_int(10)).or_not()) + .map(|(abi_alignment, preferred_alignment)| { + let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment); + + Self { + abi_alignment, + preferred_alignment, + } + }) + } +} + +/// A specification of the way function pointers are treated as part of this +/// data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum FunctionPointerType { + /// The alignment of function pointers is independent of the alignment of + /// functions, and is a multiple of the ABI alignment. + Independent, + + /// The alignment of function pointers is a multiple of the explicit + /// alignment specified on the function, and is a multiple of the ABI + /// alignment. + Multiple, +} + +impl FunctionPointerType { + /// Parses the function pointer type as part of the data layout string. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + choice(( + just("i").to(FunctionPointerType::Independent), + just("n").to(FunctionPointerType::Multiple), + )) + } +} + +/// A specification of the function pointer layout for this data-layout. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct FunctionPointerLayout { + /// The way that the function pointer is treated in the data layout. + pub ptr_type: FunctionPointerType, + + /// The alignment of function pointers in this data layout. + pub abi_alignment: usize, +} + +impl FunctionPointerLayout { + /// Parses the function pointer layout specification as part of this data + /// layout. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("F") + .ignore_then(FunctionPointerType::parser()) + .then(parsing::pos_int(10)) + .map(|(ptr_type, abi_alignment)| Self { + ptr_type, + abi_alignment, + }) + } +} + +/// A specification of the native integer widths for this data-layout. +/// +/// The CPU must have _at least one_ native integer width. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct NativeIntegerWidths { + /// The integer widths that are natively supported on the CPU. + pub widths: Vec, +} + +impl NativeIntegerWidths { + /// Parses the specification of native integer widths for the target CPU. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("n") + .ignore_then(parsing::pos_int(10)) + .then(parsing::field(parsing::pos_int(10)).repeated()) + .map(|(first, mut rest)| { + rest.insert(0, first); + Self { widths: rest } + }) + } +} + +/// A specification of the address spaces in which the pointers should be +/// treated as [non-integral](https://llvm.org/docs/LangRef.html#nointptrtype). +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct NonIntegralPointerAddressSpaces { + /// The address spaces in which pointers should be treated as non-integral. + pub address_spaces: Vec, +} + +impl NonIntegralPointerAddressSpaces { + /// Parses the specification of address-spaces in which pointers are + /// non-integral. + #[must_use] + pub fn parser() -> impl parsing::DLParser { + just("ni") + .ignore_then(parsing::field(parsing::pos_int(10)).repeated().at_least(1)) + .try_map(|address_spaces, span| { + if address_spaces.contains(&0) { + Err(Simple::custom( + span, + "The 0 address space cannot be specified as using non-integral pointers", + ))?; + } + + Ok(Self { address_spaces }) + }) + } +} + +/// Utility parsing functions to aid in the parsing of data-layouts but that are +/// not associated directly with any type. +pub mod parsing { + use chumsky::{error::Simple, prelude::just, text::int, Parser}; + + use crate::{constant::BYTE_SIZE, llvm::data_layout::parsing}; + + /// Simply to avoid typing out the whole parser type parameter specification + /// every single time given it only varies in one parameter. + pub trait DLParser: Parser> {} + + /// A blanket impl to make this work, because yay. + impl DLParser for U where U: Parser> {} + + /// Parses an element separator. + #[must_use] + pub fn elem_sep<'a>() -> impl DLParser<&'a str> { + just("-") + } + + /// Parses a field separator. + #[must_use] + pub fn field_sep<'a>() -> impl DLParser<&'a str> { + just(":") + } + + /// Parses a field, namely a colon followed by something as given by the + /// `then` parser. + pub fn field(then: impl DLParser) -> impl DLParser { + field_sep().ignore_then(then) + } + + /// Parses a positive integer in the specified `radix`. + #[must_use] + pub fn pos_int(radix: u32) -> impl DLParser { + int(radix).try_map(|num: String, span| { + num.parse::().map_err(|_| { + Simple::custom(span, format!("Could not parse {num} as a positive integer")) + }) + }) + } + + /// Parses the stack alignment specification part of the data-layout. + #[must_use] + pub fn stack_alignment() -> impl DLParser { + just("S").ignore_then(pos_int(10)).validate(|alignment, span, emit| { + if alignment % BYTE_SIZE != 0 { + emit(Simple::custom( + span, + format!("{alignment} must be aligned to a byte offset"), + )); + } + alignment + }) + } + + /// Parses the address space specification part of the data-layout. + fn address_space(space: &str) -> impl DLParser + '_ { + just(space).ignore_then(parsing::pos_int(10)) + } + + #[must_use] + pub fn program_address_space() -> impl DLParser { + address_space("P") + } + + #[must_use] + pub fn global_address_space() -> impl DLParser { + address_space("G") + } + + #[must_use] + pub fn alloc_address_space() -> impl DLParser { + address_space("A") + } +} + +#[cfg(test)] +mod test { + use chumsky::Parser; + + use crate::llvm::data_layout::{ + parsing, + AggregateLayout, + DataLayout, + Endianness, + FloatLayout, + FunctionPointerLayout, + FunctionPointerType, + IntegerLayout, + Mangling, + NativeIntegerWidths, + NonIntegralPointerAddressSpaces, + PointerLayout, + VectorLayout, + }; + + #[test] + fn can_parse_data_layout() { + let dl_string = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"; + + // It should parse correctly + let parsed_layout = DataLayout::new(dl_string); + assert!(parsed_layout.is_ok()); + + // Now we can check that the fields have their proper values. + let layout = parsed_layout.unwrap(); + + // Little endian with ELF mangling + assert_eq!(layout.endianness, Endianness::Little); + assert_eq!(layout.mangling, Mangling::ELF); + + // Stack aligned to 128 bits, with all address spaces in zero. + assert_eq!(layout.stack_alignment, 128); + assert_eq!(layout.program_address_space, 0); + assert_eq!(layout.global_address_space, 0); + assert_eq!(layout.alloc_address_space, 0); + + // Pointers in address space zero are aligned to 64 bits. + assert_eq!( + layout.pointer_layouts, + vec![PointerLayout { + address_space: 0, + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + index_size: 64, + }] + ); + + // Integers are semi-customized, with 8, 16, 64, and 128 using layouts specified + // in the string + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 1, + abi_alignment: 8, + preferred_alignment: 8, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 8, + abi_alignment: 8, + preferred_alignment: 32, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 32, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 32, + abi_alignment: 32, + preferred_alignment: 32, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + })); + + // For vector layouts we only have the defaults + assert!(layout.vector_layouts.contains(&VectorLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + })); + assert!(layout.vector_layouts.contains(&VectorLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + })); + + // For float layouts we also use the defaults + assert!(layout.float_layouts.contains(&FloatLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 16, + })); + assert!(layout.float_layouts.contains(&FloatLayout { + size: 32, + abi_alignment: 32, + preferred_alignment: 32, + })); + assert!(layout.float_layouts.contains(&FloatLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + })); + assert!(layout.float_layouts.contains(&FloatLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + })); + + // For the aggregate layout we have the default + assert_eq!( + layout.aggregate_layout, + AggregateLayout { + abi_alignment: 0, + preferred_alignment: 64, + } + ); + + // For the function pointer layout we also have our default + assert_eq!( + layout.function_pointer_layout, + FunctionPointerLayout { + ptr_type: FunctionPointerType::Independent, + abi_alignment: 64, + } + ); + + // For native integer widths this string specifies 32, 64 + assert_eq!( + layout.native_integer_widths, + NativeIntegerWidths { + widths: vec![32, 64], + } + ); + + // And no address spaces should be using non-integral pointers + assert_eq!( + layout.nointptr_address_spaces, + NonIntegralPointerAddressSpaces { + address_spaces: Vec::new(), + } + ); + } + + #[test] + fn can_parse_data_layout_to_default() { + let dl_string = ""; + + // It should parse correctly + let parsed_layout = DataLayout::new(dl_string); + assert!(parsed_layout.is_ok()); + + // Now we can check that the fields have their proper values. + let layout = parsed_layout.unwrap(); + + // Little endian with ELF mangling + assert_eq!(layout.endianness, Endianness::Little); + assert_eq!(layout.mangling, Mangling::ELF); + + // Stack alignment is arbitrary, with all address spaces in zero. + assert_eq!(layout.stack_alignment, 0); + assert_eq!(layout.program_address_space, 0); + assert_eq!(layout.global_address_space, 0); + assert_eq!(layout.alloc_address_space, 0); + + // Pointers in address space zero are aligned to 64 bits. + assert_eq!( + layout.pointer_layouts, + vec![PointerLayout { + address_space: 0, + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + index_size: 64, + }] + ); + + // All the integer layouts should be default + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 1, + abi_alignment: 8, + preferred_alignment: 8, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 8, + abi_alignment: 8, + preferred_alignment: 8, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 16, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 32, + abi_alignment: 32, + preferred_alignment: 32, + })); + assert!(layout.integer_layouts.contains(&IntegerLayout { + size: 64, + abi_alignment: 32, + preferred_alignment: 64, + })); + + // For vector layouts we only have the defaults + assert!(layout.vector_layouts.contains(&VectorLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + })); + assert!(layout.vector_layouts.contains(&VectorLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + })); + + // For float layouts we also use the defaults + assert!(layout.float_layouts.contains(&FloatLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 16, + })); + assert!(layout.float_layouts.contains(&FloatLayout { + size: 32, + abi_alignment: 32, + preferred_alignment: 32, + })); + assert!(layout.float_layouts.contains(&FloatLayout { + size: 64, + abi_alignment: 64, + preferred_alignment: 64, + })); + assert!(layout.float_layouts.contains(&FloatLayout { + size: 128, + abi_alignment: 128, + preferred_alignment: 128, + })); + + // For the aggregate layout we have the default + assert_eq!( + layout.aggregate_layout, + AggregateLayout { + abi_alignment: 0, + preferred_alignment: 64, + } + ); + + // For the function pointer layout we also have our default + assert_eq!( + layout.function_pointer_layout, + FunctionPointerLayout { + ptr_type: FunctionPointerType::Independent, + abi_alignment: 64, + } + ); + + // For native integer widths we should have the default + assert_eq!( + layout.native_integer_widths, + NativeIntegerWidths { + widths: vec![32, 64], + } + ); + + // And no address spaces should be using non-integral pointers + assert_eq!( + layout.nointptr_address_spaces, + NonIntegralPointerAddressSpaces { + address_spaces: Vec::new(), + } + ); + } + + #[test] + fn can_parse_endianness_segment() { + // Failures + assert!(Endianness::parser().parse("foo").is_err()); + + // Successes + assert_eq!( + Endianness::parser() + .parse("e") + .expect("Little endian spec did not parse"), + Endianness::Little + ); + assert_eq!( + Endianness::parser() + .parse("E") + .expect("Big endian spec did not parse"), + Endianness::Big + ); + } + + #[test] + fn can_parse_mangling_segment() { + // Failures + assert!(Mangling::parser().parse("m:").is_err()); + assert!(Mangling::parser().parse("m:f").is_err()); + assert!(Mangling::parser().parse("f").is_err()); + + // Successes + assert_eq!(Mangling::parser().parse("m:a"), Ok(Mangling::XCOFF)); + assert_eq!(Mangling::parser().parse("m:e"), Ok(Mangling::ELF)); + assert_eq!(Mangling::parser().parse("m:l"), Ok(Mangling::GOFF)); + assert_eq!(Mangling::parser().parse("m:m"), Ok(Mangling::MIPS)); + assert_eq!(Mangling::parser().parse("m:o"), Ok(Mangling::MachO)); + assert_eq!(Mangling::parser().parse("m:w"), Ok(Mangling::COFF)); + assert_eq!(Mangling::parser().parse("m:x"), Ok(Mangling::COFF86)); + } + + #[test] + fn can_parse_stack_alignment_segment() { + // Failures + assert!(parsing::stack_alignment().parse("m:").is_err()); + assert!(parsing::stack_alignment().parse("S").is_err()); + assert!(parsing::stack_alignment().parse("S15").is_err()); + + // Successes + assert_eq!(parsing::stack_alignment().parse("S8"), Ok(8)); + assert_eq!(parsing::stack_alignment().parse("S32"), Ok(32)); + assert_eq!(parsing::stack_alignment().parse("S64"), Ok(64)); + assert_eq!(parsing::stack_alignment().parse("S128"), Ok(128)); + assert_eq!(parsing::stack_alignment().parse("S256"), Ok(256)); + } + + #[test] + fn can_parse_program_address_space() { + // Failures + assert!(parsing::program_address_space().parse("PA").is_err()); + assert!(parsing::program_address_space().parse("P").is_err()); + + // Successes + assert_eq!(parsing::program_address_space().parse("P1"), Ok(1)); + assert_eq!(parsing::program_address_space().parse("P0"), Ok(0)); + } + + #[test] + fn can_parse_global_address_space() { + // Failures + assert!(parsing::global_address_space().parse("GA").is_err()); + assert!(parsing::global_address_space().parse("G").is_err()); + + // Successes + assert_eq!(parsing::global_address_space().parse("G1"), Ok(1)); + assert_eq!(parsing::global_address_space().parse("G0"), Ok(0)); + } + + #[test] + fn can_parse_alloc_address_space() { + // Failures + assert!(parsing::alloc_address_space().parse("AA").is_err()); + assert!(parsing::alloc_address_space().parse("A").is_err()); + + // Successes + assert_eq!(parsing::alloc_address_space().parse("A1"), Ok(1)); + assert_eq!(parsing::alloc_address_space().parse("A0"), Ok(0)); + } + + #[test] + fn can_parse_pointer_spec() { + // Failures + assert!(PointerLayout::parser().parse("p[1]:64:128:128:68").is_err()); + assert!(PointerLayout::parser().parse("p[]:64:128:128:32").is_err()); + + // Successes + assert_eq!( + PointerLayout::parser().parse("p[1]:64:128:128:64"), + Ok(PointerLayout { + address_space: 1, + size: 64, + abi_alignment: 128, + preferred_alignment: 128, + index_size: 64, + }) + ); + assert_eq!( + PointerLayout::parser().parse("p:64:128:128:64"), + Ok(PointerLayout { + address_space: 0, + size: 64, + abi_alignment: 128, + preferred_alignment: 128, + index_size: 64, + }) + ); + assert_eq!( + PointerLayout::parser().parse("p:64:128"), + Ok(PointerLayout { + address_space: 0, + size: 64, + abi_alignment: 128, + preferred_alignment: 128, + index_size: 64, + }) + ); + } + + #[test] + fn can_parse_integer_spec() { + // Failures + assert!(IntegerLayout::parser().parse("i").is_err()); + assert!(IntegerLayout::parser().parse("i8:16").is_err()); + + // Successes + assert_eq!( + IntegerLayout::parser().parse("i8:8"), + Ok(IntegerLayout { + size: 8, + abi_alignment: 8, + preferred_alignment: 8, + }) + ); + assert_eq!( + IntegerLayout::parser().parse("i32:64"), + Ok(IntegerLayout { + size: 32, + abi_alignment: 64, + preferred_alignment: 64, + }) + ); + assert_eq!( + IntegerLayout::parser().parse("i32:64:128"), + Ok(IntegerLayout { + size: 32, + abi_alignment: 64, + preferred_alignment: 128, + }) + ); + } + + #[test] + fn can_parse_vector_spec() { + // Failures + assert!(VectorLayout::parser().parse("v").is_err()); + assert!(VectorLayout::parser().parse("v8").is_err()); + + // Successes + assert_eq!( + VectorLayout::parser().parse("v8:8"), + Ok(VectorLayout { + size: 8, + abi_alignment: 8, + preferred_alignment: 8, + }) + ); + assert_eq!( + VectorLayout::parser().parse("v32:64"), + Ok(VectorLayout { + size: 32, + abi_alignment: 64, + preferred_alignment: 64, + }) + ); + assert_eq!( + VectorLayout::parser().parse("v32:64:128"), + Ok(VectorLayout { + size: 32, + abi_alignment: 64, + preferred_alignment: 128, + }) + ); + } + + #[test] + fn can_parse_float_spec() { + // Failures + assert!(FloatLayout::parser().parse("f").is_err()); + assert!(FloatLayout::parser().parse("f8:16").is_err()); + assert!(FloatLayout::parser().parse("f96:128").is_err()); + + // Successes + assert_eq!( + FloatLayout::parser().parse("f16:16"), + Ok(FloatLayout { + size: 16, + abi_alignment: 16, + preferred_alignment: 16, + }) + ); + assert_eq!( + FloatLayout::parser().parse("f32:64"), + Ok(FloatLayout { + size: 32, + abi_alignment: 64, + preferred_alignment: 64, + }) + ); + assert_eq!( + FloatLayout::parser().parse("f32:64:128"), + Ok(FloatLayout { + size: 32, + abi_alignment: 64, + preferred_alignment: 128, + }) + ); + } + + #[test] + fn can_parse_aggregate_spec() { + // Failures + assert!(FloatLayout::parser().parse("a").is_err()); + + // Successes + assert_eq!( + AggregateLayout::parser().parse("a64"), + Ok(AggregateLayout { + abi_alignment: 64, + preferred_alignment: 64, + }) + ); + assert_eq!( + AggregateLayout::parser().parse("a64:128"), + Ok(AggregateLayout { + abi_alignment: 64, + preferred_alignment: 128, + }) + ); + } + + #[test] + fn can_parse_function_pointer_type() { + // Failures + assert!(FunctionPointerType::parser().parse("a").is_err()); + + // Successes + assert_eq!( + FunctionPointerType::parser().parse("i"), + Ok(FunctionPointerType::Independent) + ); + assert_eq!( + FunctionPointerType::parser().parse("n"), + Ok(FunctionPointerType::Multiple) + ); + } + + #[test] + fn can_parse_function_pointer_spec() { + // Failures + assert!(FunctionPointerLayout::parser().parse("Fi").is_err()); + assert!(FunctionPointerLayout::parser().parse("Fb64").is_err()); + + // Successes + assert_eq!( + FunctionPointerLayout::parser().parse("Fi64"), + Ok(FunctionPointerLayout { + ptr_type: FunctionPointerType::Independent, + abi_alignment: 64, + }) + ); + assert_eq!( + FunctionPointerLayout::parser().parse("Fi128"), + Ok(FunctionPointerLayout { + ptr_type: FunctionPointerType::Independent, + abi_alignment: 128, + }) + ); + assert_eq!( + FunctionPointerLayout::parser().parse("Fn32"), + Ok(FunctionPointerLayout { + ptr_type: FunctionPointerType::Multiple, + abi_alignment: 32, + }) + ); + } + + #[test] + fn can_parse_native_integer_widths_spec() { + // Failures + assert!(NativeIntegerWidths::parser().parse("Fi").is_err()); + assert!(NativeIntegerWidths::parser().parse("n").is_err()); + + // Successes + assert_eq!( + NativeIntegerWidths::parser().parse("n64"), + Ok(NativeIntegerWidths { widths: vec![64] }) + ); + assert_eq!( + NativeIntegerWidths::parser().parse("n16:32:64"), + Ok(NativeIntegerWidths { + widths: vec![16, 32, 64], + }) + ); + } + + #[test] + fn can_parse_nointptr_address_spaces_spec() { + // Failures + assert!(NonIntegralPointerAddressSpaces::parser().parse("ni").is_err()); + assert!(NonIntegralPointerAddressSpaces::parser().parse("ni:").is_err()); + assert!(NonIntegralPointerAddressSpaces::parser().parse("ni:0").is_err()); + + // Successes + assert_eq!( + NonIntegralPointerAddressSpaces::parser().parse("ni:1"), + Ok(NonIntegralPointerAddressSpaces { + address_spaces: vec![1], + }) + ); + assert_eq!( + NonIntegralPointerAddressSpaces::parser().parse("ni:1:3:5"), + Ok(NonIntegralPointerAddressSpaces { + address_spaces: vec![1, 3, 5], + }) + ); + } +} diff --git a/crates/compiler/src/llvm/mod.rs b/crates/compiler/src/llvm/mod.rs new file mode 100644 index 0000000..d5cc4f9 --- /dev/null +++ b/crates/compiler/src/llvm/mod.rs @@ -0,0 +1,37 @@ +//! Utilities for working with LLVM concepts inside the codebase. They are +//! intended to bridge between the worlds of LLVM and the worlds of our compiler +//! itself, and hence aid in analysis and transformation of the LLVM IR. + +use crate::llvm::typesystem::LLVMType; + +pub mod data_layout; +pub mod typesystem; + +/// The type of top-level entry that is encountered in the module. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub enum TopLevelEntryKind { + /// A declaration of an external symbol including the name, attributes, and + /// signature. + Declaration, + + /// A definition of symbol including the name, attributes, signature, and + /// **body** (consisting of basic blocks). + Definition, +} + +/// A trait representing objects that have an [`LLVMType`] ascribed to them. +/// +/// This is to enable a uniform interface for richer compilation and metadata +/// structures to easily provide their type to a caller. +pub trait HasLLVMType { + /// Gets the LLVM type for the implementing object. + fn get_type(&self) -> LLVMType; +} + +/// An `LLVMType` _obviously_ has an LLVM type, so we provide a blanket +/// implementation here. +impl HasLLVMType for LLVMType { + fn get_type(&self) -> LLVMType { + self.clone() + } +} diff --git a/crates/compiler/src/llvm/typesystem.rs b/crates/compiler/src/llvm/typesystem.rs new file mode 100644 index 0000000..aeee4c3 --- /dev/null +++ b/crates/compiler/src/llvm/typesystem.rs @@ -0,0 +1,544 @@ +//! The compiler's internal representation of LLVM types, without being tied to +//! the context as the [`BasicTypeEnum`] is. + +use std::fmt::{Display, Formatter}; + +use inkwell::types::{ + AnyTypeEnum, + ArrayType, + BasicTypeEnum, + FloatType, + FunctionType, + IntType, + PointerType, + StructType, + VectorType, + VoidType, +}; +use itertools::Itertools; +use ltc_errors::{compile, compile::Error}; + +use crate::constant::BYTE_SIZE; + +/// A representation of the LLVM [types](https://llvm.org/docs/LangRef.html#type-system) +/// for use within the compiler. +/// +/// # Why Not Use `BasicTypeEnum`? +/// +/// The definition of Inkwell's [`BasicTypeEnum`] and [`AnyTypeEnum`] depends on +/// being tied directly to the host LLVM context. This is not something we want +/// for metadata that is likely to be passed around liberally within this +/// compiler and potentially even cross program boundaries. +/// +/// To that end, we convert it to our own internal representation with the +/// knowledge that this static and does not update if the internal LLVM +/// representation changes. +/// +/// We additionally want to restrict the allowable types in our use-case. This +/// enum **does not** match LLVM IR's type system 1:1, instead restricting the +/// allowable types—particularly the integers—to be the ones that we care about. +/// +/// # Value Semantics +/// +/// It is intended that this type is used as having value semantics, and not +/// ever have a reference returned to it. +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum LLVMType { + /// The boolean type, represented inside LLVM by the `i1` + /// [integer type](https://llvm.org/docs/LangRef.html#integer-type). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + bool, + + /// The 8-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + i8, + + /// The 16-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + i16, + + /// The 32-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + i32, + + /// The 64-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + i64, + + /// The 128-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + i128, + + /// The IEEE-754 `binary16` [floating point type](https://llvm.org/docs/LangRef.html#floating-point-types). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + half, + + /// The IEEE-754 `binary32` [floating point type](https://llvm.org/docs/LangRef.html#floating-point-types). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + float, + + /// The IEEE-754 `binary64` [floating point type](https://llvm.org/docs/LangRef.html#floating-point-types). + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + double, + + /// Used to specify locations in memory as described in the + /// [LLVM IR reference](https://llvm.org/docs/LangRef.html#pointer-type). + /// + /// Note that pointers in our use only support the base address space, and + /// do not specify the corresponding pointee type as was available in + /// earlier versions of LLVM. + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + ptr, + + /// A [type](https://llvm.org/docs/LangRef.html#void-type) that does not + /// represent any value and has no size. + #[allow(non_camel_case_types)] // To better match the LLVM internal convention + void, + + /// An [array](https://llvm.org/docs/LangRef.html#array-type) is a + /// sequential arrangement of a number of elements of the given type + /// linearly in memory. + Array { + /// The number of elements in the array type. + count: usize, + + /// The type of elements in the array type. + ty: Box, + }, + + /// A [structure](https://llvm.org/docs/LangRef.html#structure-type) + /// represents a number of elements together in memory. + /// + /// Note that struct elements do not have names, and can only be accessed by + /// index. This makes LLVM struct types far more akin to what we call a + /// Tuple in most languages. + Structure { + /// If the structure is packed, it has one-byte alignment with no + /// padding between elements. + /// + /// If it is not packed, then the padding and alignment of struct + /// elements is given by the module's data-layout string. + packed: bool, + + /// The element types in the structure type. + /// + /// The order is semantically meaninful here. + elements: Vec, + }, + + /// A [function](https://llvm.org/docs/LangRef.html#function-type) is akin + /// to a function signature. + Function { + /// The type returned from the function. + return_type: Box, + + /// The types of the parameters to the function. + /// + /// Note that these are never named, and are purely matched + /// positionally. + parameter_types: Vec, + }, + + /// Embedded [metadata](https://llvm.org/docs/LangRef.html#metadata-type) + /// used as a value has this type. + Metadata, +} + +/// Additional utility constructors for creating the compound types without +/// having to manage boxing manually. +impl LLVMType { + /// Builds an array type containing the provided `elem_count` number of + /// elements of type `elem_type`. + #[must_use] + pub fn make_array(elem_count: usize, elem_type: LLVMType) -> Self { + Self::Array { + count: elem_count, + ty: Box::new(elem_type), + } + } + + /// Creates a struct type from the provided `elem_types` and whether it is + /// `packed`. + #[must_use] + pub fn make_struct(packed: bool, elem_types: &[LLVMType]) -> Self { + Self::Structure { + packed, + elements: Vec::from(elem_types), + } + } + + /// Creates a function type from the provided `return_type` and + /// `param_types`. + #[must_use] + pub fn make_function(return_type: LLVMType, param_types: &[LLVMType]) -> Self { + Self::Function { + return_type: Box::new(return_type), + parameter_types: Vec::from(param_types), + } + } +} + +/// Operations for working with LLVM types, such as asserting properties on +/// them, or processing them. +impl LLVMType { + /// Checks if the LLVM type represented by `self` unifies with the type + /// represented by `other`. + /// + /// Please note that this is currently purely an equality check. It exists + /// so that in the future we can seamlessly implement more complex + /// unification rules if needed. + #[must_use] + pub fn unifies_with(&self, other: &LLVMType) -> bool { + self == other + } + + /// Returns `true` if `self` is a primitive type, and `false` otherwise. + #[must_use] + pub fn is_prim(&self) -> bool { + matches!( + self, + Self::bool + | Self::i8 + | Self::i32 + | Self::i64 + | Self::i128 + | Self::half + | Self::float + | Self::double + | Self::ptr + | Self::void + | Self::Metadata + ) + } + + /// Returns `true` if `self` is a compound type, and `false` otherwise. + #[must_use] + pub fn is_compound(&self) -> bool { + !self.is_prim() + } + + /// Returns `true` if `self` is an integral type, and `false` otherwise. + #[must_use] + pub fn is_integral(&self) -> bool { + matches!( + self, + Self::bool | Self::i8 | Self::i16 | Self::i32 | Self::i64 | Self::i128 + ) + } + + /// Returns `true` if `self` is a floating-point type, and `false` + /// otherwise. + #[must_use] + pub fn is_float(&self) -> bool { + matches!(self, Self::half | Self::float | Self::double) + } +} + +/// This attempts to match the LLVM representations for these types where it is +/// reasonable. +/// +/// For Array types we currently use the Rust syntax as that is clearer to read +/// than the LLVM product-style syntax. +impl Display for LLVMType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let result = match self { + LLVMType::bool => "bool".to_string(), + LLVMType::i8 => "i8".to_string(), + LLVMType::i16 => "i16".to_string(), + LLVMType::i32 => "i32".to_string(), + LLVMType::i64 => "i64".to_string(), + LLVMType::i128 => "i128".to_string(), + LLVMType::half => "half".to_string(), + LLVMType::float => "float".to_string(), + LLVMType::double => "double".to_string(), + LLVMType::ptr => "ptr".to_string(), + LLVMType::void => "void".to_string(), + LLVMType::Metadata => "metadata".to_string(), + LLVMType::Array { count, ty } => { + let ty_str = ty.to_string(); + format!("[{ty_str}; {count}]") + } + LLVMType::Structure { packed, elements } => { + let elem_strs = elements.iter().map(std::string::ToString::to_string).join(", "); + if *packed { + format!("<{{ {elem_strs} }}>") + } else { + format!("{{ {elem_strs} }}") + } + } + LLVMType::Function { + return_type, + parameter_types, + } => { + let params_string = parameter_types + .iter() + .map(std::string::ToString::to_string) + .join(", "); + format!("({params_string}) -> {return_type}") + } + }; + + writeln!(f, "{result}") + } +} + +/// Conversion from Inkwell's generic type enum to our type language. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: AnyTypeEnum<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's generic type enum to our type language. +impl<'ctx> TryFrom<&AnyTypeEnum<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &AnyTypeEnum<'ctx>) -> Result { + match value { + AnyTypeEnum::ArrayType(array_type) => Self::try_from(array_type), + AnyTypeEnum::FloatType(float_type) => Self::try_from(float_type), + AnyTypeEnum::FunctionType(fn_ty) => Self::try_from(fn_ty), + AnyTypeEnum::IntType(int_type) => Self::try_from(int_type), + AnyTypeEnum::PointerType(ptr_type) => Self::try_from(ptr_type), + AnyTypeEnum::StructType(struct_type) => Self::try_from(struct_type), + AnyTypeEnum::VoidType(void_type) => Self::try_from(void_type), + AnyTypeEnum::VectorType(vector_type) => Self::try_from(vector_type), + } + } +} + +/// Conversion from Inkwell's basic type enum to our type language. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: BasicTypeEnum<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's basic type enum to our type language. +impl<'ctx> TryFrom<&BasicTypeEnum<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &BasicTypeEnum<'ctx>) -> Result { + match value { + BasicTypeEnum::ArrayType(array_type) => Self::try_from(array_type), + BasicTypeEnum::FloatType(float_type) => Self::try_from(float_type), + BasicTypeEnum::IntType(int_type) => Self::try_from(int_type), + BasicTypeEnum::PointerType(ptr_type) => Self::try_from(ptr_type), + BasicTypeEnum::StructType(struct_type) => Self::try_from(struct_type), + BasicTypeEnum::VectorType(vector_type) => Self::try_from(vector_type), + } + } +} + +/// Conversion from Inkwell's array type to our type language. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: ArrayType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's array type to our type language. +impl<'ctx> TryFrom<&ArrayType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &ArrayType<'ctx>) -> Result { + let length = value.len() as usize; + let elem_type = Self::try_from(value.get_element_type())?; + Ok(Self::make_array(length, elem_type)) + } +} + +/// Conversion from Inkwell's generic float type to our specific float types. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: FloatType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's generic float type to our specific float types. +impl<'ctx> TryFrom<&FloatType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &FloatType<'ctx>) -> Result { + #[allow(clippy::cast_possible_wrap)] // Our byte size should never be large enough + let float_size_bits = value + .size_of() + .get_sign_extended_constant() + .ok_or(Error::UnsupportedType(value.to_string()))? + * BYTE_SIZE as i64; + let ret_val = match float_size_bits { + 16 => Self::half, + 32 => Self::float, + 64 => Self::double, + _ => Err(Error::UnsupportedType(value.to_string()))?, + }; + Ok(ret_val) + } +} + +/// Conversion from Inkwell's generic integer type to our specific integer +/// types. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: IntType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's generic integer type to our specific integer +/// types. +impl<'ctx> TryFrom<&IntType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &IntType<'ctx>) -> Result { + let res = match value.get_bit_width() { + 1 => Self::bool, + 8 => Self::i8, + 16 => Self::i16, + 32 => Self::i32, + 64 => Self::i64, + 128 => Self::i128, + _ => Err(Error::UnsupportedType(value.to_string()))?, + }; + + Ok(res) + } +} + +/// Conversion from Inkwell's pointer type to our type language. +/// +/// We centralize it here despite it being trivial as this gives us one place to +/// potentially need to change if we ever add type system support for typed +/// pointers. Otherwise, we would have to change every site performing +/// conversion of pointer types. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: PointerType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's pointer type to our type language. +/// +/// We centralize it here despite it being trivial as this gives us one place to +/// potentially need to change if we ever add type system support for typed +/// pointers. Otherwise, we would have to change every site performing +/// conversion of pointer types. +impl<'ctx> TryFrom<&PointerType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(_: &PointerType<'ctx>) -> Result { + Ok(Self::ptr) + } +} + +/// Conversion from Inkwell's struct type to our type language. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: StructType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's struct type to our type language. +impl<'ctx> TryFrom<&StructType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &StructType<'ctx>) -> Result { + let field_types: Vec = value + .get_field_types() + .iter() + .map(Self::try_from) + .collect::, Error>>()?; + let packed = value.is_packed(); + Ok(Self::make_struct(packed, &field_types)) + } +} + +/// Conversion from Inkwell's vector type to our type language. +/// +/// Currently, our type language **cannot represent** the SIMD vector types, so +/// this operation will error. It exists to ensure that in the future we can +/// seamlessly add support without having to change multiple conversion sites +/// that would currently need to produce errors. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: VectorType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's vector type to our type language. +/// +/// Currently, our type language **cannot represent** the SIMD vector types, so +/// this operation will error. It exists to ensure that in the future we can +/// seamlessly add support without having to change multiple conversion sites +/// that would currently need to produce errors. +impl<'ctx> TryFrom<&VectorType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &VectorType<'ctx>) -> Result { + Err(Error::UnsupportedType(value.to_string()))? + } +} + +/// Conversion from Inkwell's function type to our type language. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: FunctionType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's function type to our type language. +impl<'ctx> TryFrom<&FunctionType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(value: &FunctionType<'ctx>) -> Result { + let return_type = value.get_return_type().map_or(Ok(LLVMType::void), Self::try_from)?; + let param_types = value + .get_param_types() + .iter() + .map(Self::try_from) + .collect::, Error>>()?; + + Ok(Self::make_function(return_type, ¶m_types)) + } +} + +/// Conversion from Inkwell's void type to our type language. +/// +/// We centralize this in a conversion to ensure that it is consistent at all +/// use sites. +impl<'ctx> TryFrom> for LLVMType { + type Error = compile::Error; + + fn try_from(value: VoidType<'ctx>) -> Result { + Self::try_from(&value) + } +} + +/// Conversion from Inkwell's void type to our type language. +/// +/// We centralize this in a conversion to ensure that it is consistent at all +/// use sites. +impl<'ctx> TryFrom<&VoidType<'ctx>> for LLVMType { + type Error = compile::Error; + + fn try_from(_: &VoidType<'ctx>) -> Result { + Ok(Self::void) + } +} diff --git a/crates/compiler/src/compile/pass/analysis/mod.rs b/crates/compiler/src/pass/analysis/mod.rs similarity index 100% rename from crates/compiler/src/compile/pass/analysis/mod.rs rename to crates/compiler/src/pass/analysis/mod.rs diff --git a/crates/compiler/src/pass/analysis/module_map.rs b/crates/compiler/src/pass/analysis/module_map.rs new file mode 100644 index 0000000..7880954 --- /dev/null +++ b/crates/compiler/src/pass/analysis/module_map.rs @@ -0,0 +1,455 @@ +//! This pass is responsible for generating a map of the top-level structure of +//! an LLVM IR module as described by an `.ll` file. This map encompasses both +//! function and global entries at the module level, as well as data layout +//! description for the module. +//! +//! The [`ModuleMap`] that results from this pass is intended for downstream +//! usage during the compilation step, primarily for consistency checking. + +use std::collections::HashMap; + +use inkwell::{ + module::{Linkage, Module}, + values::{FunctionValue, GlobalValue}, + GlobalVisibility, +}; +use ltc_errors::compile::{Error, Result}; + +use crate::{ + llvm::{data_layout::DataLayout, typesystem::LLVMType, TopLevelEntryKind}, + pass::{ + data::{ConcretePassData, DynPassDataMap, PassDataOps}, + ConcretePass, + DynamicPassReturnData, + Pass, + PassKey, + PassOps, + }, + source::SourceContext, +}; + +/// Generates a map of the top-level structure of an LLVM module. +/// +/// This map includes both functions and globals, as well as the [`DataLayout`] +/// definition for the module. +#[derive(Clone, Debug, PartialEq)] +pub struct BuildModuleMap { + /// The passes that this pass depends upon the results of for its execution. + depends: Vec, + + /// The passes that this pass invalidates the results of by executing. + invalidates: Vec, + + /// LLVM intrinsics that need to be handled specially. + special_intrinsics: SpecialIntrinsics, +} + +impl Default for BuildModuleMap { + fn default() -> Self { + Self::new() + } +} + +impl BuildModuleMap { + /// Creates a new instance of the module mapping pass. + #[must_use] + pub fn new() -> Self { + // This pass depends on the results of no other passes. + let depends = vec![]; + + // This pass's operation is purely analytical and hence it does not invalidate + // any other passes. + let invalidates = vec![]; + + let special_intrinsics = SpecialIntrinsics::new(); + Self { + depends, + invalidates, + special_intrinsics, + } + } + + /// Creates a new trait object of the module mapping pass. + #[must_use] + pub fn new_dyn() -> Box { + Box::new(Self::new()) + } +} + +impl BuildModuleMap { + /// Generates a module map for the provided module in the source context, + /// returning the module map if successful. + /// + /// # Errors + /// + /// - [`Error`] if the module cannot be mapped successfully. + pub fn map_module(&mut self, module: &Module) -> Result { + // We start by analyzing the data-layout of the module, which is important to + // ensure that things match later on and that we are not being asked for things + // that we do not or cannot support. This _may_ currently return errors due to + // unsupported data layouts, but this could potentially be moved into the + // compilation step in the future. + let data_layout = self.process_data_layout(module.get_data_layout().as_str().to_str()?)?; + + // With our data layout obtained succesffully, we can build our module map and + // start adding top-level entries to it. + let mut mod_map = ModuleMap::new(data_layout); + + // We then process the global definitions in scope and gather the relevant + // information about them. + module + .get_globals() + .map(|g| self.map_global(&g, &mut mod_map)) + .collect::>>()?; + + // Finally we use the top-level information about functions to create a map of + // the remaining symbols that occur in the module. + module + .get_functions() + .map(|f| self.map_function(&f, &mut mod_map)) + .collect::>>()?; + + // Our map is complete, so we can just return it. + Ok(mod_map) + } + + /// Processes the data layout declaration from the module. + /// + /// # Future-Gazing + /// + /// In the future we may well want to treat our target as a proper Harvard + /// architecture with the separate program address space and allocation + /// address space that it actually has. For now, we are relying on a stopgap + /// target (`aarch64-unknown-none-softfloat`) which does not give us this + /// control, and so we are raising an error if the configuration is + /// incorrect. + /// + /// # Errors + /// + /// - [`Error::UnsupportedAdditionalAddressSpaces`] if the data layout + /// declares pointers in any address space other than the default 0. + /// - [`Error::UnsupportedNonIntegralPointerConfiguration`] if the data + /// layout requests non-integral pointers for any address space. + pub fn process_data_layout(&mut self, layout_string: &str) -> Result { + // We start by analyzing the data-layout of the module, which is important to + // ensure that things match later on and that we are not being asked for things + // that we do not or cannot support. + let data_layout = DataLayout::new(layout_string)?; + + // We do not support split address spaces (for now). Later we may want to use + // this to properly state that our target architecture is a Harvard one rather + // than a Von-Neumann one, but we are leaving it for now. + if data_layout.pointer_layouts.iter().any(|p| p.address_space != 0) + || data_layout.alloc_address_space != 0 + || data_layout.global_address_space != 0 + || data_layout.program_address_space != 0 + { + Err(Error::UnsupportedAdditionalAddressSpaces)?; + } + + // We do not support non-integral pointers in any address space. + if !data_layout.nointptr_address_spaces.address_spaces.is_empty() { + Err(Error::UnsupportedNonIntegralPointerConfiguration)?; + } + + Ok(data_layout) + } + + /// Gathers the data for a module-level global and writes it into the + /// `mod_map`. + /// + /// # Errors + /// + /// - [`Error`] if the global information cannot be gathered successfully. + pub fn map_global(&mut self, global: &GlobalValue, mod_map: &mut ModuleMap) -> Result<()> { + let name = global.get_name().to_str()?.to_string(); + + let kind = if global.is_declaration() { + TopLevelEntryKind::Declaration + } else { + TopLevelEntryKind::Definition + }; + + let typ = global.get_value_type().try_into()?; + let is_const = global.is_constant(); + let alignment = global.get_alignment() as usize; + let linkage = global.get_linkage(); + let visibility = global.get_visibility(); + let is_initialized = global.get_initializer().is_some(); + + let global_info = GlobalInfo { + kind, + typ, + linkage, + visibility, + alignment, + is_const, + is_initialized, + }; + + mod_map.globals.insert(name, global_info); + + Ok(()) + } + + /// Gathers the data for a module-level function and writes it into the + /// `mod_map`. + /// + /// # Errors + /// + /// - [`Error`] if the function information cannot be gathered successfully. + pub fn map_function(&mut self, func: &FunctionValue, mod_map: &mut ModuleMap) -> Result<()> { + let name = func.get_name().to_str()?.to_string(); + + if let Some(intrinsic) = self.special_intrinsics.info_for(&name) { + mod_map.functions.insert(name, intrinsic); + } else { + let kind = if func.as_global_value().is_declaration() { + TopLevelEntryKind::Declaration + } else { + TopLevelEntryKind::Definition + }; + let typ = LLVMType::try_from(func.get_type())?; + let linkage = func.get_linkage(); + let intrinsic = func.get_intrinsic_id() != 0; + let visibility = func.as_global_value().get_visibility(); + let f_info = FunctionInfo { + kind, + intrinsic, + typ, + linkage, + visibility, + }; + + mod_map.functions.insert(name, f_info); + } + + Ok(()) + } +} + +/// We need to be able to run this pass using the pass manager, so we are +/// obliged to implement `PassOps` for it to make this possible. +impl PassOps for BuildModuleMap { + fn run( + &mut self, + context: SourceContext, + _pass_data: &DynPassDataMap, + ) -> Result { + let analysis_result = context.analyze_module(|module| self.map_module(module))?; + Ok(DynamicPassReturnData::new( + context, + Box::new(analysis_result), + )) + } + + fn depends(&self) -> &[PassKey] { + self.depends.as_slice() + } + + fn invalidates(&self) -> &[PassKey] { + self.invalidates.as_slice() + } + + fn dupe(&self) -> Pass { + Box::new(self.clone()) + } +} + +/// We also want to be able to work with the pass when it is not type-erased to +/// `dyn PassOps`, so we are obliged to implement the concrete pass operations +/// trait here too. +impl ConcretePass for BuildModuleMap { + type Data = ModuleMap; +} + +/// The module map that results from executing this analysis pass on an LLVM IR +/// module. +/// +/// It contains information on the module's: +/// +/// - Data layout, as given by the embedded data layout string. +/// - Functions, as given by the function definitions and declarations. +/// - Globals, as given by the global definitions and declarations. +#[derive(Clone, Debug, PartialEq)] +pub struct ModuleMap { + /// The data layout provided for this module. + pub data_layout: DataLayout, + + /// The globals that are contained within the module. + pub globals: HashMap, + + /// The functions that are contained within the module. + pub functions: HashMap, +} + +impl ModuleMap { + /// Creates a new instance of the output data for the module mapping pass. + #[must_use] + pub fn new(data_layout: DataLayout) -> Self { + let functions = HashMap::new(); + let globals = HashMap::new(); + Self { + data_layout, + globals, + functions, + } + } + + /// Creates a new trait object of the output data for the module mapping + /// pass. + #[must_use] + pub fn new_dyn(data_layout: DataLayout) -> Box { + Box::new(Self::new(data_layout)) + } +} + +/// We need to work with this type as a generic piece of pass data. +impl PassDataOps for ModuleMap {} + +/// We also need to work with this type as a piece of _concrete_ pass data for +/// non type-erased workflows. +impl ConcretePassData for ModuleMap { + type Pass = BuildModuleMap; +} + +/// Information about a function to be stored in the module map. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionInfo { + /// The type of function entity that was encountered here. + pub kind: TopLevelEntryKind, + + /// Set if this function is an LLVM intrinsic, and unset otherwise. + pub intrinsic: bool, + + /// The LLVM type of our function. + pub typ: LLVMType, + + /// The linkage for our function. + pub linkage: Linkage, + + /// The visibility of our function. + pub visibility: GlobalVisibility, +} + +/// Information about a global to be stored in the module map. +#[derive(Clone, Debug, PartialEq)] +pub struct GlobalInfo { + /// The type of global entity that was encountered here. + pub kind: TopLevelEntryKind, + + /// The LLVM type of our global. + pub typ: LLVMType, + + /// The linkage for our global. + pub linkage: Linkage, + + /// The visibility of our global. + pub visibility: GlobalVisibility, + + /// The alignment of the global value. + pub alignment: usize, + + /// `true` if this global is constant, and `false` otherwise. + pub is_const: bool, + + /// `true` if this global is initialized, and `false` otherwise. + pub is_initialized: bool, +} + +/// A registry of LLVM intrinsic functions that need to be handled specially. +/// +/// # Avoiding an Issue in Inkwell +/// +/// Unfortunately [`inkwell`] does not deal well with `metadata`-typed function +/// arguments, despite them being valid argument types for function-typed values +/// in LLVM IR. For now, we handle them by delegating to known signatures for +/// these functions, rather than trying to introspect the functions themselves. +/// +/// See [this issue](https://github.com/TheDan64/inkwell/issues/546) for more +/// information. +#[derive(Clone, Debug, PartialEq)] +pub struct SpecialIntrinsics { + /// The intrinsics that need to be handled specially. + intrinsics: HashMap, +} + +impl SpecialIntrinsics { + /// Constructs the special intrinsics mapping, providing the appropriate + /// [`FunctionInfo`] metadata for the intrinsics that we insert. + #[must_use] + pub fn new() -> Self { + let mut intrinsics = HashMap::new(); + intrinsics.insert( + "llvm.dbg.declare".to_string(), + FunctionInfo { + kind: TopLevelEntryKind::Declaration, + intrinsic: true, + typ: LLVMType::make_function( + LLVMType::void, + &[LLVMType::Metadata, LLVMType::Metadata, LLVMType::Metadata], + ), + linkage: Linkage::External, + visibility: GlobalVisibility::Default, + }, + ); + intrinsics.insert( + "llvm.dbg.value".to_string(), + FunctionInfo { + kind: TopLevelEntryKind::Declaration, + intrinsic: true, + typ: LLVMType::make_function( + LLVMType::void, + &[LLVMType::Metadata, LLVMType::Metadata, LLVMType::Metadata], + ), + linkage: Linkage::External, + visibility: GlobalVisibility::Default, + }, + ); + intrinsics.insert( + "llvm.dbg.assign".to_string(), + FunctionInfo { + kind: TopLevelEntryKind::Declaration, + intrinsic: true, + typ: LLVMType::make_function( + LLVMType::void, + &[ + LLVMType::Metadata, + LLVMType::Metadata, + LLVMType::Metadata, + LLVMType::Metadata, + LLVMType::Metadata, + ], + ), + linkage: Linkage::External, + visibility: GlobalVisibility::Default, + }, + ); + + Self { intrinsics } + } + + /// Gets the function information for `function_name` if it exists, and + /// returns [`None`] otherwise. + #[must_use] + pub fn info_for(&self, function_name: &str) -> Option { + self.intrinsics.get(function_name).cloned() + } + + /// Gets the function information for `function_name` if it exists. + /// + /// # Panics + /// + /// If `function_name` does not exist in the special intrinsics container. + #[must_use] + pub fn info_for_unchecked(&self, function_name: &str) -> FunctionInfo { + self.info_for(function_name) + .unwrap_or_else(|| panic!("No information found for {function_name}")) + } +} + +impl Default for SpecialIntrinsics { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/compiler/src/compile/pass/data.rs b/crates/compiler/src/pass/data.rs similarity index 87% rename from crates/compiler/src/compile/pass/data.rs rename to crates/compiler/src/pass/data.rs index f08a195..dd181e8 100644 --- a/crates/compiler/src/compile/pass/data.rs +++ b/crates/compiler/src/pass/data.rs @@ -10,7 +10,7 @@ use std::{ use derivative::Derivative; use downcast_rs::Downcast; -use crate::compile::pass::{ConcretePass, Pass, PassKey}; +use crate::pass::{ConcretePass, Pass, PassKey}; /// Pass data is output by any given pass pub type PassData = Box; @@ -20,6 +20,16 @@ pub type PassData = Box; /// The implementation is designed to be used via dynamic dispatch, and hence /// can provide the requisite operations however it is able. /// +/// # Recommended Functions +/// +/// On the concrete type that implements this trait, we recommend implementing: +/// +/// - An appropriate `new(...) -> Self` associated function. +/// - An appropriate `new_dyn(...) -> PassData` associated function. This one +/// can usually simply call `Box::new(Self::new(...))`. +/// +/// These aid in providing a uniform way to construct pass data. +/// /// # Self Bounds /// /// The bounds on `Self` are required by these traits for the following reasons: @@ -85,6 +95,16 @@ impl dyn PassDataOps { /// Provides additional operations that can be called when operating on a /// concrete instance of a specific pass, rather than any pass instance. +/// +/// # Recommended Functions +/// +/// On the concrete type that implements this trait, we recommend implementing: +/// +/// - An appropriate `new(...) -> Self` associated function. +/// - An appropriate `new_dyn(...) -> PassData` associated function. This one +/// can usually simply call `Box::new(Self::new(...))`. +/// +/// These aid in providing a uniform way to construct pass data. pub trait ConcretePassData where Self: Clone + Debug + PassDataOps, diff --git a/crates/compiler/src/compile/pass/mod.rs b/crates/compiler/src/pass/mod.rs similarity index 90% rename from crates/compiler/src/compile/pass/mod.rs rename to crates/compiler/src/pass/mod.rs index f870331..73c488d 100644 --- a/crates/compiler/src/compile/pass/mod.rs +++ b/crates/compiler/src/pass/mod.rs @@ -44,7 +44,7 @@ use derivative::Derivative; use downcast_rs::Downcast; use ltc_errors::compile::{Error, Result}; -use crate::compile::{ +use crate::{ pass::data::{ConcretePassData, DynPassDataMap, PassData}, source::SourceContext, }; @@ -124,6 +124,16 @@ pub type DynamicPassReturnData = PassReturnData; /// The implementation is designed te be used via dynamic dispatch, and hence /// can provide the requisite operations however it is able. /// +/// # Recommended Functions +/// +/// On the concrete type that implements this trait, we recommend implementing: +/// +/// - An appropriate `new(...) -> Self` associated function. +/// - An appropriate `new_dyn(...) -> PassData` associated function. This one +/// can usually simply call `Box::new(Self::new(...))`. +/// +/// These aid in providing a uniform way to construct pass data. +/// /// # Self Bounds /// /// The bounds on `Self` are required by these traits for the following reasons: @@ -220,6 +230,16 @@ impl dyn PassOps { /// Provides extra operations that can be called when operating on a concrete /// instance of a specific pass, rather than on any instance of a pass. +/// +/// # Recommended Functions +/// +/// On the concrete type that implements this trait, we recommend implementing: +/// +/// - An appropriate `new(...) -> Self` associated function. +/// - An appropriate `new_dyn(...) -> PassData` associated function. This one +/// can usually simply call `Box::new(Self::new(...))`. +/// +/// These aid in providing a uniform way to construct pass data. pub trait ConcretePass where Self: Clone + Debug + PassOps, @@ -315,7 +335,12 @@ impl PassManager { /// generated from the provided `passes`. This will usually occur due to /// circular dependencies between passes. pub fn generate_pass_ordering(passes: Vec) -> Result> { - // TODO Actually implement this (#56). The current constraint is silly. + // TODO Actually implement this (#56). The current constraint is silly for the + // future but sane for now as we only have the one pass. + // + // In future it should actually construct a topological ordering of passes based + // on their declared dependencies and invalidations, only returning an error if + // there is an unbreakable topological cycle. let no_deps = passes.iter().all(|p| p.depends().is_empty()); if no_deps { Ok(passes) @@ -337,9 +362,9 @@ impl Default for PassManager { /// assembled into a correct ordering, and will not necessarily be executed /// in the order in which they are presented here. /// - /// - [`analysis::module_map::ModuleMap`] + /// - [`analysis::module_map::BuildModuleMap`] fn default() -> Self { - Self::new(vec![analysis::module_map::ModuleMap::new()]) + Self::new(vec![analysis::module_map::BuildModuleMap::new_dyn()]) .expect("Default pass ordering was invalid") } } diff --git a/crates/compiler/src/polyfill.rs b/crates/compiler/src/polyfill.rs index d071a81..b14dd80 100644 --- a/crates/compiler/src/polyfill.rs +++ b/crates/compiler/src/polyfill.rs @@ -10,8 +10,7 @@ //! Our polyfill mechanism aims to be generic, such that we can implement and //! improve our polyfills without requiring invasive changes to the code-base. //! In order to do this, we have created a _library_ of polyfills that the -//! compilation process (see [`crate::compile::Compiler`]) can select from -//! dynamically. +//! compilation process (see [`crate::Compiler`]) can select from dynamically. //! //! # Polyfills and Optimization //! diff --git a/crates/compiler/src/source/mod.rs b/crates/compiler/src/source/mod.rs new file mode 100644 index 0000000..bd051c7 --- /dev/null +++ b/crates/compiler/src/source/mod.rs @@ -0,0 +1,115 @@ +//! Contains the source compilation context, which is a way of tracking the +//! compilation units being processed by the compiler. + +use inkwell::{context::Context as LLVMContext, module::Module}; +use ltc_errors::compile::{Error, Result}; +use ouroboros::self_referencing; + +pub mod module; + +use module::SourceModule; + +/// The source compilation context manages the LLVM state across compiler +/// operations. +/// +/// It is intended to exist only throughout the compilation process, after which +/// it may be safely discarded. +/// +/// # Self-Referential Structure Definition +/// +/// Inkwell's [`Module`] (and many other structs returned from the Inkwell API) +/// are bound by lifetime to the LLVM context object. We don't want these +/// lifetimes to leak into our API and propagate throughout the compiler, so +/// instead we encapsulate them within this struct. +/// +/// In order to do this, we use the [`ouroboros`] crate to create a +/// self-referential struct. What this means is that the struct can contain an +/// object, and also have fields that _reference_ those objects. This is +/// disallowed by Rust's ownership model without `unsafe` code, so by using a +/// crate we encapsulate that unsafety and take advantage of the fact that it +/// has likely been looked at by more people than just us. +/// +/// As part of using this crate, @iamrecursion has checked it for correctness in +/// this use-case. +#[self_referencing] +#[derive(Debug)] +pub struct SourceContext { + /// The underlying context that contains the LLVM representation of the + /// input IR. + llvm_context: LLVMContext, + + /// The module in this LLVM context. This contains the objects that will be + /// directly compiled here. + #[borrows(llvm_context)] + #[not_covariant] + module: Module<'this>, +} + +impl SourceContext { + /// Creates a new, empty, source compilation context, wrapping the provided + /// `module` for compilation. + /// + /// # Errors + /// + /// - [`Error::UnableToAddModuleToContext`] if the provided `module` cannot + /// be added to the context. + pub fn create(module: impl TryInto) -> Result { + let llvm_context = LLVMContext::create(); + let module_source = module + .try_into() + .map_err(|e| Error::UnableToAddModuleToContext(e.to_string()))?; + + SourceContextTryBuilder { + llvm_context, + module_builder: |llvm_context| { + let module = llvm_context + .create_module_from_ir(module_source.into()) + .map_err(|e| Error::UnableToAddModuleToContext(e.to_string()))?; + Ok(module) + }, + } + .try_build() + } + + /// Runs analysis on the module in the context using the provided function, + /// and returns the analysis results. + /// + /// It does not have the ability to modify the underlying module at all. + /// + /// # Function Mutability + /// + /// As we may want to pass methods with mutable receivers as the operation + /// here, we say it can be an instance of [`FnMut`]. Do note that this is a + /// super-trait of [`Fn`] and hence `op` is not _required_ to capture + /// anything mutably. + /// + /// # Errors + /// + /// - [`Error`] if the provided `op` returns an error. + pub fn analyze_module(&self, op: impl FnMut(&Module) -> Result) -> Result { + self.with_module(op) + } + + /// Runs a transformation on the module in the context using the provided + /// function, returning any results from the modification. + /// + /// # Function Mutability + /// + /// As we may want to pass methods with mutable receivers as the operation + /// here, we say it can be an instance of [`FnMut`]. Do note that this is a + /// super-trait of [`Fn`] and hence `op` is not _required_ to capture + /// anything mutably. + /// + /// # Errors + /// + /// - [`Error`] if the provided `op` returns an error. + pub fn modify_module(&mut self, op: impl FnMut(&mut Module) -> Result) -> Result { + self.with_module_mut(op) + } +} + +impl From for LLVMContext { + fn from(value: SourceContext) -> Self { + value.into_heads().llvm_context + } +} diff --git a/crates/compiler/src/compile/source/module.rs b/crates/compiler/src/source/module.rs similarity index 95% rename from crates/compiler/src/compile/source/module.rs rename to crates/compiler/src/source/module.rs index d830f6d..bebb812 100644 --- a/crates/compiler/src/compile/source/module.rs +++ b/crates/compiler/src/source/module.rs @@ -7,7 +7,7 @@ use std::path::Path; use inkwell::{memory_buffer::MemoryBuffer, support::LLVMString}; /// A unified type for all the different ways that we support adding a module to -/// the compiler's [`crate::compile::source::SourceContext`]. +/// the compiler's [`crate::source::SourceContext`]. pub struct SourceModule { /// The underlying representation of the module to be passed to LLVM. memory_buffer: MemoryBuffer, diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml index b514a4f..ac95013 100644 --- a/crates/driver/Cargo.toml +++ b/crates/driver/Cargo.toml @@ -15,9 +15,6 @@ rust-version.workspace = true [dependencies] ariadne.workspace = true -cairo-lang-lowering.workspace = true -cairo-lang-sierra-generator.workspace = true -cairo-lang-utils.workspace = true itertools.workspace = true thiserror.workspace = true tracing.workspace = true diff --git a/crates/error/src/compile.rs b/crates/error/src/compile.rs index 9939d2b..ab589db 100644 --- a/crates/error/src/compile.rs +++ b/crates/error/src/compile.rs @@ -1,6 +1,8 @@ //! Error types and utilities to do with the compilation from LLVM IR to Cairo //! IR. +use std::str::Utf8Error; + use inkwell::support::LLVMString; use thiserror::Error; @@ -11,9 +13,18 @@ pub type Result = std::result::Result; /// the Cairo IR. #[derive(Debug, Error)] pub enum Error { + /// An error that occurs when trying to convert from the LLVM string + /// representation used by Inkwell to the UTF-8 string representation used + /// by Rust. + #[error("Could not create Rust string from C string: {_0}")] + CStrConversionError(#[from] Utf8Error), + + #[error("`{_0}` with invalid segment `{_1}` could not be parsed as an LLVM data layout")] + InvalidDataLayoutSpecification(String, String), + /// Emitted when code tries to construct an invalid ordering of compiler /// passes. - #[error("Pass ordering was invalid: {_0}")] + #[error("Invalid Pass Ordering: {_0}")] InvalidPassOrdering(String), /// An error when doing IO during compilation. @@ -31,6 +42,16 @@ pub enum Error { /// context, but cannot do soe compilation context, but cannot do so. #[error("Unable to add module to context: {_0}")] UnableToAddModuleToContext(String), + + #[error("We only support targets that use a single address space numbered 0")] + UnsupportedAdditionalAddressSpaces, + + #[error("We do not support targets with non-integral pointers configured.")] + UnsupportedNonIntegralPointerConfiguration, + + /// Emitted when we encounter an LLVM type that we do not support. + #[error("The LLVM basic type {_0} is not supported")] + UnsupportedType(String), } impl From for Error {