blob: 562efc4c5f7fa3e7d122256f2591ad90ee34fca2 [file] [log] [blame] [edit]
//===- ThreadedFunctionQueue.h - Function work units for threads -*- C++ -*-==//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef THREADEDFUNCTIONQUEUE_H
#define THREADEDFUNCTIONQUEUE_H
#include <limits>
#include "llvm/IR/Module.h"
#include "llvm/Support/ErrorHandling.h"
namespace llvm {
// A "queue" that keeps track of which functions have been assigned to
// threads and which functions have not yet been assigned. It does not
// actually use a queue data structure and instead uses a number which
// tracks the minimum unassigned function ID, expecting each thread
// to have the same view of function IDs.
class ThreadedFunctionQueue {
public:
ThreadedFunctionQueue(Module *mod, unsigned NumThreads)
: NumThreads(NumThreads),
NumFunctions(0),
CurrentFunction(0) {
assert(NumThreads > 0);
size_t Size = 0;
for (Module::iterator I = mod->begin(), E = mod->end(); I != E; ++I) {
// Only count functions with bodies. At this point nothing should
// be "already materialized", so if functions with bodies are
// materializable.
if (I->isMaterializable() || !I->isDeclaration())
Size++;
}
if (Size > static_cast<size_t>(std::numeric_limits<int>::max()))
report_fatal_error("Too many functions");
NumFunctions = Size;
}
~ThreadedFunctionQueue() {}
// Assign functions in a static manner between threads.
bool GrabFunctionStatic(unsigned FuncIndex, unsigned ThreadIndex) const {
// Note: This assumes NumThreads == SplitModuleCount, so that
// (a) every function of every module is covered by the NumThreads and
// (b) no function is covered twice by the threads.
assert(ThreadIndex < NumThreads);
return FuncIndex % NumThreads == ThreadIndex;
}
// Assign functions between threads dynamically.
// Returns true if FuncIndex is unassigned and the calling thread
// is assigned functions [FuncIndex, FuncIndex + ChunkSize).
// Returns false if the calling thread is not assigned functions
// [FuncIndex, FuncIndex + ChunkSize).
//
// NextIndex will be set to the next unassigned function ID, so that
// the calling thread will know which function ID to attempt to grab
// next. Each thread may have a different value for the ideal ChunkSize
// so it is hard to predict the next available function solely based
// on incrementing by ChunkSize.
bool GrabFunctionDynamic(unsigned FuncIndex, unsigned ChunkSize,
unsigned &NextIndex) {
unsigned Cur = CurrentFunction;
if (FuncIndex < Cur) {
NextIndex = Cur;
return false;
}
NextIndex = Cur + ChunkSize;
unsigned Index;
if (Cur == (Index = __sync_val_compare_and_swap(&CurrentFunction,
Cur, NextIndex))) {
return true;
}
// If this thread did not grab the function, its idea of NextIndex
// may be incorrect since ChunkSize can vary between threads.
// Reset NextIndex in that case.
NextIndex = Index;
return false;
}
// Returns a recommended ChunkSize for use in calling GrabFunctionDynamic().
// ChunkSize starts out "large" to reduce synchronization cost. However,
// it cannot be too large, otherwise it will encompass too many bytes
// and defeats streaming translation. Assigning too many functions to
// a single thread also throws off load balancing, so the ChunkSize is
// reduced when the remaining number of functions is low so that
// load balancing can be achieved near the end.
unsigned RecommendedChunkSize() const {
int RemainingFuncs = NumFunctions - CurrentFunction;
int DynamicChunkSize = RemainingFuncs / (NumThreads * 4);
return std::max(1, std::min(8, DynamicChunkSize));
}
// Total number of functions with bodies that should be processed.
unsigned Size() const {
return NumFunctions;
}
private:
const unsigned NumThreads;
unsigned NumFunctions;
volatile unsigned CurrentFunction;
ThreadedFunctionQueue(
const ThreadedFunctionQueue&) = delete;
void operator=(const ThreadedFunctionQueue&) = delete;
};
} // end namespace llvm
#endif