Rust + LLVM ORC JIT cannot find symbol address - rust

I have been trying to use the ORC JIT compiler from the LLVM C bindings in Rust, but I keep running into the problem that LLVMOrcGetSymbolAddress is not able to find the symbol of my function run in the module I provide it. The code below combines the most important parts of my code that unfortunately doesn't work. All goes well until the last part of foo, where an error is returned because the function cannot be found. The function is definitely in the module, as LLVMGetNamedFunction is able to find it, but somehow the ORC engine cannot see it. Can anyone see what I am doing wrong? I am using LLVM 6.0 and the llvm-sys Rust bindings. Everything works fine if I use MCJIT but I need ORC for lazy compilation.
fn foo(module: LLVMModuleRef) -> Result<I64Func, LlvmError> {
let def_triple = LLVMGetDefaultTargetTriple();
let mut target_ref = ptr::null_mut();
let mut error_str = ptr::null_mut();
// Get target from default triple
if LLVMGetTargetFromTriple(def_triple, &mut target_ref, &mut error_str) != 0 {
let msg = format!("Creating target from triple failed: {}", CStr::from_ptr(error_str).to_str().unwrap());
LLVMDisposeMessage(def_triple);
LLVMDisposeMessage(error_str);
return Err(LlvmError(msg));
}
// Check if JIT is available
if LLVMTargetHasJIT(target_ref) == 0 {
let msg = format!("Cannot do JIT on this platform");
LLVMDisposeMessage(def_triple);
return Err(LlvmError(msg));
}
// Create target machine
let tm_ref = LLVMCreateTargetMachine(target_ref,
def_triple,
CString::new("").unwrap().as_ptr(),
CString::new("").unwrap().as_ptr(),
llvm_opt_level(optimization_level)?,
LLVMRelocMode::LLVMRelocDefault,
LLVMCodeModel::LLVMCodeModelJITDefault);
LLVMDisposeMessage(def_triple);
let engine = LLVMOrcCreateInstance(tm_ref);
// Add eagerly compiled IR
let mut handle = LLVMOrcModuleHandle::default();
let shared_module = LLVMOrcMakeSharedModule(module);
let ctx = engine as *mut libc::c_void;
map_orc_err(engine, LLVMOrcAddEagerlyCompiledIR(engine,
&mut handle,
shared_module,
symbol_resolver_callback,
ctx))?;
// Find function named 'run'
let c_name = CString::new("run").unwrap().as_ptr();
let mut func_addr = LLVMOrcTargetAddress::default();
map_orc_err(engine, LLVMOrcGetSymbolAddress(engine, &mut func_addr, c_name))?;
if func_addr == 0 {
// This errors always gets thrown
return Err(LlvmError(format!("No function named {} in module", name)));
}
let function: I64Func = mem::transmute(func_addr);
Ok(function)
}
extern "C" fn symbol_resolver_callback(symbol: *const libc::c_char, ctx: *mut libc::c_void) -> LLVMOrcTargetAddress {
let mut address = LLVMOrcTargetAddress::default();
let engine: LLVMOrcJITStackRef = ctx as LLVMOrcJITStackRef;
unsafe { LLVMOrcGetSymbolAddress(engine, &mut address, symbol) };
address
}
unsafe fn map_orc_err(engine: LLVMOrcJITStackRef, error_code: LLVMOrcErrorCode) -> Result<(), LlvmError> {
match error_code {
LLVMOrcErrorCode::LLVMOrcErrSuccess => Ok(()),
LLVMOrcErrorCode::LLVMOrcErrGeneric => {
let c_str: &CStr = CStr::from_ptr(LLVMOrcGetErrorMsg(engine));
let str_slice: &str = c_str.to_str().unwrap();
let str_buf: String = str_slice.to_owned();
Err(LlvmError(str_buf))
}
}
}
EDIT: I tried downgrading to LLVM 4.0 just to see what effect that might have. I still cannot resolve the function, but now I'm getting an assertion error:
Assertion failed: (!Name.empty() && "getNameWithPrefix requires non-empty name"), function getNameWithPrefixImpl, file /tmp/llvm-4.0-20180412-49671-1pw0nxu/llvm-4.0.1.src/lib/IR/Mangler.cpp, line 37.
EDIT 2: Below is some basic IR for which the engine fails to find the function address:
define i64 #bar(i64 %arg) {
%1 = add i64 %arg, 1
ret i64 %1
}
define i64 #run(i64 %arg) {
%1 = add i64 %arg, 1
%2 = call i64 #bar(i64 %1)
ret i64 %2
}

Related

What is the equivalent working Rust code example for this WAT (WebAssembly Text) example?

The below WAT, adapted from a couple of Wasmtime examples, runs absolutely fine embedded in my application but what I thought was the equivalent Rust code fails with:
Running `target\debug\hello_world.exe`
Error: expected 5 imports, found 1
error: process didn't exit successfully: `target\debug\hello_world.exe` (exit code: 1)
Here's the working WAT:
(module
(import "host" "greet" (func $greet (param i32 i32)))
(func (export "run")
i32.const 4 ;; ptr
i32.const 22 ;; len
call $greet)
(memory (export "memory") 1)
(data (i32.const 4) "Calling back from WAT!")
)
And the failing Rust:
use std::ffi::CString;
#[link(wasm_import_module = "host")]
extern "C" {
fn greet(ptr: i32, len: i32);
}
static GREETING: &str = "Calling back from Rust!";
#[no_mangle]
pub extern "C" fn run() {
let greeting = CString::new(GREETING).expect("contains null byte");
let ptr = greeting.as_ptr();
std::mem::forget(ptr);
unsafe {
greet(ptr as i32, GREETING.len() as i32);
}
}
My minimal example app that embeds the WASM modules:
use std::str;
use anyhow::Result;
use wasmtime::{Caller, Engine, Extern, Func, Instance, Module, Store};
struct MyState {
name: String,
count: usize,
}
fn main() -> Result<()> {
let engine = Engine::default();
let code = include_bytes!("../hello.wat");
let module = Module::new(&engine, code)?;
let mut store = Store::new(
&engine,
MyState {
name: "Hello, junglie85!".to_string(),
count: 0,
},
);
let greet_func = Func::wrap(
&mut store,
|mut caller: Caller<'_, MyState>, ptr: i32, len: i32| {
let mem = match caller.get_export("memory") {
Some(Extern::Memory(mem)) => mem,
_ => anyhow::bail!("failed to find host memory"),
};
let data = mem
.data(&caller)
.get(ptr as u32 as usize..)
.and_then(|arr| arr.get(..len as u32 as usize));
let string = match data {
Some(data) => match str::from_utf8(data) {
Ok(s) => s,
Err(_) => anyhow::bail!("invalid utf-8"),
},
None => anyhow::bail!("pointer/length out of bounds"),
};
println!("> {} {}", caller.data().name, string);
caller.data_mut().count += 1;
Ok(())
},
);
let imports = [greet_func.into()];
let instance = Instance::new(&mut store, &module, &imports)?;
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
println!("# Global count = {}", store.data().count);
run.call(&mut store, ())?;
println!("# Global count = {}", store.data().count);
Ok(())
}
What is the Rust equivalent of the WAT example?

Rust giving error for Option<usize> when using variable inside of method

I am very new to Rust and decided my first program to be a brainfuck interpreter.
I plan on using jump tables as the solution for the loops.
However I decided to rewrite the method to make it look better (for my tastes) and i got an error that I can't quite understand why
Code before causes no errors:
fn process_jumps(jump_map: &mut Vec<usize>, instructions: &Vec<Inst>){
let mut stack: Vec<usize> = Vec::new();
for (i, inst) in instructions.iter().enumerate() {
match inst {
Inst::LoopOpen => stack.push(i),
Inst::LoopClose => {
jump_map[i] = stack.pop();
jump_map[jump_map[i]] = i;
}
_ => ()
}
}
}
Code after has an error (marked in code):
fn process_jumps(instructions: &Vec<Inst>) -> Vec<usize> {
let mut jump_table: Vec<usize> = Vec::new();
let mut stack: Vec<usize> = Vec::new();
for (i, inst) in instructions.iter().enumerate() {
match inst {
Inst::LoopOpen => stack.push(i),
Inst::LoopClose => {
jump_table[i] = stack.pop(); // expected `usize`, found `Option<usize>`
jump_table[jump_map[i]] = i;
}
_ => ()
}
}
return jump_table;
}
My main question is why my code before didn't need me to check the optional?
Vec's pop() method returns Option<T>, not T.
You need to get the usize value from inside that Option, just make sure you've handled the None case correctly. When you are sure None is not possible, the simplest thing you could do is to unwrap() it.
Neither of your examples should really compile, as they both try to assign Option<usize> to a Vec<usize>.

Does Rust have anything similar to CallerMembrerName in C#

I'd like to know the name of the function that called my function in Rust.
In C# there's CallerMemberName attribute which tells the compiler to replace the value of a string argument to which it's applied with the name of the caller.
Does Rust have anything like that?
I don't know of a compile time solution, but you can use the backtrace functionality to resolve it at runtime.
use backtrace::Backtrace;
fn caller_name_slow() -> Option<String> {
let backtrace = Backtrace::new();
let symbolname = backtrace.frames().get(2)?.symbols().first()?.name();
symbolname.map(|s| format!("{:#?}", s))
}
fn caller_name_fast() -> Option<String> {
let mut count = 0;
let mut result = None;
backtrace::trace({
|frame| {
count += 1;
if count == 5 {
// Resolve this instruction pointer to a symbol name
backtrace::resolve_frame(frame, |symbol| {
if let Some(name) = symbol.name() {
result = Some(format!("{:#?}", name));
}
});
false
} else {
true // keep going to the next frame
}
}
});
result
}
fn my_function() {
println!("I got called by '{}'.", caller_name_slow().unwrap());
println!("I got called by '{}'.", caller_name_fast().unwrap());
}
fn main() {
my_function();
}
I got called by 'rust_tmp::main'.
I got called by 'rust_tmp::main'.
Note, however, that his is unreliable. The amount of stack frames we have to go up differs between targets and release/debug (due to inlining). For example, on my machine, in release I had to modify count == 5 to count == 2.

Assertion Error When Accessing Struct Field

I'm trying to declare a global variable, who's type is a struct with a function pointer and a char pointer element { i64 ()*, i8* }, and then set the fields to null during main, but I'm getting an assertion error using a debug version of LLVM.
/media/work/contrib/llvm-project/llvm/lib/IR/ConstantsContext.h:745: void llvm::ConstantUniqueMap<ConstantClass>::remove(ConstantClass*) [with ConstantClass = llvm::ConstantExpr]: Assertion `I != Map.end() && "Constant not found in constant table!"' failed.
I believe this problem is causing another issue during optimization when compiling something a bit more complicated. The error itself occurs when disposing of the module at the end. A distilled runnable example in rust is:
use std::ffi::CString;
extern crate llvm_sys;
pub use self::llvm_sys::prelude::{ LLVMValueRef };
use self::llvm_sys::*;
use self::llvm_sys::prelude::*;
use self::llvm_sys::core::*;
use self::llvm_sys::target::*;
use self::llvm_sys::target_machine::*;
use self::llvm_sys::transforms::pass_manager_builder::*;
fn main() {
unsafe {
let context = LLVMContextCreate();
let module = LLVMModuleCreateWithName(cstr("module"));
build(module, context);
println!("{}", emit_module(module));
LLVMDisposeModule(module);
LLVMContextDispose(context);
}
}
pub fn cstr(string: &str) -> *mut i8 {
CString::new(string).unwrap().into_raw()
}
pub unsafe fn build(module: LLVMModuleRef, context: LLVMContextRef) {
let builder = LLVMCreateBuilderInContext(context);
let mut argtypes = vec!();
let func_type = LLVMFunctionType(LLVMInt64TypeInContext(context), argtypes.as_mut_ptr(), argtypes.len() as u32, false as i32);
let fptr_type = LLVMPointerType(func_type, 0);
let context_type = LLVMPointerType(LLVMInt8TypeInContext(context), 0);
let mut structfields = vec!(fptr_type, context_type);
let struct_type = LLVMStructType(structfields.as_mut_ptr(), structfields.len() as u32, false as i32);
let initializer = LLVMConstNull(struct_type);
let global = LLVMAddGlobal(module, struct_type, cstr("function"));
LLVMSetInitializer(global, initializer);
LLVMSetLinkage(global, LLVMLinkage::LLVMExternalLinkage);
let mut argtypes = vec!();
let main_type = LLVMFunctionType(LLVMInt64TypeInContext(context), argtypes.as_mut_ptr(), argtypes.len() as u32, false as i32);
let function = LLVMAddFunction(module, cstr("main"), main_type);
let bb = LLVMAppendBasicBlockInContext(context, function, cstr("entry"));
LLVMPositionBuilderAtEnd(builder, bb);
let mut indices = vec!(LLVMConstInt(LLVMInt32TypeInContext(context), 0, 0), LLVMConstInt(LLVMInt32TypeInContext(context), 0, 0));
let field = LLVMBuildGEP(builder, global, indices.as_mut_ptr(), indices.len() as u32, cstr(""));
LLVMBuildStore(builder, LLVMConstNull(fptr_type), field);
LLVMBuildRet(builder, LLVMConstInt(LLVMInt64TypeInContext(context), 0, 0));
LLVMDisposeBuilder(builder);
}
pub fn emit_module(module: LLVMModuleRef) -> String {
unsafe { CString::from_raw(LLVMPrintModuleToString(module)).into_string().unwrap() }
}
The full output is:
; ModuleID = 'module'
source_filename = "module"
#function = global { i64 ()*, i8* } zeroinitializer
define i64 #main() {
entry:
store i64 ()* null, i64 ()** getelementptr inbounds ({ i64 ()*, i8* }, { i64 ()*, i8* }* #function, i32 0, i32 0), align 8
ret i64 0
}
/media/work/contrib/llvm-project/llvm/lib/IR/ConstantsContext.h:745: void llvm::ConstantUniqueMap<ConstantClass>::remove(ConstantClass*) [with ConstantClass = llvm::ConstantExpr]: Assertion `I != Map.end() && "Constant not found in constant table!"' failed.
Aborted (core dumped)
Any help or suggestions would be most appreciated. Thanks
I finally figured out what the issue is. In the example above, the function LLVMStructType() also has an alternate version LLVMStructTypeInContext(), which if used instead will fix the assertion error. There is also another function LLVMModuleCreateWithNameInContext, which should probably be used instead, but in the example above, it will work without fixing this.
It's also possible to replace all the InContext versions with their non-context versions to fix the problem in the example
In my actual program, removing the InContext versions didn't work for some reason, but the two non-context functions mentioned above were being used, as well a couple uses of the non-context LLVMAppendBasicBlock. Replacing these with the InContext version fixed all the assertion errors, include the one that started this:
void llvm::Value::doRAUW(llvm::Value*, llvm::Value::ReplaceMetadataUses): Assertion `New->getType() == getType() && "replaceAllUses of value with new value of different type!"' failed.
The type pointers weren't the same, so it must have been because they were defined in different contexts.

How can I figure out why a call to LLVMTargetMachineEmitToFile fails when called using llvm-sys?

extern crate llvm_sys;
use llvm_sys::*;
use llvm_sys::prelude::*;
use llvm_sys::core::*;
pub fn emit(module: LLVMModuleRef) {
unsafe {
use llvm_sys::target::*;
use llvm_sys::target_machine::*;
let triple = LLVMGetDefaultTargetTriple();
LLVM_InitializeNativeTarget();
let target = LLVMGetFirstTarget();
let cpu = "x86-64\0".as_ptr() as *const i8;
let feature = "\0".as_ptr() as *const i8;
let opt_level = LLVMCodeGenOptLevel::LLVMCodeGenLevelNone;
let reloc_mode = LLVMRelocMode::LLVMRelocDefault;
let code_model = LLVMCodeModel::LLVMCodeModelDefault;
let target_machine = LLVMCreateTargetMachine(target, triple, cpu, feature, opt_level, reloc_mode, code_model);
let file_type = LLVMCodeGenFileType::LLVMObjectFile;
LLVMTargetMachineEmitToFile(target_machine, module, "/Users/andyshiue/Desktop/main.o\0".as_ptr() as *mut i8, file_type, ["Cannot generate file.\0".as_ptr()].as_mut_ptr() as *mut *mut i8);
}
}
I'm writing a toy compiler and I want to generate object files, but the file LLVM outputs is empty.
I found that LLVMTargetMachineEmitToFile returns 1, which means something I'm doing is wrong, but what am I doing wrong?
It would be better if I can know how I can know what is wrong. Is there any way I can get some error message? I don't have any experience in C/C++.
As commenters have already said, to do what you want to do (write a compiler using LLVM), you are going to need to be able to read (and probably write) at the very least C and maybe C++.
Even though you are compiling code with the Rust compiler, you aren't really writing any Rust yet. Your entire program is wrapped in unsafe blocks because you are calling the C functions exposed by LLVM (which is written in C++). This may be why some commenters are asking if you have gotten your code to work in C first.
As in your other question, you are still calling the LLVM methods incorrectly. In this case, review the documentation for LLVMTargetMachineEmitToFile:
LLVMBool LLVMTargetMachineEmitToFile(LLVMTargetMachineRef T,
LLVMModuleRef M,
char *Filename,
LLVMCodeGenFileType codegen,
char **ErrorMessage)
Returns any error in ErrorMessage. Use LLVMDisposeMessage to dispose the message.
The method itself will tell you what is wrong, but you have to give it a place to store the error message. You should not provide an error string to it. I'm pretty sure that the current code is likely to generate some exciting memory errors when it tries to write to the string literal.
If I rewrite your code to use the error message:
extern crate llvm_sys;
use llvm_sys::*;
use llvm_sys::prelude::*;
use llvm_sys::core::*;
use std::ptr;
use std::ffi::{CStr, CString};
pub fn emit(module: LLVMModuleRef) {
let cpu = CString::new("x86-64").expect("invalid cpu");
let feature = CString::new("").expect("invalid feature");
let output_file = CString::new("/tmp/output.o").expect("invalid file");
unsafe {
use llvm_sys::target::*;
use llvm_sys::target_machine::*;
let triple = LLVMGetDefaultTargetTriple();
LLVM_InitializeNativeTarget();
let target = LLVMGetFirstTarget();
let opt_level = LLVMCodeGenOptLevel::LLVMCodeGenLevelNone;
let reloc_mode = LLVMRelocMode::LLVMRelocDefault;
let code_model = LLVMCodeModel::LLVMCodeModelDefault;
let target_machine = LLVMCreateTargetMachine(target, triple, cpu.as_ptr(), feature.as_ptr(), opt_level, reloc_mode, code_model);
let file_type = LLVMCodeGenFileType::LLVMObjectFile;
let mut error_str = ptr::null_mut();
let res = LLVMTargetMachineEmitToFile(target_machine, module, output_file.as_ptr() as *mut i8, file_type, &mut error_str);
if res == 1 {
let x = CStr::from_ptr(error_str);
panic!("It failed! {:?}", x);
// TODO: Use LLVMDisposeMessage here
}
}
}
fn main() {
unsafe {
let module = LLVMModuleCreateWithName("Main\0".as_ptr() as *const i8);
emit(module);
}
}
TargetMachine can't emit a file of this type
So that's your problem.
Rust-wise, you may want to wrap up the work needed to handle the silly LLVMBool so you can reuse it. One way would be:
fn llvm_bool<F>(f: F) -> Result<(), String>
where F: FnOnce(&mut *mut i8) -> i32
{
let mut error_str = ptr::null_mut();
let res = f(&mut error_str);
if res == 1 {
let err = unsafe { CStr::from_ptr(error_str) };
Err(err.to_string_lossy().into_owned())
//LLVMDisposeMessage(error_str);
} else {
Ok(())
}
}
// later
llvm_bool(|error_str| LLVMTargetMachineEmitToFile(target_machine, module, output_file.as_ptr() as *mut i8, file_type, error_str)).expect("Couldn't output");

Resources