bytecodealliance / wasmtime

A fast and secure runtime for WebAssembly
https://wasmtime.dev/
Apache License 2.0
14.87k stars 1.24k forks source link

Cranelift: provide a C API #1164

Open carlokok opened 4 years ago

carlokok commented 4 years ago

What I'd like to see is a way to use Cranelift apis from another (non rust) language. Currently the cranelift apis are rust only, but having a layer on top of that like "llvm-c" is, would make the cranelift api usable from all languages, and thus a much wider range of compilers could use it as a backend.

bjorn3 commented 4 years ago

https://github.com/eqrion/cbindgen may be useful for this.

carlokok commented 4 years ago

@bjorn3 once there IS a public api then yes. cbindgen requires the Rust side to already define a nomangle interface.

carlokok commented 4 years ago

If it's of any interest to anyone, I wrote a C api that allows this.

api: https://gist.github.com/carlokok/d0d079dba76ee253a1571d58f2e791fb

#include <cranelift.h>
#include <stdio.h>

void cb1(uintptr_t userdata, const char* err, const char* fn) {
  printf("Error %s: %s\n", fn, err);
}

void cb2(uintptr_t userdata, const char* err, const char* fn) {
  printf("Message %s: %s\n", fn, err);
}

void cb(uintptr_t userdata, FunctionData* fd)
{
  BlockCode entry = cranelift_create_block(fd);
  BlockCode larger = cranelift_create_block(fd);
  BlockCode exit = cranelift_create_block(fd);

  cranelift_append_block_params_for_function_params(fd, entry);

  cranelift_switch_to_block(fd, entry);

  ValueCode res[2];
  cranelift_block_params(fd, entry, res);

  VariableCode a = cranelift_declare_var(fd, TypeI32);
  cranelift_def_var(fd, a, res[0]);

  VariableCode b = cranelift_declare_var(fd, TypeI32);
  cranelift_def_var(fd, b, res[1]);

  VariableCode retvar = cranelift_declare_var(fd, TypeI32);
  cranelift_def_var(fd, retvar, cranelift_iconst(fd, TypeI32, 0));

  cranelift_ins_br_icmp(fd, CraneliftIntCCSignedLessThan, a, b, larger, 0, NULL);

  cranelift_def_var(fd, retvar, cranelift_use_var(fd, a));

  cranelift_ins_jump(fd, exit, 0, NULL);

  cranelift_switch_to_block(fd, larger);

  cranelift_def_var(fd, retvar, cranelift_use_var(fd, b));

  cranelift_ins_jump(fd, exit, 0, NULL);

  cranelift_switch_to_block(fd, exit);

  ValueCode retval = cranelift_use_var(fd, retvar);
  cranelift_return(fd, 1, &retval);

  cranelift_seal_all_blocks(fd);

}

void cbp(uintptr_t ud, char* data)
{
  printf("\n%s\n", data);
}

int main()
{
  ModuleData* mod = cranelift_module_new("x86_64-pc-windows", "is_pic,enable_simd,enable_atomics,enable_verifier", "mymodule", 0, cb1, cb2);

  cranelift_signature_builder_add_param(mod, TypeI32);
  cranelift_signature_builder_add_param(mod, TypeI32);
  cranelift_signature_builder_add_result(mod, TypeI32);
  cranelift_build_function(mod, 0, cb);
  cranelift_function_to_string(mod, 0, cbp);
  uint32_t id;  
  cranelift_declare_function(mod, "max", Export, &id);
  cranelift_define_function(mod, id);

  uint32_t id2;
  uint8_t c[4] = { 1,2,3,4 };
  cranelift_set_data_value(mod, c, 4);
  cranelift_define_data(mod, "testdata_w", Export, Writable, 0, &id2);
  cranelift_assign_data_to_global(mod, id2);
  cranelift_clear_data(mod);

  cranelift_set_data_value(mod, c, 4);
  cranelift_define_data(mod, "testdata_r", Export, None, 0, &id2);
  cranelift_assign_data_to_global(mod, id2);
  cranelift_clear_data(mod);

  cranelift_set_data_value(mod, c, 4);
  cranelift_define_data(mod, "testdata_w2", Export, Writable, 0, &id2);
  cranelift_set_data_section(mod, "SEG", "SEC");
  cranelift_assign_data_to_global(mod, id2);
  //  cranelift_clear_data(mod);

  cranelift_module_emit_object(mod, "c:\\projects\\test.o");
  cranelift_module_delete(mod);
}
bjorn3 commented 4 years ago

I think cranelift_block_params should take the expected param count as argument to prevent accidental out-of-bounds memory accesses.

bjorn3 commented 4 years ago

It would also be nice if cranelift-codegen-meta automatically generated all cranelift_ins_* functions.

carlokok commented 4 years ago

agreed :) but if there's no interest or if the interfaces don't want to be tied down then I won't bother. Also this was my very first foray into Rust.

rishavs commented 1 year ago

Is this likely to happen? I'd really like to try out cranelift for my toy language, but the lack of a simple c api will mean that I will have to stick with llvm instead.

cfallin commented 1 year ago

I personally think it would be nice to have this, but it's not a small task to build, and no one currently has the time to do it. If you (or anyone) would like to contribute this, we'd be happy to talk further -- the first step would probably be gathering feedback in an RFC.

rishavs commented 1 year ago

This is way out of my league so likely a stupid question; but if I build a so/dll of all the cranelift crates like cranelift = "0.89.0" using cargo, can I use this .so file in other languages which support FFI?

bjorn3 commented 1 year ago

No, rust's ABI is not stable, so you can't call any of the methods in this dylib. You have to write a C interface for every function you want to call from non-rust code and then use this C interface.

cfallin commented 1 year ago

This is way out of my league so likely a stupid question; but if I build a so/dll of all the cranelift crates like cranelift = "0.89.0" using cargo, can I use this .so file in other languages which support FFI?

To add a little more nuance: (i) Rust does have the ability to produce .sos that can be called with a C ABI; but (ii) that requires explicit work, in defining the C-ABI-compatible exports (#[no_mangle] and extern "C" fn are the relevant search terms, as well as care about data structure layout and use of #[repr(C)] on structs where needed).

tgross35 commented 4 months ago

Is there a place for a C API in-tree, or is that solidly out of scope for the time being?

@coffeebe4code did some reworking of @carlokok's gist, https://github.com/coffeebe4code/craneliftc. If the methodology looks correct enough, that could likely be a reasonable starting point.