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

Implement stuff required to elide instruction loading in yklua. #1642

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
4 changes: 3 additions & 1 deletion ykcapi/yk.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ void yk_location_drop(YkLocation);
int: __yk_promote_c_int, \
unsigned int: __yk_promote_c_unsigned_int, \
long long: __yk_promote_c_long_long, \
uintptr_t: __yk_promote_usize \
uintptr_t: __yk_promote_usize, \
void *: __yk_promote_ptr \
)(X)
int __yk_promote_c_int(int);
unsigned int __yk_promote_c_unsigned_int(unsigned int);
long long __yk_promote_c_long_long(long long);
// Rust defines `usize` to be layout compatible with `uintptr_t`.
uintptr_t __yk_promote_usize(uintptr_t);
void *__yk_promote_ptr(void *);

/// Associate a UTF-8 compatible string to the next instruction to be traced.
/// The string will be copied by this function, so callers can safely reuse the
Expand Down
35 changes: 28 additions & 7 deletions ykrt/src/compile/jitc_yk/codegen/x64/lsregalloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,18 @@ impl LSRegAlloc<'_> {
self.spills[usize::from(iidx)] = SpillState::Stack(frame_off);
}

/// Forcibly assign a constant to an instruction. This typically only happens when traces pass
/// live variables that have been optimised to constants into side-traces.
pub(crate) fn assign_const(&mut self, iidx: InstIdx, bits: u32, v: u64) {
/// Forcibly assign a constant integer to an instruction. This typically only happens when
/// traces pass live variables that have been optimised to constants into side-traces.
pub(crate) fn assign_const_int(&mut self, iidx: InstIdx, bits: u32, v: u64) {
self.spills[usize::from(iidx)] = SpillState::ConstInt { bits, v };
}

/// Forcibly assign a constant pointer to an instruction. This typically only happens when
/// traces pass live variables that have been optimised to constants into side-traces.
pub(crate) fn assign_const_ptr(&mut self, iidx: InstIdx, v: usize) {
self.spills[usize::from(iidx)] = SpillState::ConstPtr(v);
}

/// Return a currently unused general purpose register, if one exists.
///
/// Note: you must be *very* careful in when you use the register that's returned.
Expand Down Expand Up @@ -710,7 +716,7 @@ impl LSRegAlloc<'_> {
}
}
},
SpillState::ConstInt { .. } => {
SpillState::ConstInt { .. } | SpillState::ConstPtr(_) => {
// Should we encounter multiple constants in registers
// (which isn't very likely...), we want to spill the one
// in the lowest register, since that's more likely to be
Expand Down Expand Up @@ -1143,6 +1149,15 @@ impl LSRegAlloc<'_> {
}
_ => todo!("{bits}"),
},
SpillState::ConstPtr(v) => {
// unwrap cannot fail since pointers are sized.
let bitw = self.m.type_(self.m.ptr_tyidx()).bitw().unwrap();

match bitw {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an assert masquerading as a todo I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just the result of copying code from elsewhere :) Will fix.

64 => dynasm!(asm; mov Rq(reg.code()), QWORD v as i64),
_ => todo!("{bitw}"),
}
}
}
self.gp_regset.set(reg);
self.gp_reg_states[usize::from(reg.code())] =
Expand Down Expand Up @@ -1220,6 +1235,7 @@ impl LSRegAlloc<'_> {
size,
},
SpillState::ConstInt { bits, v } => VarLocation::ConstInt { bits, v },
SpillState::ConstPtr(v) => VarLocation::ConstPtr(v),
},
}
}
Expand Down Expand Up @@ -1636,8 +1652,8 @@ impl LSRegAlloc<'_> {
self.fp_regset.set(reg);
}
SpillState::Direct(_off) => todo!(),
SpillState::ConstInt { bits: _bits, v: _v } => {
todo!()
SpillState::ConstInt { .. } | SpillState::ConstPtr(_) => {
panic!(); // would indicate some kind of type confusion.
}
}
}
Expand Down Expand Up @@ -2056,7 +2072,12 @@ enum SpillState {
/// Note: two SSA variables can alias to the same `Direct` location.
Direct(i32),
/// This variable is a constant.
ConstInt { bits: u32, v: u64 },
ConstInt {
bits: u32,
v: u64,
},
// This variable is a constant pointer.
ConstPtr(usize),
}

#[cfg(test)]
Expand Down
11 changes: 8 additions & 3 deletions ykrt/src/compile/jitc_yk/codegen/x64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ impl From<&VarLocation> for yksmp::Location {
todo!(">32 bit constant")
}
}
VarLocation::ConstPtr(v) => yksmp::Location::LargeConstant(u64::try_from(*v).unwrap()),
e => todo!("{:?}", e),
}
}
Expand Down Expand Up @@ -1271,7 +1272,7 @@ impl<'a> Assemble<'a> {
.force_assign_inst_indirect(iidx, i32::try_from(frame_off).unwrap());
}
VarLocation::ConstInt { bits, v } => {
self.ra.assign_const(iidx, bits, v);
self.ra.assign_const_int(iidx, bits, v);
}
e => panic!("{:?}", e),
}
Expand Down Expand Up @@ -2465,7 +2466,7 @@ impl<'a> Assemble<'a> {
// match. But since the value can't change we can safely ignore this.
}
VarLocation::Register(_reg) => (), // Handled in the earlier loop
_ => todo!(),
_ => todo!("{dst:?}"),
}
}

Expand Down Expand Up @@ -2609,7 +2610,11 @@ impl<'a> Assemble<'a> {
);
}
VarLocation::ConstInt { bits, v } => {
self.ra.assign_const(iidx, bits, v);
self.ra.assign_const_int(iidx, bits, v);
}
VarLocation::ConstPtr(v) => {
// unwrap can't fail because pointers are sized.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no unwrap here.

self.ra.assign_const_ptr(iidx, v);
}
e => panic!("{:?}", e),
}
Expand Down
9 changes: 7 additions & 2 deletions ykrt/src/compile/jitc_yk/opt/heapvalues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//!
//! Broadly speaking, loads add new information; stores tend to remove most old information and add
//! new information; and barriers remove all information.
use super::super::jit_ir::{Inst, InstIdx, Module, Operand};
use super::super::jit_ir::{Const, Inst, InstIdx, Module, Operand};
use std::collections::HashMap;

/// An abstract "address" representing a location in RAM.
Expand Down Expand Up @@ -40,7 +40,12 @@ impl Address {
}
Address::PtrPlusOff(iidx, off)
}
Operand::Const(_) => todo!(),
Operand::Const(cidx) => {
let Const::Ptr(v) = m.const_(cidx) else {
panic!();
};
Address::Const(*v)
}
}
}
}
Expand Down
40 changes: 39 additions & 1 deletion ykrt/src/compile/jitc_yk/opt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,20 @@ impl Opt {
loop {
off += pa_inst.off();
match self.an.op_map(&self.m, pa_inst.ptr(&self.m)) {
Operand::Const(_) => todo!(),
Operand::Const(cidx) => {
let Const::Ptr(cptr) = self.m.const_(cidx) else {
panic!();
};
// Important details:
// - offsets can be negative
// - LLVM semantics require the offset to be sign extended or truncated to
// the "pointer index type" before computing the offset.
let cidx = self
.m
.insert_const(Const::Ptr((*cptr as isize + off as isize) as usize))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We avoid as if at all possible. These should probably be isize::try_from(...).unwrap() (etc.) and you will need to decide if you want saturating_add or wrapped_add etc. to deal with poison cases. + will bork in debug mode when it detects overflow, which is not what we want, particularly as these situations do happen because select later gets rid of the poisoned value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By as casting the offset to isize we truncate or sign extend, as is (I believe) required by LLVM.

This section of the llvm manual:

The indices are first converted to offsets in the pointer’s index type. If the currently indexed type is a struct type, the struct offset corresponding to the index is sign-extended or truncated to the pointer index type. Otherwise, the index itself is sign-extended or truncated, and then multiplied by the type allocation size (that is, the size rounded up to the ABI alignment) of the currently indexed type.

I must admit, I find this part of the documentation confusing. I hope I've interpreted it correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with as is that if you change the types later, it still "works" but does weird things. It's better to use the full type names, as we do elsewhere. Only if you really have to should you use as (regrettably sometimes you do have to; I don't think this is one of those cases, but I might be wrong).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can split this into two cases: truncate and sign-extend.

todo! the former, since I don't anticipate seeing that any time soon. isize::try_from() the latter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, in terms of the + stuff, the BinOp::Add optimisation probably serves as a useful template for this.

self.m.replace(iidx, Inst::Const(cidx));
break;
}
Operand::Var(op_iidx) => {
if let Inst::PtrAdd(x) = self.m.inst(op_iidx) {
pa_inst = x;
Expand Down Expand Up @@ -861,6 +874,7 @@ impl Opt {
debug_assert_eq!(lhs.bitw(), rhs.bitw());
lhs == rhs
}
(Const::Ptr(lhs), Const::Ptr(rhs)) => lhs == rhs,
x => todo!("{x:?}"),
},
},
Expand Down Expand Up @@ -1762,6 +1776,12 @@ mod test {

#[test]
fn opt_ptradd() {
// FIXME: The offset operand of ptr_add should allow specifying a type (e.g. `ptr_add %0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be fixed in this PR. We need to make the optimiser tests as complete as possible, because reasoning about it is already beyond my pea brain.

// 8i8`) so that we can test the different offset types that can arise. Requires some
// parser hacking.
//
// FIXME: test two's compliment overflow/underflow of a ptr_add, which according to LLVM
// semantics is permitted. Probably should come after fixing the above FIXME.
Module::assert_ir_transform_eq(
"
entry:
Expand Down Expand Up @@ -1795,6 +1815,24 @@ mod test {
black_box %0
",
);

// constant pointer optimisations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to test all the edge cases too e.g. adding to the max value etc.

Module::assert_ir_transform_eq(
"
entry:
%0: ptr = ptr_add 0x0, 0
%1: ptr = ptr_add 0x6, 10
black_box %0
black_box %1
",
|m| opt(m).unwrap(),
"
...
entry:
black_box 0x0
black_box 0x10
",
);
}

#[test]
Expand Down
12 changes: 11 additions & 1 deletion ykrt/src/compile/jitc_yk/trace_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,17 @@ impl<Register: Send + Sync + 'static> TraceBuilder<Register> {
self.promote_idx += bytew;
Const::Int(tyidx, ArbBitInt::from_u64(*bitw, v))
}
jit_ir::Ty::Ptr => todo!(),
jit_ir::Ty::Ptr => {
let bytew = ty.byte_size().unwrap();
assert_eq!(bytew, std::mem::size_of::<usize>());
let v = usize::from_ne_bytes(
self.promotions[self.promote_idx..self.promote_idx + bytew]
.try_into()
.unwrap(),
);
self.promote_idx += bytew;
Const::Ptr(v)
}
jit_ir::Ty::Func(_) => todo!(),
jit_ir::Ty::Float(_) => todo!(),
jit_ir::Ty::Unimplemented(_) => todo!(),
Expand Down
12 changes: 11 additions & 1 deletion ykrt/src/promote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! right method in this module to call.

use crate::mt::MTThread;
use std::ffi::{c_int, c_longlong, c_uint};
use std::ffi::{c_int, c_longlong, c_uint, c_void};

/// Promote a `c_int` during trace recording.
#[no_mangle]
Expand Down Expand Up @@ -49,6 +49,16 @@ pub extern "C" fn __yk_promote_usize(val: usize) -> usize {
val
}

/// Promote a pointer during trace recording.
#[no_mangle]
pub extern "C" fn __yk_promote_ptr(val: *const c_void) -> *const c_void {
MTThread::with_borrow_mut(|mtt| {
// We ignore the return value as we can't really cancel tracing from this function.
mtt.promote_usize(val as usize);
});
val
}

/// Records the return value of an idempotent function during trace recording.
#[no_mangle]
pub extern "C" fn __yk_idempotent_promote_usize(val: usize) -> usize {
Expand Down