diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b729d1a..1143cf8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - name: Build Lix Dependencies shell: "bash" run: | - eval "$DEV_SHELL" + eval "$DEV_SHELL --command which cc" - name: Build Tests shell: "bash" run: | diff --git a/Cargo.lock b/Cargo.lock index 04d5643..8a8446e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ariadne" @@ -213,16 +213,6 @@ dependencies = [ "itertools 0.12.1", ] -[[package]] -name = "cairo-lang-eq-solver" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0cd844e568f51e39729e8ac18bd27ada2e2b6dc9138f8c81adad48456480681" -dependencies = [ - "cairo-lang-utils", - "good_lp", -] - [[package]] name = "cairo-lang-filesystem" version = "2.8.2" @@ -278,7 +268,7 @@ dependencies = [ "itertools 0.12.1", "log", "num-bigint", - "num-traits 0.2.19", + "num-traits", "rust-analyzer-salsa", "smol_str", ] @@ -297,7 +287,7 @@ dependencies = [ "colored", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "rust-analyzer-salsa", "smol_str", "unescaper", @@ -367,7 +357,7 @@ dependencies = [ "indoc", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "rust-analyzer-salsa", "smol_str", "toml", @@ -389,7 +379,7 @@ dependencies = [ "lalrpop-util", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "regex", "rust-analyzer-salsa", "serde", @@ -417,7 +407,7 @@ dependencies = [ "cairo-lang-syntax", "cairo-lang-utils", "itertools 0.12.1", - "num-traits 0.2.19", + "num-traits", "rust-analyzer-salsa", "serde", "serde_json", @@ -434,7 +424,7 @@ dependencies = [ "cairo-lang-filesystem", "cairo-lang-utils", "num-bigint", - "num-traits 0.2.19", + "num-traits", "rust-analyzer-salsa", "smol_str", "unescaper", @@ -473,7 +463,7 @@ dependencies = [ "indexmap 2.5.0", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "schemars", "serde", ] @@ -495,18 +485,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.16" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", @@ -691,12 +681,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "genco" version = "0.17.9" @@ -753,16 +737,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "good_lp" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3198bd13dea84c76a64621d6ee8ee26a4960a9a0d538eca95ca8f1320a469ac9" -dependencies = [ - "fnv", - "minilp", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1004,27 +978,6 @@ dependencies = [ "semver", ] -[[package]] -name = "llvm-to-cairo" -version = "0.1.0" -dependencies = [ - "anyhow", - "ariadne", - "cairo-lang-compiler", - "cairo-lang-debug", - "cairo-lang-diagnostics", - "cairo-lang-eq-solver", - "cairo-lang-lowering", - "cairo-lang-semantic", - "cairo-lang-utils", - "clap", - "inkwell", - "itertools 0.13.0", - "miette", - "thiserror", - "tracing", -] - [[package]] name = "lock_api" version = "0.4.12" @@ -1042,65 +995,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "matrixmultiply" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +name = "ltc-cli" +version = "0.1.0" dependencies = [ - "rawpointer", + "ariadne", + "clap", + "itertools 0.13.0", + "tracing", ] [[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +name = "ltc-compiler" +version = "0.1.0" +dependencies = [ + "anyhow", + "cairo-lang-compiler", + "cairo-lang-debug", + "cairo-lang-lowering", + "cairo-lang-semantic", + "clap", + "inkwell", + "itertools 0.13.0", + "ltc-errors", + "tracing", +] [[package]] -name = "miette" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +name = "ltc-driver" +version = "0.1.0" dependencies = [ - "cfg-if", - "miette-derive", + "ariadne", + "cairo-lang-lowering", + "cairo-lang-sierra-generator", + "cairo-lang-utils", + "itertools 0.13.0", "thiserror", - "unicode-width", + "tracing", ] [[package]] -name = "miette-derive" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +name = "ltc-errors" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", + "ariadne", + "thiserror", ] [[package]] -name = "minilp" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a7750a9e5076c660b7bec5e6457b4dbff402b9863c8d112891434e18fd5385" -dependencies = [ - "log", - "sprs", -] +name = "ltc-rust-test-input" +version = "0.1.0" [[package]] -name = "ndarray" -version = "0.13.1" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac06db03ec2f46ee0ecdca1a1c34a99c0d188a0d83439b84bf0cb4b386e4ab09" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits 0.2.19", - "rawpointer", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "new_debug_unreachable" @@ -1125,36 +1074,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", - "num-traits 0.2.19", + "num-traits", "serde", ] -[[package]] -name = "num-complex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" -dependencies = [ - "autocfg", - "num-traits 0.2.19", -] - [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -1278,12 +1208,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.10.0" @@ -1561,17 +1485,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sprs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec63571489873d4506683915840eeb1bb16b3198ee4894cc6f2fe3013d505e56" -dependencies = [ - "ndarray", - "num-complex", - "num-traits 0.1.43", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1589,7 +1502,7 @@ dependencies = [ "lazy_static", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index c8653d6..4bd3ff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,33 @@ -[package] -name = "llvm-to-cairo" +# We use a workspace to centralize dependency and metadata management for the crates in this +# workspace. +[workspace] +resolver = "2" +members = [ + "crates/cli", + "crates/compiler", + "crates/driver", + "crates/error", + "crates/rust-test-input", +] + +# Here we set keys that are relevant across all packages, allowing them to be inherited. +[workspace.package] version = "0.1.0" homepage = "https://github.com/reilabs/llvm-to-cairo" repository = "https://github.com/reilabs/llvm-to-cairo" license-file = "LICENSE" authors = ["Reilabs"] -description = "A compiler to allow LLVM bytecode to run on the Starknet ecosystem in a provable fashion." keywords = ["compiler", "starknet", "starkware"] categories = ["compilers", "virtualization", "cryptography::cryptocurrencies"] edition = "2021" rust-version = "1.81.0" -[dependencies] +# Dependencies that are used by more than one crate are specified here, allowing us to ensure that +# we match versions in all crates. +[workspace.dependencies] +anyhow = "1.0.89" ariadne = "0.4.1" cairo-lang-compiler = "2.8.2" cairo-lang-debug = "2.8.2" @@ -21,22 +35,17 @@ 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" -miette = "7.2.0" +ltc-cli = { path = "crates/cli" } +ltc-driver = { path = "crates/driver" } +ltc-errors = { path = "crates/error" } thiserror = "1.0.63" tracing = "0.1.40" -[dev-dependencies] -anyhow = "1.0.86" - [profile.release] debug = true # Include full debug information in release builds. overflow-checks = true # Keep overflow checks in production builds. lto = "thin" # Thin LTO performs cheaper cross-crate LTO. - -[[bin]] -name = "ltc" # Named for Llvm-To-Cairo -path = "src/bin/main.rs" diff --git a/crates/README.md b/crates/README.md new file mode 100644 index 0000000..3a54cb5 --- /dev/null +++ b/crates/README.md @@ -0,0 +1,5 @@ +# Component Crates + +In order to enable flexibility and reuse of components in other projects if required, this project +is designed as a set of component crates that assist in its operation. Please see the documentation +of the individual crates for more information on what they encompass. diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 0000000..3cd0692 --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ltc-cli" +version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +authors.workspace = true +description = "The CLI for working with the LLVM to Cairo compilation process." +keywords.workspace = true +categories.workspace = true + +edition.workspace = true +rust-version.workspace = true + +[dependencies] +ariadne.workspace = true +clap.workspace = true +itertools.workspace = true +tracing.workspace = true + +[[bin]] +name = "ltc" # Named for "LLVM To Cairo" +path = "src/main.rs" diff --git a/crates/cli/README.md b/crates/cli/README.md new file mode 100644 index 0000000..22a836e --- /dev/null +++ b/crates/cli/README.md @@ -0,0 +1,4 @@ +# LLVM to Cairo CLI + +This crate implements the CLI for interacting with the compiler and compilation process. It is +primarily a user interface wrapper over the [compiler driver](../driver). diff --git a/src/bin/main.rs b/crates/cli/src/main.rs similarity index 100% rename from src/bin/main.rs rename to crates/cli/src/main.rs diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml new file mode 100644 index 0000000..133093c --- /dev/null +++ b/crates/compiler/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ltc-compiler" +version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +authors.workspace = true +description = "A compiler to allow LLVM bytecode to run on the Starknet ecosystem in a provable fashion." +keywords.workspace = true +categories.workspace = true + +edition.workspace = true +rust-version.workspace = true + +[dependencies] +cairo-lang-compiler.workspace = true +cairo-lang-debug.workspace = true +cairo-lang-lowering.workspace = true +cairo-lang-semantic.workspace = true +clap.workspace = true +inkwell = { version = "0.5.0", features = ["llvm18-0"] } +itertools.workspace = true +ltc-errors.workspace = true +tracing.workspace = true + +[dev-dependencies] +anyhow.workspace = true diff --git a/crates/compiler/README.md b/crates/compiler/README.md new file mode 100644 index 0000000..bd4c473 --- /dev/null +++ b/crates/compiler/README.md @@ -0,0 +1,4 @@ +# LLVM to Cairo Compiler + +This crate implements the full compilation behavior from LLVM IR to `FlatLowered`, but no further +parts of the process. Those are combined using the [driver](../driver). diff --git a/crates/compiler/src/compile.rs b/crates/compiler/src/compile.rs new file mode 100644 index 0000000..9263248 --- /dev/null +++ b/crates/compiler/src/compile.rs @@ -0,0 +1,51 @@ +//! 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. diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs new file mode 100644 index 0000000..75a826f --- /dev/null +++ b/crates/compiler/src/lib.rs @@ -0,0 +1,48 @@ +//! This library implements the functionality necessary for the compilation of +//! [LLVM IR](https://llvm.org/docs/LangRef.html) to the +//! [Cairo](https://www.cairo-lang.org) programming language in order to enable +//! the execution of LLVM-compiled languages on top of the +//! [CairoVM](https://github.com/lambdaclass/cairo-vm) and hence on the +//! [Starknet](https://www.starknet.io) rollup L2. +//! +//! The goals of this project are twofold: +//! +//! 1. To enable writing contracts in languages that compile to LLVM. +//! 2. To enable use of libraries written in such languages as part of the Cairo +//! ecosystem (e.g. from a contract written in Cairo itself). +//! +//! # Process Overview +//! +//! While more information can be found in the module-level documentation of +//! each part of this codebase, a brief overview of the compilation process can +//! be stated as follows: +//! +//! 1. We ingest LLVM IR in textual format. +//! 2. We translate that LLVM IR to a combination of Cairo's internal IR, and +//! invocation of polyfills for operations that our target CPU does not +//! support. +//! 3. We optimize those polyfills to achieve better performance. +//! +//! It should be noted that point 2 above is doing a lot of heavy lifting. As +//! part of this translation we have to account for mismatches between calling +//! conventions, stack and memory semantics, and perform translations of these +//! things where they cannot directly be implemented using a polyfill. +//! +//! # Language Support +//! +//! The major focus in the initial phases of the project is on using +//! [Rust](https://rust-lang.org) as the source language, but the goal is to +//! eventually support _any_ major language (Swift, C++, and so on) that can +//! target LLVM. +//! +//! While most of the work is source-language agnostic, each language does +//! require _some_ specialized work to allow those languages to properly call +//! intrinsics that can interact with the chain and the larger Starknet +//! ecosystem. + +#![warn(clippy::all, clippy::cargo, clippy::pedantic)] +#![allow(clippy::module_name_repetitions)] // Allows for better API naming +#![allow(clippy::multiple_crate_versions)] // Enforced by our dependencies + +pub mod compile; +pub mod polyfill; diff --git a/crates/compiler/src/polyfill.rs b/crates/compiler/src/polyfill.rs new file mode 100644 index 0000000..ee57585 --- /dev/null +++ b/crates/compiler/src/polyfill.rs @@ -0,0 +1,52 @@ +//! In the context of this project, a polyfill is an implementation of some +//! functionality that is _not_ supported by our target CPU in terms of +//! functionality that _is_ supported by our target. +//! +//! By way of example, consider that our CPU does not support floating point +//! arithmetic, so to compile LLVM code that uses such a thing we need to +//! implement it and call _our_ functions where it needs to perform these +//! operations. +//! +//! 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::compiler`]) can select from dynamically. +//! +//! # Polyfills and Optimization +//! +//! We are implementing our polyfills in Cairo-the-language, thereby enabling us +//! to have them in the same form as our compiled LLVM IR: `FlatLowered`. This +//! means that we can combine the polyfills and source into a compilation unit +//! seamlessly. +//! +//! While implementing these in Cairo means that they are amenable to rapid +//! iteration and experimentation, the polyfill is not the _end goal_ of this +//! process. +//! +//! 1. **Polyfills:** Implemented in Cairo, these implement functionality that +//! our CPU is missing using functionality that it is not. They are slow in +//! that they take more steps than the other options to perform an operation, +//! but are much easier to experiment with and iterate on. +//! 2. **Builtins:** Builtins are units of functionality written in Rust that +//! act as coprocessors using a DMA-like mechanism to receive operands and +//! provide results back to the executing code. These are much faster to +//! execute, taking few steps at most, but are more invasive to experiment +//! with and change. They may also require more memory than an equivalent +//! polyfill, which would increase the verification time. +//! 3. **AIR Instructions:** AIR instructions are the fastest option here, but +//! adding a new instruction has the downside of increasing the width of the +//! trace table. Any increase in table width increases the size of the table +//! and also the time to prove the execution. +//! +//! Starting with the polyfills, however, allows us to experiment and iterate +//! rapidly to arrive at a design that we are happy with. This would be far more +//! complex for a builtin, and more complex still for an AIR instruction. +//! +//! Perhaps more importantly, the polyfills allow us to examine and profile to +//! find which operations will be most effective to "upgrade". Rather than a +//! scattershot approach based on hunches, the polyfills allow us to base these +//! decisions on real-world data. +//! +//! To that end, there are certainly polyfills that will still exist. It is very +//! unlikely that every single operation is beneficial to implement as a builtin +//! or AIR instruction. diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml new file mode 100644 index 0000000..b514a4f --- /dev/null +++ b/crates/driver/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ltc-driver" +version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +authors.workspace = true +description = "The compiler driver responsible for plumbing together the various portions of the compilation process." +keywords.workspace = true +categories.workspace = true + +edition.workspace = true +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/driver/README.md b/crates/driver/README.md new file mode 100644 index 0000000..6b2c3d9 --- /dev/null +++ b/crates/driver/README.md @@ -0,0 +1,5 @@ +# LLVM to Cairo Compiler Driver + +The compiler driver is responsible for marrying up the compilation process that we control with the +further parts of the Cairo compilation pipeline. This includes generating and checking Sierra, and +the final emission of CASM. diff --git a/crates/driver/src/main.rs b/crates/driver/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/crates/driver/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/crates/error/Cargo.toml b/crates/error/Cargo.toml new file mode 100644 index 0000000..f4db351 --- /dev/null +++ b/crates/error/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ltc-errors" +version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +authors.workspace = true +description = "The error types used by the LLVM to Cairo project." +keywords.workspace = true +categories.workspace = true + +edition.workspace = true +rust-version.workspace = true + +[dependencies] +ariadne.workspace = true +thiserror.workspace = true diff --git a/crates/error/README.md b/crates/error/README.md new file mode 100644 index 0000000..ba45484 --- /dev/null +++ b/crates/error/README.md @@ -0,0 +1,4 @@ +# LLVM to Cairo Errors + +Strongly-typed errors that can be used by multiple crates, therefore simplifying error handling +throughout the project. diff --git a/src/error/mod.rs b/crates/error/src/lib.rs similarity index 95% rename from src/error/mod.rs rename to crates/error/src/lib.rs index eacabb8..d306600 100644 --- a/src/error/mod.rs +++ b/crates/error/src/lib.rs @@ -14,7 +14,7 @@ pub mod llvm_compile; use thiserror::Error; -/// The result type to be used at the boundaries of the library. +/// The result type to be used at the boundaries of the project. pub type Result = std::result::Result; /// The root of the error hierarchy for this crate. diff --git a/src/error/llvm_compile.rs b/crates/error/src/llvm_compile.rs similarity index 100% rename from src/error/llvm_compile.rs rename to crates/error/src/llvm_compile.rs diff --git a/crates/rust-test-input/Cargo.toml b/crates/rust-test-input/Cargo.toml new file mode 100644 index 0000000..c60cfc7 --- /dev/null +++ b/crates/rust-test-input/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ltc-rust-test-input" +version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +authors.workspace = true +description = "Rust input files for compilation to LLVM IR for use as test inputs to the compiler." +keywords.workspace = true +categories.workspace = true + +edition.workspace = true +rust-version.workspace = true + +# Make these specifically impossible to publish as they have no independent use. +publish = false + +[dependencies] diff --git a/crates/rust-test-input/README.md b/crates/rust-test-input/README.md new file mode 100644 index 0000000..764e29f --- /dev/null +++ b/crates/rust-test-input/README.md @@ -0,0 +1,4 @@ +# Rust Test Inputs + +This small rust library exists to act as a test input for the compilation process. It is used to +generate LLVM IR that is then fed into the compiler. diff --git a/crates/rust-test-input/src/lib.rs b/crates/rust-test-input/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/crates/rust-test-input/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b593d16..aa5be64 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -63,9 +63,9 @@ provides all of these, or to have the following installed and available on your Due to the complexities of getting some IDEs to work with nix-based Rust projects, we recommend using a system-wide installation of the correct toolchain (as specified in -[`Cargo.toml`](../Cargo.toml)). Then, we recommend launching your IDE from the development shell -provided by `nix develop`, as this will give it the correct environment variables to find the LLVM -install. +[`Cargo.toml`](../crates/compiler/Cargo.toml)). Then, we recommend launching your IDE from the +development shell provided by `nix develop`, as this will give it the correct environment variables +to find the LLVM install. We cannot provide instructions for all IDEs out of the box, but please feel free to add instructions for your own IDE or editor here if you are working on the project. diff --git a/docs/Getting LLVM IR Output.md b/docs/Getting LLVM IR Output.md index a39d03c..efc8fea 100644 --- a/docs/Getting LLVM IR Output.md +++ b/docs/Getting LLVM IR Output.md @@ -7,8 +7,9 @@ this project. ## Rust -`rustc` will emit LLVM IR when passed the `--emit=llvm-ir` flag. This will output `.ll` files into -the `target` directory corresponding to your compiled file. For more information, see the +`rustc` will emit LLVM IR when passed the `--emit=llvm-ir` flag, and LLVM bytecode when passed the +`--emit=llvm-bc`. This will output `.ll` files into the `target` directory corresponding to your +compiled file. For more information, see the [`rustc` developer guide](https://rustc-dev-guide.rust-lang.org/backend/debugging.html). - This can be passed to the correct compiler when using cargo by calling diff --git a/flake.nix b/flake.nix index 4b4dca3..e0c92d0 100644 --- a/flake.nix +++ b/flake.nix @@ -18,10 +18,11 @@ flake-utils.lib.eachDefaultSystem (system: let # We grab our expected rust version from the Cargo.toml. - rustVersion = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.rust-version; + rustVersion = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.rust-version; # Then we set up our libraries for building this thing. pkgs = nixpkgs.legacyPackages.${system}; + inherit (pkgs) lib; fenixLib = fenix.packages.${system}; fenixStable = fenixLib.fromToolchainName { name = rustVersion; @@ -45,11 +46,21 @@ # to have. craneLib = (crane.mkLib pkgs).overrideToolchain fenixToolchain; - # Then we build our actual package, which is our application. - llvmToCairo = pkgs.callPackage ./package.nix { + # Collect our workspace packages, including our application. + workspacePackages = pkgs.callPackage ./workspace.nix { inherit craneLib; }; + # Filter out things that aren't derivations for the `packages` output, or Nix gets mad. + llvmToCairo = lib.filterAttrs (lib.const lib.isDerivation) workspacePackages; + + # And for convenience, collect all the workspace members into a single derivation, + # so we can check they all compile with one command, `nix build '.#all'`. + all = pkgs.symlinkJoin { + name = "llvm-to-cairo-all"; + paths = lib.attrValues llvmToCairo; + }; + # We get your default shell to make sure things feel familiar in the dev shell. getUserShellCommand = if pkgs.stdenv.hostPlatform.isDarwin then "dscl . -read ~ UserShell | cut -d ' ' -f2" @@ -57,9 +68,9 @@ "getent passwd $USER | cut -d ':' -f7"; in { packages = { - inherit llvmToCairo; - default = llvmToCairo; - }; + inherit all; + default = llvmToCairo.ltc-cli; + } // llvmToCairo; # The default dev shell puts you in your native shell to make things feel happy. devShells.default = craneLib.devShell { diff --git a/package.nix b/package.nix deleted file mode 100644 index ba836c4..0000000 --- a/package.nix +++ /dev/null @@ -1,27 +0,0 @@ -# This file defines the actual package. -# -# We want to be able to run commands from a nix shell using this package -# definition. -{ craneLib, lib, llvmPackages_18, libiconv, stdenv }: let - src = craneLib.cleanCargoSource ./.; -in craneLib.buildPackage { - inherit src; - - # Disallow confusing buildInputs and nativeBuildInputs for sanity. - strictDeps = true; - - # Things that are needed at build time on the system doing building. - nativeBuildInputs = [ - llvmPackages_18.llvm - ]; - - # The things that we need available at build and runtime on the target system. - buildInputs = [ - llvmPackages_18.llvm - ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ - libiconv - ]; - - # We name this so we can quickly `nix-run` - meta.mainProgram = "llvm-to-cairo"; -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index be3a097..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! This library implements the functionality necessary for the compilation of -//! [LLVM IR](https://llvm.org/docs/LangRef.html) to the -//! [Cairo](https://www.cairo-lang.org) programming language in order to enable -//! the execution of LLVM-compiled languages on top of the -//! [CairoVM](https://github.com/lambdaclass/cairo-vm) and hence on -//! [Starknet](https://www.starknet.io). -//! -//! The goals of this project are twofold: -//! -//! 1. To enable writing contracts in languages that compile to LLVM. -//! 2. To enable use of libraries in such languages as part of the Cairo -//! ecosystem. -//! -//! The major focus in the initial phases of the project is on using -//! [Rust](https://rust-lang.org) as the source language, but the goal is to -//! eventually support _any_ major language (Swift, C++, and so on) that can -//! target LLVM. - -#![warn(clippy::all, clippy::cargo, clippy::pedantic)] -#![allow(clippy::module_name_repetitions)] // Allows for better API naming -#![allow(clippy::multiple_crate_versions)] // Enforced by our dependencies - -pub mod error; diff --git a/workspace.nix b/workspace.nix new file mode 100644 index 0000000..4d017c1 --- /dev/null +++ b/workspace.nix @@ -0,0 +1,80 @@ +# This file defines the actual package. +# +# We want to be able to run commands from a nix shell using this package +# definition. +{ + craneLib, + lib, + llvmPackages_18, + libiconv, + stdenv, +}: + let + workspaceToml = lib.importTOML ./Cargo.toml; + # nb: if any crates set `version.workspace = false`, this will need to be updated, + # but otherwise we can use the workspace version for every crate. + version = workspaceToml.workspace.package.version; + workspaceMemberPaths = workspaceToml.workspace.members; + + # Attrsets in this will add additional arguments to craneLib.buildPackage for the + # crate with the matching package name. + crateSpecificArgs = { + ltc-cli = { + meta.mainProgram = "ltc"; + }; + }; + + # These are added as arguments to both the Cargo dependencies and each crate in the + # workspace. + commonArgs = { + src = craneLib.cleanCargoSource ./.; + + # Disallow confusing buildInputs and nativeBuildInputs for sanity. + strictDeps = true; + + # Things that are needed at build time on the system doing building. + nativeBuildInputs = [ + llvmPackages_18.llvm + ]; + + # The things that we need available at build and runtime on the target system. + buildInputs = [ + llvmPackages_18.llvm + ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ + libiconv + ]; + }; + + # The Cargo.lock dependencies are global to the workspace, so we can build them + # separately, and thus only build them once for the whole workspace. + workspaceDeps = craneLib.buildDepsOnly (commonArgs // { + # Workspaces don't have names, so we'll give it the repo name for the dependencies. + pname = "llvm-to-cairo-deps"; + inherit version; + }); + + # A list of all the crates in this workspace, where each item in the list is a + # name-value pair for `lib.listToAttrs`. That way we get the crate names as the + # attrset keys. + memberCrates = lib.forEach workspaceMemberPaths (cratePath: + let + # The syntax for dynamic relative paths is weird, I know. + crateToml = lib.importTOML (./. + "/${cratePath}/Cargo.toml"); + pname = crateToml.package.name; + # Add any arguments specific to this crate's args, if there are any. + crateOverrideArgs = crateSpecificArgs.${pname} or { }; + in { + name = pname; + value = craneLib.buildPackage (commonArgs // { + inherit pname version; + + # Note that `-p` takes the Cargo package name, not the workspace member path. + cargoExtraArgs = "-p ${pname}"; + cargoArtifacts = workspaceDeps; + + } // crateOverrideArgs); + } + ); + +in +lib.listToAttrs memberCrates