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

Dead load store analysis / optimisation #1535

Merged
merged 2 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion ykrt/src/compile/jitc_yk/codegen/x64/rev_analyse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ impl<'a> RevAnalyse<'a> {

fn analyse(&mut self, iidx: InstIdx, inst: Inst) {
if self.used_insts.get(usize::from(iidx)).unwrap()
|| inst.is_internal_inst()
|| inst.is_guard()
|| inst.has_store_effect(self.m)
|| inst.is_barrier(self.m)
{
self.used_insts.set(usize::from(iidx), true);

Expand Down
3 changes: 2 additions & 1 deletion ykrt/src/compile/jitc_yk/jit_ir/dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ impl Module {
let mut tombstone = Vob::from_elem(false, usize::from(self.last_inst_idx()) + 1);
for (iidx, inst) in self.iter_skipping_insts().rev() {
if used.get(usize::from(iidx)).unwrap()
|| inst.is_internal_inst()
|| inst.is_guard()
|| inst.has_store_effect(self)
|| inst.is_barrier(self)
ptersilie marked this conversation as resolved.
Show resolved Hide resolved
{
used.set(usize::from(iidx), true);
inst.map_operand_vars(self, &mut |x| {
Expand Down
2 changes: 1 addition & 1 deletion ykrt/src/compile/jitc_yk/jit_ir/jit_ir.l
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ double "DOUBLE_TYPE"
-?[0-9]+i[0-9]+ "CONST_INT"
-?[0-9]+(\.[0-9]+)?float "CONST_FLOAT"
-?[0-9]+(\.[0-9]+)?double "CONST_DOUBLE"
[0-9]+ "UINT"
-?[0-9]+ "INT"
0[xX][0-9a-fA-F]+ "CONST_PTR"
add "ADD"
and "AND"
Expand Down
6 changes: 3 additions & 3 deletions ykrt/src/compile/jitc_yk/jit_ir/jit_ir.y
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Inst -> Result<ASTInst, Box<dyn Error>>:
| "GUARD" "FALSE" "," Operand "," "[" OperandsList "]" {
Ok(ASTInst::Guard{cond: $4?, is_true: false, operands: $7?})
}
| "LOCAL_OPERAND" ":" Type "=" "PARAM" "UINT" {
| "LOCAL_OPERAND" ":" Type "=" "PARAM" "INT" {
Ok(ASTInst::Param{assign: $1?.span(), type_: $3?, tiidx: $6?.span()})
}
| "LOCAL_OPERAND" ":" Type "=" BinOp Operand "," Operand {
Expand Down Expand Up @@ -110,10 +110,10 @@ Inst -> Result<ASTInst, Box<dyn Error>>:
| "LOCAL_OPERAND" ":" Type "=" "LOAD" Operand "," "VOLATILE" {
Ok(ASTInst::Load{assign: $1?.span(), type_: $3?, val: $6?, volatile: true})
}
| "LOCAL_OPERAND" ":" Type "=" "PTR_ADD" Operand "," "UINT" {
| "LOCAL_OPERAND" ":" Type "=" "PTR_ADD" Operand "," "INT" {
Ok(ASTInst::PtrAdd{assign: $1?.span(), type_: $3?, ptr: $6?, off: $8?.span()})
}
| "LOCAL_OPERAND" ":" Type "=" "DYN_PTR_ADD" Operand "," Operand "," "UINT" {
| "LOCAL_OPERAND" ":" Type "=" "DYN_PTR_ADD" Operand "," Operand "," "INT" {
Ok(ASTInst::DynPtrAdd{assign: $1?.span(), type_: $3?, ptr: $6?, num_elems: $8?, elem_size: $10?.span()})
}
| "LOCAL_OPERAND" ":" Type "=" "SEXT" Operand {
Expand Down
52 changes: 33 additions & 19 deletions ykrt/src/compile/jitc_yk/jit_ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,11 @@ impl Module {
pub(crate) fn iter_skipping_insts(
&self,
) -> impl DoubleEndedIterator<Item = (InstIdx, Inst)> + '_ {
// The `unchecked_from` is safe because we know from `Self::push` that we can't have
// exceeded `InstIdx`'s bounds.
(0..self.insts.len()).filter_map(|i| {
let inst = &self.insts[i];
if !matches!(inst, Inst::Const(_) | Inst::Copy(_) | Inst::Tombstone) {
// The `unchecked_from` is safe because we know from `Self::push` that we can't have
// exceeded `InstIdx`'s bounds.
Some((InstIdx::unchecked_from(i), *inst))
} else {
None
Expand Down Expand Up @@ -1530,30 +1530,34 @@ impl Inst {
/// Does this instruction have a store side effect?
pub(crate) fn has_store_effect(&self, m: &Module) -> bool {
match self {
#[cfg(test)]
Inst::BlackBox(_) => true,
Inst::Copy(x) => m.inst_raw(*x).has_store_effect(m),
Inst::Store(_) => true,
Inst::Call(_) | Inst::IndirectCall(_) => false,
Inst::Call(_) | Inst::IndirectCall(_) => true,
_ => false,
}
}

/// Is this instruction a compiler barrier / fence? Our simple semantics are that barriers
/// cannot be reordered at all. Note that load/store effects are not considered
/// barriers.
pub(crate) fn is_barrier(&self, m: &Module) -> bool {
match self {
#[cfg(test)]
Inst::BlackBox(_) => true,
Inst::Copy(x) => m.inst_raw(*x).is_barrier(m),
Inst::Guard(_) => true,
Inst::Call(_) | Inst::IndirectCall(_) => true,
/// Is this instruction an "internal" trace instruction (e.g. `TraceHeaderStart` /
/// `TraceHeaderEnd`)? Such instructions must not be removed, and should be thought of as
/// similar to a barrier.
pub(crate) fn is_internal_inst(&self) -> bool {
matches!(
self,
Inst::TraceHeaderStart
| Inst::TraceHeaderEnd
| Inst::TraceBodyStart
| Inst::TraceBodyEnd
| Inst::SidetraceEnd => true,
_ => false,
}
| Inst::TraceHeaderEnd
| Inst::TraceBodyStart
| Inst::TraceBodyEnd
| Inst::SidetraceEnd
)
}

/// Is this instruction a guard?
///
/// This is a convenience function to allow an easy mirror of `is_internal_inst`.
pub(crate) fn is_guard(&self) -> bool {
matches!(self, Inst::Guard(_))
}

/// Apply the function `f` to each of this instruction's [Operand]s iff they are of type
Expand Down Expand Up @@ -2270,6 +2274,12 @@ impl LoadInst {
pub(crate) fn tyidx(&self) -> TyIdx {
self.tyidx
}

/// Is this a volatile load?
pub(crate) fn is_volatile(&self) -> bool {
// FIXME: We don't yet record this in AOT?!
false
}
}

/// The `Param` instruction.
Expand Down Expand Up @@ -2532,6 +2542,10 @@ impl StoreInst {
pub(crate) fn tgt(&self, m: &Module) -> Operand {
self.tgt.unpack(m)
}

pub(crate) fn is_volatile(&self) -> bool {
self.volatile
}
}

/// The operands for a [Inst::Select]
Expand Down
51 changes: 46 additions & 5 deletions ykrt/src/compile/jitc_yk/opt/analyse.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
//! Analyse a trace and gradually refine what values we know a previous instruction can produce.

use super::{
super::jit_ir::{GuardInst, Inst, InstIdx, Module, Operand, Predicate},
Value,
super::jit_ir::{ConstIdx, GuardInst, Inst, InstIdx, Module, Operand, Predicate},
heapvalues::{Address, HeapValues},
};
use std::cell::RefCell;

#[derive(Clone, Debug)]
pub(super) enum Value {
Unknown,
Const(ConstIdx),
}

impl Value {
fn to_operand(&self) -> Operand {
match self {
Value::Unknown => todo!(),
Value::Const(cidx) => Operand::Const(*cidx),
}
}
}

/// Ongoing analysis of a trace: what value can a given instruction in the past produce?
///
/// Note that the analysis is forward-looking: just because an instruction's `Value` is (say) a
Expand All @@ -16,6 +31,7 @@ pub(super) struct Analyse {
/// it allows [Analyse::op_map] to take `&self`: changing that to `&mut self` upsets a lot of
/// other parts of the system w.r.t. the borrow checker.
values: RefCell<Vec<Value>>,
heapvalues: RefCell<HeapValues>,
}

impl Analyse {
Expand All @@ -26,6 +42,7 @@ impl Analyse {
// don't copy over [Tombstone]s and [Copy]s it will be slightly less than that.
// FIXME: Can we calculate this more accurately?
values: RefCell::new(vec![Value::Unknown; m.insts_len() * 2]),
heapvalues: RefCell::new(HeapValues::new()),
}
}

Expand Down Expand Up @@ -60,8 +77,32 @@ impl Analyse {
}

/// Update our idea of what value the instruction at `iidx` can produce.
pub(super) fn set_value(&self, iidx: InstIdx, v: Value) {
self.values.borrow_mut()[usize::from(iidx)] = v;
pub(super) fn set_value(&self, m: &Module, iidx: InstIdx, v: Value) {
self.values.borrow_mut()[usize::from(iidx)] = v.clone();
if let Some(Inst::Load(linst)) = m.inst_nocopy(iidx) {
let addr = Address::from_operand(m, linst.operand(m));
self.heapvalues.borrow_mut().load(m, addr, v.to_operand());
}
}

/// What, if any, is the currently known value of `bytesize` bytes stored at `addr`?
pub(super) fn heapvalue(&self, m: &Module, addr: Address, bytesize: usize) -> Option<Operand> {
self.heapvalues.borrow_mut().get(m, addr, bytesize)
}

/// Associate the value derived from a load in `op` with the address `addr`.
pub(super) fn push_heap_load(&self, m: &Module, addr: Address, op: Operand) {
self.heapvalues.borrow_mut().load(m, addr, op);
}

/// Associate the value derived from a store in `op` with the address `addr`.
pub(super) fn push_heap_store(&self, m: &Module, addr: Address, op: Operand) {
self.heapvalues.borrow_mut().store(m, addr, op);
}

/// Clear all known heap values.
pub(super) fn heap_barrier(&self) {
self.heapvalues.borrow_mut().barrier();
}

/// Use the guard `inst` to update our knowledge about the variable used as its condition.
Expand All @@ -81,7 +122,7 @@ impl Analyse {
if (g_inst.expect && pred == Predicate::Equal)
|| (!g_inst.expect && pred == Predicate::NotEqual)
{
self.set_value(iidx, Value::Const(cidx));
self.set_value(m, iidx, Value::Const(cidx));
}
}
(&Operand::Var(_), &Operand::Var(_)) => (),
Expand Down
Loading
Loading