Add affinity options to the configuration.

Default behavior is unaffected by these changes.

Bug: b/216019572
Change-Id: Ie464d1d63a02c25f8aff327437e1a578dd42f889
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/61888
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Daniele Vettorel <dvet@google.com>
Commit-Queue: Daniele Vettorel <dvet@google.com>
diff --git a/src/System/BUILD.gn b/src/System/BUILD.gn
index b48c927..6f61d9f 100644
--- a/src/System/BUILD.gn
+++ b/src/System/BUILD.gn
@@ -55,6 +55,11 @@
   }
 
   include_dirs = [ ".." ]
+
+  deps = [
+    "../../third_party/marl:Marl",
+  ]
+
   public_deps = [
     ":System_headers",
   ]
diff --git a/src/System/Configurator.cpp b/src/System/Configurator.cpp
index 57214a4..dfe615b 100644
--- a/src/System/Configurator.cpp
+++ b/src/System/Configurator.cpp
@@ -18,7 +18,6 @@
 #include <algorithm>
 #include <fstream>
 #include <istream>
-#include <sstream>
 
 namespace {
 inline std::string trimSpaces(const std::string &str)
@@ -177,25 +176,6 @@
 	sections[sectionName].keyValuePairs[keyName] = value;
 }
 
-int Configurator::getInteger(const std::string &sectionName, const std::string &keyName, int defaultValue) const
-{
-	auto strValue = getValueIfExists(sectionName, keyName);
-	if(!strValue)
-	{
-		return defaultValue;
-	}
-
-	std::stringstream ss{ *strValue };
-	if(strValue->find("0x") != std::string::npos)
-	{
-		ss >> std::hex;
-	}
-
-	int val = 0;
-	ss >> val;
-	return val;
-}
-
 bool Configurator::getBoolean(const std::string &sectionName, const std::string &keyName, bool defaultValue) const
 {
 	auto strValue = getValueIfExists(sectionName, keyName);
diff --git a/src/System/Configurator.hpp b/src/System/Configurator.hpp
index 6c1aa77..1989781 100644
--- a/src/System/Configurator.hpp
+++ b/src/System/Configurator.hpp
@@ -16,6 +16,7 @@
 #define sw_Configurator_hpp
 
 #include <optional>
+#include <sstream>
 #include <string>
 #include <unordered_map>
 
@@ -32,7 +33,8 @@
 
 	void writeFile(const std::string &filePath, const std::string &title = "Configuration File");
 
-	int getInteger(const std::string &sectionName, const std::string &keyName, int defaultValue = 0) const;
+	template<typename T>
+	int getInteger(const std::string &sectionName, const std::string &keyName, T defaultValue = 0) const;
 	bool getBoolean(const std::string &sectionName, const std::string &keyName, bool defaultValue = false) const;
 	double getFloat(const std::string &sectionName, const std::string &keyName, double defaultValue = 0.0) const;
 
@@ -51,6 +53,24 @@
 	std::unordered_map<std::string, Section> sections;
 };
 
+template<typename T>
+int Configurator::getInteger(const std::string &sectionName, const std::string &keyName, T defaultValue) const
+{
+	static_assert(std::is_integral_v<T>, "getInteger must be used with integral types");
+
+	auto strValue = getValueIfExists(sectionName, keyName);
+	if(!strValue)
+		return defaultValue;
+
+	std::stringstream ss{ *strValue };
+	if(strValue->find("0x") != std::string::npos)
+		ss >> std::hex;
+
+	T val = 0;
+	ss >> val;
+	return val;
+}
+
 }  // namespace sw
 
 #endif  // sw_Configurator_hpp
diff --git a/src/System/SwiftConfig.cpp b/src/System/SwiftConfig.cpp
index 81ba15b..46e3ae8 100644
--- a/src/System/SwiftConfig.cpp
+++ b/src/System/SwiftConfig.cpp
@@ -13,14 +13,99 @@
 // limitations under the License.
 
 #include "SwiftConfig.hpp"
+#include "CPUID.hpp"
 #include "Configurator.hpp"
+#include "Debug.hpp"
+
+#include "marl/scheduler.h"
+
+#include <algorithm>
+
+namespace {
+
+std::string toLowerStr(const std::string &str)
+{
+	std::string lower = str;
+	std::transform(lower.begin(), lower.end(), lower.begin(),
+	               [](unsigned char c) { return std::tolower(c); });
+	return lower;
+}
+
+marl::Thread::Core getCoreFromIndex(uint8_t coreIndex)
+{
+	marl::Thread::Core core = {};
+#if defined(_WIN32)
+	// We only support one processor group on Windows
+	// when an explicit affinity mask is used.
+	core.windows.group = 0;
+	core.windows.index = coreIndex;
+#else
+	core.pthread.index = coreIndex;
+#endif
+	return core;
+}
+
+marl::Thread::Affinity getAffinityFromMask(uint64_t affinityMask)
+{
+	if(affinityMask == std::numeric_limits<uint64_t>::max())
+	{
+		return marl::Thread::Affinity::all();
+	}
+
+	ASSERT(affinityMask != 0);
+	marl::containers::vector<marl::Thread::Core, 32> cores;
+	uint8_t coreIndex = 0;
+	while(affinityMask)
+	{
+		if(affinityMask & 1)
+		{
+			cores.push_back(getCoreFromIndex(coreIndex));
+		}
+		++coreIndex;
+		affinityMask >>= 1;
+	}
+
+	return marl::Thread::Affinity(cores, marl::Allocator::Default);
+}
+
+std::shared_ptr<marl::Thread::Affinity::Policy> getAffinityPolicy(marl::Thread::Affinity &&affinity, sw::Configuration::AffinityPolicy affinityPolicy)
+{
+	switch(affinityPolicy)
+	{
+	case sw::Configuration::AffinityPolicy::AnyOf:
+		return marl::Thread::Affinity::Policy::anyOf(std::move(affinity));
+	case sw::Configuration::AffinityPolicy::OneOf:
+		return marl::Thread::Affinity::Policy::oneOf(std::move(affinity));
+	default:
+		UNREACHABLE("unknown affinity policy");
+	}
+	return nullptr;
+}
+}  // namespace
 
 namespace sw {
+
 Configuration readConfigurationFromFile()
 {
 	Configurator ini("SwiftShader.ini");
 	Configuration config{};
-	config.threadCount = ini.getInteger("Processor", "ThreadCount", 0);
+	config.threadCount = ini.getInteger<uint32_t>("Processor", "ThreadCount", 0);
+	config.affinityMask = ini.getInteger<uint64_t>("Processor", "AffinityMask", 0xffffffffffffffff);
+	if(config.affinityMask == 0)
+	{
+		warn("Affinity mask is empty, using all-cores affinity\n");
+		config.affinityMask = 0xffffffffffffffff;
+	}
+	std::string affinityPolicy = toLowerStr(ini.getValue("Processor", "AffinityPolicy", "any"));
+	if(affinityPolicy == "one")
+	{
+		config.affinityPolicy = Configuration::AffinityPolicy::OneOf;
+	}
+	else
+	{
+		// Default.
+		config.affinityPolicy = Configuration::AffinityPolicy::AnyOf;
+	}
 
 	return config;
 }
@@ -30,4 +115,21 @@
 	static Configuration config = readConfigurationFromFile();
 	return config;
 }
+
+marl::Scheduler::Config getSchedulerConfiguration(const Configuration &config)
+{
+	uint32_t threadCount = (config.threadCount == 0) ? std::min<size_t>(marl::Thread::numLogicalCPUs(), 16)
+	                                                 : config.threadCount;
+	auto affinity = getAffinityFromMask(config.affinityMask);
+	auto affinityPolicy = getAffinityPolicy(std::move(affinity), config.affinityPolicy);
+
+	marl::Scheduler::Config cfg;
+	cfg.setWorkerThreadCount(threadCount);
+	cfg.setWorkerThreadAffinityPolicy(affinityPolicy);
+	cfg.setWorkerThreadInitializer([](int) {
+		sw::CPUID::setFlushToZero(true);
+		sw::CPUID::setDenormalsAreZero(true);
+	});
+	return cfg;
+}
 }  // namespace sw
\ No newline at end of file
diff --git a/src/System/SwiftConfig.hpp b/src/System/SwiftConfig.hpp
index b5b61a7..32ba2b4 100644
--- a/src/System/SwiftConfig.hpp
+++ b/src/System/SwiftConfig.hpp
@@ -15,14 +15,35 @@
 #ifndef sw_SwiftConfig_hpp
 #define sw_SwiftConfig_hpp
 
+#include <stdint.h>
+
+#include "marl/scheduler.h"
+
 namespace sw {
 struct Configuration
 {
-	int threadCount;
+	enum class AffinityPolicy : int
+	{
+		// A thread has affinity with any core in the affinity mask.
+		AnyOf = 0,
+		// A thread has affinity with a single core in the affinity mask.
+		OneOf = 1,
+	};
+
+	// Number of threads used by the scheduler. A thread count of 0 is
+	// interpreted as min(cpu_cores_available, 16).
+	uint32_t threadCount = 0;
+
+	// Core affinity and affinity policy used by the scheduler.
+	uint64_t affinityMask = 0xffffffffffffffff;
+	AffinityPolicy affinityPolicy = AffinityPolicy::AnyOf;
 };
 
 // Get the configuration as parsed from a configuration file.
 const Configuration &getConfiguration();
+
+// Get the scheduler configuration given a configuration.
+marl::Scheduler::Config getSchedulerConfiguration(const Configuration &config);
 }  // namespace sw
 
 #endif
\ No newline at end of file
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 17b73ac..a3c3c5a 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -137,20 +137,12 @@
 
 	static Scheduler scheduler;  // TODO(b/208256248): Avoid exit-time destructor.
 
-	const sw::Configuration &config = sw::getConfiguration();
-	int threadCount = config.threadCount;
-
 	marl::lock lock(scheduler.mutex);
 	auto sptr = scheduler.weakptr.lock();
 	if(!sptr)
 	{
-		marl::Scheduler::Config cfg;
-		cfg.setWorkerThreadCount(threadCount == 0 ? std::min<size_t>(marl::Thread::numLogicalCPUs(), 16)
-		                                          : threadCount);
-		cfg.setWorkerThreadInitializer([](int) {
-			sw::CPUID::setFlushToZero(true);
-			sw::CPUID::setDenormalsAreZero(true);
-		});
+		const sw::Configuration &config = sw::getConfiguration();
+		marl::Scheduler::Config cfg = sw::getSchedulerConfiguration(config);
 		sptr = std::make_shared<marl::Scheduler>(cfg);
 		scheduler.weakptr = sptr;
 	}