Skip to content

Commit

Permalink
Merge branch 'main' into workflow-small-execute-blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
FrancoGiachetta authored Oct 18, 2024
2 parents e02e677 + 4ba9e16 commit 9d25c93
Show file tree
Hide file tree
Showing 24 changed files with 610 additions and 184 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/bench-hyperfine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:
jobs:
bench-hyperfine:
name: Hyperfine
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
Expand Down Expand Up @@ -97,7 +97,7 @@ jobs:
matrix:
branch: [ base, head ]
name: Build cairo-native-run for ${{ matrix.branch }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Cache binary
uses: actions/cache@v3
Expand Down Expand Up @@ -171,7 +171,7 @@ jobs:
hyperfine-prs:
name: Bench PR (linux, amd64)
needs: [ build-binaries ]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
PROGRAM: fib_2M
OUTPUT_DIR: bench-outputs
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ concurrency:
jobs:
check:
name: clippy
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
LLVM_SYS_191_PREFIX: /usr/lib/llvm-19/
Expand All @@ -38,7 +38,7 @@ jobs:

fmt:
name: rustfmt
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: dtolnay/[email protected]
Expand Down Expand Up @@ -81,7 +81,7 @@ jobs:

# Check for unnecessary dependencies.
udeps:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
LLVM_SYS_191_PREFIX: /usr/lib/llvm-19/
Expand All @@ -106,7 +106,7 @@ jobs:

test:
name: test (linux, amd64)
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
LLVM_SYS_191_PREFIX: /usr/lib/llvm-19/
Expand Down Expand Up @@ -185,7 +185,7 @@ jobs:

coverage:
name: coverage
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
matrix:
partition: [1, 2, 3, 4]
Expand Down Expand Up @@ -274,7 +274,7 @@ jobs:

upload-coverage:
name: Upload Coverage
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs: [coverage]
steps:
- name: Setup rust env
Expand Down Expand Up @@ -317,7 +317,7 @@ jobs:

dockerfile:
name: dockerfile (linux, amd64)
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: check and free hdd space left
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

jobs:
release:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
LLVM_SYS_191_PREFIX: /usr/lib/llvm-19/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

jobs:
release:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
LLVM_SYS_191_PREFIX: /usr/lib/llvm-19/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rustdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:
jobs:
publish-docs:
name: GitHub Pages
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
env:
MLIR_SYS_190_PREFIX: /usr/lib/llvm-19/
LLVM_SYS_191_PREFIX: /usr/lib/llvm-19/
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ doc-open: check-llvm
cargo doc --all-features --no-deps --workspace --open

.PHONY: bench
bench: build needs-cairo2 runtime
bench: needs-cairo2 runtime
cargo b --release --bin cairo-native-run
./scripts/bench-hyperfine.sh

.PHONY: bench-ci
Expand Down
2 changes: 1 addition & 1 deletion docs/execution_walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Builtin stats: BuiltinStats { bitwise: 1, ec_op: 0, range_check: 1, pedersen: 0,
## The Cairo Native runtime
Sometimes we need to use stuff that would be too complicated or error-prone to implement in MLIR, but that we have readily available from Rust. That's when we use the runtime library.

When using the JIT it'll be automatically linked (if compiled with support for it, which is enabled by default). If using the AOT, the `CAIRO_NATIVE_RUNTIME_LIBDIR` environment variable will have to be modified to point to the directory that contains `libcairo_native_runtime.a`, which is built and placed in said folder by `make build`.
When using the JIT it'll be automatically linked (if compiled with support for it, which is enabled by default). If using the AOT, the `CAIRO_NATIVE_RUNTIME_LIBRARY` environment variable will have to be modified to point to the `libcairo_native_runtime.a` file, which is built and placed in said folder by `make build`.

Although it's implemented in Rust, its functions use the C ABI and have Rust's name mangling disabled. This means that to the extern observer it's technically indistinguishible from a library written in C. By doing this we're making the functions callable from MLIR.

Expand Down
78 changes: 33 additions & 45 deletions docs/implementing_libfuncs.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,45 @@ A type that doesn't have size would be `Layout::new::<()>()`, or if the
type is a pointer like box: `Layout::new::<*mut ()>()`

When adding a type, we also need to add the **serialization** and
**deserialization** functionality, so we can use it with the JIT runner.
**deserialization** functionality to convert the value to a memory representation that works with cairo-native.

You can find this functionality under `src/values.rs` and
`src/values/{typename}.rs`. As you can see, the project is quite organized
if you have a feel of its layout.
You can find this functionality under `src/values.rs`.

Serialization is done using `Serde`, and each type provides a `deserialize`
and `serialize` function. The inner workings of such functions can be a bit
complex due to how the JIT runner works. You need to work with pointers and
unsafe rust.
There is a `Value` enum with all the possible values that can be passed as input/output.

In `values.rs` we should also declare whether the type is complex under
`is_complex` in the `ValueBuilder` trait implementation.
This enum has a `impl` block with 2 important methods `to_ptr` and `from_ptr` which convert the Value into and from said
memory representation, held behind a pointer.

When passing the values as inputs, there is one more required step, that is to pass the bytes of those values in the
target platform ABI compatible way, this is done with the `AbiArgument` trait and the `to_bytes` method.

This trait, located in `src/arch.rs` is implemented currently for aarch64 and x86_64 (depending on the host platform) for some basic types, such as u64, u128, pointers, etc. Most importantly, it is also implemented for `ValueWithInfoWrapper` which allows to convert the Value using it's `to_ptr` method and correctly passing it as an argument in the given `buffer: &mut Vec<u8>`.

In `types.rs` we should also declare whether the type is complex under
`is_complex`, whether its a builtin in `is_builtin`, a zst in `is_zst` and define it's layout in the `TypeBuilder` trait implementation.

> Complex types are always passed by pointer (both as params and return
> values) and require a stack allocation. Examples of complex values include
> structs and enums, but not felts since LLVM considers them integers.
### Deserializing a type
When **deserializing** (a.k.a converting the inputs so the JIT runner
When **deserializing** (a.k.a converting the inputs so the runner
accepts them), you are passed a bump allocator arena from `Bumpalo`, the
general idea is to get the layout and size of the type, allocate it under
the arena, get a pointer, and return it. Which will later be passed to the
MLIR JIT runner. It is important the pointers passed are allocated by the
the arena, get a pointer, and return it (Some types require additional data on the heap, such as arrays, such allocations should be done with libc's malloc.). Which will later be passed to the runner. It is important the pointers passed are allocated by the
arena and not Rust itself.

Then we need to hookup de `deserialize` method in `values.rs` `deserialize`
method.
This is done in the `to_ptr` method.

### Serializing a type
When **serializing** a type, you will get a `ptr: NonNull<()>` (non null
pointer), which you will have to cast, dereference and then deserialize.

For a simple type to learn how it works, we recommend checking
`src/values/uint8.rs`, for more complex types, check `src/values/felt252.rs`.
`src/values.rs`, in the `from_ptr` method, look the u8 type in the match, for more complex types, check the felt252 type.
The hardest types to understand are the enums, dictionaries and arrays,
since they are complex types.

Then we need to hookup de `serialize` method in `values.rs` `serialize` method.

### Implementing the library function
Libfuncs are implemented under `src/libfuncs.rs` and
`src/libfuncs/{libfunc_name}.rs`. Just like types.
Expand All @@ -67,21 +66,15 @@ Using the `src/libfuncs/felt252.rs` libfuncs as a aid:

```rust,ignore
/// Select and call the correct libfunc builder function from the selector.
pub fn build<'ctx, 'this, TType, TLibfunc>(
pub fn build<'ctx, 'this>(
context: &'ctx Context,
registry: &ProgramRegistry<TType, TLibfunc>,
registry: &ProgramRegistry<CoreType, CoreLibfunc>,
entry: &'this Block<'ctx>,
location: Location<'ctx>,
helper: &LibfuncHelper<'ctx, 'this>,
metadata: &mut MetadataStorage,
selector: &Felt252Concrete,
) -> Result<()>
where
TType: GenericType,
TLibfunc: GenericLibfunc,
<TType as GenericType>::Concrete: TypeBuilder<TType, TLibfunc, Error = CoreTypeBuilderError>,
<TLibfunc as GenericLibfunc>::Concrete: LibfuncBuilder<TType, TLibfunc, Error = Error>,
{
) -> Result<()> {
match selector {
Felt252Concrete::BinaryOperation(info) => {
build_binary_operation(context, registry, entry, location, helper, metadata, info)
Expand Down Expand Up @@ -109,11 +102,11 @@ An example libfunc, converting a u8 to a felt252, extensively commented:

```rust,ignore
/// Generate MLIR operations for the `u8_to_felt252` libfunc.
pub fn build_to_felt252<'ctx, 'this, TType, TLibfunc>(
pub fn build_to_felt252<'ctx, 'this>(
// The Context from MLIR, this is like the heart of the MLIR API, its required to create most stuff like types.
context: &'ctx Context,
// This is the sierra program registry, it aids us at finding types, functions, etc.
registry: &ProgramRegistry<TType, TLibfunc>,
registry: &ProgramRegistry<CoreType, CoreLibfunc>,
// This is the MLIR entry block for this libfunc. Remember we append operations to blocks.
entry: &'this Block<'ctx>,
// The already created MLIR location for this libfunc, we need to pass this to all the MLIR operations.
Expand All @@ -125,28 +118,24 @@ pub fn build_to_felt252<'ctx, 'this, TType, TLibfunc>(
// The sierra information for this specific library function. This libfunc only contains signature information, but
// others which are generic over a type will contain information about that type, for example array related libfuncs.
info: &SignatureOnlyConcreteLibfunc,
) -> Result<()>
where
TType: GenericType,
TLibfunc: GenericLibfunc,
<TType as GenericType>::Concrete: TypeBuilder<TType, TLibfunc, Error = CoreTypeBuilderError>,
<TLibfunc as GenericLibfunc>::Concrete: LibfuncBuilder<TType, TLibfunc, Error = Error>,
{
) -> Result<()> {
// We retrieve the felt252 type from the registry and call the "build" method to create the MLIR type.
// We could also just call get_type() to hold on to the sierra type, and then `.layout(registry)` to get the type layout,
// which is needed in some libfuncs doing more complex stuff.
let felt252_ty = registry
.get_type(&info.branch_signatures()[0].vars[0].ty)?
.build(context, helper, registry, metadata)?;
let felt252_ty = registry.build_type(
context,
helper,
registry,
metadata,
&info.branch_signatures()[0].vars[0].ty,
)?;
// Retrieve the first argument passed to this library function, in this case its the u8 value we need to convert.
let value: Value = entry.argument(0)?.into();
// We create a "extui" operation from the "arith" dialect, which basically zero extends the value to have the same bits as the given type.
let op = entry.append_operation(arith::extui(value, felt252_ty, location));
// Get the result from the operation, in this case it's the extended value
let result = op.result(0)?.into();
// We create a "extui" operation from the "arith" dialect, which basically
// zero extends the value to have the same bits as the given type.
let result = entry.append_op_result(arith::extui(value, felt252_ty, location))?;
// Using the helper argument, append the branching operation to the next statement, passing result as our output variable.
entry.append_operation(helper.br(0, &[result], location));
Expand All @@ -156,4 +145,3 @@ where
```

More info on the `extui` operation: <https://mlir.llvm.org/docs/Dialects/ArithOps/#arithextui-arithextuiop>

7 changes: 5 additions & 2 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
[package]
name = "cairo-native-runtime"
version = "0.2.0-alpha.4"
description = "A compiler to convert Cairo's intermediate representation Sierra code to MLIR."
description = "The runtime for cairo-native."
edition = "2021"
license = "Apache-2.0"
keywords = ["starknet", "cairo", "runtime"]

[lib]
crate-type = ["rlib", "cdylib", "staticlib"]

[dependencies]
starknet-types-core = { version = "0.1.7", default-features = false, features = [
"std", "serde", "hash"
"std",
"serde",
"hash",
] }
cairo-lang-sierra-gas = "2.8.4"
starknet-curve = "0.5.1"
Expand Down
Loading

0 comments on commit 9d25c93

Please sign in to comment.