Hi all,
I'm implementing a language in Cranelift. How can I make a compiled function print to stdout/stderr? Thanks :smile:
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.
@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.
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.
@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).
@T0b1 @Chris Fallin This worked perfectly. Thanks!!
Do I to do anything special if I want to link an external function with cranelift-object instead of jit?
And, as an aside, what the name
parameter in ObjectBuilder::new
is used for?
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.
Ivan Chinenov said:
And, as an aside, what the
name
parameter inObjectBuilder::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.
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?
For strings and the like you will have to use module.define_data()
just like you have to use for global variables.
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
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()?;
Then you can write the object to a file and link using whatever linker you have.
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
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
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.
Yes, but at least you can look at the order of operations with module
Yes, that helps, sorry, didn't mean to be harsh.
No offense taken
Okay, I've managed to resolve borrowing issues, and make it build & output some IR. Moving finish() call later helped a lot.
Last updated: Dec 23 2024 at 12:05 UTC