Skip to content

Commit 57fe9ee

Browse files
committed
[BatchMode] Add driver::PerformJobsState logic for BatchJobs.
1 parent 3d9ce0a commit 57fe9ee

File tree

1 file changed

+231
-9
lines changed

1 file changed

+231
-9
lines changed

lib/Driver/Compilation.cpp

Lines changed: 231 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "swift/Driver/ToolChain.h"
2828
#include "llvm/ADT/DenseSet.h"
2929
#include "llvm/ADT/MapVector.h"
30+
#include "llvm/ADT/SetVector.h"
3031
#include "llvm/ADT/StringExtras.h"
3132
#include "llvm/ADT/TinyPtrVector.h"
3233
#include "llvm/Option/Arg.h"
@@ -136,6 +137,17 @@ namespace driver {
136137
/// don't need to run.
137138
CommandSet ScheduledCommands;
138139

140+
/// A temporary buffer to hold commands that were scheduled but haven't been
141+
/// added to the Task Queue yet, because we might try batching them together
142+
/// first.
143+
CommandSet PendingExecution;
144+
145+
/// Set of synthetic BatchJobs that serve to cluster subsets of jobs waiting
146+
/// in PendingExecution. Also used to identify (then unpack) BatchJobs back
147+
/// to their underlying non-Batch Jobs, when running a callback from
148+
/// TaskQueue.
149+
CommandSet BatchJobs;
150+
139151
/// All jobs which have finished execution or which have been determined
140152
/// that they don't need to run.
141153
CommandSet FinishedCommands;
@@ -222,14 +234,24 @@ namespace driver {
222234
return;
223235
}
224236

237+
// Adding to scheduled means we've committed to its completion (not
238+
// distinguished from skipping). We never remove it once inserted.
239+
ScheduledCommands.insert(Cmd);
240+
241+
// Adding to pending means it should be in the next round of additions to
242+
// the task queue (either batched or singularly); we remove Jobs from
243+
// PendingExecution once we hand them over to the TaskQueue.
244+
PendingExecution.insert(Cmd);
245+
}
246+
247+
void addPendingJobToTaskQueue(const Job *Cmd) {
225248
// FIXME: Failing here should not take down the whole process.
226249
bool success = writeFilelistIfNecessary(Cmd, Comp.Diags);
227250
assert(success && "failed to write filelist");
228251
(void)success;
229252

230253
assert(Cmd->getExtraEnvironment().empty() &&
231254
"not implemented for compilations with multiple jobs");
232-
ScheduledCommands.insert(Cmd);
233255
if (Comp.ShowJobLifecycle)
234256
llvm::outs() << "Added to TaskQueue: " << LogJob(Cmd) << "\n";
235257
TQ->addTask(Cmd->getExecutable(), Cmd->getArguments(), llvm::None,
@@ -289,6 +311,8 @@ namespace driver {
289311
parseable_output::emitBeganMessage(llvm::errs(), *BeganCmd, Pid);
290312
}
291313

314+
/// Note that a .swiftdeps file failed to load and take corrective actions:
315+
/// disable incremental logic and schedule all existing deferred commands.
292316
void
293317
dependencyLoadFailed(StringRef DependenciesFile, bool Warn=true) {
294318
if (Warn && Comp.ShowIncrementalBuildDecisions)
@@ -301,9 +325,10 @@ namespace driver {
301325
DeferredCommands.clear();
302326
}
303327

304-
/// Helper that reloads a job's .swiftdeps file after the job exits, and
305-
/// re-runs transitive marking to ensure everything is properly invalidated
306-
/// by any new dependency edges introduced by it.
328+
/// Helper that attmepts to reload a job's .swiftdeps file after the job
329+
/// exits, and re-run transitive marking to ensure everything is properly
330+
/// invalidated by any new dependency edges introduced by it. If reloading
331+
/// fails, this can cause deferred jobs to be immediately scheduled.
307332
template <unsigned N>
308333
void reloadAndRemarkDeps(const Job *FinishedCmd,
309334
int ReturnCode,
@@ -381,6 +406,28 @@ namespace driver {
381406
}
382407
}
383408

409+
/// Unpack a \c BatchJob that has finished into its constituent \c Job
410+
/// members, and call \c taskFinished on each, propagating any \c
411+
/// TaskFinishedResponse other than \c
412+
/// TaskFinishedResponse::ContinueExecution from any of the constituent
413+
/// calls.
414+
TaskFinishedResponse
415+
unpackAndFinishBatch(ProcessId Pid, int ReturnCode, StringRef Output,
416+
StringRef Errors, const BatchJob *B) {
417+
if (Comp.ShowJobLifecycle)
418+
llvm::outs() << "Batch job finished: " << LogJob(B) << "\n";
419+
auto res = TaskFinishedResponse::ContinueExecution;
420+
for (const Job *J : B->getCombinedJobs()) {
421+
if (Comp.ShowJobLifecycle)
422+
llvm::outs() << " ==> Unpacked batch constituent finished: "
423+
<< LogJob(J) << "\n";
424+
auto r = taskFinished(Pid, ReturnCode, Output, Errors, (void *)J);
425+
if (r != TaskFinishedResponse::ContinueExecution)
426+
res = r;
427+
}
428+
return res;
429+
}
430+
384431
/// Callback which will be called immediately after a task has finished
385432
/// execution. Determines if execution should continue, and also schedule
386433
/// any additional Jobs which we now know we need to run.
@@ -393,6 +440,11 @@ namespace driver {
393440
DriverTimers[FinishedCmd]->stopTimer();
394441
}
395442

443+
if (BatchJobs.count(FinishedCmd) != 0) {
444+
return unpackAndFinishBatch(Pid, ReturnCode, Output, Errors,
445+
static_cast<const BatchJob *>(FinishedCmd));
446+
}
447+
396448
if (Comp.Level == OutputLevel::Parseable) {
397449
// Parseable output was requested.
398450
parseable_output::emitFinishedMessage(llvm::errs(), *FinishedCmd, Pid,
@@ -446,6 +498,30 @@ namespace driver {
446498
return TaskFinishedResponse::ContinueExecution;
447499
}
448500

501+
/// Unpack a \c BatchJob that has finished into its constituent \c Job
502+
/// members, and call \c taskSignalled on each, propagating any \c
503+
/// TaskFinishedResponse other than \c
504+
/// TaskFinishedResponse::ContinueExecution from any of the constituent
505+
/// calls.
506+
TaskFinishedResponse
507+
unpackAndSignalBatch(ProcessId Pid, StringRef ErrorMsg, StringRef Output,
508+
StringRef Errors, const BatchJob *B,
509+
Optional<int> Signal) {
510+
if (Comp.ShowJobLifecycle)
511+
llvm::outs() << "Batch job signalled: " << LogJob(B) << "\n";
512+
auto res = TaskFinishedResponse::ContinueExecution;
513+
for (const Job *J : B->getCombinedJobs()) {
514+
if (Comp.ShowJobLifecycle)
515+
llvm::outs() << " ==> Unpacked batch constituent signalled: "
516+
<< LogJob(J) << "\n";
517+
auto r = taskSignalled(Pid, ErrorMsg, Output, Errors,
518+
(void *)J, Signal);
519+
if (r != TaskFinishedResponse::ContinueExecution)
520+
res = r;
521+
}
522+
return res;
523+
}
524+
449525
TaskFinishedResponse
450526
taskSignalled(ProcessId Pid, StringRef ErrorMsg, StringRef Output,
451527
StringRef Errors, void *Context, Optional<int> Signal) {
@@ -455,6 +531,12 @@ namespace driver {
455531
DriverTimers[SignalledCmd]->stopTimer();
456532
}
457533

534+
if (BatchJobs.count(SignalledCmd) != 0) {
535+
return unpackAndSignalBatch(Pid, ErrorMsg, Output, Errors,
536+
static_cast<const BatchJob *>(SignalledCmd),
537+
Signal);
538+
}
539+
458540
if (Comp.Level == OutputLevel::Parseable) {
459541
// Parseable output was requested.
460542
parseable_output::emitSignalledMessage(llvm::errs(), *SignalledCmd,
@@ -596,6 +678,126 @@ namespace driver {
596678
}
597679
}
598680

681+
/// Insert all jobs in \p Cmds (of descriptive name \p Kind) to the \c
682+
/// TaskQueue, and clear \p Cmds.
683+
void transferJobsToTaskQueue(CommandSet &Cmds, StringRef Kind) {
684+
for (const Job *Cmd : Cmds) {
685+
if (Comp.ShowJobLifecycle)
686+
llvm::outs() << "Adding " << Kind
687+
<< " job to task queue: "
688+
<< LogJob(Cmd) << "\n";
689+
addPendingJobToTaskQueue(Cmd);
690+
}
691+
Cmds.clear();
692+
}
693+
694+
/// Partition the jobs in \c PendingExecution into those that are \p
695+
/// Batchable and those that are \p NonBatchable, clearing \p
696+
/// PendingExecution.
697+
void getPendingBatchableJobs(CommandSet &Batchable,
698+
CommandSet &NonBatchable) {
699+
for (const Job *Cmd : PendingExecution) {
700+
if (Comp.getToolChain().jobIsBatchable(Comp, Cmd)) {
701+
if (Comp.ShowJobLifecycle)
702+
llvm::outs() << "Batchable: " << LogJob(Cmd) << "\n";
703+
Batchable.insert(Cmd);
704+
} else {
705+
if (Comp.ShowJobLifecycle)
706+
llvm::outs() << "Not batchable: " << LogJob(Cmd) << "\n";
707+
NonBatchable.insert(Cmd);
708+
}
709+
}
710+
PendingExecution.clear();
711+
}
712+
713+
/// If \p CurrentBatch is nonempty, construct a new \c BatchJob from its
714+
/// contents by calling \p ToolChain::constructBatchJob, then insert the
715+
/// new \c BatchJob into \p Batches and clear \p CurrentBatch.
716+
void
717+
formBatchJobFromCurrentBatch(CommandSet &Batches,
718+
llvm::SetVector<const Job *> &CurrentBatch) {
719+
if (CurrentBatch.empty())
720+
return;
721+
if (Comp.ShowJobLifecycle)
722+
llvm::outs() << "Forming batch job from "
723+
<< CurrentBatch.size() << " constituents\n";
724+
auto const &TC = Comp.getToolChain();
725+
auto J = TC.constructBatchJob(CurrentBatch.getArrayRef(), Comp);
726+
if (J)
727+
Batches.insert(Comp.addJob(std::move(J)));
728+
CurrentBatch.clear();
729+
}
730+
731+
/// Return true iff \p Cmd can be expanded by \p CurrentBatch, meaning
732+
/// that \p CurrentBatch is smaller than \p TargetBatchSize and \p Cmd
733+
/// is batch-combinable with the equivalence class of \p CurrentBatch
734+
/// (as represented by element 0 of \p CurrentBatch).
735+
bool canExpandBatch(const Job *Cmd,
736+
llvm::SetVector<const Job *> &CurrentBatch,
737+
size_t TargetBatchSize) {
738+
auto const &TC = Comp.getToolChain();
739+
return (CurrentBatch.empty() ||
740+
(TC.jobsAreBatchCombinable(Comp, Cmd, CurrentBatch[0]) &&
741+
CurrentBatch.size() < TargetBatchSize));
742+
}
743+
744+
/// If \p CurrentBatch can't be expanded with \p Cmd, form a new \c BatchJob
745+
/// from \p CurrentBatch, add it to \p Batches, and reset\p CurrentBatch;
746+
/// then in either case, insert \p Cmd into \p CurrentBatch.
747+
void expandBatch(const Job *Cmd,
748+
CommandSet &Batches,
749+
llvm::SetVector<const Job *> &CurrentBatch,
750+
size_t TargetBatchSize) {
751+
if (!canExpandBatch(Cmd, CurrentBatch, TargetBatchSize)) {
752+
formBatchJobFromCurrentBatch(Batches, CurrentBatch);
753+
}
754+
llvm::outs() << "Adding to batch: " << LogJob(Cmd) << "\n";
755+
CurrentBatch.insert(Cmd);
756+
}
757+
758+
/// Select jobs that are batch-combinable from \c PendingExecution, combine
759+
/// them together into \p BatchJob instances (also inserted into \p
760+
/// BatchJobs), and enqueue all \c PendingExecution jobs (whether batched or
761+
/// not) into the \c TaskQueue for execution.
762+
void formBatchJobsAndAddPendingJobsToTaskQueue() {
763+
764+
// If batch mode is not enabled, just transfer the set of pending jobs to
765+
// the task queue, as-is.
766+
if (!Comp.getBatchModeEnabled()) {
767+
transferJobsToTaskQueue(PendingExecution, "standard");
768+
return;
769+
}
770+
771+
// Partition the pending jobs.
772+
CommandSet Batchable, NonBatchable, Batches;
773+
getPendingBatchableJobs(Batchable, NonBatchable);
774+
size_t TargetBatchSize = Batchable.size() / Comp.NumberOfParallelCommands;
775+
776+
if (Comp.ShowJobLifecycle) {
777+
llvm::outs() << "Found " << Batchable.size() << " batchable jobs\n";
778+
llvm::outs() << "Aiming for batch size " << TargetBatchSize << '\n';
779+
}
780+
781+
// Batch the batchable jobs.
782+
llvm::SetVector<const Job *> CurrentBatch;
783+
for (const Job *Cmd : Batchable) {
784+
expandBatch(Cmd, Batches, CurrentBatch, TargetBatchSize);
785+
}
786+
787+
// Form a residual incomplete batch if any jobs remain.
788+
if (!CurrentBatch.empty()) {
789+
formBatchJobFromCurrentBatch(Batches, CurrentBatch);
790+
}
791+
792+
// Save batches so we can locate and decompose them on task-exit.
793+
for (const Job *Cmd : Batches)
794+
BatchJobs.insert(Cmd);
795+
796+
// Enqueue the resulting jobs, batched and non-batched alike.
797+
transferJobsToTaskQueue(Batches, "batch");
798+
transferJobsToTaskQueue(NonBatchable, "non-batch");
799+
}
800+
599801
void runTaskQueueToCompletion() {
600802
do {
601803
using namespace std::placeholders;
@@ -607,20 +809,39 @@ namespace driver {
607809
std::bind(&PerformJobsState::taskSignalled, this,
608810
_1, _2, _3, _4, _5, _6));
609811

610-
// Mark all remaining deferred commands as skipped.
812+
// Returning from TaskQueue::execute should mean either an empty
813+
// TaskQueue or a failed subprocess.
814+
assert(!(Result == 0 && TQ->hasRemainingTasks()));
815+
816+
// Task-exit callbacks from TaskQueue::execute may have unblocked jobs,
817+
// which means there might be PendingExecution jobs to enqueue here. If
818+
// there are, we need to continue trying to make progress on the
819+
// TaskQueue before we start marking deferred jobs as skipped, below.
820+
if (!PendingExecution.empty() && Result == 0) {
821+
formBatchJobsAndAddPendingJobsToTaskQueue();
822+
continue;
823+
}
824+
825+
// If we got here, all the queued and pending work we know about is
826+
// done; mark anything still in deferred state as skipped.
611827
for (const Job *Cmd : DeferredCommands) {
612828
if (Comp.Level == OutputLevel::Parseable) {
613-
// Provide output indicating this command was skipped if parseable output
614-
// was requested.
829+
// Provide output indicating this command was skipped if parseable
830+
// output was requested.
615831
parseable_output::emitSkippedMessage(llvm::errs(), *Cmd);
616832
}
617-
618833
ScheduledCommands.insert(Cmd);
619834
markFinished(Cmd, /*Skipped=*/true);
620835
}
621836
DeferredCommands.clear();
622837

623-
// ...which may allow us to go on and do later tasks.
838+
// It's possible that by marking some jobs as skipped, we unblocked
839+
// some jobs and thus have entries in PendingExecution again; push
840+
// those through to the TaskQueue.
841+
formBatchJobsAndAddPendingJobsToTaskQueue();
842+
843+
// If we added jobs to the TaskQueue, and we are not in an error state,
844+
// we want to give the TaskQueue another run.
624845
} while (Result == 0 && TQ->hasRemainingTasks());
625846
}
626847

@@ -848,6 +1069,7 @@ int Compilation::performJobsImpl(bool &abnormalExit) {
8481069

8491070
State.scheduleInitialJobs();
8501071
State.scheduleAdditionalJobs();
1072+
State.formBatchJobsAndAddPendingJobsToTaskQueue();
8511073
State.runTaskQueueToCompletion();
8521074
State.checkUnfinishedJobs();
8531075

0 commit comments

Comments
 (0)