diff --git a/clients/drcachesim/analyzer_multi.cpp b/clients/drcachesim/analyzer_multi.cpp index ca68fd2dcf8..fb71f986f76 100644 --- a/clients/drcachesim/analyzer_multi.cpp +++ b/clients/drcachesim/analyzer_multi.cpp @@ -509,6 +509,7 @@ analyzer_multi_tmpl_t::init_dynamic_schedule() sched_ops.block_time_scale = op_sched_block_scale.get_value(); sched_ops.block_time_max = op_sched_block_max_us.get_value(); sched_ops.randomize_next_input = op_sched_randomize.get_value(); + sched_ops.honor_direct_switches = !op_sched_disable_direct_switches.get_value(); #ifdef HAS_ZIP if (!op_record_file.get_value().empty()) { record_schedule_zip_.reset(new zipfile_ostream_t(op_record_file.get_value())); diff --git a/clients/drcachesim/common/options.cpp b/clients/drcachesim/common/options.cpp index e59c0554f81..7105192347b 100644 --- a/clients/drcachesim/common/options.cpp +++ b/clients/drcachesim/common/options.cpp @@ -920,6 +920,15 @@ droption_t op_sched_randomize( "set), and FIFO order and instead selects the next input randomly. " "This is intended for experimental use in sensitivity studies."); +droption_t op_sched_disable_direct_switches( + DROPTION_SCOPE_FRONTEND, "sched_disable_direct_switches", false, + "Ignore direct thread switch requests", + "Applies to -core_sharded and -core_serial. Disables switching to the recorded " + "targets of TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH system call metadata markers " + "and causes the associated system call to be treated like any other call with a " + "switch being determined by latency and the next input in the queue. The " + "TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH markers are not removed from the trace."); + // Schedule_stats options. droption_t op_schedule_stats_print_every(DROPTION_SCOPE_ALL, "schedule_stats_print_every", diff --git a/clients/drcachesim/common/options.h b/clients/drcachesim/common/options.h index 096a45fe5b1..316d1acf063 100644 --- a/clients/drcachesim/common/options.h +++ b/clients/drcachesim/common/options.h @@ -207,6 +207,7 @@ extern dynamorio::droption::droption_t op_cpu_schedule_file; #endif extern dynamorio::droption::droption_t op_sched_switch_file; extern dynamorio::droption::droption_t op_sched_randomize; +extern dynamorio::droption::droption_t op_sched_disable_direct_switches; extern dynamorio::droption::droption_t op_schedule_stats_print_every; extern dynamorio::droption::droption_t op_syscall_template_file; extern dynamorio::droption::droption_t op_filter_stop_timestamp; diff --git a/clients/drcachesim/scheduler/scheduler.cpp b/clients/drcachesim/scheduler/scheduler.cpp index 49b36a4559c..61d51681a4f 100644 --- a/clients/drcachesim/scheduler/scheduler.cpp +++ b/clients/drcachesim/scheduler/scheduler.cpp @@ -2781,7 +2781,8 @@ scheduler_tmpl_t::next_record(output_ordinal_t output, // boundaries so we live with those being before the switch. // XXX: Once we insert kernel traces, we may have to try harder // to stop before the post-syscall records. - if (record_type_is_marker(record, marker_type, marker_value) && + if (options_.honor_direct_switches && + record_type_is_marker(record, marker_type, marker_value) && marker_type == TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH) { memref_tid_t target_tid = marker_value; auto it = diff --git a/clients/drcachesim/scheduler/scheduler.h b/clients/drcachesim/scheduler/scheduler.h index f14e275943d..c18bcf11cf1 100644 --- a/clients/drcachesim/scheduler/scheduler.h +++ b/clients/drcachesim/scheduler/scheduler.h @@ -636,6 +636,18 @@ template class scheduler_tmpl_t { * ahead. */ bool read_inputs_in_init = true; + /** + * If true, the scheduler will attempt to switch to the recorded targets of + * #TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH system call metadata markers + * regardless of system call latency. If the target is not available, the + * current implementation will select the next available input in the regular + * scheduling queue, but in the future a forced migration may be applied for an + * input currently on another output. If false, the direct switch markers are + * ignored and only system call latency thresholds are used to determine + * switches (the #TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH markers remain: they + * are not removed from the trace). + */ + bool honor_direct_switches = true; }; /** diff --git a/clients/drcachesim/tests/scheduler_unit_tests.cpp b/clients/drcachesim/tests/scheduler_unit_tests.cpp index 54d6fc32993..e0ed0702f92 100644 --- a/clients/drcachesim/tests/scheduler_unit_tests.cpp +++ b/clients/drcachesim/tests/scheduler_unit_tests.cpp @@ -3794,42 +3794,83 @@ test_direct_switch() make_marker(TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH, TID_BASE + 3), make_exit(TID_C), }; - std::vector readers; - readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_A)), - std::unique_ptr(new mock_reader_t()), TID_A); - readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_B)), - std::unique_ptr(new mock_reader_t()), TID_B); - readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_C)), - std::unique_ptr(new mock_reader_t()), TID_C); - // The string constructor writes "." for markers. - // We expect A's first switch to be to C even though B has an earlier timestamp. - // We expect C's direct switch to A to proceed immediately even though A still - // has significant blocked time left. But then after B is scheduled and finishes, - // we still have to wait for C's block time so we see idle underscores: - static const char *const CORE0_SCHED_STRING = - "...AA.........CC......A....BBBB.______________C..."; + { + // Test the defaults with direct switches enabled. + std::vector readers; + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_A)), + std::unique_ptr(new mock_reader_t()), TID_A); + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_B)), + std::unique_ptr(new mock_reader_t()), TID_B); + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_C)), + std::unique_ptr(new mock_reader_t()), TID_C); + // The string constructor writes "." for markers. + // We expect A's first switch to be to C even though B has an earlier timestamp. + // We expect C's direct switch to A to proceed immediately even though A still + // has significant blocked time left. But then after B is scheduled and finishes, + // we still have to wait for C's block time so we see idle underscores: + static const char *const CORE0_SCHED_STRING = + "...AA.........CC......A....BBBB.______________C..."; - std::vector sched_inputs; - sched_inputs.emplace_back(std::move(readers)); - scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT, - scheduler_t::DEPENDENCY_TIMESTAMPS, - scheduler_t::SCHEDULER_DEFAULTS, - /*verbosity=*/3); - sched_ops.quantum_duration = QUANTUM_DURATION; - // We use our mock's time==instruction count for a deterministic result. - sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME; - sched_ops.blocking_switch_threshold = BLOCK_LATENCY; - sched_ops.block_time_scale = BLOCK_SCALE; - scheduler_t scheduler; - if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) != - scheduler_t::STATUS_SUCCESS) - assert(false); - std::vector sched_as_string = - run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE, /*send_time=*/true); - for (int i = 0; i < NUM_OUTPUTS; i++) { - std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n"; + std::vector sched_inputs; + sched_inputs.emplace_back(std::move(readers)); + scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT, + scheduler_t::DEPENDENCY_TIMESTAMPS, + scheduler_t::SCHEDULER_DEFAULTS, + /*verbosity=*/3); + sched_ops.quantum_duration = QUANTUM_DURATION; + // We use our mock's time==instruction count for a deterministic result. + sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME; + sched_ops.blocking_switch_threshold = BLOCK_LATENCY; + sched_ops.block_time_scale = BLOCK_SCALE; + scheduler_t scheduler; + if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) != + scheduler_t::STATUS_SUCCESS) + assert(false); + std::vector sched_as_string = + run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE, /*send_time=*/true); + for (int i = 0; i < NUM_OUTPUTS; i++) { + std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n"; + } + assert(sched_as_string[0] == CORE0_SCHED_STRING); + } + { + // Test disabling direct switches. + std::vector readers; + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_A)), + std::unique_ptr(new mock_reader_t()), TID_A); + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_B)), + std::unique_ptr(new mock_reader_t()), TID_B); + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_C)), + std::unique_ptr(new mock_reader_t()), TID_C); + // The string constructor writes "." for markers. + // We expect A's first switch to be to B with an earlier timestamp. + // We expect C's direct switch to A to not happen until A's blocked time ends. + static const char *const CORE0_SCHED_STRING = + "...AA.........BBBB....CC......___________________C...___A."; + + std::vector sched_inputs; + sched_inputs.emplace_back(std::move(readers)); + scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT, + scheduler_t::DEPENDENCY_TIMESTAMPS, + scheduler_t::SCHEDULER_DEFAULTS, + /*verbosity=*/3); + sched_ops.quantum_duration = QUANTUM_DURATION; + // We use our mock's time==instruction count for a deterministic result. + sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME; + sched_ops.blocking_switch_threshold = BLOCK_LATENCY; + sched_ops.block_time_scale = BLOCK_SCALE; + sched_ops.honor_direct_switches = false; + scheduler_t scheduler; + if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) != + scheduler_t::STATUS_SUCCESS) + assert(false); + std::vector sched_as_string = + run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE, /*send_time=*/true); + for (int i = 0; i < NUM_OUTPUTS; i++) { + std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n"; + } + assert(sched_as_string[0] == CORE0_SCHED_STRING); } - assert(sched_as_string[0] == CORE0_SCHED_STRING); } static void