Stream: cranelift

Topic: "hello world" example?


view this post on Zulip Victor Maia (Jan 06 2022 at 19:17):

Where can I find a simple "hello world" Cranelift example, that just builds a simple (adder?) function, JIT compile and calls it? The simplest example I could find is the "toy language", which has an entire programming language, parser and extra stuff. Really hard to get started on Cranelift right now. I just need to be able to produce the IR (already learned how, by using FunctionBuilder), and then compile it to a callable Rust function, which mutates a Rust &[u8] buffer. A simple "hello world" adder would be immensely helpful to get started.

view this post on Zulip Victor Maia (Jan 06 2022 at 19:22):

these slides seems to contain such small example: https://www.slideshare.net/RReverser/building-fast-interpreters-in-rust

Presentation I gave at a Rust Austin meetup in November 2018 about exploring different approaches for interpreting custom DSLs in Rust with varying speed chara…

view this post on Zulip Victor Maia (Jan 06 2022 at 19:22):

but I can't find the code for it

view this post on Zulip Chris Fallin (Jan 06 2022 at 19:23):

@Victor Maia I'm not aware of any literal "hello world" example, but I agree, we should have something like that. https://github.com/bytecodealliance/cranelift-jit-demo is the closest to a "getting started" example that we have (probably what you are referring to). We're pretty resource-constrained and haven't been able to build up a lot of tutorial documentation unfortunately (docs are a priority for work in 2022, though, assuming other priorities don't get in the way)

JIT compiler and runtime for a toy language, using Cranelift - GitHub - bytecodealliance/cranelift-jit-demo: JIT compiler and runtime for a toy language, using Cranelift

view this post on Zulip Chris Fallin (Jan 06 2022 at 19:24):

If you do work out a very simple minimal example we'd be happy to take a PR to our docs directory though, to at least help future folks!

view this post on Zulip Victor Maia (Jan 06 2022 at 19:25):

I see, that is fine. Have been in your shoes.

view this post on Zulip Teymour Aldridge (Jan 06 2022 at 19:31):

Maybe it would be helpful to link more prominently to the JIT example from the documentation?

view this post on Zulip Victor Maia (Jan 06 2022 at 19:36):

Problem is, people that code to Cranelift probably already know how parsers, ASTs etc. work, so all the extra stuff get in the way of finding the Cranelift functions I'm interested in. But that is fine, I'll try to dig the JIT example and make a small "hello world" that just compiles an adder, and then I'll post it in case anyone else is interested.

view this post on Zulip Teymour Aldridge (Jan 06 2022 at 19:43):

The https://docs.rs/cranelift-codegen/latest/cranelift_codegen/ir/trait.InstBuilder.html trait shows what instructions can be built.

view this post on Zulip Victor Maia (Jan 06 2022 at 20:56):

I guess I was almost there: https://gist.github.com/MaiaVictor/3a1b0a07517574c348c6131fbb7ab6d3

GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip Victor Maia (Jan 06 2022 at 20:57):

But I got a sad error: thread 'main' panicked at 'PLT is currently only supported on x86_64'

view this post on Zulip Victor Maia (Jan 06 2022 at 20:57):

I guess that means Cranelift doesn't support ARM / Apple M1 yet? :(

view this post on Zulip Chris Fallin (Jan 06 2022 at 21:04):

Cranelift does support aarch64 and M1! But there may be an issue with the particular kind of relocation you're using

view this post on Zulip Chris Fallin (Jan 06 2022 at 21:04):

I unfortunately don't have M1 hardware so I'm not an expert; cc @Benjamin Bouvier to help (he is in UTC+1 so probably not online at the moment)

view this post on Zulip Victor Maia (Jan 06 2022 at 21:08):

What's "relocation"? The problematic code is:

let builder = JITBuilder::new(cranelift_module::default_libcall_names());
let module = JITModule::new(builder);

After googling, I realized there is a way to change flags of JITBuilder, so I'm messing with that to see if I get lucky

view this post on Zulip Chris Fallin (Jan 06 2022 at 21:17):

Relocations are the way in which the runtime edits the code to refer to particular functions that are being called. The PLT (procedure linkage table) is a particular mechanism used by some relocations. Depending on the environment in which Cranelift is used, it will use different relocation types. It seems here that the particular configuration is causing Cranelift to try to use the PLT, which we apparently don't support on aarch64.

Wasmtime (which uses Cranelift) definitely works on M1, so a good starting point might be to see how it is generating calls and configuring things. IIRC we end up with Abs8 references (absolute 64-bit addresses) to libcalls, so there is definitely a way to make that work.

I'm not an expert in the cranelift-jit frontend to Cranelift, and mostly work in the backends, so you'll probably need help from @Benjamin Bouvier or possibly @bjorn3 (who also uses Cranelift outside of Wasmtime). Sorry I can't offer more than that!

view this post on Zulip Victor Maia (Jan 06 2022 at 21:19):

I see, that's already immensely helpful input, thank you. I don't think I have the knowledge to go further than that either, sadly.

view this post on Zulip bjorn3 (Jan 06 2022 at 21:31):

yeah, there is an open issue for making cranelift-jit work on aarch64.

view this post on Zulip Victor Maia (Jan 06 2022 at 21:31):

yep I'm reading it now

view this post on Zulip bjorn3 (Jan 06 2022 at 21:32):

this is an issue specific to cranelift-jit. wasmtime and afaik aot compilation using cranelift-object are fully supported on aarch64.

view this post on Zulip Victor Maia (Jan 06 2022 at 21:34):

I'm avoiding wasmtime and going directly to Cranelift because I need each % of performance I can get. I noticed wasmtime has some safety memory features that would likely make it slightly slower than a direct compilation. Also in wasmtime I can't apply a wasm function directly to a &mut [u8], I need to use wasm's linear memory, which in turn would mean I need to update the rest of my functions to also use wasm's linear memory (even though they're not related to JIT). Does that make sense, or am I going in the wrong direction?

view this post on Zulip bjorn3 (Jan 06 2022 at 21:34):

i am responsible for the cranelift-jit change that caused this issue. it was part of a change i did to allow replacing function definitions.

view this post on Zulip Victor Maia (Jan 06 2022 at 21:35):

That's quite cool actually

view this post on Zulip Victor Maia (Jan 06 2022 at 21:36):

seems like benmk's solution on that thread solved the issue here, too. but it disables "PIC", which I don't know what is.

view this post on Zulip Victor Maia (Jan 06 2022 at 21:36):

is PIC what allows replacing function definitions?

view this post on Zulip bjorn3 (Jan 06 2022 at 21:38):

yes. this is not the normal purpose of pic, but I repurposed it in cranelift-jit for replacing function definitions. disabling it is likely slightly faster.

view this post on Zulip Victor Maia (Jan 06 2022 at 22:27):

Any idea what thread 'main' panicked at 'remove_constant_phis: entry block unknown' could mean? I thought the first block was the entry already?

view this post on Zulip Victor Maia (Jan 06 2022 at 22:28):

where I'm at: https://gist.github.com/MaiaVictor/d41ae7af9e9ceaebdd778f27870a4ac4

GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip Victor Maia (Jan 06 2022 at 22:33):

hmm never mind, seems like codegen already creates a func inside it, which is what I need to use, rather than creating a new one (?)

view this post on Zulip Victor Maia (Jan 06 2022 at 22:34):

the good news is: IT WORKS! yaaaay

view this post on Zulip Victor Maia (Jan 06 2022 at 22:34):

the bad news it is probably mostly wrong

view this post on Zulip Victor Maia (Jan 06 2022 at 22:34):

here is the code: https://gist.github.com/MaiaVictor/6f62947839c485c01751655d156ef35c

GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip Victor Maia (Jan 06 2022 at 22:35):

but I'm confused, it seems like add42 can only be called once, since it uses the func object in codegen_ctx

view this post on Zulip Victor Maia (Jan 06 2022 at 22:35):

should add42 instead return a codegen_ctx?

view this post on Zulip Victor Maia (Jan 06 2022 at 22:35):

// Builds a `f(x) = add(42,x)` function in Cranelift IR
fn add42(jit: &mut JIT) : codegen::Context {

like this?

view this post on Zulip Victor Maia (Jan 06 2022 at 22:36):

basically I'm confused about the internal states, what should be kept and what should be created dynamically every time I compile a function

view this post on Zulip Victor Maia (Jan 06 2022 at 22:40):

ideally add42 should return CraneliftIR, but since FunctionBuilder::new receives a function, I can't just build the IR separate from an existing/declared function, I guess?

view this post on Zulip bjorn3 (Jan 06 2022 at 22:58):

The Context is meant to be reused between functions, but after every function you defined you need to call .clear() to clear out all cached state. Basically it is meant to allow reusing allocations between compilation of different functions.

view this post on Zulip bjorn3 (Jan 06 2022 at 22:59):

As for the panic can you show the printed Function?

view this post on Zulip bjorn3 (Jan 06 2022 at 23:01):

I am going to sleep. I will look at any reply tomorrow.

view this post on Zulip Victor Maia (Jan 06 2022 at 23:18):

Thanks, for now I think all is fine. If you could just have a quick look on my code and let me know if you spot any obvious mistake (such as using the static objects incorrectly):

https://gist.github.com/MaiaVictor/682eeca48da1f2d6db77875b331cd88a

GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip Victor Maia (Jan 06 2022 at 23:20):

Specifically, I'm worried that make_adder starts with jit.codegen_ctx.func.signature.params.push, wouldn't that cause the params to accumulate through different functions?

view this post on Zulip Victor Maia (Jan 07 2022 at 02:46):

Well I have just 2 quick questions if I may:

  1. How do I use iconst.u64 with ins()? builder.ins().iconst(I64, amount) is signed, if I understand correctly
  2. How do I pass and access arrays to the generated functions? I've spend a while trying to understand the load/store prims, but couldn't get it quite right. This is my best attempt: https://gist.github.com/MaiaVictor/171cf093c49aab67ce0fbffe5dd7898f#file-wrong-rs-L109 (yes it is obviously wrong but I'm not sure what I'm even looking for)
GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip bjorn3 (Jan 07 2022 at 09:51):

For 1. it is fine to just cast a u64 to an i64. It will fill the registers with the provided bits without interpreting it in any way. For integers smaller than 64bit the most significant bits in the immediate should be ignored, so it is fine to both sign or zero extend the immediate to 64 bits.

view this post on Zulip bjorn3 (Jan 07 2022 at 09:52):

For 2. you need to multiply the array index by the element size and then add the resulting offset to the array pointer. You can then pass the resulting element pointer to the load instruction.

view this post on Zulip bjorn3 (Jan 07 2022 at 09:56):

To answer https://gist.github.com/MaiaVictor/682eeca48da1f2d6db77875b331cd88a#file-it_works-rs-L73 FunctionBuilderContext is used to reuse some allocations between two FunctionBuilder's. There is only a perf difference between using a single FunctionBuilderContext and using a new one every time you make a FunctionBuilder. It is cleared automatically by FunctionBuilder::new().

GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip bjorn3 (Jan 07 2022 at 09:58):

To answer https://gist.github.com/MaiaVictor/682eeca48da1f2d6db77875b331cd88a#file-it_works-rs-L118 it checks that some cranelift ir invariants are not violated. Violating these could result in crashes during compilation or even to miscompilation. It mainly exists as a debugging tool. Once you are certain that you are always producing correct cranelift ir it is fine to disable it for release builds. (keeping it for debug builds can still be useful)

GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip bjorn3 (Jan 07 2022 at 09:59):

@Victor Maia The it_works.rs code looks fine to me.

view this post on Zulip Victor Maia (Jan 07 2022 at 10:23):

I get it. And got the array code to work now. That is immensely helpful. Thank you so much for taking your time to answer my questions!

view this post on Zulip bjorn3 (Jan 07 2022 at 11:49):

You're welcome!

view this post on Zulip Benjamin Bouvier (Jan 10 2022 at 17:33):

Hi! Cool that you found another way to do it, I unfortunately don't have much insights into the PLT issue on aarch64 :thinking:

view this post on Zulip Mike-Neto (Oct 19 2024 at 10:57):

Hey last week I started working on my own programming language for the sake of learning how to do it, meaning I'm a complete novice into this world.

I started by outputting assembly (I'm on a M1 Mac) and then calling as & ld to create the final binary, while trying to make it run in linux as well I went down the rabbit hole of compiler backends and found cranelift.

Scouring for an updated version of a native binary example led me to nothing but I did find the JIT examples which are good enough for me to get started.

At this point I have a repo that has a minimal version of what @Victor Maia had but I wanted to extend it to also allow for printing (so think printf/puts) can anyone give me any pointers into how I would go for trying to achieve that?

My plan is to also extend the example to have a native binary version of the same thing.

Here is the repo https://github.com/Mike-Neto/cranelift-simple-jit/tree/main .

Contribute to Mike-Neto/cranelift-simple-jit development by creating an account on GitHub.

view this post on Zulip Floppy (Oct 21 2024 at 10:46):

Usually this is accomplished by linking to the targeted platforms system library (such as glibc on Linux) and then calling those functions with the appropriate calling convention (such as SystemV).

For that route you'll also need to make sure your language supports the necessary parts to construct the C-compatible data for those function calls. (raw pointer to bytes in the case of puts)

An alternative is to write a basic standard library in Rust to piggy-back of Rust's platform-agnostic standard library, and then expose those Rust functions to your language by declaring them with extern and compiling the Rust side to a static library.


Last updated: Oct 23 2024 at 20:03 UTC