diff --git a/Makefile b/Makefile
index 00aac72ad365bf16e7dd261cc991fbc61c1c6bf3..b37b15261654f165f87267ab62461e59e42e0920 100644
--- a/Makefile
+++ b/Makefile
@@ -23,9 +23,11 @@ debug:
 	rm -f build
 	$(MAKE) build BUILDTYPE=$@
 
+SMOKE_TEST_NINJA_TARGETS += iwyu
 
 smoke-test: all check-format
 	cd build && meson test --suite smoke
+	$(NINJA) -C build $(SMOKE_TEST_NINJA_TARGETS)
 
 
 TEST_NINJA_TARGETS += test
diff --git a/apps/Main.cpp b/apps/Main.cpp
index 42d1e102f8f148ed13f61432aba41f7c2580edf2..0d5686088072e8cf3d19eddb1f4192f5557b7c3e 100644
--- a/apps/Main.cpp
+++ b/apps/Main.cpp
@@ -1,17 +1,14 @@
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <iostream>
-#include <list>
-#include <string>
-#include <thread>
-
-#include "BinaryPrivateSemaphore.hpp"
-#include "Common.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "Debug.hpp"
-#include "PrivateSemaphore.hpp"
-#include "Runtime.hpp"
+#include <stdlib.h>	 // for exit, EXIT_SUCCESS
+
+#include <iostream>	 // for basic_ostream::operator<<
+
+#include "BinaryPrivateSemaphore.hpp"		 // for BPS
+#include "CountingPrivateSemaphore.hpp"	 // for CPS
+#include "Debug.hpp"										 // for WDBG
+#include "Fiber.hpp"										 // for Fiber
+#include "PrivateSemaphore.hpp"					 // for PS
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG
 
 typedef struct {
 	int n;
diff --git a/apps/WorkerSleepExample.cpp b/apps/WorkerSleepExample.cpp
index 093ac184921460dc2a84cfad50013b006fe834fa..b6e2e0d31d39ff8f2e32a783ff89368d3aeadcd2 100644
--- a/apps/WorkerSleepExample.cpp
+++ b/apps/WorkerSleepExample.cpp
@@ -1,18 +1,15 @@
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <iostream>
-#include <list>
-#include <string>
-
-#include "BinaryPrivateSemaphore.hpp"
-#include "Common.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "Debug.hpp"
-#include "PrivateSemaphore.hpp"
-#include "Runtime.hpp"
-#include "emper-version.h"
-#include "emper.hpp"
+#include <stdlib.h>	 // for exit, EXIT_SUCCESS
+
+#include <chrono>		 // for milliseconds, operator+, hig...
+#include <iostream>	 // for operator<<, basic_ostream, endl
+#include <ratio>		 // for ratio
+
+#include "CountingPrivateSemaphore.hpp"	 // for CPS
+#include "Fiber.hpp"										 // for Fiber
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG, workerid_t
+#include "emper-version.h"							 // for EMPER_FULL_VERSION
+#include "emper.hpp"										 // for spawn
 
 static unsigned int ITERATIONS = 10;
 
diff --git a/emper/BinaryPrivateSemaphore.cpp b/emper/BinaryPrivateSemaphore.cpp
index 491e9e2810f7cbc9f8ae5e692f88df923bd7abff..c20e0ea35512ed28f1c262d33028c350ae03b47c 100644
--- a/emper/BinaryPrivateSemaphore.cpp
+++ b/emper/BinaryPrivateSemaphore.cpp
@@ -1,9 +1,10 @@
 #include "BinaryPrivateSemaphore.hpp"
 
-#include <cassert>
+#include <cassert>	// for assert
 
-#include "Common.hpp"
-#include "Debug.hpp"
+#include "Common.hpp"		// for unlikely
+#include "Context.hpp"	// for Context
+#include "Debug.hpp"		// for LOGDD
 
 void BinaryPrivateSemaphore::wait() {
 	State state = bpsState.load();
diff --git a/emper/BinaryPrivateSemaphore.hpp b/emper/BinaryPrivateSemaphore.hpp
index 1da3ee815989fd40d4544d38ee79539a76822d65..7e3083353ddbb74e47d355b32cb577f4371e1aaa 100644
--- a/emper/BinaryPrivateSemaphore.hpp
+++ b/emper/BinaryPrivateSemaphore.hpp
@@ -1,10 +1,10 @@
 #pragma once
 
-#include <atomic>
+#include <atomic>	 // for atomic
 
-#include "Debug.hpp"
-#include "Dispatcher.hpp"
-#include "PrivateSemaphore.hpp"
+#include "PrivateSemaphore.hpp"	 // for PrivateSemaphore
+
+class Context;
 
 class BinaryPrivateSemaphore : public PrivateSemaphore {
  private:
diff --git a/emper/Common.cpp b/emper/Common.cpp
index 3655e119142ab9ce77a36609858ad4089cc6efd2..99cda0745f5353e6d49ceeeb2a04fc31d010c9ed 100644
--- a/emper/Common.cpp
+++ b/emper/Common.cpp
@@ -1,7 +1,5 @@
 #include "Common.hpp"
 
-#include <stdio.h>
-
 #include <cstdio>
 #include <cstdlib>
 #include <iostream>
diff --git a/emper/Common.hpp b/emper/Common.hpp
index 1407b9792e144b8823f1a280072eaaa95b8d92c9..43b611fb299ac616021ae038b62986517bc40f32 100644
--- a/emper/Common.hpp
+++ b/emper/Common.hpp
@@ -2,8 +2,6 @@
 
 #include <functional>
 
-#include "emper-common.h"
-
 typedef std::function<void(void)> func_t;
 
 #define STRINGIFY(x) #x
diff --git a/emper/Context.hpp b/emper/Context.hpp
index 28169b476b05ae04024431188ba879d70c44c482..312d60d33357cf943ac8d72725064850394fe0bc 100644
--- a/emper/Context.hpp
+++ b/emper/Context.hpp
@@ -1,15 +1,14 @@
 #pragma once
 
-#include <valgrind/valgrind.h>
+#include <stdint.h>	 // for uintptr_t
 
-#include <cassert>
-#include <cstring>
-#include <functional>
+#include <cassert>		 // for assert
+#include <cstring>		 // for memset
+#include <functional>	 // for function
+#include <iosfwd>			 // for ostream
 
-#include "Common.hpp"
-#include "Debug.hpp"
-
-class Context;
+#include "Common.hpp"	 // for func_t, DIE, ALIGN_TO_CACHE_LINE
+#include "Debug.hpp"	 // for LOGD, LogSubsystem, LogSubsystem::C, Logger
 
 extern "C" [[noreturn]] void switch_and_load_context(void** toTos);
 // *Not* marked as 'noreturn' because save_and_switch_context does
diff --git a/emper/ContextManager.cpp b/emper/ContextManager.cpp
index 90627f008d331dd8f67a765c207f12c8f8155477..ea000a87ce55fe3a5d033c64e1ec184c2c169836 100644
--- a/emper/ContextManager.cpp
+++ b/emper/ContextManager.cpp
@@ -1,10 +1,13 @@
 #include "ContextManager.hpp"
 
-#include <ostream>
+#include <assert.h>	 // for assert
 
-#include "Context.hpp"
-#include "Debug.hpp"
-#include "Runtime.hpp"
+#include <new>	// for operator new, operator delete
+
+#include "Context.hpp"		 // for Context
+#include "Debug.hpp"			 // for LOGD
+#include "Dispatcher.hpp"	 // for Dispatcher
+#include "Runtime.hpp"		 // for Runtime
 
 ContextManager::ContextManager(Runtime& runtime) : MemoryManager(runtime), runtime(runtime) {
 	auto newWorkerHook = [this]() {
diff --git a/emper/ContextManager.hpp b/emper/ContextManager.hpp
index 529bdb6604ff83ccf48098b144490439f8f4d0e4..2037b00c4bd5c9ff226690801fb3ed2a23a3b955 100644
--- a/emper/ContextManager.hpp
+++ b/emper/ContextManager.hpp
@@ -1,12 +1,11 @@
 #pragma once
 
-#include "Common.hpp"
-#include "Context.hpp"
-#include "MemoryManager.hpp"
-#include "Runtime.hpp"
-#include "lib/adt/WsClQueue.hpp"
+#include "Common.hpp"					// for func_t
+#include "Debug.hpp"					// for LogSubsystem, LogSubsystem::CM, Logger
+#include "MemoryManager.hpp"	// for MemoryManager
 
 class Context;
+class Runtime;
 
 #define CONTEXT_MANAGER_FIRST_LAYER_QUEUE_SIZE 64
 
diff --git a/emper/CountingPrivateSemaphore.cpp b/emper/CountingPrivateSemaphore.cpp
index e1e3fae687bb70cb5b439837894007987cf0f487..cae315d93572b3835597708eb66124bad06119f4 100644
--- a/emper/CountingPrivateSemaphore.cpp
+++ b/emper/CountingPrivateSemaphore.cpp
@@ -1,6 +1,8 @@
 #include "CountingPrivateSemaphore.hpp"
 
-#include <cassert>
+#include <cassert>	// for assert
+
+#include "Context.hpp"	// for Context
 
 CountingPrivateSemaphore::CountingPrivateSemaphore() : CountingPrivateSemaphore(0) {}
 
diff --git a/emper/CountingPrivateSemaphore.hpp b/emper/CountingPrivateSemaphore.hpp
index ee90f8a08bcf8840f8d144cccbc88365e52e9ed3..586275932f185c2b5715f9d7baebceb28ae4e1dd 100644
--- a/emper/CountingPrivateSemaphore.hpp
+++ b/emper/CountingPrivateSemaphore.hpp
@@ -1,8 +1,10 @@
 #pragma once
 
-#include <atomic>
+#include <atomic>	 // for atomic_uint, atomic
 
-#include "PrivateSemaphore.hpp"
+#include "PrivateSemaphore.hpp"	 // for PrivateSemaphore
+
+class Context;
 
 /**
  * A counting private semaphore.
diff --git a/emper/Debug.cpp b/emper/Debug.cpp
index 2d34fe585a35957826163c8a2d8f319c39754ef0..e833074f3a7fec7b0a455ba1c9c2a90712f34665 100644
--- a/emper/Debug.cpp
+++ b/emper/Debug.cpp
@@ -1,10 +1,10 @@
 #include "Debug.hpp"
 
-#include <iostream>
-#include <mutex>
+#include <iostream>	 // for operator<<, cerr, ostream, basic_ostream
+#include <mutex>		 // for mutex, unique_lock
 
-#include "Common.hpp"
-#include "Runtime.hpp"
+#include "Runtime.hpp"		 // for Runtime
+#include "emper-common.h"	 // for workerid_t
 
 static std::mutex worker_log_mutex;
 
diff --git a/emper/Debug.hpp b/emper/Debug.hpp
index 0d5a5db3bdef6d81d6d9ade0b0c09bd254951ba2..0fed216b60ee7486ca3db0ce2bb5bea446b2f23e 100644
--- a/emper/Debug.hpp
+++ b/emper/Debug.hpp
@@ -1,9 +1,7 @@
 #pragma once
 
-#include <cstdlib>
-#include <iostream>
-#include <map>
-#include <sstream>
+#include <map>		 // for allocator, map
+#include <string>	 // for string, operator<<
 
 #ifdef NDEBUG
 
@@ -14,7 +12,8 @@
 
 #else
 
-#include <sstream>
+#include <iostream>	 // IWYU pragma: keep
+#include <sstream>	 // IWYU pragma: keep
 
 #define DBG(x)                   \
 	do {                           \
diff --git a/emper/Dispatcher.cpp b/emper/Dispatcher.cpp
index d789db77c5aaca21f93541a63ea50a572c0b88be..cc98a092e3fc53856a6636a48cf962aa9745b1b9 100644
--- a/emper/Dispatcher.cpp
+++ b/emper/Dispatcher.cpp
@@ -1,10 +1,8 @@
 #include "Dispatcher.hpp"
 
-//#include <pthread.h>
-#include <functional>
+#include <functional>	 // for _Bind_helper<>::type, bind
 
-#include "Debug.hpp"
-#include "Runtime.hpp"
+#include "Runtime.hpp"	// for Runtime
 
 thread_local const Fiber* Dispatcher::currentFiber;
 
diff --git a/emper/Dispatcher.hpp b/emper/Dispatcher.hpp
index 2ab0e971bc454a6429a116c2fdeaa3411b6aba58..30e6d52de3e190e71ffb4752bbd578ecc4d4f58d 100644
--- a/emper/Dispatcher.hpp
+++ b/emper/Dispatcher.hpp
@@ -1,8 +1,13 @@
 #pragma once
 
-#include "Common.hpp"
-#include "Debug.hpp"
-#include "Fiber.hpp"
+#include <assert.h>	 // for assert
+
+#include <new>	// for operator delete
+
+#include "Common.hpp"			 // for func_t
+#include "Debug.hpp"			 // for LOGD, LogSubsystem, LogSubsystem::DISP
+#include "Fiber.hpp"			 // for Fiber
+#include "emper-common.h"	 // for workeraffinity_t
 
 class Runtime;
 class ContextManager;
diff --git a/emper/Fiber.cpp b/emper/Fiber.cpp
index 32109563482f1f62ee0c7d33bbbc22e1f9dec668..caa61837c0215e9175cbf18b362b927e94ba4be2 100644
--- a/emper/Fiber.cpp
+++ b/emper/Fiber.cpp
@@ -1,6 +1,7 @@
 #include "Fiber.hpp"
 
-#include <ostream>
+#include <iostream>	 // for operator<<, basic_ostream, ostream, basic_ostrea...
+#include <typeinfo>	 // for type_info
 
 void Fiber::run() const {
 	LOGD("run() calling " << function.target<FIBER_FUN_TEMPLATE_ARG>() << " ("
diff --git a/emper/Fiber.hpp b/emper/Fiber.hpp
index 8132274da3028d52bdb71b079725ff6047230563..e3fa62c0c2eec9225643d0c2cfb5a4f53ca969e2 100644
--- a/emper/Fiber.hpp
+++ b/emper/Fiber.hpp
@@ -1,21 +1,24 @@
 #pragma once
 
-#include <atomic>
-#include <cassert>
-#include <climits>
-#include <functional>
-
-#include "Common.hpp"
-#include "Debug.hpp"
-#include "lib/adt/MpscQueue.hpp"
+#include <atomic>			 // for atomic_uint, atomic, __atomic_base, memory...
+#include <cassert>		 // for assert
+#include <climits>		 // for UINT_MAX
+#include <functional>	 // for function
+#include <iosfwd>			 // for ostream
+#include <new>				 // for operator new
+
+#include "Common.hpp"			 // for ALIGN_TO_CACHE_LINE
+#include "Debug.hpp"			 // for LogSubsystem, LogSubsystem::F, Logger
+#include "emper-common.h"	 // for workeraffinity_t, UNUSED_ARG
+
+namespace adt {
+template <typename T>
+class MpscQueue;
+}
 
 #define FIBER_FUN_TEMPLATE_ARG void(void*)
 #define FIBER_FUN0_TEMPLATE_ARG void(void)
 
-class Scheduler;
-class Dispatcher;
-class LawsScheduler;
-
 class ALIGN_TO_CACHE_LINE Fiber : public Logger<LogSubsystem::F> {
  public:
 	typedef std::function<FIBER_FUN_TEMPLATE_ARG> fiber_fun_t;
diff --git a/emper/FiberManager.cpp b/emper/FiberManager.cpp
index 01efdc3bf106d714b312b3b3128aeb3b96b2730c..3979bfafa66be6fb48a96e0e60296a0bcff8b780 100644
--- a/emper/FiberManager.cpp
+++ b/emper/FiberManager.cpp
@@ -1,6 +1,10 @@
 #include "FiberManager.hpp"
 
-#include <cstdlib>
+#include <cstdlib>	// for abort, free, posix_memalign
+
+#include "Fiber.hpp"	// for Fiber
+
+class Runtime;
 
 FiberManager::FiberManager(Runtime& runtime) : MemoryManager(runtime) {}
 
diff --git a/emper/FiberManager.hpp b/emper/FiberManager.hpp
index ae745e188b209cae77c2599fd1d8b2245d375fa3..d9234bd346fb2aa8a5e3c96b98d888b9f481e678 100644
--- a/emper/FiberManager.hpp
+++ b/emper/FiberManager.hpp
@@ -1,8 +1,9 @@
 #pragma once
 
-#include "Fiber.hpp"
-#include "MemoryManager.hpp"
-#include "Runtime.hpp"
+#include "MemoryManager.hpp"	// for MemoryManager
+
+class Fiber;
+class Runtime;
 
 class FiberManager : protected MemoryManager<Fiber, 128, 64> {
  private:
diff --git a/emper/PrivateSemaphore.hpp b/emper/PrivateSemaphore.hpp
index 4c9b4ce0c9631737199ec16ab9679a881503bd72..ae435ae65b677f5601fca13b2925ad1290c75af6 100644
--- a/emper/PrivateSemaphore.hpp
+++ b/emper/PrivateSemaphore.hpp
@@ -4,6 +4,7 @@
 #include "Context.hpp"
 #include "ContextManager.hpp"
 #include "Debug.hpp"
+#include "Dispatcher.hpp"
 #include "Fiber.hpp"
 #include "Runtime.hpp"
 
diff --git a/emper/Runtime.cpp b/emper/Runtime.cpp
index 273e6936c316944c414f8e6e2178a693a93d88bd..0eae91fb3666cca7ec145d60f75a36117e8de14e 100644
--- a/emper/Runtime.cpp
+++ b/emper/Runtime.cpp
@@ -1,20 +1,26 @@
 #include "Runtime.hpp"
 
-#include <pthread.h>
-#include <sys/syscall.h>
-#include <sys/sysinfo.h>
-#include <unistd.h>
-
-#include <cstdlib>
-#include <ctime>
-
+#include <errno.h>		// for errno
+#include <pthread.h>	// for pthread_t, pthread_attr_init
 // Non portable.
-#include <sched.h>
-
-#include "Common.hpp"
-#include "ContextManager.hpp"
-#include "Debug.hpp"
-#include "emper-config.h"
+#include <sched.h>				// for cpu_set_t, CPU_SET, CPU_ZERO
+#include <sys/sysinfo.h>	// for get_nprocs
+#include <syscall.h>			// for SYS_gettid
+#include <unistd.h>				// for syscall
+
+#include <cstdlib>	// for rand, srand, abort
+#include <ctime>		// for time
+#include <memory>		// for __shared_ptr_access, shared_ptr
+#include <ostream>	// for operator<<, basic_ostream<>:...
+
+#include "Common.hpp"										 // for DIE_MSG_ERRNO, DIE, DIE_MSG
+#include "ContextManager.hpp"						 // for ContextManager
+#include "Debug.hpp"										 // for DBG, ABORT, LOGD, LOGE
+#include "Dispatcher.hpp"								 // for Dispatcher
+#include "Fiber.hpp"										 // for Fiber
+#include "RuntimeStrategy.hpp"					 // for RuntimeStrategy
+#include "RuntimeStrategyStats.hpp"			 // for RuntimeStrategyStats
+#include "strategies/ws/WsStrategy.hpp"	 // for WsStrategy, WsStrategy::INST...
 
 std::mutex Runtime::currentRuntimeMutex;
 Runtime* Runtime::currentRuntime;
diff --git a/emper/Runtime.hpp b/emper/Runtime.hpp
index c446473eaa6020a901fd8aa7f4e03e868f392e99..e1a29071a261e45cdb07d62473a2b4d344282794 100644
--- a/emper/Runtime.hpp
+++ b/emper/Runtime.hpp
@@ -1,17 +1,26 @@
 #pragma once
 
-#include <thread>
-#include <vector>
-
-#include "Common.hpp"
-#include "Debug.hpp"
-#include "Dispatcher.hpp"
-#include "RuntimeStrategy.hpp"
-#include "Scheduler.hpp"
-#include "lib/sync/Latch.hpp"
-#include "strategies/ws/WsStrategy.hpp"
+#include <pthread.h>	// for pthread_t
+#include <stddef.h>		// for size_t
+#include <stdint.h>		// for intptr_t
+
+#include <atomic>							 // for atomic, memory_order_relaxed
+#include <condition_variable>	 // for condition_variable
+#include <functional>					 // for function
+#include <mutex>							 // for mutex, lock_guard, unique_lock
+#include <thread>							 // for thread
+#include <vector>							 // for vector
+
+#include "Common.hpp"					 // for ALIGN_TO_CACHE_LINE
+#include "Debug.hpp"					 // for LogSubsystem, LogSubsystem::RUNTI, Logger
+#include "Scheduler.hpp"			 // for Scheduler
+#include "emper-common.h"			 // for workerid_t
+#include "lib/sync/Latch.hpp"	 // for Latch
 
 class ContextManager;
+class Dispatcher;
+class Fiber;
+class RuntimeStrategy;
 
 class Runtime : public Logger<LogSubsystem::RUNTI> {
  private:
diff --git a/emper/Scheduler.hpp b/emper/Scheduler.hpp
index a14b7a067762fce2622b8f617c3c98f5aa5c4616..9c13b5f05d20be4cc64ee0ac620b1136121052e0 100644
--- a/emper/Scheduler.hpp
+++ b/emper/Scheduler.hpp
@@ -1,10 +1,10 @@
 #pragma once
 
-#include <functional>
+#include <functional>	 // for function
 
-#include "Common.hpp"
-#include "Debug.hpp"
-#include "Fiber.hpp"
+#include "Debug.hpp"			 // for LogSubsystem, LogSubsystem::SCHED, Logger
+#include "Fiber.hpp"			 // for Fiber
+#include "emper-common.h"	 // for workeraffinity_t
 
 class Runtime;
 
diff --git a/emper/Semaphore.cpp b/emper/Semaphore.cpp
index 91c92144a887d0c2f679ab27d50e3d7af009bf89..00df3cab21e7744b9b9fb52ba9b4418a212e5726 100644
--- a/emper/Semaphore.cpp
+++ b/emper/Semaphore.cpp
@@ -1,5 +1,9 @@
 #include "Semaphore.hpp"
 
+#include <iostream>	 // for operator<<, basic_ostream, basic_o...
+
+#include "lib/sync/Semaphore.hpp"	 // for emper
+
 using namespace emper;
 
 void Semaphore::print() {
diff --git a/emper/c_emper.cpp b/emper/c_emper.cpp
index 6b8a9211031af41da144fb95c00c7870f22e2473..dc2bb84547d9995dabb5a80f72f260d8753c8d3f 100644
--- a/emper/c_emper.cpp
+++ b/emper/c_emper.cpp
@@ -1,8 +1,11 @@
-#include "BinaryPrivateSemaphore.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "Fiber.hpp"
-#include "Runtime.hpp"
-#include "emper.h"
+#include <new>	// for operator new
+
+#include "BinaryPrivateSemaphore.hpp"		 // for BinaryPrivateSemaphore, BPS
+#include "CountingPrivateSemaphore.hpp"	 // for CountingPrivateSemaphore, CPS
+#include "Fiber.hpp"										 // for Fiber, Fiber::NOT_AFFINE
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for workeraffinity_t
+#include "emper.h"											 // for fiber, cps, bps, runtime
 
 runtime* init_runtime(void) {
 	Runtime* r = new Runtime();
diff --git a/emper/strategies/laws/LawsDispatcher.cpp b/emper/strategies/laws/LawsDispatcher.cpp
index 06a8566af7ae14284a7883d2cc3d3f19d7275199..fa6c27bfcd0db86399c18b83ef71595f273fafe1 100644
--- a/emper/strategies/laws/LawsDispatcher.cpp
+++ b/emper/strategies/laws/LawsDispatcher.cpp
@@ -1,9 +1,11 @@
 #include "LawsDispatcher.hpp"
 
-#include "LawsStrategy.hpp"
+#include "LawsStrategy.hpp"	 // IWYU pragma: keep
 #include "Runtime.hpp"
 #include "emper-config.h"
 
+class Fiber;
+
 void LawsDispatcher::dispatchLoop() {
 	while (true) {
 		Fiber* const fiber = runtime.nextFiber();
diff --git a/emper/strategies/laws/LawsDispatcher.hpp b/emper/strategies/laws/LawsDispatcher.hpp
index f45b6dceebbfc0dcc5c399c8f4286b901c82cb8c..80446a12cc6d94fbc54d959d7697e3d659480f49 100644
--- a/emper/strategies/laws/LawsDispatcher.hpp
+++ b/emper/strategies/laws/LawsDispatcher.hpp
@@ -1,9 +1,10 @@
 #pragma once
 
 #include "Dispatcher.hpp"
-#include "emper-config.h"
+#include "emper-common.h"
 
 class LawsStrategy;
+class Runtime;
 
 class LawsDispatcher : public Dispatcher {
  private:
diff --git a/emper/strategies/laws/LawsScheduler.cpp b/emper/strategies/laws/LawsScheduler.cpp
index a9fea7016ceaa47f81f2474182834525bb9e329e..c29f36c7f6225ea407c42e4ef72e43f97731c2c9 100644
--- a/emper/strategies/laws/LawsScheduler.cpp
+++ b/emper/strategies/laws/LawsScheduler.cpp
@@ -2,7 +2,7 @@
 
 #include "Common.hpp"
 #include "Debug.hpp"
-#include "LawsStrategy.hpp"
+#include "LawsStrategy.hpp"	 // IWYU pragma: keep
 #include "Runtime.hpp"
 #include "emper-config.h"
 
diff --git a/emper/strategies/laws/LawsScheduler.hpp b/emper/strategies/laws/LawsScheduler.hpp
index 5a8e11315b261509e0edd663ce2fb81ee6bedf1c..07b4f3d3297ec3f38cc4b99cf90a4985eb471b79 100644
--- a/emper/strategies/laws/LawsScheduler.hpp
+++ b/emper/strategies/laws/LawsScheduler.hpp
@@ -1,14 +1,15 @@
 #pragma once
 
+#include <stddef.h>
+
 #include "Fiber.hpp"
 #include "Scheduler.hpp"
-#include "emper-config.h"
-#include "lib/adt/LockedQueue.hpp"
-#include "lib/adt/LockedUnboundedQueue.hpp"
+#include "emper-common.h"
 #include "lib/adt/MpscQueue.hpp"
 #include "lib/adt/WsClQueue.hpp"
 
 class LawsStrategy;
+class Runtime;
 
 class LawsScheduler : public Scheduler {
 	template <size_t SIZE>
diff --git a/emper/strategies/laws/LawsStrategy.cpp b/emper/strategies/laws/LawsStrategy.cpp
index 9e8c5a6f64d8316cd970e2fcbba7adea32b8d9ae..c5fc5f6424f7f775b539a392159dedda5095e117 100644
--- a/emper/strategies/laws/LawsStrategy.cpp
+++ b/emper/strategies/laws/LawsStrategy.cpp
@@ -1,5 +1,9 @@
 #include "LawsStrategy.hpp"
 
+#include "strategies/laws/LawsDispatcher.hpp"			// for LawsDispatcher
+#include "strategies/laws/LawsScheduler.hpp"			// for LawsScheduler
+#include "strategies/laws/LawsStrategyStats.hpp"	// for LawsStrategyStats
+
 LawsStrategy LawsStrategy::INSTANCE;
 
 Scheduler& LawsStrategy::getScheduler(Runtime& runtime) {
diff --git a/emper/strategies/laws/LawsStrategy.hpp b/emper/strategies/laws/LawsStrategy.hpp
index 3b692dcf3602bad56b45071db978dd317656e7b4..4d6a6b13fe585ad6aac29ffbc3c3008ea19170be 100644
--- a/emper/strategies/laws/LawsStrategy.hpp
+++ b/emper/strategies/laws/LawsStrategy.hpp
@@ -4,13 +4,15 @@
 #include <cstdint>
 #include <memory>
 
-#include "LawsDispatcher.hpp"
-#include "LawsScheduler.hpp"
-#include "LawsStrategyStats.hpp"
 #include "RuntimeStrategy.hpp"
 
+class Dispatcher;
+class LawsDispatcher;
 class LawsScheduler;
-class LawsDispater;
+class LawsStrategyStats;
+class Runtime;
+class RuntimeStrategyStats;
+class Scheduler;
 
 class LawsStrategy : public RuntimeStrategy {
  private:
diff --git a/emper/strategies/laws/LawsStrategyStats.cpp b/emper/strategies/laws/LawsStrategyStats.cpp
index 81584dac7518f8807fbfddaf1e8a6fd258c97ebe..5d7365483669ff22c229979ecce827050e12635f 100644
--- a/emper/strategies/laws/LawsStrategyStats.cpp
+++ b/emper/strategies/laws/LawsStrategyStats.cpp
@@ -1,5 +1,6 @@
 #include "LawsStrategyStats.hpp"
 
+#include <atomic>
 #include <iostream>
 
 #include "LawsStrategy.hpp"
diff --git a/emper/strategies/ws/WsDispatcher.cpp b/emper/strategies/ws/WsDispatcher.cpp
index 5f070d6e47cf58e5fc74e2cfe0436b51b444e13d..80de76fb7251d8c0ac4e69c161ca0460bd93173b 100644
--- a/emper/strategies/ws/WsDispatcher.cpp
+++ b/emper/strategies/ws/WsDispatcher.cpp
@@ -1,8 +1,9 @@
 #include "WsDispatcher.hpp"
 
-#include "Debug.hpp"
-#include "Runtime.hpp"
-#include "emper-config.h"
+#include "Runtime.hpp"		 // for Runtime
+#include "emper-config.h"	 // for EMPER_WORKER_SLEEP
+
+class Fiber;
 
 void WsDispatcher::dispatchLoop() {
 	while (true) {
diff --git a/emper/strategies/ws/WsDispatcher.hpp b/emper/strategies/ws/WsDispatcher.hpp
index c3d70015071cc9d32841fbe60d25dc2942f1cf19..b754d672095b1f42d3ce7be1f33227a0c5fba8da 100644
--- a/emper/strategies/ws/WsDispatcher.hpp
+++ b/emper/strategies/ws/WsDispatcher.hpp
@@ -2,6 +2,8 @@
 
 #include "Dispatcher.hpp"
 
+class Runtime;
+
 class WsDispatcher : public Dispatcher {
  public:
 	WsDispatcher(Runtime& runtime) : Dispatcher(runtime){};
diff --git a/emper/strategies/ws/WsScheduler.cpp b/emper/strategies/ws/WsScheduler.cpp
index eec4cfa691c2bfe59c380ea2fab988bc67397b19..2083376f8dbe318da48923ec72cf3ecffb34b2b0 100644
--- a/emper/strategies/ws/WsScheduler.cpp
+++ b/emper/strategies/ws/WsScheduler.cpp
@@ -1,10 +1,14 @@
 #include "WsScheduler.hpp"
 
+#include <ostream>
+
 #include "Common.hpp"
 #include "Debug.hpp"
 #include "Runtime.hpp"
 #include "emper-config.h"
 
+class Fiber;
+
 thread_local WsScheduler::WsQueue<WsScheduler::QUEUE_SIZE> WsScheduler::queue;
 
 WsScheduler::WsScheduler(Runtime& runtime, WsStrategy& wsStrategy)
diff --git a/emper/strategies/ws/WsScheduler.hpp b/emper/strategies/ws/WsScheduler.hpp
index 9d1c345460aaf42238c2d1cc9b55be2b8a37dd02..3286f6da1e3ffc2ecd8d6c1e9555ebbc5e4da86e 100644
--- a/emper/strategies/ws/WsScheduler.hpp
+++ b/emper/strategies/ws/WsScheduler.hpp
@@ -1,11 +1,13 @@
 #pragma once
 
-#include "Scheduler.hpp"
-#include "emper-common.h"
-#include "emper-config.h"
-#include "lib/adt/LockedQueue.hpp"
-#include "lib/adt/WsClQueue.hpp"
+#include <stddef.h>	 // for size_t
 
+#include "Scheduler.hpp"					// for Scheduler
+#include "emper-common.h"					// for ATTR_UNUSED
+#include "lib/adt/WsClQueue.hpp"	// for WsClQueue
+
+class Fiber;
+class Runtime;
 class WsStrategy;
 
 class WsScheduler : public Scheduler {
diff --git a/emper/strategies/ws/WsStrategy.cpp b/emper/strategies/ws/WsStrategy.cpp
index b9b78d91323fd3ecd868322a55cd69a9b4fab933..3162da16d5d0b911edceab41a8846fe37b587b25 100644
--- a/emper/strategies/ws/WsStrategy.cpp
+++ b/emper/strategies/ws/WsStrategy.cpp
@@ -2,6 +2,10 @@
 
 #include "WsDispatcher.hpp"
 #include "WsScheduler.hpp"
+#include "strategies/ws/WsStrategyStats.hpp"	// for WsStrategyStats
+
+class Runtime;
+class RuntimeStrategyStats;
 
 WsStrategy WsStrategy::INSTANCE;
 
diff --git a/emper/strategies/ws/WsStrategy.hpp b/emper/strategies/ws/WsStrategy.hpp
index 269e93f64d921cbcbc1e8db84d3991f15247fca1..8386b5a25c3783abf943679d9ea32ca978daf2d7 100644
--- a/emper/strategies/ws/WsStrategy.hpp
+++ b/emper/strategies/ws/WsStrategy.hpp
@@ -5,13 +5,14 @@
 #include <memory>
 
 #include "RuntimeStrategy.hpp"
-#include "WsStrategyStats.hpp"
 
 class Scheduler;
 class Dispatcher;
-
+class Runtime;
+class RuntimeStrategyStats;
 class WsScheduler;
 class WsDispatcher;
+class WsStrategyStats;
 
 class WsStrategy : public RuntimeStrategy {
  private:
diff --git a/emper/strategies/ws/WsStrategyStats.cpp b/emper/strategies/ws/WsStrategyStats.cpp
index d3925dfcacb1140ffff1f4b869648761202d31c7..e5a50e8893e0e48000e5bae778545e87211051ea 100644
--- a/emper/strategies/ws/WsStrategyStats.cpp
+++ b/emper/strategies/ws/WsStrategyStats.cpp
@@ -1,5 +1,6 @@
 #include "WsStrategyStats.hpp"
 
+#include <atomic>
 #include <iostream>
 
 #include "WsStrategy.hpp"
diff --git a/eval/Locality.cpp b/eval/Locality.cpp
index 7d1f7dedd55f2e6618680de8b6fa84aa09a5df4a..625e7d97c437a0bf017a852ac3f8e5d84c3ad153 100644
--- a/eval/Locality.cpp
+++ b/eval/Locality.cpp
@@ -1,17 +1,24 @@
-#include <unistd.h>
-
-#include <algorithm>
-#include <cstdint>
-#include <random>
-#include <thread>
-
-#include "CountingPrivateSemaphore.hpp"
-#include "Dispatcher.hpp"
-#include "Fiber.hpp"
-#include "PrivateSemaphore.hpp"
-#include "Runtime.hpp"
-#include "lib/DebugUtil.hpp"
-#include "strategies/laws/LawsStrategy.hpp"
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2020 Florian Schmaus
+#include <stdlib.h>	 // for abort, exit, EXIT_SUCCESS
+#include <unistd.h>	 // for getopt, optarg
+
+#include <algorithm>	// for generate
+#include <chrono>			// for microseconds, high_resol...
+#include <cstdint>		// for uint8_t, UINT8_MAX
+#include <iostream>		// for operator<<, basic_ostream
+#include <new>				// for operator new
+#include <random>			// for mt19937, uniform_int_dis...
+#include <string>			// for string, operator<<, oper...
+
+#include "CountingPrivateSemaphore.hpp"			 // for CPS
+#include "Debug.hpp"												 // for DBG
+#include "Fiber.hpp"												 // for Fiber, Fiber::NOT_AFFINE
+#include "PrivateSemaphore.hpp"							 // for PS
+#include "Runtime.hpp"											 // for Runtime
+#include "emper-common.h"										 // for workeraffinity_t, UNUSED...
+#include "lib/DebugUtil.hpp"								 // for enableStacktraceOnAborts
+#include "strategies/laws/LawsStrategy.hpp"	 // for LawsStrategy, LawsStrate...
 
 #define L1_CACHE_LINE_SIZE 64	 // 64 Bytes
 
diff --git a/eval/SpawnALot.cpp b/eval/SpawnALot.cpp
index 9b99d72eb205de1ecb5e7d7400f2addba34ef702..eff5cf35be3ef4a35e35bad1ba85b0e7ef547945 100644
--- a/eval/SpawnALot.cpp
+++ b/eval/SpawnALot.cpp
@@ -1,8 +1,17 @@
-#include "BinaryPrivateSemaphore.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "PrivateSemaphore.hpp"
-#include "Runtime.hpp"
-#include "lib/DebugUtil.hpp"
+#include <stdint.h>	 // for uint8_t, uint64_t
+#include <stdlib.h>	 // for EXIT_SUCCESS
+
+#include <chrono>		 // for nanoseconds, time_point, dur...
+#include <iostream>	 // for operator<<, basic_ostream
+#include <thread>		 // for thread
+
+#include "BinaryPrivateSemaphore.hpp"		 // for BPS
+#include "CountingPrivateSemaphore.hpp"	 // for CPS
+#include "Fiber.hpp"										 // for Fiber
+#include "PrivateSemaphore.hpp"					 // for PS
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG
+#include "lib/DebugUtil.hpp"						 // for enableStacktraceOnAborts
 
 #define CACHE_LINE_SIZE 64
 
diff --git a/eval/TimeToSpawn.cpp b/eval/TimeToSpawn.cpp
index 9be3ee437e5633724fa8c2ca9abf8f7b893323b8..488c1e304b48b1fd1eea20d19de559d6e1353f3a 100644
--- a/eval/TimeToSpawn.cpp
+++ b/eval/TimeToSpawn.cpp
@@ -1,9 +1,12 @@
-#include <chrono>
-#include <mutex>
-#include <thread>
-
-#include "BinaryPrivateSemaphore.hpp"
-#include "Runtime.hpp"
+#include <chrono>		 // for nanoseconds, duration_cast
+#include <iostream>	 // for operator<<, basic_ostream, endl
+#include <mutex>		 // for mutex
+#include <thread>		 // for thread
+
+#include "BinaryPrivateSemaphore.hpp"	 // for BPS
+#include "Fiber.hpp"									 // for Fiber
+#include "Runtime.hpp"								 // for Runtime
+#include "emper-common.h"							 // for UNUSED_ARG
 
 static std::chrono::nanoseconds threadTimeToSpawn() {
 	std::chrono::time_point<std::chrono::high_resolution_clock> end;
diff --git a/iwyu-mappings.imp b/iwyu-mappings.imp
new file mode 100644
index 0000000000000000000000000000000000000000..8a64523b20e91b6a76971ec7398b9dbbcd5bfa9f
--- /dev/null
+++ b/iwyu-mappings.imp
@@ -0,0 +1,4 @@
+[
+	{ include: ["<bits/getopt_core.h>", "private", "<unistd.h>", "public"] },
+	{ include: ["@<gtest/.*>", "private", "<gtest/gtest.h>", "public"] },
+]
diff --git a/meson.build b/meson.build
index 0733d8e9a733df39e6f71ecc8575996b6f9a4458..325105c849c06f727a5fa8c03dfff3561e7ee435 100644
--- a/meson.build
+++ b/meson.build
@@ -14,6 +14,9 @@ add_project_arguments('-Wno-non-virtual-dtor', language: 'cpp')
 thread_dep = dependency('threads')
 emper_dependencies = [thread_dep]
 
+run_target('iwyu',
+		   command: 'tools/check-iwyu')
+
 conf_data = configuration_data()
 conf_data.set('EMPER_WORKER_SLEEP', get_option('worker_sleep'))
 conf_data.set('EMPER_LOCKED_WS_QUEUE', get_option('locked_ws_queue'))
diff --git a/tests/CppApiTest.cpp b/tests/CppApiTest.cpp
index 6308da482b51dbb21001bc421846a34855f08353..954ad76ce784b2b9ca01f4d2697c6db6a5de1b70 100644
--- a/tests/CppApiTest.cpp
+++ b/tests/CppApiTest.cpp
@@ -1,7 +1,11 @@
-#include <atomic>
+#include <stdlib.h>	 // for exit, EXIT_FAILURE, EXIT_SUC...
 
-#include "CountingPrivateSemaphore.hpp"
-#include "emper.hpp"
+#include <atomic>	 // for atomic_uint, __atomic_base
+
+#include "CountingPrivateSemaphore.hpp"	 // for CountingPrivateSemaphore
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG
+#include "emper.hpp"										 // for async, spawn
 
 static std::atomic_uint counter;
 
diff --git a/tests/SimpleActorTest.cpp b/tests/SimpleActorTest.cpp
index d0fd44f464da70892c55dc5e4e3ba318445a1b19..d00480a6919cf97a7757a9ee76bb97b3df05f290 100644
--- a/tests/SimpleActorTest.cpp
+++ b/tests/SimpleActorTest.cpp
@@ -1,12 +1,17 @@
-#include <atomic>
-#include <mutex>
-
-#include "Actor.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "Debug.hpp"
-#include "Dispatcher.hpp"
-#include "Runtime.hpp"
-#include "emper.hpp"
+#include <stdlib.h>	 // for exit, EXIT_FAILURE, EXIT_SUC...
+
+#include <atomic>		 // for atomic_thread_fence, memory_...
+#include <cstdint>	 // for uint64_t
+#include <iostream>	 // for operator<<, basic_ostream
+
+#include "Actor.hpp"										 // for Actor
+#include "CountingPrivateSemaphore.hpp"	 // for CPS
+#include "Debug.hpp"										 // for WDBG
+#include "Dispatcher.hpp"								 // for Dispatcher
+#include "Fiber.hpp"										 // for Fiber
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG
+#include "emper.hpp"										 // for spawn
 
 class SumActor : public Actor<uint64_t> {
  private:
diff --git a/tests/SimpleFibTest.cpp b/tests/SimpleFibTest.cpp
index cd29790a0df0e75be3c12c88ca87a3bac43328a9..1ac4a78687721577c26ec052ae4dce1d0ab4cafb 100644
--- a/tests/SimpleFibTest.cpp
+++ b/tests/SimpleFibTest.cpp
@@ -1,16 +1,11 @@
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <iostream>
-#include <list>
-#include <string>
-
-#include "BinaryPrivateSemaphore.hpp"
-#include "Common.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "Debug.hpp"
-#include "PrivateSemaphore.hpp"
-#include "Runtime.hpp"
+#include <stdlib.h>	 // for exit, EXIT_FAILURE, EXIT_SUC...
+
+#include "BinaryPrivateSemaphore.hpp"		 // for BPS
+#include "CountingPrivateSemaphore.hpp"	 // for CPS
+#include "Fiber.hpp"										 // for Fiber
+#include "PrivateSemaphore.hpp"					 // for PS
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG
 
 typedef struct {
 	int n;
diff --git a/tests/SimplestFibTest.cpp b/tests/SimplestFibTest.cpp
index 753b26f64714ec96b0a41674fd05a3a49e5dbf8e..3f5a5a95312adeda02058cb1cf603905f39c452d 100644
--- a/tests/SimplestFibTest.cpp
+++ b/tests/SimplestFibTest.cpp
@@ -1,16 +1,14 @@
-#include <cstdlib>
-#include <iostream>
-#include <list>
-#include <string>
-#include <thread>
-
-#include "BinaryPrivateSemaphore.hpp"
-#include "Common.hpp"
-#include "CountingPrivateSemaphore.hpp"
-#include "Debug.hpp"
-#include "PrivateSemaphore.hpp"
-#include "Runtime.hpp"
-#include "emper.hpp"
+#include <cstdlib>	 // for abort, exit, EXIT_SUCCESS
+#include <iostream>	 // for operator<<, basic_ostream::o...
+
+#include "BinaryPrivateSemaphore.hpp"		 // for BPS
+#include "CountingPrivateSemaphore.hpp"	 // for CPS
+#include "Debug.hpp"										 // for WDBG
+#include "Fiber.hpp"										 // for Fiber
+#include "PrivateSemaphore.hpp"					 // for PS
+#include "Runtime.hpp"									 // for Runtime
+#include "emper-common.h"								 // for UNUSED_ARG
+#include "emper.hpp"										 // for async
 
 typedef struct {
 	int n;
diff --git a/tools/check-iwyu b/tools/check-iwyu
new file mode 100755
index 0000000000000000000000000000000000000000..24f74423dce4f0aea3e4209e0e5c5cead1db32db
--- /dev/null
+++ b/tools/check-iwyu
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Meson issue regarding iwyu integration: https://github.com/mesonbuild/meson/issues/2637
+
+# Pretty fancy method to get reliable the absolute path of a shell
+# script, *even if it is sourced*. Credits go to GreenFox on
+# stackoverflow: http://stackoverflow.com/a/12197518/194894
+pushd . > /dev/null
+SCRIPTDIR="${BASH_SOURCE[0]}";
+while([ -h "${SCRIPTDIR}" ]); do
+    cd "`dirname "${SCRIPTDIR}"`"
+    SCRIPTDIR="$(readlink "`basename "${SCRIPTDIR}"`")";
+done
+cd "`dirname "${SCRIPTDIR}"`" > /dev/null
+SCRIPTDIR="`pwd`";
+popd  > /dev/null
+
+set +u
+if [[ -n "${MESON_BUILD_ROOT}" ]]; then
+	MESON_BUILD_ROOT_SET=true
+else
+	MESON_BUILD_ROOT_SET=false
+fi
+set -u
+
+if ! ${MESON_BUILD_ROOT_SET}; then
+	ROOTDIR=$(readlink -f "${SCRIPTDIR}/..")
+	make -C "${ROOTDIR}"
+	MESON_BUILD_ROOT="${ROOTDIR}/build"
+	exec ninja -C "${MESON_BUILD_ROOT}" iwyu
+fi
+
+readonly IWYU_LOG="${MESON_BUILD_ROOT}/iwyu.log"
+
+NPROC=$(nproc)
+
+for POSSIBLE_CMD in iwyu_tool iwyu-tool iwyu_tool.py; do
+	if command -v ${POSSIBLE_CMD} >/dev/null; then
+		IWYU_TOOL=${POSSIBLE_CMD}
+		break
+	fi
+done
+
+if [[ -z "${IWYU_TOOL}" ]]; then
+	echo "iwyu_tool not found"
+	exit 1
+fi
+
+${IWYU_TOOL} -p "${MESON_BUILD_ROOT}" --jobs "${NPROC}" -- \
+			 -Xiwyu --mapping_file="${MESON_SOURCE_ROOT}/iwyu-mappings.imp" \
+			 > "${IWYU_LOG}"
+
+# Sadly, iwyu_tool.py does not (yet) return an non-zero exit value if
+# there are include issues, so we have to check the output manually.
+# See https://github.com/include-what-you-use/include-what-you-use/issues/790
+# Also note that the output contains "error: nos uch file or
+# directory: 'cc'" if ccache is used (which meson does by default if
+# ccache is available).
+# See https://github.com/include-what-you-use/include-what-you-use/issues/789
+ERROR_STRINGS=()
+ERROR_STRINGS+=("should add these lines:")
+ERROR_STRINGS+=("fatal error:")
+
+for ERROR_STRING in "${ERROR_STRINGS[@]}"; do
+	if grep -q "${ERROR_STRING}" "${IWYU_LOG}"; then
+		echo "IWYU found errors!"  
+		cat "${IWYU_LOG}"		   
+		exit 1
+	fi
+done