Replace rr::Config with an integer optimization level pragma

Extensive run-time control over the LLVM optimization passes we run
used to be part of SwiftConfig, but it hasn't been used in many years.
They're not relevant to Subzero at all, and passing around the config
adds unnecessary complexity.

We only need control over the optimization level for the LargeStack
Reactor unit test. It is also likely of value to keep a rudimentary
optimization level selection for future tiered compilation approaches.

This change eliminates rr::Config and replaces it with the simple
`OptimizationLevel` integer that can be set using Pragma(). Note that
while the values have no strict semantics, this is also true for typical
compiler optimization levels such as O0, O1, O2, etc.

Level 0 omits SROA and InstructionCombining passes, just like LLVM's
buildO0DefaultPipeline().

The SwiftConfig 'AsmEmitDir' was removed and instead one can now define
REACTOR_ASM_EMIT_DIR to change the asm output directory at build time.

Bug: b/191050320
Change-Id: I0283eac4520aef84a4b37ab5fd4c08224219a99f
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/65250
Reviewed-by: Alexis Hétu <sugoi@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/docs/RuntimeConfiguration.md b/docs/RuntimeConfiguration.md
index cbe5c19..5083a8a 100644
--- a/docs/RuntimeConfiguration.md
+++ b/docs/RuntimeConfiguration.md
@@ -8,15 +8,15 @@
 
 SwiftShader looks for a file named `SwiftShader.ini` (case-sensitive) in the working directory. At startup, SwiftShader reads this file, if it exists, and sets the options specified in it.
 
-The configuration file syntax is a series of key-value pairs, divided into sections. The following example shows three key-value pairs in two sections (`ThreadCount` and `AffinityMask` in the `[Processor]` section, and `AsmEmitDir` in the `[Debug]` section):
+The configuration file syntax is a series of key-value pairs, divided into sections. The following example shows three key-value pairs in two sections (`ThreadCount` and `AffinityMask` in the `[Processor]` section, and `EnableSpirvProfiling` in the `[Profiler]` section):
 ```
 [Processor]
 ThreadCount=4
 AffinityMask=0xf
 
 # Comment
-[Debug]
-AsmEmitDir=/home/user/asm_dumps
+[Profiler]
+EnableSpirvProfiling=true
 ```
 
 The syntax rules are as follows:
diff --git a/src/Reactor/Coroutine.hpp b/src/Reactor/Coroutine.hpp
index dd2fd92..8c9e15f 100644
--- a/src/Reactor/Coroutine.hpp
+++ b/src/Reactor/Coroutine.hpp
@@ -139,7 +139,7 @@
 	// finalize() *must* be called explicitly on the same thread that
 	// instantiates the Coroutine instance if operator() is to be invoked on
 	// different threads.
-	inline void finalize(const char *name = "coroutine", const Config::Edit *cfg = nullptr);
+	inline void finalize(const char *name = "coroutine");
 
 	// Starts execution of the coroutine and returns a unique_ptr to a
 	// Stream<> that exposes the await() function for obtaining yielded
@@ -169,11 +169,11 @@
 }
 
 template<typename Return, typename... Arguments>
-void Coroutine<Return(Arguments...)>::finalize(const char *name /*= "coroutine"*/, const Config::Edit *cfg /* = nullptr */)
+void Coroutine<Return(Arguments...)>::finalize(const char *name /*= "coroutine"*/)
 {
 	if(core != nullptr)
 	{
-		routine = core->acquireCoroutine(name, cfg);
+		routine = core->acquireCoroutine(name);
 		core.reset(nullptr);
 	}
 }
diff --git a/src/Reactor/LLVMJIT.cpp b/src/Reactor/LLVMJIT.cpp
index b15987a..977c292 100644
--- a/src/Reactor/LLVMJIT.cpp
+++ b/src/Reactor/LLVMJIT.cpp
@@ -62,6 +62,10 @@
     __pragma(warning(pop))
 #endif
 
+#ifndef REACTOR_ASM_EMIT_DIR
+#	define REACTOR_ASM_EMIT_DIR "./"
+#endif
+
 #if defined(_WIN64)
         extern "C" void __chkstk();
 #elif defined(_WIN32)
@@ -147,14 +151,14 @@
 public:
 	static JITGlobals *get();
 
-	llvm::orc::JITTargetMachineBuilder getTargetMachineBuilder(rr::Optimization::Level optLevel) const;
+	llvm::orc::JITTargetMachineBuilder getTargetMachineBuilder() const;
 	const llvm::DataLayout &getDataLayout() const;
 	const llvm::Triple &getTargetTriple() const;
 
 private:
 	JITGlobals(llvm::orc::JITTargetMachineBuilder &&jitTargetMachineBuilder, llvm::DataLayout &&dataLayout);
 
-	static llvm::CodeGenOpt::Level toLLVM(rr::Optimization::Level level);
+	static llvm::CodeGenOpt::Level toLLVM(int level);
 
 	const llvm::orc::JITTargetMachineBuilder jitTargetMachineBuilder;
 	const llvm::DataLayout dataLayout;
@@ -219,10 +223,10 @@
 	return &instance;
 }
 
-llvm::orc::JITTargetMachineBuilder JITGlobals::getTargetMachineBuilder(rr::Optimization::Level optLevel) const
+llvm::orc::JITTargetMachineBuilder JITGlobals::getTargetMachineBuilder() const
 {
 	llvm::orc::JITTargetMachineBuilder out = jitTargetMachineBuilder;
-	out.setCodeGenOptLevel(toLLVM(optLevel));
+	out.setCodeGenOptLevel(toLLVM(rr::getPragmaState(rr::OptimizationLevel)));
 
 	return out;
 }
@@ -243,7 +247,7 @@
 {
 }
 
-llvm::CodeGenOpt::Level JITGlobals::toLLVM(rr::Optimization::Level level)
+llvm::CodeGenOpt::Level JITGlobals::toLLVM(int level)
 {
 	// TODO(b/173257647): MemorySanitizer instrumentation produces IR which takes
 	// a lot longer to process by the machine code optimization passes. Disabling
@@ -255,10 +259,10 @@
 
 	switch(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;
+	case 0: return llvm::CodeGenOpt::None;
+	case 1: return llvm::CodeGenOpt::Less;
+	case 2: return llvm::CodeGenOpt::Default;
+	case 3: return llvm::CodeGenOpt::Aggressive;
 	default: UNREACHABLE("Unknown Optimization Level %d", int(level));
 	}
 
@@ -712,8 +716,7 @@
 	    std::unique_ptr<llvm::LLVMContext> context,
 	    const char *name,
 	    llvm::Function **funcs,
-	    size_t count,
-	    const rr::Config &config)
+	    size_t count)
 	    : name(name)
 #if LLVM_VERSION_MAJOR >= 13
 	    , session(std::move(Unwrap(llvm::orc::SelfExecutorProcessControl::Create())))
@@ -774,15 +777,15 @@
 		}
 
 #ifdef ENABLE_RR_EMIT_ASM_FILE
-		const auto asmFilename = rr::AsmFile::generateFilename(config.getDebugConfig().asmEmitDir, name);
-		rr::AsmFile::emitAsmFile(asmFilename, JITGlobals::get()->getTargetMachineBuilder(config.getOptimization().getLevel()), *module);
+		const auto asmFilename = rr::AsmFile::generateFilename(REACTOR_ASM_EMIT_DIR, name);
+		rr::AsmFile::emitAsmFile(asmFilename, JITGlobals::get()->getTargetMachineBuilder(), *module);
 #endif
 
 		// Once the module is passed to the compileLayer, the llvm::Functions are freed.
 		// Make sure funcs are not referenced after this point.
 		funcs = nullptr;
 
-		llvm::orc::IRCompileLayer compileLayer(session, objectLayer, std::make_unique<llvm::orc::ConcurrentIRCompiler>(JITGlobals::get()->getTargetMachineBuilder(config.getOptimization().getLevel())));
+		llvm::orc::IRCompileLayer compileLayer(session, objectLayer, std::make_unique<llvm::orc::ConcurrentIRCompiler>(JITGlobals::get()->getTargetMachineBuilder()));
 		llvm::orc::JITDylib &dylib(Unwrap(session.createJITDylib("<routine>")));
 		dylib.addGenerator(std::make_unique<ExternalSymbolGenerator>());
 
@@ -841,9 +844,8 @@
 
 namespace rr {
 
-JITBuilder::JITBuilder(const rr::Config &config)
-    : config(config)
-    , context(new llvm::LLVMContext())
+JITBuilder::JITBuilder()
+    : context(new llvm::LLVMContext())
     , module(new llvm::Module("", *context))
     , builder(new llvm::IRBuilder<>(*context))
 {
@@ -857,7 +859,7 @@
 	}
 }
 
-void JITBuilder::runPasses(const rr::Config &cfg)
+void JITBuilder::runPasses()
 {
 	if(coroutine.id)  // Run manadory coroutine transforms.
 	{
@@ -927,27 +929,10 @@
 		pm.addPass(llvm::createModuleToFunctionPassAdaptor(llvm::MemorySanitizerPass(msanOpts)));
 	}
 
-	for(auto pass : cfg.getOptimization().getPasses())
+	if(getPragmaState(OptimizationLevel) > 0)
 	{
-		switch(pass)
-		{
-		case rr::Optimization::Pass::Disabled: break;
-		case rr::Optimization::Pass::CFGSimplification: fpm.addPass(llvm::SimplifyCFGPass()); break;
-		case rr::Optimization::Pass::LICM:
-			fpm.addPass(llvm::createFunctionToLoopPassAdaptor(
-			    llvm::LICMPass(llvm::SetLicmMssaOptCap, llvm::SetLicmMssaNoAccForPromotionCap, true)));
-			break;
-		case rr::Optimization::Pass::AggressiveDCE: fpm.addPass(llvm::ADCEPass()); break;
-		case rr::Optimization::Pass::GVN: fpm.addPass(llvm::GVNPass()); break;
-		case rr::Optimization::Pass::InstructionCombining: fpm.addPass(llvm::InstCombinePass()); break;
-		case rr::Optimization::Pass::Reassociate: fpm.addPass(llvm::ReassociatePass()); break;
-		case rr::Optimization::Pass::DeadStoreElimination: fpm.addPass(llvm::DSEPass()); break;
-		case rr::Optimization::Pass::SCCP: fpm.addPass(llvm::SCCPPass()); break;
-		case rr::Optimization::Pass::ScalarReplAggregates: fpm.addPass(llvm::SROAPass()); break;
-		case rr::Optimization::Pass::EarlyCSEPass: fpm.addPass(llvm::EarlyCSEPass()); break;
-		default:
-			UNREACHABLE("pass: %d", int(pass));
-		}
+		fpm.addPass(llvm::SROAPass());
+		fpm.addPass(llvm::InstCombinePass());
 	}
 
 	if(!fpm.isEmpty())
@@ -964,34 +949,20 @@
 		passManager.add(llvm::createMemorySanitizerLegacyPassPass());
 	}
 
-	for(auto pass : cfg.getOptimization().getPasses())
+	if(getPragmaState(OptimizationLevel) > 0)
 	{
-		switch(pass)
-		{
-		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;
-		case rr::Optimization::Pass::EarlyCSEPass: passManager.add(llvm::createEarlyCSEPass()); break;
-		default:
-			UNREACHABLE("pass: %d", int(pass));
-		}
+		passManager.add(llvm::createSROAPass());
+		passManager.add(llvm::createInstructionCombiningPass());
 	}
 
 	passManager.run(*module);
 #endif
 }
 
-std::shared_ptr<rr::Routine> JITBuilder::acquireRoutine(const char *name, llvm::Function **funcs, size_t count, const rr::Config &cfg)
+std::shared_ptr<rr::Routine> JITBuilder::acquireRoutine(const char *name, llvm::Function **funcs, size_t count)
 {
 	ASSERT(module);
-	return std::make_shared<JITRoutine>(std::move(module), std::move(context), name, funcs, count, cfg);
+	return std::make_shared<JITRoutine>(std::move(module), std::move(context), name, funcs, count);
 }
 
 }  // namespace rr
diff --git a/src/Reactor/LLVMReactor.cpp b/src/Reactor/LLVMReactor.cpp
index d72e405..a181f29 100644
--- a/src/Reactor/LLVMReactor.cpp
+++ b/src/Reactor/LLVMReactor.cpp
@@ -66,19 +66,6 @@
 // for destructing objects at exit. See crbug.com/1074222
 thread_local rr::JITBuilder *jit = nullptr;
 
-// 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()
-	                               .add(rr::Optimization::Pass::ScalarReplAggregates)
-	                               .add(rr::Optimization::Pass::InstructionCombining)
-	                               .apply({});
-	return config;
-}
-
 llvm::Value *lowerPAVG(llvm::Value *x, llvm::Value *y)
 {
 	llvm::VectorType *ty = llvm::cast<llvm::VectorType>(x->getType());
@@ -544,7 +531,7 @@
 	ASSERT(Variable::unmaterializedVariables == nullptr);
 #endif
 
-	jit = new JITBuilder(Nucleus::getDefaultConfig());
+	jit = new JITBuilder();
 	Variable::unmaterializedVariables = new Variable::UnmaterializedVariables();
 }
 
@@ -557,26 +544,7 @@
 	jit = nullptr;
 }
 
-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();
-}
-
-std::shared_ptr<Routine> Nucleus::acquireRoutine(const char *name, const Config::Edit *cfgEdit /* = nullptr */)
+std::shared_ptr<Routine> Nucleus::acquireRoutine(const char *name)
 {
 	if(jit->builder->GetInsertBlock()->empty() || !jit->builder->GetInsertBlock()->back().isTerminator())
 	{
@@ -595,14 +563,8 @@
 	std::shared_ptr<Routine> routine;
 
 	auto acquire = [&](rr::JITBuilder *jit) {
-		// ::jit is thread-local, so when this is executed on a separate thread (see JIT_IN_SEPARATE_THREAD)
-		// it needs to only use the jit variable passed in as an argument.
-
-		Config cfg = jit->config;
-		if(cfgEdit)
-		{
-			cfg = cfgEdit->apply(jit->config);
-		}
+	// ::jit is thread-local, so when this is executed on a separate thread (see JIT_IN_SEPARATE_THREAD)
+	// it needs to only use the jit variable passed in as an argument.
 
 #ifdef ENABLE_RR_DEBUG_INFO
 		if(jit->debugInfo != nullptr)
@@ -618,7 +580,7 @@
 			jit->module->print(file, 0);
 		}
 
-		jit->runPasses(cfg);
+		jit->runPasses();
 
 		if(false)
 		{
@@ -627,7 +589,7 @@
 			jit->module->print(file, 0);
 		}
 
-		routine = jit->acquireRoutine(name, &jit->function, 1, cfg);
+		routine = jit->acquireRoutine(name, &jit->function, 1);
 	};
 
 #ifdef JIT_IN_SEPARATE_THREAD
@@ -4351,7 +4313,7 @@
 	jit->builder->SetInsertPoint(resumeBlock);
 }
 
-std::shared_ptr<Routine> Nucleus::acquireCoroutine(const char *name, const Config::Edit *cfgEdit /* = nullptr */)
+std::shared_ptr<Routine> Nucleus::acquireCoroutine(const char *name)
 {
 	if(jit->coroutine.id)
 	{
@@ -4385,12 +4347,7 @@
 		jit->module->print(file, 0);
 	}
 
-	Config cfg = jit->config;
-	if(cfgEdit)
-	{
-		cfg = cfgEdit->apply(jit->config);
-	}
-	jit->runPasses(cfg);
+	jit->runPasses();
 
 	if(false)
 	{
@@ -4404,7 +4361,7 @@
 	funcs[Nucleus::CoroutineEntryAwait] = jit->coroutine.await;
 	funcs[Nucleus::CoroutineEntryDestroy] = jit->coroutine.destroy;
 
-	auto routine = jit->acquireRoutine(name, funcs, Nucleus::CoroutineEntryCount, cfg);
+	auto routine = jit->acquireRoutine(name, funcs, Nucleus::CoroutineEntryCount);
 
 	delete jit;
 	jit = nullptr;
diff --git a/src/Reactor/LLVMReactor.hpp b/src/Reactor/LLVMReactor.hpp
index dad54a7..19b9040 100644
--- a/src/Reactor/LLVMReactor.hpp
+++ b/src/Reactor/LLVMReactor.hpp
@@ -82,19 +82,17 @@
 void Nop();
 
 class Routine;
-class Config;
 
 // JITBuilder holds all the LLVM state for building routines.
 class JITBuilder
 {
 public:
-	JITBuilder(const rr::Config &config);
+	JITBuilder();
 
-	void runPasses(const rr::Config &cfg);
+	void runPasses();
 
-	std::shared_ptr<rr::Routine> acquireRoutine(const char *name, llvm::Function **funcs, size_t count, const rr::Config &cfg);
+	std::shared_ptr<rr::Routine> acquireRoutine(const char *name, llvm::Function **funcs, size_t count);
 
-	const Config config;
 	std::unique_ptr<llvm::LLVMContext> context;
 	std::unique_ptr<llvm::Module> module;
 	std::unique_ptr<llvm::IRBuilder<>> builder;
diff --git a/src/Reactor/Nucleus.hpp b/src/Reactor/Nucleus.hpp
index 502fb24..aa65568 100644
--- a/src/Reactor/Nucleus.hpp
+++ b/src/Reactor/Nucleus.hpp
@@ -39,134 +39,6 @@
 class BasicBlock;
 class Routine;
 
-// Optimization holds the optimization settings for code generation.
-class Optimization
-{
-public:
-	enum class Level
-	{
-		None,
-		Less,
-		Default,
-		Aggressive,
-	};
-
-	enum class Pass
-	{
-		Disabled,
-		InstructionCombining,
-		CFGSimplification,
-		LICM,
-		AggressiveDCE,
-		GVN,
-		Reassociate,
-		DeadStoreElimination,
-		SCCP,
-		ScalarReplAggregates,
-		EarlyCSEPass,
-
-		Count,
-	};
-
-	using Passes = std::vector<Pass>;
-
-	Optimization(Level level = Level::Default, const Passes &passes = {})
-	    : level(level)
-	    , passes(passes)
-	{
-#if defined(REACTOR_DEFAULT_OPT_LEVEL)
-		{
-			this->level = Level::REACTOR_DEFAULT_OPT_LEVEL;
-		}
-#endif
-	}
-
-	Level getLevel() const { return level; }
-	const Passes &getPasses() const { return passes; }
-
-private:
-	Level level = Level::Default;
-	Passes passes;
-};
-
-struct DebugConfig
-{
-	std::string asmEmitDir = "";
-};
-
-// Config holds the Reactor configuration settings.
-class Config
-{
-public:
-	// Edit holds a number of modifications to a config, that can be applied
-	// on an existing Config to produce a new Config with the specified
-	// changes.
-	class Edit
-	{
-	public:
-		Edit &set(Optimization::Level level)
-		{
-			optLevel = level;
-			optLevelChanged = true;
-			return *this;
-		}
-		Edit &add(Optimization::Pass pass)
-		{
-			optPassEdits.push_back({ ListEdit::Add, pass });
-			return *this;
-		}
-		Edit &remove(Optimization::Pass pass)
-		{
-			optPassEdits.push_back({ ListEdit::Remove, pass });
-			return *this;
-		}
-		Edit &clearOptimizationPasses()
-		{
-			optPassEdits.push_back({ ListEdit::Clear, Optimization::Pass::Disabled });
-			return *this;
-		}
-		Edit &setDebugConfig(const DebugConfig &cfg)
-		{
-			debugCfg = cfg;
-			debugCfgChanged = true;
-			return *this;
-		}
-
-		Config apply(const Config &cfg) const;
-
-	private:
-		enum class ListEdit
-		{
-			Add,
-			Remove,
-			Clear
-		};
-		using OptPassesEdit = std::pair<ListEdit, Optimization::Pass>;
-
-		template<typename T>
-		void apply(const std::vector<std::pair<ListEdit, T>> &edits, std::vector<T> &list) const;
-
-		Optimization::Level optLevel;
-		bool optLevelChanged = false;
-		std::vector<OptPassesEdit> optPassEdits;
-		DebugConfig debugCfg;
-		bool debugCfgChanged = false;
-	};
-
-	Config() = default;
-	Config(const Optimization &optimization, const DebugConfig &debugCfg)
-	    : optimization(optimization)
-	    , debugCfg(debugCfg)
-	{}
-
-	const Optimization &getOptimization() const { return optimization; }
-	const DebugConfig &getDebugConfig() const { return debugCfg; }
-
-private:
-	Optimization optimization;
-	DebugConfig debugCfg;
-};
-
 class Nucleus
 {
 public:
@@ -174,13 +46,7 @@
 
 	virtual ~Nucleus();
 
-	// Default configuration to use when no other configuration is specified.
-	// The new configuration will be applied to subsequent reactor calls.
-	static void setDefaultConfig(const Config &cfg);
-	static void adjustDefaultConfig(const Config::Edit &cfgEdit);
-	static Config getDefaultConfig();
-
-	std::shared_ptr<Routine> acquireRoutine(const char *name, const Config::Edit *cfgEdit = nullptr);
+	std::shared_ptr<Routine> acquireRoutine(const char *name);
 
 	static Value *allocateStackVariable(Type *type, int arraySize = 0);
 	static BasicBlock *createBasicBlock();
@@ -217,7 +83,7 @@
 	static void yield(Value *val);
 	// Called to finalize coroutine creation. After this call, Routine::getEntry can be called to retrieve the entry point to any
 	// of the three coroutine functions. Called by Coroutine::finalize.
-	std::shared_ptr<Routine> acquireCoroutine(const char *name, const Config::Edit *cfg = nullptr);
+	std::shared_ptr<Routine> acquireCoroutine(const char *name);
 	// Called by Coroutine::operator() to execute CoroutineEntryBegin wrapped up in func. This is needed in case
 	// the call must be run on a separate thread of execution (e.g. on a fiber).
 	static CoroutineHandle invokeCoroutineBegin(Routine &routine, std::function<CoroutineHandle()> func);
diff --git a/src/Reactor/Pragma.cpp b/src/Reactor/Pragma.cpp
index f8780ff..73d5920 100644
--- a/src/Reactor/Pragma.cpp
+++ b/src/Reactor/Pragma.cpp
@@ -30,7 +30,8 @@
 
 struct PragmaState
 {
-	bool memorySanitizerInstrumentation;
+	bool memorySanitizerInstrumentation = false;
+	int optimizationLevel = 2;  // Default
 };
 
 // The initialization of static thread-local data is not observed by MemorySanitizer
@@ -73,6 +74,20 @@
 	}
 }
 
+void Pragma(IntegerPragmaOption option, int value)
+{
+	PragmaState &state = ::getPragmaState();
+
+	switch(option)
+	{
+	case OptimizationLevel:
+		state.optimizationLevel = value;
+		break;
+	default:
+		UNSUPPORTED("Unknown integer pragma option %d", int(option));
+	}
+}
+
 bool getPragmaState(BooleanPragmaOption option)
 {
 	PragmaState &state = ::getPragmaState();
@@ -87,16 +102,44 @@
 	}
 }
 
+int getPragmaState(IntegerPragmaOption option)
+{
+	PragmaState &state = ::getPragmaState();
+
+	switch(option)
+	{
+	case OptimizationLevel:
+		return state.optimizationLevel;
+	default:
+		UNSUPPORTED("Unknown integer pragma option %d", int(option));
+		return 0;
+	}
+}
+
 ScopedPragma::ScopedPragma(BooleanPragmaOption option, bool enable)
 {
 	oldState = BooleanPragma{ option, getPragmaState(option) };
 	Pragma(option, enable);
 }
 
+ScopedPragma::ScopedPragma(IntegerPragmaOption option, int value)
+{
+	oldState = IntegerPragma{ option, getPragmaState(option) };
+	Pragma(option, value);
+}
+
 ScopedPragma::~ScopedPragma()
 {
-	auto &restore = std::get<BooleanPragma>(oldState);
-	Pragma(restore.option, restore.enable);
+	if(std::holds_alternative<BooleanPragma>(oldState))
+	{
+		auto &restore = std::get<BooleanPragma>(oldState);
+		Pragma(restore.option, restore.enable);
+	}
+	else
+	{
+		auto &restore = std::get<IntegerPragma>(oldState);
+		Pragma(restore.option, restore.value);
+	}
 }
 
 }  // namespace rr
\ No newline at end of file
diff --git a/src/Reactor/Pragma.hpp b/src/Reactor/Pragma.hpp
index 1dd9b16..3b86a03 100644
--- a/src/Reactor/Pragma.hpp
+++ b/src/Reactor/Pragma.hpp
@@ -24,12 +24,19 @@
 	MemorySanitizerInstrumentation,
 };
 
+enum IntegerPragmaOption
+{
+	OptimizationLevel,  // O0, O1, O2 (default), O3
+};
+
 void Pragma(BooleanPragmaOption option, bool enable);
+void Pragma(IntegerPragmaOption option, int value);
 
 class ScopedPragma
 {
 public:
 	ScopedPragma(BooleanPragmaOption option, bool enable);
+	ScopedPragma(IntegerPragmaOption option, int value);
 
 	~ScopedPragma();
 
@@ -40,9 +47,15 @@
 		bool enable;
 	};
 
-	std::variant<BooleanPragma> oldState;
+	struct IntegerPragma
+	{
+		IntegerPragmaOption option;
+		int value;
+	};
+
+	std::variant<BooleanPragma, IntegerPragma> oldState;
 };
 
 }  // namespace rr
 
-#endif  // rr_Pragma_hpp
\ No newline at end of file
+#endif  // rr_Pragma_hpp
diff --git a/src/Reactor/PragmaInternals.hpp b/src/Reactor/PragmaInternals.hpp
index 59296db..ecbf759 100644
--- a/src/Reactor/PragmaInternals.hpp
+++ b/src/Reactor/PragmaInternals.hpp
@@ -20,6 +20,7 @@
 namespace rr {
 
 bool getPragmaState(BooleanPragmaOption option);
+int getPragmaState(IntegerPragmaOption option);
 
 }  // namespace rr
 
diff --git a/src/Reactor/Reactor.cpp b/src/Reactor/Reactor.cpp
index 3d6459f..5b344f9 100644
--- a/src/Reactor/Reactor.cpp
+++ b/src/Reactor/Reactor.cpp
@@ -36,38 +36,6 @@
 
 namespace rr {
 
-Config Config::Edit::apply(const Config &cfg) const
-{
-	auto newDebugCfg = debugCfgChanged ? debugCfg : cfg.debugCfg;
-	auto level = optLevelChanged ? optLevel : cfg.optimization.getLevel();
-	auto passes = cfg.optimization.getPasses();
-	apply(optPassEdits, passes);
-	return Config{ Optimization{ level, passes }, newDebugCfg };
-}
-
-template<typename T>
-void rr::Config::Edit::apply(const std::vector<std::pair<ListEdit, T>> &edits, std::vector<T> &list) const
-{
-	for(auto &edit : edits)
-	{
-		switch(edit.first)
-		{
-		case ListEdit::Add:
-			list.push_back(edit.second);
-			break;
-		case ListEdit::Remove:
-			list.erase(std::remove_if(list.begin(), list.end(), [&](T item) {
-				           return item == edit.second;
-			           }),
-			           list.end());
-			break;
-		case ListEdit::Clear:
-			list.clear();
-			break;
-		}
-	}
-}
-
 thread_local Variable::UnmaterializedVariables *Variable::unmaterializedVariables = nullptr;
 
 void Variable::UnmaterializedVariables::add(const Variable *v)
diff --git a/src/Reactor/Reactor.hpp b/src/Reactor/Reactor.hpp
index aeaad54..0f992da 100644
--- a/src/Reactor/Reactor.hpp
+++ b/src/Reactor/Reactor.hpp
@@ -2227,7 +2227,6 @@
 	}
 
 	std::shared_ptr<Routine> operator()(const char *name, ...);
-	std::shared_ptr<Routine> operator()(const Config::Edit &cfg, const char *name, ...);
 
 protected:
 	std::unique_ptr<Nucleus> core;
@@ -2266,12 +2265,6 @@
 	{
 		return RoutineType(BaseType::operator()(name, std::forward<VarArgs>(varArgs)...));
 	}
-
-	template<typename... VarArgs>
-	RoutineType operator()(const Config::Edit &cfg, const char *name, VarArgs... varArgs)
-	{
-		return RoutineType(BaseType::operator()(cfg, name, std::forward<VarArgs>(varArgs)...));
-	}
 };
 
 RValue<Long> Ticks();
@@ -2867,23 +2860,7 @@
 	vsnprintf(fullName, 1024, name, vararg);
 	va_end(vararg);
 
-	auto routine = core->acquireRoutine(fullName, nullptr);
-	core.reset(nullptr);
-
-	return routine;
-}
-
-template<typename Return, typename... Arguments>
-std::shared_ptr<Routine> Function<Return(Arguments...)>::operator()(const Config::Edit &cfg, const char *name, ...)
-{
-	char fullName[1024 + 1];
-
-	va_list vararg;
-	va_start(vararg, name);
-	vsnprintf(fullName, 1024, name, vararg);
-	va_end(vararg);
-
-	auto routine = core->acquireRoutine(fullName, &cfg);
+	auto routine = core->acquireRoutine(fullName);
 	core.reset(nullptr);
 
 	return routine;
diff --git a/src/Reactor/SubzeroReactor.cpp b/src/Reactor/SubzeroReactor.cpp
index 8fe7e31..c5c083a 100644
--- a/src/Reactor/SubzeroReactor.cpp
+++ b/src/Reactor/SubzeroReactor.cpp
@@ -19,6 +19,7 @@
 
 #include "ExecutableMemory.hpp"
 #include "Optimizer.hpp"
+#include "PragmaInternals.hpp"
 
 #include "src/IceCfg.h"
 #include "src/IceCfgNode.h"
@@ -203,17 +204,6 @@
 // Used to automatically invoke llvm_shutdown() when driver is unloaded
 llvm::llvm_shutdown_obj llvmShutdownObj;
 
-// 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()
-	                               .apply({});
-	return config;
-}
-
 Ice::GlobalContext *context = nullptr;
 Ice::Cfg *function = nullptr;
 Ice::CfgNode *entryBlock = nullptr;
@@ -255,15 +245,15 @@
 #	define __x86_64__ 1
 #endif
 
-Ice::OptLevel toIce(rr::Optimization::Level level)
+Ice::OptLevel toIce(int level)
 {
 	switch(level)
 	{
-	// Note that Opt_0 and Opt_1 are not implemented by Subzero
-	case rr::Optimization::Level::None: return Ice::Opt_m1;
-	case rr::Optimization::Level::Less: return Ice::Opt_m1;
-	case rr::Optimization::Level::Default: return Ice::Opt_2;
-	case rr::Optimization::Level::Aggressive: return Ice::Opt_2;
+	// Note that O0 and O1 are not implemented by Subzero
+	case 0: return Ice::Opt_m1;
+	case 1: return Ice::Opt_m1;
+	case 2: return Ice::Opt_2;
+	case 3: return Ice::Opt_2;
 	default: UNREACHABLE("Unknown Optimization Level %d", int(level));
 	}
 	return Ice::Opt_2;
@@ -907,7 +897,7 @@
 	Flags.setTargetInstructionSet(CPUID::SSE4_1 ? Ice::X86InstructionSet_SSE4_1 : Ice::X86InstructionSet_SSE2);
 #endif
 	Flags.setOutFileType(Ice::FT_Elf);
-	Flags.setOptLevel(toIce(getDefaultConfig().getOptimization().getLevel()));
+	Flags.setOptLevel(toIce(rr::getPragmaState(rr::OptimizationLevel)));
 	Flags.setVerbose(subzeroDumpEnabled ? Ice::IceV_Most : Ice::IceV_None);
 	Flags.setDisableHybridAssembly(true);
 
@@ -976,29 +966,10 @@
 	::codegenMutex.unlock();
 }
 
-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();
-}
-
 // This function lowers and produces executable binary code in memory for the input functions,
 // and returns a Routine with the entry points to these functions.
 template<size_t Count>
-static std::shared_ptr<Routine> acquireRoutine(Ice::Cfg *const (&functions)[Count], const char *const (&names)[Count], const Config::Edit *cfgEdit)
+static std::shared_ptr<Routine> acquireRoutine(Ice::Cfg *const (&functions)[Count], const char *const (&names)[Count])
 {
 	// This logic is modeled after the IceCompiler, as well as GlobalContext::translateFunctions
 	// and GlobalContext::emitItems.
@@ -1101,10 +1072,10 @@
 	return std::shared_ptr<Routine>(handoffRoutine);
 }
 
-std::shared_ptr<Routine> Nucleus::acquireRoutine(const char *name, const Config::Edit *cfgEdit /* = nullptr */)
+std::shared_ptr<Routine> Nucleus::acquireRoutine(const char *name)
 {
 	finalizeFunction();
-	return rr::acquireRoutine({ ::function }, { name }, cfgEdit);
+	return rr::acquireRoutine({ ::function }, { name });
 }
 
 Value *Nucleus::allocateStackVariable(Type *t, int arraySize)
@@ -5077,7 +5048,7 @@
 {
 }
 
-std::shared_ptr<Routine> Nucleus::acquireCoroutine(const char *name, const Config::Edit *cfgEdit /* = nullptr */)
+std::shared_ptr<Routine> Nucleus::acquireCoroutine(const char *name)
 {
 	if(::coroGen)
 	{
@@ -5095,8 +5066,7 @@
 		::coroYieldType = nullptr;
 
 		auto routine = rr::acquireRoutine({ ::function, awaitFunc.get(), destroyFunc.get() },
-		                                  { name, "await", "destroy" },
-		                                  cfgEdit);
+		                                  { name, "await", "destroy" });
 
 		return routine;
 	}
@@ -5110,7 +5080,7 @@
 		::coroYieldType = nullptr;
 
 		// Not an actual coroutine (no yields), so return stubs for await and destroy
-		auto routine = rr::acquireRoutine({ ::function }, { name }, cfgEdit);
+		auto routine = rr::acquireRoutine({ ::function }, { name });
 
 		auto routineImpl = std::static_pointer_cast<ELFMemoryStreamer>(routine);
 		routineImpl->setEntry(Nucleus::CoroutineEntryAwait, reinterpret_cast<const void *>(&coroutineEntryAwaitStub));
diff --git a/src/System/SwiftConfig.cpp b/src/System/SwiftConfig.cpp
index 680bf4c..9e00a5d 100644
--- a/src/System/SwiftConfig.cpp
+++ b/src/System/SwiftConfig.cpp
@@ -13,10 +13,10 @@
 // limitations under the License.
 
 #include "SwiftConfig.hpp"
+
 #include "CPUID.hpp"
 #include "Configurator.hpp"
 #include "Debug.hpp"
-
 #include "marl/scheduler.h"
 
 #include <algorithm>
@@ -92,11 +92,11 @@
 
 	// Processor flags.
 	config.threadCount = ini.getInteger<uint32_t>("Processor", "ThreadCount", 0);
-	config.affinityMask = ini.getInteger<uint64_t>("Processor", "AffinityMask", 0xffffffffffffffff);
+	config.affinityMask = ini.getInteger<uint64_t>("Processor", "AffinityMask", 0xFFFFFFFFFFFFFFFFu);
 	if(config.affinityMask == 0)
 	{
 		warn("Affinity mask is empty, using all-cores affinity\n");
-		config.affinityMask = 0xffffffffffffffff;
+		config.affinityMask = 0xFFFFFFFFFFFFFFFFu;
 	}
 	std::string affinityPolicy = toLowerStr(ini.getValue("Processor", "AffinityPolicy", "any"));
 	if(affinityPolicy == "one")
@@ -109,13 +109,6 @@
 		config.affinityPolicy = Configuration::AffinityPolicy::AnyOf;
 	}
 
-	// Debug flags.
-	config.asmEmitDir = ini.getValue("Debug", "AsmEmitDir");
-	if(config.asmEmitDir.size() > 0 && *config.asmEmitDir.rend() != '/')
-	{
-		config.asmEmitDir.push_back('/');
-	}
-
 	// Profiling flags.
 	config.enableSpirvProfiling = ini.getBoolean("Profiler", "EnableSpirvProfiling");
 	config.spvProfilingReportPeriodMs = ini.getInteger<uint64_t>("Profiler", "SpirvProfilingReportPeriodMs");
@@ -147,10 +140,4 @@
 	return cfg;
 }
 
-rr::DebugConfig getReactorDebugConfig(const Configuration &config)
-{
-	rr::DebugConfig debugCfg;
-	debugCfg.asmEmitDir = config.asmEmitDir;
-	return debugCfg;
-}
 }  // namespace sw
\ No newline at end of file
diff --git a/src/System/SwiftConfig.hpp b/src/System/SwiftConfig.hpp
index 73ec336..a6945b5 100644
--- a/src/System/SwiftConfig.hpp
+++ b/src/System/SwiftConfig.hpp
@@ -15,12 +15,13 @@
 #ifndef sw_SwiftConfig_hpp
 #define sw_SwiftConfig_hpp
 
-#include <stdint.h>
-
 #include "Reactor/Nucleus.hpp"
 #include "marl/scheduler.h"
 
+#include <stdint.h>
+
 namespace sw {
+
 struct Configuration
 {
 	enum class AffinityPolicy : int
@@ -37,13 +38,9 @@
 	uint32_t threadCount = 0;
 
 	// Core affinity and affinity policy used by the scheduler.
-	uint64_t affinityMask = 0xffffffffffffffff;
+	uint64_t affinityMask = 0xFFFFFFFFFFFFFFFFu;
 	AffinityPolicy affinityPolicy = AffinityPolicy::AnyOf;
 
-	// -------- [Debug] --------
-	// Directory where ASM listings of JITted code will be emitted.
-	std::string asmEmitDir = "";
-
 	// -------- [Profiler] --------
 	// Whether SPIR-V profiling is enabled.
 	bool enableSpirvProfiling = false;
@@ -59,8 +56,6 @@
 // Get the scheduler configuration given a configuration.
 marl::Scheduler::Config getSchedulerConfiguration(const Configuration &config);
 
-// Get the debug configuration for Reactor given a configuration.
-rr::DebugConfig getReactorDebugConfig(const Configuration &config);
 }  // namespace sw
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 5cee79c..6a8e004 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -110,25 +110,6 @@
 }
 #endif  // __ANDROID__ && ENABLE_BUILD_VERSION_OUTPUT
 
-// setReactorDefaultConfig() sets the default configuration for Vulkan's use of
-// Reactor.
-void setReactorDefaultConfig()
-{
-	auto swConfig = sw::getConfiguration();
-	auto cfg = rr::Config::Edit()
-	               .set(rr::Optimization::Level::Default)
-	               .clearOptimizationPasses()
-	               .add(rr::Optimization::Pass::ScalarReplAggregates)
-	               .add(rr::Optimization::Pass::SCCP)
-	               .add(rr::Optimization::Pass::CFGSimplification)
-	               .add(rr::Optimization::Pass::EarlyCSEPass)
-	               .add(rr::Optimization::Pass::CFGSimplification)
-	               .add(rr::Optimization::Pass::InstructionCombining)
-	               .setDebugConfig(sw::getReactorDebugConfig(swConfig));
-
-	rr::Nucleus::adjustDefaultConfig(cfg);
-}
-
 std::shared_ptr<marl::Scheduler> getOrCreateScheduler()
 {
 	struct Scheduler
@@ -159,7 +140,6 @@
 #if defined(__ANDROID__) && defined(ENABLE_BUILD_VERSION_OUTPUT)
 		logBuildVersionInformation();
 #endif  // __ANDROID__ && ENABLE_BUILD_VERSION_OUTPUT
-		setReactorDefaultConfig();
 		return true;
 	}();
 	(void)doOnce;
diff --git a/tests/ReactorUnitTests/ReactorUnitTests.cpp b/tests/ReactorUnitTests/ReactorUnitTests.cpp
index f1ac129..c33e81e 100644
--- a/tests/ReactorUnitTests/ReactorUnitTests.cpp
+++ b/tests/ReactorUnitTests/ReactorUnitTests.cpp
@@ -2041,14 +2041,11 @@
 		}
 	}
 
-	// LLVM takes very long to generate this routine when InstructionCombining
-	// and O2 optimizations are enabled. Disable for now.
-	// TODO(b/174031014): Remove this once we fix LLVM taking so long
-	auto cfg = Config::Edit{}
-	               .remove(Optimization::Pass::InstructionCombining)
-	               .set(Optimization::Level::None);
+	// LLVM takes very long to generate this routine when O2 optimizations are enabled. Disable for now.
+	// TODO(b/174031014): Remove this once we fix LLVM taking so long.
+	ScopedPragma O0(OptimizationLevel, 0);
 
-	auto routine = function(cfg, testName().c_str());
+	auto routine = function(testName().c_str());
 
 	std::array<int32_t, ArraySize> v;