From 05b2f567f4d4449b1268f789e07c5b482f8d0880 Mon Sep 17 00:00:00 2001 From: Laurence Tratt Date: Sat, 14 Dec 2024 10:47:26 +0000 Subject: [PATCH] Detect and abort tracing if "early return" occurs. If we start tracing a loop and the function-with-a-control-point returns whilst tracing is ongoing, we are in dangerous territory. For quite a while we assumed that "dangerous territory" meant (gongs of doom!) "UB". Actually, we just hadn't created very good examples: when I did, it turns out that an `unwrap` in trace_builder fails if early return happens. We *could*, therefore, detect early returns there, but that means that we've done a lot of work before realising this. This commit instead has `mt.rs` detect early returns, and abort tracing, as soon as we reasonably can. [Note, however, that if a user annotates `return`s from the function, they could stop things even earlier. Is this worth it? It is quite complicated to explain and, as we'll see in a bit, it's not a very common case] All that said, it turns out early returns aren't that common: you have to start tracing on the same iteration that you "early return" from. This is definitely possible -- indeed the two C tests in the PR do that, one "normally" and one when side-tracing -- but it seems unlikely that it will happen very often. So although we could force the user to help us detect this even sooner, I'm not sure it's worth burdening them or us with that responsibility right now: the cases where it would save serious resources are borderline pathological. Indeed, the only such case I can think of is: if you start tracing from the "root" control point call frame, and then return to the main program (i.e. you will never go back to the interpreter), then tracing is never turned off. One day we can fix this, but I don't think we will encounter it very soon. --- tests/c/early_return1.c | 65 ++++++ tests/c/early_return2.c | 118 ++++++++++ ykrt/src/compile/jitc_yk/codegen/reg_alloc.rs | 6 - ykrt/src/compile/jitc_yk/codegen/x64/deopt.rs | 6 +- ykrt/src/compile/jitc_yk/codegen/x64/mod.rs | 5 +- ykrt/src/compile/jitc_yk/trace_builder.rs | 4 +- ykrt/src/lib.rs | 1 + ykrt/src/mt.rs | 215 +++++++++++++----- ykrt/src/stack.rs | 12 + 9 files changed, 367 insertions(+), 65 deletions(-) create mode 100644 tests/c/early_return1.c create mode 100644 tests/c/early_return2.c create mode 100644 ykrt/src/stack.rs diff --git a/tests/c/early_return1.c b/tests/c/early_return1.c new file mode 100644 index 000000000..2e7346ea4 --- /dev/null +++ b/tests/c/early_return1.c @@ -0,0 +1,65 @@ +// Run-time: +// env-var: YKD_SERIALISE_COMPILATION=1 +// env-var: YKD_LOG_IR=jit-pre-opt +// env-var: YK_LOG=4 +// stderr: +// yk-jit-event: start-tracing +// early return +// 5 +// yk-jit-event: tracing-aborted +// 4 +// yk-jit-event: start-tracing +// 3 +// yk-jit-event: stop-tracing +// --- Begin jit-pre-opt --- +// ... +// --- End jit-pre-opt --- +// 2 +// yk-jit-event: enter-jit-code +// 1 +// yk-jit-event: deoptimise +// return +// exit + +// Check that early return from a recursive interpreter loop aborts tracing, +// but doesn't stop a location being retraced. + +#include +#include +#include +#include +#include +#include + +void loop(YkMT *, YkLocation *, int); + +void loop(YkMT *mt, YkLocation *loc, int i) { + NOOPT_VAL(i); + while (i > 0) { + yk_mt_control_point(mt, loc); + if (i == 6) { + loop(mt, loc, i - 1); + i--; + } else if (i == 5) { + fprintf(stderr, "early return\n"); + return; + } + fprintf(stderr, "%d\n", i); + i--; + } + fprintf(stderr, "return\n"); + return; +} + +int main(int argc, char **argv) { + YkMT *mt = yk_mt_new(NULL); + yk_mt_hot_threshold_set(mt, 1); + YkLocation loc = yk_location_new(); + + NOOPT_VAL(loc); + loop(mt, &loc, 6); + fprintf(stderr, "exit\n"); + yk_location_drop(loc); + yk_mt_shutdown(mt); + return (EXIT_SUCCESS); +} diff --git a/tests/c/early_return2.c b/tests/c/early_return2.c new file mode 100644 index 000000000..3b9062cea --- /dev/null +++ b/tests/c/early_return2.c @@ -0,0 +1,118 @@ + +// Run-time: +// env-var: YKD_SERIALISE_COMPILATION=1 +// env-var: YKD_LOG_IR=jit-pre-opt +// env-var: YK_LOG=4 +// stderr: +// yk-jit-event: start-tracing +// b3 +// 8 +// yk-jit-event: stop-tracing +// --- Begin jit-pre-opt --- +// ... +// --- End jit-pre-opt --- +// b1 +// 7 +// yk-jit-event: enter-jit-code +// yk-jit-event: deoptimise +// yk-jit-event: start-side-tracing +// b1 +// 9 +// yk-jit-event: tracing-aborted +// b3 +// 8 +// yk-jit-event: enter-jit-code +// yk-jit-event: deoptimise +// yk-jit-event: start-side-tracing +// b3 +// 7 +// yk-jit-event: stop-tracing +// --- Begin jit-pre-opt --- +// ... +// --- End jit-pre-opt --- +// b3 +// 6 +// yk-jit-event: enter-jit-code +// yk-jit-event: execute-side-trace +// yk-jit-event: deoptimise +// yk-jit-event: start-side-tracing +// b2 +// 5 +// yk-jit-event: stop-tracing +// --- Begin jit-pre-opt --- +// ... +// --- End jit-pre-opt --- +// b2 +// 4 +// yk-jit-event: enter-jit-code +// yk-jit-event: execute-side-trace +// yk-jit-event: execute-side-trace +// b2 +// 3 +// yk-jit-event: execute-side-trace +// yk-jit-event: execute-side-trace +// b2 +// 2 +// yk-jit-event: execute-side-trace +// yk-jit-event: execute-side-trace +// b2 +// 1 +// yk-jit-event: execute-side-trace +// yk-jit-event: execute-side-trace +// b2 +// 0 +// yk-jit-event: deoptimise +// yk-jit-event: start-side-tracing +// exit + +// Check that early return from a recursive interpreter loop aborts tracing, +// but doesn't stop a location being retraced. + +#include +#include +#include +#include +#include +#include +#include + +void loop(YkMT *, YkLocation *, int, bool); + +void loop(YkMT *mt, YkLocation *loc, int i, bool inner) { + NOOPT_VAL(i); + while (i > 0) { + yk_mt_control_point(mt, loc); + if (i == 10) { + loop(mt, loc, i - 1, true); + i--; + } else if (inner && i <= 8) { + fprintf(stderr, "b1\n"); + i--; + } else if (!inner && i <= 6) { + fprintf(stderr, "b2\n"); + i--; + } else { + fprintf(stderr, "b3\n"); + i--; + } + if (inner && i == 6) { + return; + } + fprintf(stderr, "%d\n", i); + } + return; +} + +int main(int argc, char **argv) { + YkMT *mt = yk_mt_new(NULL); + yk_mt_hot_threshold_set(mt, 1); + yk_mt_sidetrace_threshold_set(mt, 1); + YkLocation loc = yk_location_new(); + + NOOPT_VAL(loc); + loop(mt, &loc, 10, false); + fprintf(stderr, "exit\n"); + yk_location_drop(loc); + yk_mt_shutdown(mt); + return (EXIT_SUCCESS); +} diff --git a/ykrt/src/compile/jitc_yk/codegen/reg_alloc.rs b/ykrt/src/compile/jitc_yk/codegen/reg_alloc.rs index 55d206eff..7bbf8ec91 100644 --- a/ykrt/src/compile/jitc_yk/codegen/reg_alloc.rs +++ b/ykrt/src/compile/jitc_yk/codegen/reg_alloc.rs @@ -50,12 +50,6 @@ pub(crate) enum Register { FP(Rx), // floating point } -/// Indicates the direction of stack growth. -pub(crate) enum StackDirection { - GrowsUp, - GrowsDown, -} - #[cfg(target_arch = "x86_64")] impl VarLocation { pub(crate) fn from_yksmp_location(m: &Module, iidx: InstIdx, x: &yksmp::Location) -> Self { diff --git a/ykrt/src/compile/jitc_yk/codegen/x64/deopt.rs b/ykrt/src/compile/jitc_yk/codegen/x64/deopt.rs index af7562ca3..592d4beea 100644 --- a/ykrt/src/compile/jitc_yk/codegen/x64/deopt.rs +++ b/ykrt/src/compile/jitc_yk/codegen/x64/deopt.rs @@ -94,7 +94,7 @@ fn running_trace(gidxs: &[usize]) -> Arc { /// * glen - Length for list in `gptr`. #[no_mangle] pub(crate) extern "C" fn __yk_deopt( - frameaddr: *const c_void, + frameaddr: *mut c_void, gidx: u64, gp_regs: &[u64; 16], fp_regs: &[u64; 16], @@ -353,13 +353,13 @@ pub(crate) extern "C" fn __yk_deopt( // The `clone` should really be `Arc::clone(&ctr)` but that doesn't play well with type // inference in this (unusual) case. - ctr.mt.guard_failure(ctr.clone(), gidx); + ctr.mt.guard_failure(ctr.clone(), gidx, frameaddr); // Since we won't return from this function, drop `ctr` manually. drop(ctr); // Now overwrite the existing stack with our newly recreated one. - unsafe { replace_stack(newframedst as *mut c_void, newstack, memsize) }; + unsafe { replace_stack(newframedst, newstack, memsize) }; } /// Writes the stack frames that we recreated in [__yk_deopt] onto the current stack, overwriting diff --git a/ykrt/src/compile/jitc_yk/codegen/x64/mod.rs b/ykrt/src/compile/jitc_yk/codegen/x64/mod.rs index 2f2936706..0ed3691d0 100644 --- a/ykrt/src/compile/jitc_yk/codegen/x64/mod.rs +++ b/ykrt/src/compile/jitc_yk/codegen/x64/mod.rs @@ -26,7 +26,7 @@ use super::{ jit_ir::{self, BinOp, FloatTy, Inst, InstIdx, Module, Operand, PtrAddInst, Ty}, CompilationError, }, - reg_alloc::{self, StackDirection, VarLocation}, + reg_alloc::{self, VarLocation}, CodeGen, }; #[cfg(any(debug_assertions, test))] @@ -140,9 +140,6 @@ static RBP_DWARF_NUM: u16 = 6; /// The x64 SysV ABI requires a 16-byte aligned stack prior to any call. const SYSV_CALL_STACK_ALIGN: usize = 16; -/// On x64 the stack grows down. -const STACK_DIRECTION: StackDirection = StackDirection::GrowsDown; - /// A function that we can put a debugger breakpoint on. /// FIXME: gross hack. #[cfg(debug_assertions)] diff --git a/ykrt/src/compile/jitc_yk/trace_builder.rs b/ykrt/src/compile/jitc_yk/trace_builder.rs index 3de0e13bf..8a63fc9b1 100644 --- a/ykrt/src/compile/jitc_yk/trace_builder.rs +++ b/ykrt/src/compile/jitc_yk/trace_builder.rs @@ -589,8 +589,10 @@ impl TraceBuilder { _aot_inst_idx: usize, val: &Option, ) -> Result<(), CompilationError> { - // FIXME: Map return value to AOT call instruction. + // If this `unwrap` fails, it means that early return detection in `mt.rs` is not working + // as expected. let frame = self.frames.pop().unwrap(); + // FIXME: Map return value to AOT call instruction. if let Some(val) = val { let op = self.handle_operand(val)?; self.local_map.insert(frame.callinst.unwrap(), op); diff --git a/ykrt/src/lib.rs b/ykrt/src/lib.rs index ee908075f..8d97fbb4f 100644 --- a/ykrt/src/lib.rs +++ b/ykrt/src/lib.rs @@ -15,6 +15,7 @@ mod location; mod log; pub(crate) mod mt; pub mod promote; +pub(crate) mod stack; pub(crate) mod thread_intercept; pub mod trace; diff --git a/ykrt/src/mt.rs b/ykrt/src/mt.rs index e86a2ce2e..e9266cd99 100644 --- a/ykrt/src/mt.rs +++ b/ykrt/src/mt.rs @@ -28,6 +28,7 @@ use crate::{ stats::{Stats, TimingState}, Log, Verbosity, }, + stack::{StackDirection, STACK_DIRECTION}, trace::{default_tracer, AOTTraceIterator, TraceRecorder, Tracer}, }; @@ -401,8 +402,19 @@ impl MT { #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn control_point(self: &Arc, loc: &Location, frameaddr: *mut c_void, smid: u64) { - match self.transition_control_point(loc) { + match self.transition_control_point(loc, frameaddr) { TransitionControlPoint::NoAction => (), + TransitionControlPoint::AbortTracing => { + let thread_tracer = + MTThread::with( + |mtt| match mtt.tstate.replace(MTThreadState::Interpreting) { + MTThreadState::Tracing { thread_tracer, .. } => thread_tracer, + _ => unreachable!(), + }, + ); + thread_tracer.stop().ok(); + self.log.log(Verbosity::JITEvent, "tracing-aborted"); + } TransitionControlPoint::Execute(ctr) => { self.log.log(Verbosity::JITEvent, "enter-jit-code"); self.stats.trace_executed(); @@ -438,6 +450,7 @@ impl MT { hl, thread_tracer: tt, promotions: Vec::new(), + frameaddr, }; }), Err(e) => { @@ -474,7 +487,11 @@ impl MT { hl, thread_tracer, promotions, - } => (hl, thread_tracer, promotions), + frameaddr: tracing_frameaddr, + } => { + assert_eq!(frameaddr, tracing_frameaddr); + (hl, thread_tracer, promotions) + } _ => unreachable!(), }, ); @@ -506,7 +523,11 @@ impl MT { hl, thread_tracer, promotions, - } => (hl, thread_tracer, promotions), + frameaddr: tracing_frameaddr, + } => { + assert_eq!(frameaddr, tracing_frameaddr); + (hl, thread_tracer, promotions) + } _ => unreachable!(), }, ); @@ -536,7 +557,11 @@ impl MT { /// Perform the next step to `loc` in the `Location` state-machine for a control point. If /// `loc` moves to the Compiled state, return a pointer to a [CompiledTrace] object. - fn transition_control_point(self: &Arc, loc: &Location) -> TransitionControlPoint { + fn transition_control_point( + self: &Arc, + loc: &Location, + frameaddr: *mut c_void, + ) -> TransitionControlPoint { MTThread::with(|mtt| { let is_tracing = mtt.is_tracing(); match loc.hot_location() { @@ -599,16 +624,49 @@ impl MT { HotLocationKind::Tracing => { let hl = loc.hot_location_arc_clone().unwrap(); match &*mtt.tstate.borrow() { - MTThreadState::Tracing { hl: thread_hl, .. } => { + MTThreadState::Tracing { + hl: thread_hl, + frameaddr: tracing_frameaddr, + .. + } => { // This thread is tracing something... if !Arc::ptr_eq(thread_hl, &hl) { // ...but not this Location. TransitionControlPoint::NoAction } else { - // ...and it's this location: we have therefore finished - // tracing the loop. - lk.kind = HotLocationKind::Compiling; - TransitionControlPoint::StopTracing + // ...and it's this location... + match STACK_DIRECTION { + StackDirection::GrowsToHigherAddress => todo!(), + StackDirection::GrowsToLowerAddress => { + if frameaddr <= *tracing_frameaddr { + // We've completed a full iteration, either + // within the same frame, or in a recursive + // call. Either way, we do not want to unroll + // the loop / recursion! + lk.kind = HotLocationKind::Compiling; + TransitionControlPoint::StopTracing + } else { + debug_assert!(frameaddr > *tracing_frameaddr); + // We fell through to a caller frame. In other + // words, the frame that started tracing + // `return`ed before it stopped tracing. It's + // probably the case that we've unluckily + // started tracing at the point in a recursive + // algorithm where it's reached the culmination + // point and is now returning many levels deep. + // If we immediately restart tracing in such a + // case, we'll just end up aborting again and + // again. So we don't consider retrying and + // instead abort tracing, and hope we can start + // at a more propitious point in the future. + self.stats.trace_recorded_err(); + // FIXME: We will endless retry tracing this + // [HotLocation]. There should be a `Counting` + // state. + TransitionControlPoint::AbortTracing + } + } + } } } _ => { @@ -629,6 +687,7 @@ impl MT { TransitionControlPoint::StartTracing(hl) } TraceFailed::DontTrace => { + // FIXME: This is stupidly brutal. lk.kind = HotLocationKind::DontTrace; TransitionControlPoint::NoAction } @@ -647,22 +706,57 @@ impl MT { } => { let hl = loc.hot_location_arc_clone().unwrap(); match &*mtt.tstate.borrow() { - MTThreadState::Tracing { hl: thread_hl, .. } => { + MTThreadState::Tracing { + hl: thread_hl, + frameaddr: tracing_frameaddr, + .. + } => { // This thread is tracing something... if !Arc::ptr_eq(thread_hl, &hl) { // ...but not this Location. TransitionControlPoint::NoAction } else { - // ...and it's this location: we have therefore finished - // tracing the loop. - let parent_ctr = Arc::clone(parent_ctr); - let root_ctr_cl = Arc::clone(root_ctr); - lk.kind = HotLocationKind::Compiled(Arc::clone(root_ctr)); - drop(lk); - TransitionControlPoint::StopSideTracing { - gidx, - parent_ctr, - root_ctr: root_ctr_cl, + match STACK_DIRECTION { + StackDirection::GrowsToHigherAddress => todo!(), + StackDirection::GrowsToLowerAddress => { + if frameaddr <= *tracing_frameaddr { + // ...and it's this location: we have therefore + // finished tracing back to the control point, + // either within the same frame, or in a + // recursive call. Either way, we do not want + // to unroll the loop / recursion! + let parent_ctr = Arc::clone(parent_ctr); + let root_ctr_cl = Arc::clone(root_ctr); + lk.kind = HotLocationKind::Compiled( + Arc::clone(root_ctr), + ); + drop(lk); + TransitionControlPoint::StopSideTracing { + gidx, + parent_ctr, + root_ctr: root_ctr_cl, + } + } else { + debug_assert!(frameaddr > *tracing_frameaddr); + // We fell through to a caller frame. In other + // words, the frame that started tracing + // `return`ed before it stopped tracing. It's + // probably the case that we've unluckily + // started tracing at the point in a recursive + // algorithm where it's reached the culmination + // point and is now returning many levels deep. + // If we immediately restart tracing in such a + // case, we'll just end up aborting again and + // again. So we don't consider retrying and + // instead abort tracing, and hope we can start + // at a more propitious point in the future. + self.stats.trace_recorded_err(); + lk.kind = HotLocationKind::Compiled( + Arc::clone(root_ctr), + ); + TransitionControlPoint::AbortTracing + } + } } } } @@ -755,7 +849,12 @@ impl MT { // FIXME: Don't side trace the last guard of a side-trace as this guard always fails. // FIXME: Don't side-trace after switch instructions: not every guard failure is equal // and a trace compiled for case A won't work for case B. - pub(crate) fn guard_failure(self: &Arc, parent: Arc, gidx: GuardIdx) { + pub(crate) fn guard_failure( + self: &Arc, + parent: Arc, + gidx: GuardIdx, + frameaddr: *mut c_void, + ) { match self.transition_guard_failure(parent, gidx) { TransitionGuardFailure::NoAction => (), TransitionGuardFailure::StartSideTracing(hl) => { @@ -770,6 +869,7 @@ impl MT { hl, thread_tracer: tt, promotions: Vec::new(), + frameaddr, }; }), Err(e) => todo!("{e:?}"), @@ -832,6 +932,9 @@ enum MTThreadState { thread_tracer: Box, /// Records the content of data recorded via `yk_promote`. promotions: Vec, + /// The `frameaddr` when tracing started. This allows us to tell if we're finishing tracing + /// at the same point that we started. + frameaddr: *mut c_void, }, /// This thread is executing a trace. Note that the `dyn CompiledTrace` serves two different purposes: /// @@ -984,6 +1087,7 @@ impl MTThread { #[derive(Debug)] enum TransitionControlPoint { NoAction, + AbortTracing, Execute(Arc), StartTracing(Arc>), StopTracing, @@ -1039,7 +1143,9 @@ mod tests { } fn expect_start_tracing(mt: &Arc, loc: &Location) { - let TransitionControlPoint::StartTracing(hl) = mt.transition_control_point(loc) else { + let TransitionControlPoint::StartTracing(hl) = + mt.transition_control_point(loc, 0 as *mut c_void) + else { panic!() }; MTThread::with(|mtt| { @@ -1047,12 +1153,15 @@ mod tests { hl, thread_tracer: Box::new(DummyTraceRecorder), promotions: Vec::new(), + frameaddr: 0 as *mut c_void, }; }); } fn expect_stop_tracing(mt: &Arc, loc: &Location) { - let TransitionControlPoint::StopTracing = mt.transition_control_point(loc) else { + let TransitionControlPoint::StopTracing = + mt.transition_control_point(loc, 0 as *mut c_void) + else { panic!() }; MTThread::with(|mtt| { @@ -1071,6 +1180,7 @@ mod tests { hl, thread_tracer: Box::new(DummyTraceRecorder), promotions: Vec::new(), + frameaddr: 0 as *mut c_void, }; }); } @@ -1084,7 +1194,7 @@ mod tests { let loc = Location::new(); for i in 0..mt.hot_threshold() { assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); assert_eq!(loc.count(), Some(i + 1)); @@ -1104,12 +1214,12 @@ mod tests { ))); loc.hot_location().unwrap().lock().kind = HotLocationKind::Compiled(ctr.clone()); assert!(matches!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::Execute(_) )); expect_start_side_tracing(&mt, ctr); - match mt.transition_control_point(&loc) { + match mt.transition_control_point(&loc, 0 as *mut c_void) { TransitionControlPoint::StopSideTracing { .. } => { MTThread::with(|mtt| { *mtt.tstate.borrow_mut() = MTThreadState::Interpreting; @@ -1122,7 +1232,7 @@ mod tests { _ => unreachable!(), } assert!(matches!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::Execute(_) )); } @@ -1146,25 +1256,25 @@ mod tests { // otherwise tracing will start, and the assertions will fail. for _ in 0..hot_thrsh / (num_threads * 4) { assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); let c1 = loc.count(); assert!(c1.is_some()); assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); let c2 = loc.count(); assert!(c2.is_some()); assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); let c3 = loc.count(); assert!(c3.is_some()); assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); let c4 = loc.count(); @@ -1184,7 +1294,7 @@ mod tests { // at or below the threshold: it could even be (although it's rather unlikely) 0! assert!(loc.count().is_some()); loop { - match mt.transition_control_point(&loc) { + match mt.transition_control_point(&loc, 0 as *mut c_void) { TransitionControlPoint::NoAction => (), TransitionControlPoint::StartTracing(hl) => { MTThread::with(|mtt| { @@ -1192,6 +1302,7 @@ mod tests { hl, thread_tracer: Box::new(DummyTraceRecorder), promotions: Vec::new(), + frameaddr: 0 as *mut c_void, }; }); break; @@ -1216,7 +1327,7 @@ mod tests { // Get the location to the point of being hot. for _ in 0..THRESHOLD { assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); } @@ -1248,7 +1359,7 @@ mod tests { HotLocationKind::Tracing )); assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); assert!(matches!( @@ -1269,7 +1380,7 @@ mod tests { // Get the location to the point of being hot. for _ in 0..THRESHOLD { assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); } @@ -1327,17 +1438,17 @@ mod tests { for _ in 0..THRESHOLD { assert_eq!( - mt.transition_control_point(&loc1), + mt.transition_control_point(&loc1, 0 as *mut c_void), TransitionControlPoint::NoAction ); assert_eq!( - mt.transition_control_point(&loc2), + mt.transition_control_point(&loc2, 0 as *mut c_void), TransitionControlPoint::NoAction ); } expect_start_tracing(&mt, &loc1); assert_eq!( - mt.transition_control_point(&loc2), + mt.transition_control_point(&loc2, 0 as *mut c_void), TransitionControlPoint::NoAction ); assert!(matches!( @@ -1374,8 +1485,9 @@ mod tests { let num_starts = Arc::clone(&num_starts); thrs.push(thread::spawn(move || { for _ in 0..THRESHOLD { - match mt.transition_control_point(&loc) { + match mt.transition_control_point(&loc, 0 as *mut c_void) { TransitionControlPoint::NoAction => (), + TransitionControlPoint::AbortTracing => panic!(), TransitionControlPoint::Execute(_) => (), TransitionControlPoint::StartTracing(hl) => { num_starts.fetch_add(1, Ordering::Relaxed); @@ -1384,6 +1496,7 @@ mod tests { hl, thread_tracer: Box::new(DummyTraceRecorder), promotions: Vec::new(), + frameaddr: 0 as *mut c_void, }; }); assert!(matches!( @@ -1396,7 +1509,7 @@ mod tests { HotLocationKind::Compiling )); assert_eq!( - mt.transition_control_point(&loc), + mt.transition_control_point(&loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); assert!(matches!( @@ -1408,7 +1521,7 @@ mod tests { ); loop { if let TransitionControlPoint::Execute(_) = - mt.transition_control_point(&loc) + mt.transition_control_point(&loc, 0 as *mut c_void) { break; } @@ -1443,11 +1556,11 @@ mod tests { for _ in 0..THRESHOLD { assert_eq!( - mt.transition_control_point(&loc1), + mt.transition_control_point(&loc1, 0 as *mut c_void), TransitionControlPoint::NoAction ); assert_eq!( - mt.transition_control_point(&loc2), + mt.transition_control_point(&loc2, 0 as *mut c_void), TransitionControlPoint::NoAction ); } @@ -1462,7 +1575,7 @@ mod tests { expect_start_tracing(&mt, &loc2); assert_eq!( - mt.transition_control_point(&loc1), + mt.transition_control_point(&loc1, 0 as *mut c_void), TransitionControlPoint::NoAction ); expect_stop_tracing(&mt, &loc2); @@ -1484,7 +1597,7 @@ mod tests { fn to_compiled(mt: &Arc, loc: &Location) -> Arc { for _ in 0..THRESHOLD { assert_eq!( - mt.transition_control_point(loc), + mt.transition_control_point(loc, 0 as *mut c_void), TransitionControlPoint::NoAction ); } @@ -1518,11 +1631,11 @@ mod tests { expect_start_side_tracing(&mt, ctr2); assert!(matches!( - dbg!(mt.transition_control_point(&loc1)), + dbg!(mt.transition_control_point(&loc1, 0 as *mut c_void)), TransitionControlPoint::NoAction )); assert!(matches!( - mt.transition_control_point(&loc2), + mt.transition_control_point(&loc2, 0 as *mut c_void), TransitionControlPoint::StopSideTracing { .. } )); } @@ -1533,7 +1646,7 @@ mod tests { let loc = Location::new(); b.iter(|| { for _ in 0..100000 { - black_box(mt.transition_control_point(&loc)); + black_box(mt.transition_control_point(&loc, 0 as *mut c_void)); } }); } @@ -1549,7 +1662,7 @@ mod tests { let mt = Arc::clone(&mt); thrs.push(thread::spawn(move || { for _ in 0..100 { - black_box(mt.transition_control_point(&loc)); + black_box(mt.transition_control_point(&loc, 0 as *mut c_void)); } })); } @@ -1580,14 +1693,14 @@ mod tests { // https://github.com/ykjit/yk/issues/519 expect_start_tracing(&mt, &loc2); assert!(matches!( - mt.transition_control_point(&loc1), + mt.transition_control_point(&loc1, 0 as *mut c_void), TransitionControlPoint::NoAction )); // But once we stop tracing for `loc2`, we should be able to execute the trace for `loc1`. expect_stop_tracing(&mt, &loc2); assert!(matches!( - mt.transition_control_point(&loc1), + mt.transition_control_point(&loc1, 0 as *mut c_void), TransitionControlPoint::Execute(_) )); } diff --git a/ykrt/src/stack.rs b/ykrt/src/stack.rs new file mode 100644 index 000000000..472225781 --- /dev/null +++ b/ykrt/src/stack.rs @@ -0,0 +1,12 @@ +/// Which direction does the CPU stack grow when more room is required? +pub(crate) enum StackDirection { + /// The CPU stack grows "downwards" i.e. growth changes it to a lower address. + #[allow(dead_code)] + GrowsToHigherAddress, + /// The CPU stack grows "upwards" i.e. growth changes it to a higher address. + #[allow(dead_code)] + GrowsToLowerAddress, +} + +#[cfg(target_arch = "x86_64")] +pub(crate) const STACK_DIRECTION: StackDirection = StackDirection::GrowsToLowerAddress;