Skip to content

Commit c3c2ee2

Browse files
authored
Cranelift: preserve_all: disallow return values. (#12447)
In #12399 a fuzzbug uncovered an issue whereby a function with `preserve_all` and with many return values cannot be called, because `emit_retval_loads` cannot codegen memory-to-memory moves (from on-stack return value slots directly to spillslots) without a temporary/clobberable register, and `preserve_all` implies no such registers exist. I thought about trying to support this by shuffling registers -- such a case implies many return values, and at least some of them will be in registers, so we might be able to temporarily spill one of those and use it as a scratch to move other values memory-to-memory. But the complexity doesn't seem worthwhile, and this path is not actually needed at the moment. Thinking more broadly, anyone using `preserve_all` for hooks meant to minimally perturb register state will likely not want to use many return values anyway (that defeats the point). We could allow *one* return value, with the knowledge that this always fits in a register in our existing ABIs, but that also feels somewhat arbitrary, and I could make the argument that "preserve all" should really mean preserve *all*. Someone wanting to return a value anyway from such a hook could use indirect means (pass in a pointer to a stackslot, for example). I'm happy to tweak this limit if others have more thoughts, however. Fixes #12399.
1 parent ab1ab70 commit c3c2ee2

4 files changed

Lines changed: 72 additions & 4 deletions

File tree

cranelift/codegen/src/isa/call_conv.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ pub enum CallConv {
5353
/// as little as possible.
5454
///
5555
/// The ABI is based on the native register-argument ABI on each
56-
/// respective platform. It does not support tail-calls.
56+
/// respective platform. It does not support tail-calls. It also
57+
/// does not support return values.
5758
PreserveAll,
5859
}
5960

cranelift/codegen/src/machinst/abi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2500,7 +2500,7 @@ impl<T> CallInfo<T> {
25002500
// The temporary must be noted as clobbered unless there are
25012501
// no returns (hence it isn't needed). The latter can only be
25022502
// the case statically for an ABI when the ABI doesn't allow
2503-
// any returns at all (e.g., patchable-call ABI).
2503+
// any returns at all (e.g., preserve-all ABI).
25042504
debug_assert!(
25052505
self.defs.is_empty()
25062506
|| M::get_regs_clobbered_by_call(self.callee_conv, self.try_call_info.is_some())

cranelift/codegen/src/verifier/mod.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ use crate::dbg::DisplayList;
6767
use crate::dominator_tree::DominatorTree;
6868
use crate::entity::SparseSet;
6969
use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
70-
use crate::ir::ExceptionTableItem;
7170
use crate::ir::entities::AnyEntity;
7271
use crate::ir::instructions::{CallInfo, InstructionFormat, ResolvedConstraint};
7372
use crate::ir::{self, ArgumentExtension, BlockArg, ExceptionTable};
@@ -76,14 +75,16 @@ use crate::ir::{
7675
JumpTable, MemFlags, MemoryTypeData, Opcode, SigRef, StackSlot, Type, Value, ValueDef,
7776
ValueList, types,
7877
};
79-
use crate::isa::TargetIsa;
78+
use crate::ir::{ExceptionTableItem, Signature};
79+
use crate::isa::{CallConv, TargetIsa};
8080
use crate::print_errors::pretty_verifier_error;
8181
use crate::settings::FlagsOrIsa;
8282
use crate::timing;
8383
use alloc::collections::BTreeSet;
8484
use alloc::string::{String, ToString};
8585
use alloc::vec::Vec;
8686
use core::fmt::{self, Display, Formatter};
87+
use cranelift_entity::packed_option::ReservedValue;
8788

8889
/// A verifier error.
8990
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -2082,12 +2083,56 @@ impl<'a> Verifier<'a> {
20822083
Ok(())
20832084
}
20842085

2086+
fn verify_signature(
2087+
&self,
2088+
sig: &Signature,
2089+
entity: impl Into<AnyEntity>,
2090+
errors: &mut VerifierErrors,
2091+
) -> VerifierStepResult {
2092+
match sig.call_conv {
2093+
CallConv::PreserveAll => {
2094+
if !sig.returns.is_empty() {
2095+
errors.fatal((
2096+
entity,
2097+
"Signature with `preserve_all` ABI cannot have return values".to_string(),
2098+
))?;
2099+
}
2100+
}
2101+
_ => {}
2102+
}
2103+
Ok(())
2104+
}
2105+
2106+
fn verify_signatures(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
2107+
// Verify this function's own signature.
2108+
self.verify_signature(&self.func.signature, AnyEntity::Function, errors)?;
2109+
// Verify signatures referenced by any extfunc, using that
2110+
// extfunc as the entity to which to attach the error.
2111+
for (func, funcdata) in &self.func.dfg.ext_funcs {
2112+
// Non-contiguous func entities result in placeholders
2113+
// with invalid signatures; skip them.
2114+
if !funcdata.signature.is_reserved_value() {
2115+
self.verify_signature(&self.func.dfg.signatures[funcdata.signature], func, errors)?;
2116+
}
2117+
}
2118+
// Verify all signatures, including those only used by
2119+
// e.g. indirect calls. Technically this re-verifies
2120+
// signatures verified above but we want the first pass to
2121+
// attach errors to funcrefs and we also need to verify all
2122+
// defined signatures.
2123+
for (sig, sigdata) in &self.func.dfg.signatures {
2124+
self.verify_signature(sigdata, sig, errors)?;
2125+
}
2126+
Ok(())
2127+
}
2128+
20852129
pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
20862130
self.verify_global_values(errors)?;
20872131
self.verify_memory_types(errors)?;
20882132
self.typecheck_entry_block_params(errors)?;
20892133
self.check_entry_not_cold(errors)?;
20902134
self.typecheck_function_signature(errors)?;
2135+
self.verify_signatures(errors)?;
20912136

20922137
for block in self.func.layout.blocks() {
20932138
if self.func.layout.first_inst(block).is_none() {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
test verifier
2+
3+
function %f0(i8) -> i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8 preserve_all { ; error: Signature with `preserve_all` ABI cannot have return values
4+
block0(v0: i8):
5+
return v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0, v0
6+
}
7+
8+
function %f1(i64) {
9+
sig0 = () -> i8, i8, i8, i8 preserve_all ; error: Signature with `preserve_all` ABI cannot have return values
10+
11+
block0(v0: i64):
12+
v1, v2, v3, v4 = call_indirect sig0, v0()
13+
return
14+
}
15+
16+
function %f2() {
17+
fn0 = %g() -> i8, i8, i8, i8 preserve_all ; error: Signature with `preserve_all` ABI cannot have return values
18+
19+
block0():
20+
v1, v2, v3, v4 = call fn0()
21+
return
22+
}

0 commit comments

Comments
 (0)