//===-- examples/ParallelJIT/ParallelJIT.cpp - Exercise threaded-safe JIT -===// | |
// | |
// The LLVM Compiler Infrastructure | |
// | |
// This file is distributed under the University of Illinois Open Source | |
// License. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
// | |
// Parallel JIT | |
// | |
// This test program creates two LLVM functions then calls them from three | |
// separate threads. It requires the pthreads library. | |
// The three threads are created and then block waiting on a condition variable. | |
// Once all threads are blocked on the conditional variable, the main thread | |
// wakes them up. This complicated work is performed so that all three threads | |
// call into the JIT at the same time (or the best possible approximation of the | |
// same time). This test had assertion errors until I got the locking right. | |
#include <pthread.h> | |
#include "llvm/LLVMContext.h" | |
#include "llvm/Module.h" | |
#include "llvm/Constants.h" | |
#include "llvm/DerivedTypes.h" | |
#include "llvm/Instructions.h" | |
#include "llvm/ExecutionEngine/JIT.h" | |
#include "llvm/ExecutionEngine/Interpreter.h" | |
#include "llvm/ExecutionEngine/GenericValue.h" | |
#include "llvm/Support/TargetSelect.h" | |
#include <iostream> | |
using namespace llvm; | |
static Function* createAdd1(Module *M) { | |
// Create the add1 function entry and insert this entry into module M. The | |
// function will have a return type of "int" and take an argument of "int". | |
// The '0' terminates the list of argument types. | |
Function *Add1F = | |
cast<Function>(M->getOrInsertFunction("add1", | |
Type::getInt32Ty(M->getContext()), | |
Type::getInt32Ty(M->getContext()), | |
(Type *)0)); | |
// Add a basic block to the function. As before, it automatically inserts | |
// because of the last argument. | |
BasicBlock *BB = BasicBlock::Create(M->getContext(), "EntryBlock", Add1F); | |
// Get pointers to the constant `1'. | |
Value *One = ConstantInt::get(Type::getInt32Ty(M->getContext()), 1); | |
// Get pointers to the integer argument of the add1 function... | |
assert(Add1F->arg_begin() != Add1F->arg_end()); // Make sure there's an arg | |
Argument *ArgX = Add1F->arg_begin(); // Get the arg | |
ArgX->setName("AnArg"); // Give it a nice symbolic name for fun. | |
// Create the add instruction, inserting it into the end of BB. | |
Instruction *Add = BinaryOperator::CreateAdd(One, ArgX, "addresult", BB); | |
// Create the return instruction and add it to the basic block | |
ReturnInst::Create(M->getContext(), Add, BB); | |
// Now, function add1 is ready. | |
return Add1F; | |
} | |
static Function *CreateFibFunction(Module *M) { | |
// Create the fib function and insert it into module M. This function is said | |
// to return an int and take an int parameter. | |
Function *FibF = | |
cast<Function>(M->getOrInsertFunction("fib", | |
Type::getInt32Ty(M->getContext()), | |
Type::getInt32Ty(M->getContext()), | |
(Type *)0)); | |
// Add a basic block to the function. | |
BasicBlock *BB = BasicBlock::Create(M->getContext(), "EntryBlock", FibF); | |
// Get pointers to the constants. | |
Value *One = ConstantInt::get(Type::getInt32Ty(M->getContext()), 1); | |
Value *Two = ConstantInt::get(Type::getInt32Ty(M->getContext()), 2); | |
// Get pointer to the integer argument of the add1 function... | |
Argument *ArgX = FibF->arg_begin(); // Get the arg. | |
ArgX->setName("AnArg"); // Give it a nice symbolic name for fun. | |
// Create the true_block. | |
BasicBlock *RetBB = BasicBlock::Create(M->getContext(), "return", FibF); | |
// Create an exit block. | |
BasicBlock* RecurseBB = BasicBlock::Create(M->getContext(), "recurse", FibF); | |
// Create the "if (arg < 2) goto exitbb" | |
Value *CondInst = new ICmpInst(*BB, ICmpInst::ICMP_SLE, ArgX, Two, "cond"); | |
BranchInst::Create(RetBB, RecurseBB, CondInst, BB); | |
// Create: ret int 1 | |
ReturnInst::Create(M->getContext(), One, RetBB); | |
// create fib(x-1) | |
Value *Sub = BinaryOperator::CreateSub(ArgX, One, "arg", RecurseBB); | |
Value *CallFibX1 = CallInst::Create(FibF, Sub, "fibx1", RecurseBB); | |
// create fib(x-2) | |
Sub = BinaryOperator::CreateSub(ArgX, Two, "arg", RecurseBB); | |
Value *CallFibX2 = CallInst::Create(FibF, Sub, "fibx2", RecurseBB); | |
// fib(x-1)+fib(x-2) | |
Value *Sum = | |
BinaryOperator::CreateAdd(CallFibX1, CallFibX2, "addresult", RecurseBB); | |
// Create the return instruction and add it to the basic block | |
ReturnInst::Create(M->getContext(), Sum, RecurseBB); | |
return FibF; | |
} | |
struct threadParams { | |
ExecutionEngine* EE; | |
Function* F; | |
int value; | |
}; | |
// We block the subthreads just before they begin to execute: | |
// we want all of them to call into the JIT at the same time, | |
// to verify that the locking is working correctly. | |
class WaitForThreads | |
{ | |
public: | |
WaitForThreads() | |
{ | |
n = 0; | |
waitFor = 0; | |
int result = pthread_cond_init( &condition, NULL ); | |
assert( result == 0 ); | |
result = pthread_mutex_init( &mutex, NULL ); | |
assert( result == 0 ); | |
} | |
~WaitForThreads() | |
{ | |
int result = pthread_cond_destroy( &condition ); | |
assert( result == 0 ); | |
result = pthread_mutex_destroy( &mutex ); | |
assert( result == 0 ); | |
} | |
// All threads will stop here until another thread calls releaseThreads | |
void block() | |
{ | |
int result = pthread_mutex_lock( &mutex ); | |
assert( result == 0 ); | |
n ++; | |
//~ std::cout << "block() n " << n << " waitFor " << waitFor << std::endl; | |
assert( waitFor == 0 || n <= waitFor ); | |
if ( waitFor > 0 && n == waitFor ) | |
{ | |
// There are enough threads blocked that we can release all of them | |
std::cout << "Unblocking threads from block()" << std::endl; | |
unblockThreads(); | |
} | |
else | |
{ | |
// We just need to wait until someone unblocks us | |
result = pthread_cond_wait( &condition, &mutex ); | |
assert( result == 0 ); | |
} | |
// unlock the mutex before returning | |
result = pthread_mutex_unlock( &mutex ); | |
assert( result == 0 ); | |
} | |
// If there are num or more threads blocked, it will signal them all | |
// Otherwise, this thread blocks until there are enough OTHER threads | |
// blocked | |
void releaseThreads( size_t num ) | |
{ | |
int result = pthread_mutex_lock( &mutex ); | |
assert( result == 0 ); | |
if ( n >= num ) { | |
std::cout << "Unblocking threads from releaseThreads()" << std::endl; | |
unblockThreads(); | |
} | |
else | |
{ | |
waitFor = num; | |
pthread_cond_wait( &condition, &mutex ); | |
} | |
// unlock the mutex before returning | |
result = pthread_mutex_unlock( &mutex ); | |
assert( result == 0 ); | |
} | |
private: | |
void unblockThreads() | |
{ | |
// Reset the counters to zero: this way, if any new threads | |
// enter while threads are exiting, they will block instead | |
// of triggering a new release of threads | |
n = 0; | |
// Reset waitFor to zero: this way, if waitFor threads enter | |
// while threads are exiting, they will block instead of | |
// triggering a new release of threads | |
waitFor = 0; | |
int result = pthread_cond_broadcast( &condition ); | |
assert(result == 0); result=result; | |
} | |
size_t n; | |
size_t waitFor; | |
pthread_cond_t condition; | |
pthread_mutex_t mutex; | |
}; | |
static WaitForThreads synchronize; | |
void* callFunc( void* param ) | |
{ | |
struct threadParams* p = (struct threadParams*) param; | |
// Call the `foo' function with no arguments: | |
std::vector<GenericValue> Args(1); | |
Args[0].IntVal = APInt(32, p->value); | |
synchronize.block(); // wait until other threads are at this point | |
GenericValue gv = p->EE->runFunction(p->F, Args); | |
return (void*)(intptr_t)gv.IntVal.getZExtValue(); | |
} | |
int main() { | |
InitializeNativeTarget(); | |
LLVMContext Context; | |
// Create some module to put our function into it. | |
Module *M = new Module("test", Context); | |
Function* add1F = createAdd1( M ); | |
Function* fibF = CreateFibFunction( M ); | |
// Now we create the JIT. | |
ExecutionEngine* EE = EngineBuilder(M).create(); | |
//~ std::cout << "We just constructed this LLVM module:\n\n" << *M; | |
//~ std::cout << "\n\nRunning foo: " << std::flush; | |
// Create one thread for add1 and two threads for fib | |
struct threadParams add1 = { EE, add1F, 1000 }; | |
struct threadParams fib1 = { EE, fibF, 39 }; | |
struct threadParams fib2 = { EE, fibF, 42 }; | |
pthread_t add1Thread; | |
int result = pthread_create( &add1Thread, NULL, callFunc, &add1 ); | |
if ( result != 0 ) { | |
std::cerr << "Could not create thread" << std::endl; | |
return 1; | |
} | |
pthread_t fibThread1; | |
result = pthread_create( &fibThread1, NULL, callFunc, &fib1 ); | |
if ( result != 0 ) { | |
std::cerr << "Could not create thread" << std::endl; | |
return 1; | |
} | |
pthread_t fibThread2; | |
result = pthread_create( &fibThread2, NULL, callFunc, &fib2 ); | |
if ( result != 0 ) { | |
std::cerr << "Could not create thread" << std::endl; | |
return 1; | |
} | |
synchronize.releaseThreads(3); // wait until other threads are at this point | |
void* returnValue; | |
result = pthread_join( add1Thread, &returnValue ); | |
if ( result != 0 ) { | |
std::cerr << "Could not join thread" << std::endl; | |
return 1; | |
} | |
std::cout << "Add1 returned " << intptr_t(returnValue) << std::endl; | |
result = pthread_join( fibThread1, &returnValue ); | |
if ( result != 0 ) { | |
std::cerr << "Could not join thread" << std::endl; | |
return 1; | |
} | |
std::cout << "Fib1 returned " << intptr_t(returnValue) << std::endl; | |
result = pthread_join( fibThread2, &returnValue ); | |
if ( result != 0 ) { | |
std::cerr << "Could not join thread" << std::endl; | |
return 1; | |
} | |
std::cout << "Fib2 returned " << intptr_t(returnValue) << std::endl; | |
return 0; | |
} |