Stream: cranelift

Topic: Printing to sdout


view this post on Zulip Shane B (Mar 23 2024 at 18:01):

Hi all,

I'm implementing a language in Cranelift. How can I make a compiled function print to stdout/stderr? Thanks :smile:

view this post on Zulip T0b1 (Mar 23 2024 at 18:54):

Before you compile your function you need to call set_disasm with true on the context.
After the function is compiled (so after define_function) you can get the disassembly like so: ctx.compiled_code().unwrap().vcode.as_ref().unwrap().

As a side-note, you may want to also disassemble the machine code (ctx.compiled_code().unwrap().code_buffer()) using a normal disassembler like iced or smth since I found that to be more readable from time to time.

view this post on Zulip Shane B (Mar 23 2024 at 19:08):

@T0b1 Sorry, maybe I worded the question poorly. I'd like there to be a built-in print function in my language, e.g. print(...) that the user can call to print text to stdout when the code is JIT-compiled/executed. I couldn't find an instruction or any documentation to do this.

view this post on Zulip T0b1 (Mar 23 2024 at 19:37):

If your print function simply take a string pointer or smth like that then you can just link it to an extern "C" function defined in your runtime library.
For the jit crate this should work like this (just compiled this from some old code, may not be exactly like this):

extern "C" fn extern_fn(test: u64) -> i64 {
    test as i64 - 40
}

// when initializing your module
let mut builder: JITBuilder = /* ... */;
builder.symbol("extern_fn", (extern_fn as *const ()) as *const u8);
// ...
let mut module: JITModule = /* ... */;
let mut sig = Signature::new(CallConv::SystemV); // linux C callconv
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));

let extern_id = module.declare_function("extern_fn", Linkage::Import, &sig).unwrap();

// when compiling func
let extern_ref = module.declare_func_in_func(extern_id, &mut ctx.func);
let inst = instruction_builder.call(extern_ref, vec![ir_value_with_arg]);
ctx.func.dfg.make_inst_results(inst, types::I64);
let inst_res = ctx.func.dfg.first_result(inst);

For the AOT case this should work similarily probably.
If your print function uses variable arguments, then it becomes a bit more complicated since cranelift didn't(/doesn't?) support calling vararg functions.

view this post on Zulip Chris Fallin (Mar 23 2024 at 19:39):

@Shane B to add a bit more to @T0b1's answer (thanks) with higher-level background: you can think of Cranelift's IR like a machine-independent assembly language. How would one print to stdout in assembly? There's no built-in machine instruction; likewise there's no built-in Cranelift operator for this. One has to call out to the operating system somehow. Cranelift gives you (i) basic computation, with arithmetic, memory, and control flow, and (ii) the ability to call, including functions that you've provided.

So for any functionality that isn't "natively" present in Cranelift, the usual answer will be to write a library function that provides it; if that library function needs access to the OS, it gets that by being written in another language that has access to the OS (as above here).

view this post on Zulip Shane B (Mar 25 2024 at 17:49):

@T0b1 @Chris Fallin This worked perfectly. Thanks!!

view this post on Zulip Ivan Chinenov (Mar 27 2024 at 09:38):

Do I to do anything special if I want to link an external function with cranelift-object instead of jit?

view this post on Zulip Ivan Chinenov (Mar 27 2024 at 10:02):

And, as an aside, what the name parameter in ObjectBuilder::new is used for?

view this post on Zulip bjorn3 (Mar 27 2024 at 13:47):

Ivan Chinenov said:

Do I to do anything special if I want to link an external function with cranelift-object instead of jit?

Just specify Linkage::Import when declaring the function and make sure to tell the linker to link against whichever library it should be imported from.

view this post on Zulip bjorn3 (Mar 27 2024 at 13:50):

Ivan Chinenov said:

And, as an aside, what the name parameter in ObjectBuilder::new is used for?

That is the name that will be embedded in the object file. It may be used by the linker for error messages. A C compiler would put the name of the source file here.

view this post on Zulip Yury Solovyov (Mar 31 2024 at 13:56):

I'm trying to do a similar thing, static AOT compilation against libc, trying to do puts("Hello, World\n") type of thing.
I wonder how do I work with constants in that case, e.g. if I want to put "Hello, World" string literal in some constant pool.
Should I worry about this at all? Can I put string literals inline?

view this post on Zulip bjorn3 (Mar 31 2024 at 14:01):

For strings and the like you will have to use module.define_data() just like you have to use for global variables.

view this post on Zulip Yury Solovyov (Mar 31 2024 at 16:01):

I was looking at jit demo, and I can't quite map these lines to updated or to ObjectModule API, any ideas?
https://github.com/bytecodealliance/cranelift-jit-demo/blob/e435835efbd7636bca230a3434d1d586587b378b/src/jit.rs#L108-L111

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

view this post on Zulip Ivan Chinenov (Mar 31 2024 at 16:15):

Yury Solovyov said:

I was looking at jit demo, and I can't quite map these lines to updated or to ObjectModule API, any ideas?
https://github.com/bytecodealliance/cranelift-jit-demo/blob/e435835efbd7636bca230a3434d1d586587b378b/src/jit.rs#L108-L111

Something like this:

    let object = module.finish().emit()?;

view this post on Zulip Ivan Chinenov (Mar 31 2024 at 16:16):

Then you can write the object to a file and link using whatever linker you have.

view this post on Zulip Yury Solovyov (Mar 31 2024 at 19:18):

I've tried putting everything into one scope, just to figure out what should call what, but now I get borrowing issues.
I'd be happy to work them out on my own, I just need to understand which part I can to separate.
https://gist.github.com/YurySolovyov/10f0ea326bf8ecb17f4c91768e135faa

In pseudo code I'm trying to do something like

fn main(i32) -> i32 {
  let greet : ptr = "Hello, Cranelift!\n";
  puts(greet);
  return 0;
}

In any case, here is my latest attempt if anyone can take a look, that would be greatly appreciated

Hello, Cranelift! GitHub Gist: instantly share code, notes, and snippets.

view this post on Zulip Ivan Chinenov (Mar 31 2024 at 19:22):

Yury Solovyov said:

https://gist.github.com/YurySolovyov/10f0ea326bf8ecb17f4c91768e135faa

module.finish() should be the last thing you do.
There's my cranelift codegen in case you want an example https://github.com/JohnDowson/rotth/blob/slate/rotth/src/emit/cranelift2.rs

Rotth is a stack based concatenative language highly inspired by Porth - JohnDowson/rotth

view this post on Zulip Yury Solovyov (Mar 31 2024 at 19:28):

Thanks! I'll look into it more.
It seems in your case you also have global values in todo!(); as well as working with string literals.
Not sure if my simple example can be made to work with local vars, but I could be wrong.

view this post on Zulip Ivan Chinenov (Mar 31 2024 at 19:29):

Yes, but at least you can look at the order of operations with module

view this post on Zulip Yury Solovyov (Mar 31 2024 at 19:36):

Yes, that helps, sorry, didn't mean to be harsh.

view this post on Zulip Ivan Chinenov (Mar 31 2024 at 19:50):

No offense taken

view this post on Zulip Yury Solovyov (Apr 01 2024 at 08:28):

Okay, I've managed to resolve borrowing issues, and make it build & output some IR. Moving finish() call later helped a lot.


Last updated: Oct 23 2024 at 20:03 UTC