Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aot library load #354

Closed
wants to merge 22 commits into from
Closed

aot library load #354

wants to merge 22 commits into from

Conversation

edg-l
Copy link
Member

@edg-l edg-l commented Nov 27, 2023

What this PR does

Using the libloading crate, we load the compiled library and call the entrypoint using the _mlir_cifafe_ wrapper that gives us a extern "C" function callable from Rust side.

The problem

The problem is figuring out the correct signature and how to pass the correct arguments.

For now I reduced the scope to just calling contract function wrappers, which are produced by cairo->sierra when compiling a starknet contract, they always have the same signature:

llvm.func @"_mlir_ciface_hello_starknet::hello_starknet::Echo::__wrapper__echo"
        (%arg0: !llvm.ptr, // this argument is the returned value behind a pointer (AFAIK), so its what will be on -> X at the rust side.
         %arg1: !llvm.array<0 x i8>,  // This is the range check builtin, zero sized.
         %arg2: i128, // The gas
         %arg3: !llvm.ptr, // The syscall handler
         %arg4: !llvm.struct<(struct<(ptr<i252>, i32, i32)>)>) // The Span<Felt252>, AKA the call data, to my knowledge a span is a struct whose first field is a array.
        attributes {llvm.emit_c_interface, sym_visibility = "public"} {}

I still havent figured out how to give the arguments correctly, right now i re-use the JIT value to construct the values:

       let calldata = JITValue::Struct {
            fields: vec![JITValue::Array(vec![
                JITValue::Felt252(1.into()),
            ])],
            debug_name: None,
        }
        .to_jit(&arena, reg, ty)
        .unwrap();

        dbg!(&calldata);

        let syscall_handler_meta = SyscallHandlerMeta::new(syscall_handler);

        let syscall_addr = syscall_handler_meta.as_ptr().as_ptr() as *const () as usize;

        let syscall_alloc = arena.alloc(syscall_addr as *mut ());
        let return_value = libc::malloc(8094);
        return_value.cast::<u32>().write(0xbeef);

        let gas_ptr: *mut u128 = arena.alloc_layout(Layout::new::<u128>()).as_ptr().cast();
        gas_ptr.write(u64::MAX.into());

        let func: libloading::Symbol<
            unsafe extern "C" fn(
                return_value: *mut (),
                range_check: *const (),
                gas_builtin: *mut u128,
                syscall_handler: *mut (),
                calldata: *mut (),
            ) -> *mut RetValue,
        > = lib.get(format!("_mlir_ciface_{}", symbol).as_bytes())?;

        let gas: u128 = u64::MAX.into();
        let range_check = arena.alloc_layout(Layout::new::<()>()).as_ptr().cast();

        // segfaults due to a memmove on the calldata pointer (?)
        let result = func(
            return_value.cast(),
            range_check,
            gas_ptr,
            syscall_alloc.cast(),
            calldata.as_ptr().cast(),
        );

But doesn't seem to work out well, because the later code calls a pop_front on the calldata we pass, and somehow when loading the first element, it SEGFAULTS, the part creating the segfault is here:

let op = block_not_empty.append_operation(llvm::load(
context,
elem_ptr,
elem_ty,
location,
LoadStoreOptions::new().align(Some(IntegerAttribute::new(
elem_layout.align() as i64,
IntegerType::new(context, 64).into(),
))),
));
let elem_value = op.result(0)?.into();

Edit: the segfault is no longer there after providing with 1 more argument, it seems the calldata pointer is picked up correctly, but there is a memmove involved in the pop_front that causes a segfault, probably due to misalignment (?) or it could also be because the pointer is not from libc, although i think the array is allocated using rust libc crate, but there may be issued if both libc versions arent exactly the same?.

macOS

On macOS i didn't get the segfault and i got to print some values returned, but they didn't make much sense, i think its also because the arguments are passed wrongly but somehow the execution went through. I also did not see any prints from the syscall handler which means something went wrong there too.

Reproducing

Testing is done using the aot.rs example.

You will need to change some lines because right now to use the cairo_runtime library from this same repo i use hardcoded paths, you can change them here:

cairo_native/src/ffi.rs

Lines 154 to 187 in 7a5a006

let args: &[&str] = {
#[cfg(target_os = "macos")]
{
&[
"-demangle",
"-no_deduplicate",
"-dynamic",
"-dylib",
"-L/usr/local/lib",
"-L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib",
"-L/Users/edgar/Documents/cairo_sierra_to_mlir/target/debug/", // change me
&file.display().to_string(),
"-o",
&output_filename.display().to_string(),
"-lSystem",
"-lcairo_native_runtime",
]
}
#[cfg(target_os = "linux")]
{
&[
"--hash-style=gnu",
"--eh-frame-hdr",
"-shared",
"-L/data2/edgar/work/native/target/debug", // change me
"-rpath=/data2/edgar/work/native/target/debug", // change me
"-rpath-link=/data2/edgar/work/native/target/debug", // change me
"-L/lib/../lib64",
"-L/usr/lib/../lib64",
"-o",
&output_filename.display().to_string(),
"-lc",
"-lcairo_native_runtime",
&file.display().to_string(),

To run:

cargo b --all-features --all-targets --examples

# Then to debug i use rust-gdb on linux and rust-lldb on macos:
rust-gdb  ./target/debug/examples/aot

@edg-l edg-l changed the title progress aot library load Nov 27, 2023
Base automatically changed from to_object_llvm to main November 28, 2023 16:25
Copy link

github-actions bot commented Nov 28, 2023

Benchmarking results

Benchmark for program factorial_2M

Open benchmarks
Command Mean [s] Min [s] Max [s] Relative
Cairo-vm (Rust, Cairo 1) 20.858 ± 0.095 20.635 20.957 44.17 ± 0.22
cairo-native (JIT MLIR ORC Engine) 1.588 ± 0.047 1.533 1.662 3.36 ± 0.10
cairo-native (AOT Native binary) 0.661 ± 0.003 0.659 0.669 1.40 ± 0.01
cairo-native (AOT Native binary with host CPU features, march=native) 0.472 ± 0.001 0.471 0.475 1.00

Benchmark for program fib_2M

Open benchmarks
Command Mean [s] Min [s] Max [s] Relative
Cairo-vm (Rust, Cairo 1) 20.415 ± 0.093 20.272 20.545 651.21 ± 9.90
cairo-native (JIT MLIR ORC Engine) 1.172 ± 0.026 1.136 1.223 37.39 ± 0.99
cairo-native (AOT Native binary) 0.031 ± 0.000 0.031 0.033 1.00
cairo-native (AOT Native binary with host CPU features, march=native) 0.032 ± 0.001 0.031 0.037 1.02 ± 0.04

Benchmark for program logistic_map

Open benchmarks
Command Mean [s] Min [s] Max [s] Relative
Cairo-vm (Rust, Cairo 1) 2.016 ± 0.031 1.966 2.068 28.62 ± 0.63
cairo-native (JIT MLIR ORC Engine) 1.509 ± 0.011 1.493 1.526 21.42 ± 0.37
cairo-native (AOT Native binary) 0.109 ± 0.001 0.109 0.113 1.55 ± 0.03
cairo-native (AOT Native binary with host CPU features, march=native) 0.070 ± 0.001 0.070 0.074 1.00

@edg-l
Copy link
Member Author

edg-l commented Dec 12, 2023

Closing as #363 supersedes this

@edg-l edg-l closed this Dec 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant