I'm currently using cranelift to create a programming environment for my language. The problem is that cranelift will panic a lot and I need to recover from errors and continue running the application. How can I handle panics? Is the only solution to implement proper error handling in cranelift itself?
You can use catch_unwind, but cranelift shouldn't panic unless you passed in ir which doesn't pass the verifier.
Right, I said language, but that's an oversimplification so I could get the problem out.
Really I want to create an interface to edit cranelift IR directly, then test and disassemble the output. The interface is what needs to live on when cranelift crashes.
Imma try catch_unwind
. Thank you!
Can you run the verifier before trying to compile it and skip compiling if the verifier reports an error?
I didn't even know about any sort of verifier. Lemme check it out.
Here is some working code I have right now so you can skim through. Basically I'm adding abstractions so that I can give the user a nicer interface for writing IR. Perhaps "compiler" was the wrong word to express this intent.
use anyhow::{anyhow, Result};
use cranelift_codegen::ir::{types, AbiParam, Function, InstBuilder, Signature, UserFuncName};
use cranelift_codegen::isa::OwnedTargetIsa;
use cranelift_codegen::settings::{self, Configurable};
use cranelift_codegen::Context;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_module::{default_libcall_names, Linkage, Module};
use cranelift_object::{ObjectBuilder, ObjectModule, ObjectProduct};
use std::io::Write;
type Ns = u32;
pub struct Compiler {
namespace: Ns,
context: Context,
module: ObjectModule,
}
impl Default for Compiler {
fn default() -> Self {
Self::new(0)
}
}
impl Compiler {
fn new(namespace: Ns) -> Self {
let module = ObjectModule::new(
ObjectBuilder::new(get_isa(), "default", default_libcall_names()).unwrap(),
);
Self {
namespace,
context: module.make_context(),
module,
}
}
fn add_fn(
&mut self,
name: &str,
params: &[AbiParam],
returns: &[AbiParam],
function_builder: impl Fn(&mut Context, FunctionBuilderContext) -> (),
) -> Result<Function> {
let fn_context = FunctionBuilderContext::new();
let sig = self.create_fn_signature(params, returns);
let func = self.module.declare_function(name, Linkage::Local, &sig)?;
self.context.func.signature = sig;
self.context.func.name = UserFuncName::user(self.namespace, func.as_u32());
function_builder(&mut self.context, fn_context);
self.module.define_function(func, &mut self.context)?;
self.module.clear_context(&mut self.context);
Ok(self.context.func.clone())
}
fn create_fn_signature(&mut self, params: &[AbiParam], returns: &[AbiParam]) -> Signature {
let mut sig = self.module.make_signature();
params.iter().for_each(|param| {
sig.params.push(*param);
});
returns.iter().for_each(|return_| {
sig.returns.push(*return_);
});
sig
}
}
fn disasm(bytes: Vec<u8>) -> Result<String> {
use mktemp::Temp;
use std::process::Command;
use std::process::Stdio;
let tempfile = Temp::new_file()?;
let mut opened_file = std::fs::File::options().write(true).open(&tempfile)?;
opened_file.write_all(&bytes)?;
let mut child = Command::new("objdump")
.arg("-S")
.arg("-s")
.arg(tempfile.to_path_buf().to_string_lossy().as_ref())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let mut stdin = child.stdin.take().ok_or(anyhow!("Failed to get stdin"))?;
std::thread::spawn(move || {
// FIXME Propagate any errors
stdin.write_all(&bytes).expect("Failed to write to stdin");
});
let output = child.wait_with_output()?;
let disassembly = String::from_utf8(output.stdout)?;
Ok(disassembly)
}
fn get_isa() -> OwnedTargetIsa {
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
// FIXME set `is_pic` back to `true` once the x64 backend supports it.
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.unwrap();
isa
}
#[derive(Debug)]
pub struct Output {
pub functions: Vec<Function>,
pub disasm: String,
}
#[no_mangle]
pub fn compile_functions() -> Result<Output> {
let mut compiler = Compiler::default();
let fn_1 = compiler.add_fn(
"move_input_to_output",
&[AbiParam::new(types::F64)],
&[AbiParam::new(types::F64)],
|context: &mut Context, mut fn_context: FunctionBuilderContext| {
let mut builder = FunctionBuilder::new(&mut context.func, &mut fn_context);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
builder.seal_block(block);
let mut params = vec![];
for param in builder.block_params(block) {
params.push(*param)
}
builder.ins().return_(¶ms);
builder.finalize();
},
)?;
let fn_2 = compiler.add_fn(
"sqaure",
&[AbiParam::new(types::I64)],
&[AbiParam::new(types::I64)],
|context: &mut Context, mut fn_context: FunctionBuilderContext| {
let mut builder = FunctionBuilder::new(&mut context.func, &mut fn_context);
let block = builder.create_block();
builder.append_block_params_for_function_params(block);
builder.switch_to_block(block);
let param0 = builder.block_params(block)[0];
let val = builder.ins().imul(param0, param0);
builder.seal_block(block);
builder.ins().return_(&[val]);
builder.finalize();
},
)?;
let objectmodule = compiler.module.finish();
let bytes = objectmodule.emit()?;
let disasm = disasm(bytes)?;
Ok(Output {
functions: vec![fn_1, fn_2],
disasm,
})
}
fn main() {
println!(
"{}",
match compile_functions() {
Ok(o) => o.disasm,
Err(e) => e.to_string(),
}
)
}
As far as I understand the verifier is always enabled, so that wouldn't help with panics if build a function incorrectly.
The verifier has to be explicitly enabled.
You can either call cranelift_codegen::verifier::verify_function
manually or set enable_verifier
on the flag_builder.
The latter will also run the verifier in between optimization passes, which wouldn't be strictly necessary here.
Ok, I enabled the verifier by flag.
If I comment one of the calls to switch_to_block
, then I still get a crash.
thread '<unnamed>' panicked at /home/main/.cargo/registry/src/index.crates.io-6f17d22bba150
01f/cranelift-entity-0.108.1/src/packed_option.rs:64:23:
Please call switch_to_block before inserting instructions
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fatal runtime error: Rust cannot catch foreign exceptions
It is possible that my abstraction can be such that I don't have to worry about these sorts of panics, but I don't know.
Would be nice if the type system enforced how and when these functions must be called.
bjorn3 said:
The verifier has to be explicitly enabled.
Not what the docs say by the way. https://docs.rs/cranelift/latest/cranelift/prelude/settings/struct.Flags.html#method.enable_verifier
West said:
bjorn3 said:
The verifier has to be explicitly enabled.
Not what the docs say by the way. https://docs.rs/cranelift/latest/cranelift/prelude/settings/struct.Flags.html#method.enable_verifier
West said:
Ok, I enabled the verifier by flag.
If I comment one of the calls toswitch_to_block
, then I still get a crash.thread '<unnamed>' panicked at /home/main/.cargo/registry/src/index.crates.io-6f17d22bba150 01f/cranelift-entity-0.108.1/src/packed_option.rs:64:23: Please call switch_to_block before inserting instructions note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace fatal runtime error: Rust cannot catch foreign exceptions
It is possible that my abstraction can be such that I don't have to worry about these sorts of panics, but I don't know.
Would be nice if the type system enforced how and when these functions must be called.
Oh, I thought you were having an issue with Cranelift panicking when compiling. This is a panic while building the clif ir in the first place. The builder api is kind of expected to panic when you misuse it. The only alternative would be to return a Result
from pretty much every method, which would both be non-ergonomic and make it much easier to accidentally ignore errors.
Ok, so basically I need to make a wrapper that avoids panicking cranelift.
Alright, then. I'll give it a shot.
West has marked this topic as resolved.
Last updated: Dec 23 2024 at 12:05 UTC