Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cranelift: return programmatic error rather than panic when temporaries run out. #7114

Merged
merged 4 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion cranelift/codegen/src/machinst/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::machinst::{
SigSet, VCode, VCodeBuilder, VCodeConstant, VCodeConstantData, VCodeConstants, VCodeInst,
ValueRegs, Writable,
};
use crate::CodegenError;
use crate::{trace, CodegenResult};
use alloc::vec::Vec;
use cranelift_control::ControlPlane;
Expand Down Expand Up @@ -163,6 +164,13 @@ pub struct Lower<'func, I: VCodeInst> {
/// VReg allocation context, given to the vcode field at build time to finalize the vcode.
vregs: VRegAllocator<I>,

/// A deferred error from vreg allocation, to be bubbled up to the
/// top level of the lowering algorithm. We take this approach
/// because we cannot currently propagate a `Result` upward
/// through ISLE code (the lowering rules), and temp allocations
/// typically happen as a result of particular lowering rules.
vreg_alloc_error: Option<CodegenError>,

/// Mapping from `Value` (SSA value in IR) to virtual register.
value_regs: SecondaryMap<Value, ValueRegs<Reg>>,

Expand Down Expand Up @@ -437,6 +445,7 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
allocatable: PRegSet::from(machine_env),
vcode,
vregs,
vreg_alloc_error: None,
value_regs,
sret_reg,
block_end_colors,
Expand Down Expand Up @@ -1077,6 +1086,12 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
self.finish_bb();
}

// Check for any deferred vreg-temp allocation errors, and
// bubble one up at this time if it exists.
if let Some(e) = self.vreg_alloc_error {
return Err(e);
}
cfallin marked this conversation as resolved.
Show resolved Hide resolved

// Now that we've emitted all instructions into the
// VCodeBuilder, let's build the VCode.
let vcode = self.vcode.build(self.allocatable, self.vregs);
Expand Down Expand Up @@ -1325,7 +1340,14 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
impl<'func, I: VCodeInst> Lower<'func, I> {
/// Get a new temp.
pub fn alloc_tmp(&mut self, ty: Type) -> ValueRegs<Writable<Reg>> {
writable_value_regs(self.vregs.alloc(ty).unwrap())
let allocated = match self.vregs.alloc(ty) {
Ok(x) => x,
Err(e) => {
self.vreg_alloc_error = Some(e);
self.vregs.invalid(ty)
}
};
writable_value_regs(allocated)
fitzgen marked this conversation as resolved.
Show resolved Hide resolved
}

/// Emit a machine instruction.
Expand Down
12 changes: 12 additions & 0 deletions cranelift/codegen/src/machinst/vcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,18 @@ impl<I: VCodeInst> VRegAllocator<I> {
Ok(regs)
}

/// Produce an bogus VReg placeholder with the proper number of
/// registers for the given type. This is meant to be used with
/// deferred allocation errors (see `Lower::alloc_tmp()`).
pub fn invalid(&self, ty: Type) -> ValueRegs<Reg> {
cfallin marked this conversation as resolved.
Show resolved Hide resolved
let (regclasses, _tys) = I::rc_for_type(ty).expect("must have valid type");
match regclasses {
&[rc0] => ValueRegs::one(VReg::new(0, rc0).into()),
&[rc0, rc1] => ValueRegs::two(VReg::new(0, rc0).into(), VReg::new(1, rc1).into()),
_ => panic!("Value must reside in 1 or 2 registers"),
}
}

/// Set the type of this virtual register.
pub fn set_vreg_type(&mut self, vreg: VirtualReg, ty: Type) {
if self.vreg_types.len() <= vreg.index() {
Expand Down
36 changes: 36 additions & 0 deletions tests/all/code_too_large.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![cfg(not(miri))]

use anyhow::Result;
use wasmtime::*;

#[test]
fn code_too_large_without_panic() -> Result<()> {
const N: usize = 120000;

// Build a module with a function whose body will allocate too many
// temporaries for our current (Cranelift-based) compiler backend to
// handle. This test ensures that we propagate the failure upward
// and return it programmatically, rather than panic'ing. If we ever
// improve our compiler backend to actually handle such a large
// function body, we'll need to increase the limits here too!
let mut s = String::new();
s.push_str("(module\n");
s.push_str("(table 1 1 funcref)\n");
s.push_str("(func (export \"\") (result i32)\n");
s.push_str("i32.const 0\n");
for _ in 0..N {
s.push_str("table.get 0\n");
s.push_str("ref.is_null\n");
}
s.push_str("))\n");

let store = Store::<()>::default();
let result = Module::new(store.engine(), &s);
match result {
Err(e) => assert!(e
.to_string()
.starts_with("Compilation error: Code for function is too large")),
Ok(_) => panic!("Please adjust limits to make the module too large to compile!"),
}
Ok(())
}
1 change: 1 addition & 0 deletions tests/all/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod async_functions;
mod call_hook;
mod cli_tests;
mod code_too_large;
mod component_model;
mod coredump;
mod custom_signal_handler;
Expand Down