Stream: cranelift

Topic: ✔ Recover from errors/panics


view this post on Zulip West (Jun 18 2024 at 05:17):

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?

view this post on Zulip bjorn3 (Jun 18 2024 at 07:50):

You can use catch_unwind, but cranelift shouldn't panic unless you passed in ir which doesn't pass the verifier.

view this post on Zulip West (Jun 18 2024 at 07:51):

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!

view this post on Zulip bjorn3 (Jun 18 2024 at 09:01):

Can you run the verifier before trying to compile it and skip compiling if the verifier reports an error?

view this post on Zulip West (Jun 18 2024 at 09:27):

I didn't even know about any sort of verifier. Lemme check it out.

view this post on Zulip West (Jun 18 2024 at 09:36):

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_(&params);
            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(),
        }
    )
}

view this post on Zulip West (Jun 18 2024 at 09:37):

As far as I understand the verifier is always enabled, so that wouldn't help with panics if build a function incorrectly.

view this post on Zulip bjorn3 (Jun 18 2024 at 11:30):

The verifier has to be explicitly enabled.

view this post on Zulip bjorn3 (Jun 18 2024 at 11:31):

You can either call cranelift_codegen::verifier::verify_function manually or set enable_verifier on the flag_builder.

view this post on Zulip bjorn3 (Jun 18 2024 at 11:32):

The latter will also run the verifier in between optimization passes, which wouldn't be strictly necessary here.

view this post on Zulip West (Jun 18 2024 at 12:56):

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.

view this post on Zulip West (Jun 18 2024 at 13:08):

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

view this post on Zulip bjorn3 (Jun 18 2024 at 15:54):

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

You're right: https://github.com/bytecodealliance/wasmtime/blob/3171ef6df3165c40bd2a2b60d2e9de248581689e/cranelift/codegen/meta/src/shared/settings.rs#L63

A fast and secure runtime for WebAssembly. Contribute to bytecodealliance/wasmtime development by creating an account on GitHub.

view this post on Zulip bjorn3 (Jun 18 2024 at 15:58):

West said:

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.

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.

view this post on Zulip West (Jun 19 2024 at 11:07):

Ok, so basically I need to make a wrapper that avoids panicking cranelift.
Alright, then. I'll give it a shot.

view this post on Zulip Notification Bot (Jun 19 2024 at 11:07):

West has marked this topic as resolved.


Last updated: Dec 23 2024 at 12:05 UTC