Reactor: Add support for specifying and modifying default configuration settings.

rr::Config holds the full reactor configuration state.
rr::Config::Edit holds edits on a config, which can be applied on top of the current defaults.

Default configurations are updated atomically, preventing modifications to the default state from tearing.

Bug: b/137167988
Change-Id: Ib05f2cfc31ab22fb9a891a267fffe33c18691028
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/33768
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/Reactor/LLVMReactor.cpp b/src/Reactor/LLVMReactor.cpp
index c2c7ae7..2e4e5ba 100644
--- a/src/Reactor/LLVMReactor.cpp
+++ b/src/Reactor/LLVMReactor.cpp
@@ -105,6 +105,20 @@
 
 namespace
 {
+	// Default configuration settings. Must be accessed under mutex lock.
+	std::mutex defaultConfigLock;
+	rr::Config &defaultConfig()
+	{
+		// This uses a static in a function to avoid the cost of a global static
+		// initializer. See http://neugierig.org/software/chromium/notes/2011/08/static-initializers.html
+		static rr::Config config = rr::Config::Edit()
+			.set(rr::Optimization::Level::Default)
+			.add(rr::Optimization::Pass::ScalarReplAggregates)
+			.add(rr::Optimization::Pass::InstructionCombining)
+			.apply({});
+		return config;
+	}
+
 	class LLVMInitializer
 	{
 	protected:
@@ -228,7 +242,7 @@
 				std::unique_ptr<llvm::Module> module,
 				llvm::Function **funcs,
 				size_t count,
-				rr::OptimizationLevel optLevel) :
+				const rr::Config &config) :
 			resolver(createLegacyLookupResolver(
 				session,
 				[&](const std::string &name) {
@@ -251,7 +265,7 @@
 #ifdef ENABLE_RR_DEBUG_INFO
 				.setOptLevel(llvm::CodeGenOpt::None)
 #else
-				.setOptLevel(toLLVM(optLevel))
+				.setOptLevel(toLLVM(config.getOptimization().getLevel()))
 #endif // ENABLE_RR_DEBUG_INFO
 				.setMCPU(JITGlobals::get()->mcpu)
 				.setMArch(JITGlobals::get()->march)
@@ -318,15 +332,15 @@
 		}
 
 	private:
-		static ::llvm::CodeGenOpt::Level toLLVM(rr::OptimizationLevel level)
+		static ::llvm::CodeGenOpt::Level toLLVM(rr::Optimization::Level level)
 		{
 			switch (level)
 			{
-				case rr::OptimizationLevel::None:       return ::llvm::CodeGenOpt::None;
-				case rr::OptimizationLevel::Less:       return ::llvm::CodeGenOpt::Less;
-				case rr::OptimizationLevel::Default:    return ::llvm::CodeGenOpt::Default;
-				case rr::OptimizationLevel::Aggressive: return ::llvm::CodeGenOpt::Aggressive;
-				default: UNREACHABLE("Unknown OptimizationLevel %d", int(level));
+				case rr::Optimization::Level::None:       return ::llvm::CodeGenOpt::None;
+				case rr::Optimization::Level::Less:       return ::llvm::CodeGenOpt::Less;
+				case rr::Optimization::Level::Default:    return ::llvm::CodeGenOpt::Default;
+				case rr::Optimization::Level::Aggressive: return ::llvm::CodeGenOpt::Aggressive;
+				default: UNREACHABLE("Unknown Optimization Level %d", int(level));
 			}
 			return ::llvm::CodeGenOpt::Default;
 		}
@@ -343,15 +357,17 @@
 	class JITBuilder
 	{
 	public:
-		JITBuilder():
+		JITBuilder(const rr::Config &config) :
+			config(config),
 			module(new llvm::Module("", context)),
 			builder(new llvm::IRBuilder<>(context))
 		{
 			module->setDataLayout(JITGlobals::get()->dataLayout);
 		}
 
-		void optimize()
+		void optimize(const rr::Config &cfg)
 		{
+
 #ifdef ENABLE_RR_DEBUG_INFO
 			if (debugInfo != nullptr)
 			{
@@ -362,36 +378,35 @@
 			std::unique_ptr<llvm::legacy::PassManager> passManager(
 				new llvm::legacy::PassManager());
 
-			passManager->add(llvm::createSROAPass());
-
-			for(int pass = 0; pass < 10 && rr::optimization[pass] != rr::Disabled; pass++)
+			for(auto pass : cfg.getOptimization().getPasses())
 			{
-				switch(rr::optimization[pass])
+				switch(pass)
 				{
-				case rr::Disabled:                                                                       break;
-				case rr::CFGSimplification:    passManager->add(llvm::createCFGSimplificationPass());    break;
-				case rr::LICM:                 passManager->add(llvm::createLICMPass());                 break;
-				case rr::AggressiveDCE:        passManager->add(llvm::createAggressiveDCEPass());        break;
-				case rr::GVN:                  passManager->add(llvm::createGVNPass());                  break;
-				case rr::InstructionCombining: passManager->add(llvm::createInstructionCombiningPass()); break;
-				case rr::Reassociate:          passManager->add(llvm::createReassociatePass());          break;
-				case rr::DeadStoreElimination: passManager->add(llvm::createDeadStoreEliminationPass()); break;
-				case rr::SCCP:                 passManager->add(llvm::createSCCPPass());                 break;
-				case rr::ScalarReplAggregates: passManager->add(llvm::createSROAPass());                 break;
+				case rr::Optimization::Pass::Disabled:                                                                       break;
+				case rr::Optimization::Pass::CFGSimplification:    passManager->add(llvm::createCFGSimplificationPass());    break;
+				case rr::Optimization::Pass::LICM:                 passManager->add(llvm::createLICMPass());                 break;
+				case rr::Optimization::Pass::AggressiveDCE:        passManager->add(llvm::createAggressiveDCEPass());        break;
+				case rr::Optimization::Pass::GVN:                  passManager->add(llvm::createGVNPass());                  break;
+				case rr::Optimization::Pass::InstructionCombining: passManager->add(llvm::createInstructionCombiningPass()); break;
+				case rr::Optimization::Pass::Reassociate:          passManager->add(llvm::createReassociatePass());          break;
+				case rr::Optimization::Pass::DeadStoreElimination: passManager->add(llvm::createDeadStoreEliminationPass()); break;
+				case rr::Optimization::Pass::SCCP:                 passManager->add(llvm::createSCCPPass());                 break;
+				case rr::Optimization::Pass::ScalarReplAggregates: passManager->add(llvm::createSROAPass());                 break;
 				default:
-					UNREACHABLE("optimization[pass]: %d, pass: %d", int(rr::optimization[pass]), int(pass));
+					UNREACHABLE("pass: %d", int(pass));
 				}
 			}
 
 			passManager->run(*module);
 		}
 
-		rr::Routine *acquireRoutine(llvm::Function **funcs, size_t count, rr::OptimizationLevel optLevel)
+		rr::Routine *acquireRoutine(llvm::Function **funcs, size_t count, const rr::Config &cfg)
 		{
 			ASSERT(module);
-			return new JITRoutine(std::move(module), funcs, count, optLevel);
+			return new JITRoutine(std::move(module), funcs, count, cfg);
 		}
 
+		const rr::Config config;
 		llvm::LLVMContext context;
 		std::unique_ptr<llvm::Module> module;
 		std::unique_ptr<llvm::IRBuilder<>> builder;
@@ -1002,8 +1017,6 @@
 		return it->second;
 	}
 
-	Optimization optimization[10] = {InstructionCombining, Disabled};
-
 	// The abstract Type* types are implemented as LLVM types, except that
 	// 64-bit vectors are emulated using 128-bit ones to avoid use of MMX in x86
 	// and VFP in ARM, and eliminate the overhead of converting them to explicit
@@ -1134,7 +1147,7 @@
 		::codegenMutex.lock();   // Reactor and LLVM are currently not thread safe
 
 		ASSERT(jit == nullptr);
-		jit.reset(new JITBuilder());
+		jit.reset(new JITBuilder(Nucleus::getDefaultConfig()));
 	}
 
 	Nucleus::~Nucleus()
@@ -1143,8 +1156,29 @@
 		::codegenMutex.unlock();
 	}
 
-	Routine *Nucleus::acquireRoutine(const char *name, OptimizationLevel optimizationLevel)
+	void Nucleus::setDefaultConfig(const Config &cfg)
 	{
+		std::unique_lock<std::mutex> lock(::defaultConfigLock);
+		::defaultConfig() = cfg;
+	}
+
+	void Nucleus::adjustDefaultConfig(const Config::Edit &cfgEdit)
+	{
+		std::unique_lock<std::mutex> lock(::defaultConfigLock);
+		auto &config = ::defaultConfig();
+		config = cfgEdit.apply(config);
+	}
+
+	Config Nucleus::getDefaultConfig()
+	{
+		std::unique_lock<std::mutex> lock(::defaultConfigLock);
+		return ::defaultConfig();
+	}
+
+	Routine *Nucleus::acquireRoutine(const char *name, const Config::Edit &cfgEdit /* = Config::Edit::None */)
+	{
+		auto cfg = cfgEdit.apply(jit->config);
+
 		if(jit->builder->GetInsertBlock()->empty() || !jit->builder->GetInsertBlock()->back().isTerminator())
 		{
 			llvm::Type *type = jit->function->getReturnType();
@@ -1181,7 +1215,7 @@
 		}
 #endif // defined(ENABLE_RR_LLVM_IR_VERIFICATION) || !defined(NDEBUG)
 
-		optimize();
+		jit->optimize(cfg);
 
 		if(false)
 		{
@@ -1190,17 +1224,12 @@
 			jit->module->print(file, 0);
 		}
 
-		auto routine = jit->acquireRoutine(&jit->function, 1, optimizationLevel);
+		auto routine = jit->acquireRoutine(&jit->function, 1, cfg);
 		jit.reset();
 
 		return routine;
 	}
 
-	void Nucleus::optimize()
-	{
-		jit->optimize();
-	}
-
 	Value *Nucleus::allocateStackVariable(Type *type, int arraySize)
 	{
 		// Need to allocate it in the entry block for mem2reg to work
@@ -4678,7 +4707,7 @@
 	jit->builder->SetInsertPoint(resumeBlock);
 }
 
-Routine* Nucleus::acquireCoroutine(const char *name, OptimizationLevel optimizationLevel)
+Routine* Nucleus::acquireCoroutine(const char *name, const Config::Edit &cfgEdit /* = Config::Edit::None */)
 {
 	ASSERT_MSG(jit->coroutine.id != nullptr, "acquireCoroutine() called without a call to createCoroutine()");
 
@@ -4707,7 +4736,8 @@
 	pm.add(llvm::createCoroCleanupPass());
 	pm.run(*jit->module);
 
-	optimize();
+	auto cfg = cfgEdit.apply(jit->config);
+	jit->optimize(cfg);
 
 	if(false)
 	{
@@ -4720,7 +4750,7 @@
 	funcs[Nucleus::CoroutineEntryBegin] = jit->function;
 	funcs[Nucleus::CoroutineEntryAwait] = jit->coroutine.await;
 	funcs[Nucleus::CoroutineEntryDestroy] = jit->coroutine.destroy;
-	auto routine = jit->acquireRoutine(funcs, Nucleus::CoroutineEntryCount, optimizationLevel);
+	auto routine = jit->acquireRoutine(funcs, Nucleus::CoroutineEntryCount, cfg);
 	jit.reset();
 
 	return routine;