Skip to content

Commit

Permalink
PCC: support x86-64. (#7352)
Browse files Browse the repository at this point in the history
* PCC: support x86-64.

This PR extends the proof-carrying-code infrastructure to support x86-64
as well as aarch64. In the process, many of the mechanisms had to be
made a little more general.

One important change is that the PCC leaves more "breadcrumbs" on the
frontend now, avoiding the need for magic handling of facts on constant
values, etc., in the backend. For the first time a lowering rule also
gains the ability to add a fact to a vreg to preserve the chain as well.

With these changes, we can validate compilation of SpiderMonkey.wasm
with Wasm static memories on x86-64 and aarch64:

```
cfallin@fastly2:~/work/wasmtime% target/release/wasmtime compile -C pcc=yes --target x86_64 ../wasm-tests/spidermonkey.wasm
cfallin@fastly2:~/work/wasmtime% target/release/wasmtime compile -C pcc=yes --target aarch64 ../wasm-tests/spidermonkey.wasm
cfallin@fastly2:~/work/wasmtime%
```

* Don't run regalloc checker if not requested in addition to PCC; it's fairly expensive.

* Refactor x64 PCC code to avoid deep pattern matches on Gpr/Xmm types; explicitly match every instruction kind.
  • Loading branch information
cfallin authored Oct 26, 2023
1 parent 0d797f7 commit f262c31
Show file tree
Hide file tree
Showing 37 changed files with 1,353 additions and 245 deletions.
133 changes: 104 additions & 29 deletions cranelift/codegen/src/ir/pcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,26 @@ impl Fact {
}
}

/// Create a range fact that specifies the maximum range for a
/// value of the given bit-width, zero-extended into a wider
/// width.
pub const fn max_range_for_width_extended(from_width: u16, to_width: u16) -> Self {
debug_assert!(from_width <= to_width);
match from_width {
from_width if from_width < 64 => Fact::Range {
bit_width: to_width,
min: 0,
max: (1u64 << from_width) - 1,
},
64 => Fact::Range {
bit_width: to_width,
min: 0,
max: u64::MAX,
},
_ => panic!("bit width too large!"),
}
}

/// Try to infer a minimal fact for a value of the given IR type.
pub fn infer_from_type(ty: ir::Type) -> Option<&'static Self> {
static FACTS: [Fact; 4] = [
Expand Down Expand Up @@ -362,13 +382,14 @@ impl<'a> FactContext<'a> {
},
) => {
// If the bitwidths we're claiming facts about are the
// same, and if the right-hand-side range is larger
// than the left-hand-side range, than the LHS
// same, or the left-hand-side makes a claim about a
// wider bitwidth, and if the right-hand-side range is
// larger than the left-hand-side range, than the LHS
// subsumes the RHS.
//
// In other words, we can always expand the claimed
// possible value range.
bw_lhs == bw_rhs && max_lhs <= max_rhs && min_lhs >= min_rhs
bw_lhs >= bw_rhs && max_lhs <= max_rhs && min_lhs >= min_rhs
}

(
Expand Down Expand Up @@ -472,28 +493,38 @@ impl<'a> FactContext<'a> {

/// Computes the `uextend` of a value with the given facts.
pub fn uextend(&self, fact: &Fact, from_width: u16, to_width: u16) -> Option<Fact> {
trace!(
"uextend: fact {:?} from {} to {}",
fact,
from_width,
to_width
);
if from_width == to_width {
return Some(fact.clone());
}

match fact {
// If we have a defined value in bits 0..bit_width, and we
// are filling zeroes into from_bits..to_bits, and
// bit_width and from_bits are exactly contiguous, then we
// have defined values in 0..to_bits (and because this is
// a zero-extend, the max value is the same).
// If the claim is already for a same-or-wider value and the min
// and max are within range of the narrower value, we can
// claim the same range.
Fact::Range {
bit_width,
min,
max,
} if *bit_width == from_width => Some(Fact::Range {
bit_width: to_width,
min: *min,
max: *max,
}),
} if *bit_width >= from_width
&& *min <= max_value_for_width(from_width)
&& *max <= max_value_for_width(from_width) =>
{
Some(Fact::Range {
bit_width: to_width,
min: *min,
max: *max,
})
}
// Otherwise, we can at least claim that the value is
// within the range of `to_width`.
Fact::Range { .. } => Some(Fact::Range {
bit_width: to_width,
min: 0,
max: max_value_for_width(to_width),
}),
// within the range of `from_width`.
Fact::Range { .. } => Some(Fact::max_range_for_width_extended(from_width, to_width)),

_ => None,
}
}
Expand All @@ -517,6 +548,44 @@ impl<'a> FactContext<'a> {
}
}

/// Computes the bit-truncation of a value with the given fact.
pub fn truncate(&self, fact: &Fact, from_width: u16, to_width: u16) -> Option<Fact> {
if from_width == to_width {
return Some(fact.clone());
}

trace!(
"truncate: fact {:?} from {} to {}",
fact,
from_width,
to_width
);

match fact {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == from_width => {
let max_val = (1u64 << to_width) - 1;
if *min <= max_val && *max <= max_val {
Some(Fact::Range {
bit_width: to_width,
min: *min,
max: *max,
})
} else {
Some(Fact::Range {
bit_width: to_width,
min: 0,
max: max_val,
})
}
}
_ => None,
}
}

/// Scales a value with a fact by a known constant.
pub fn scale(&self, fact: &Fact, width: u16, factor: u32) -> Option<Fact> {
match fact {
Expand Down Expand Up @@ -551,26 +620,29 @@ impl<'a> FactContext<'a> {

/// Offsets a value with a fact by a known amount.
pub fn offset(&self, fact: &Fact, width: u16, offset: i64) -> Option<Fact> {
// Any negative offset could underflow, and removes
// all claims of constrained range, so for now we only
// support positive offsets.
let offset = u64::try_from(offset).ok()?;

trace!(
"FactContext::offset: {:?} + {} in width {}",
fact,
offset,
width
);

let compute_offset = |base: u64| -> Option<u64> {
if offset >= 0 {
base.checked_add(u64::try_from(offset).unwrap())
} else {
base.checked_sub(u64::try_from(-offset).unwrap())
}
};

match fact {
Fact::Range {
bit_width,
min,
max,
} if *bit_width == width => {
let min = min.checked_add(offset)?;
let max = max.checked_add(offset)?;
let min = compute_offset(*min)?;
let max = compute_offset(*max)?;

Some(Fact::Range {
bit_width: *bit_width,
Expand All @@ -583,8 +655,8 @@ impl<'a> FactContext<'a> {
min_offset: mem_min_offset,
max_offset: mem_max_offset,
} => {
let min_offset = mem_min_offset.checked_add(offset)?;
let max_offset = mem_max_offset.checked_add(offset)?;
let min_offset = compute_offset(*mem_min_offset)?;
let max_offset = compute_offset(*mem_max_offset)?;
Some(Fact::Mem {
ty: *ty,
min_offset,
Expand Down Expand Up @@ -709,7 +781,10 @@ pub fn check_vcode_facts<B: LowerBackend + TargetIsa>(
let block = BlockIndex::new(block);
for inst in vcode.block_insns(block).iter() {
// Check any output facts on this inst.
backend.check_fact(&ctx, vcode, inst)?;
if let Err(e) = backend.check_fact(&ctx, vcode, inst) {
log::error!("Error checking instruction: {:?}", vcode[inst]);
return Err(e);
}

// If this is a branch, check that all block arguments subsume
// the assumed facts on the blockparams of successors.
Expand Down
4 changes: 2 additions & 2 deletions cranelift/codegen/src/isa/aarch64/inst/imms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ impl Imm12 {
}

/// Get the actual value that this immediate corresponds to.
pub fn value(&self) -> u64 {
let base = self.bits as u64;
pub fn value(&self) -> u32 {
let base = self.bits as u32;
if self.shift12 {
base << 12
} else {
Expand Down
Loading

0 comments on commit f262c31

Please sign in to comment.