Report back the Subzero optimizer results

This change adds a callback mechanism to report how many instructions
of certain types are left after the optimization passes have been run.
This enables unit tests to check that the desired optimization actually
took place, instead of just checking correct execution results.

Bug: b/180665600
Change-Id: I3916d327138516a0a0778be2b3fdd5b000fc9bdb
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/52989
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/Reactor/LLVMReactor.cpp b/src/Reactor/LLVMReactor.cpp
index bc214f3..3a56b4c 100644
--- a/src/Reactor/LLVMReactor.cpp
+++ b/src/Reactor/LLVMReactor.cpp
@@ -1781,6 +1781,12 @@
 	return V(ptr);
 }
 
+void Nucleus::setOptimizerCallback(OptimizerCallback *callback)
+{
+	// The LLVM backend does not produce optimizer reports.
+	(void)callback;
+}
+
 Type *Void::type()
 {
 	return T(llvm::Type::getVoidTy(*jit->context));
diff --git a/src/Reactor/Nucleus.hpp b/src/Reactor/Nucleus.hpp
index 4d2b003..d430845 100644
--- a/src/Reactor/Nucleus.hpp
+++ b/src/Reactor/Nucleus.hpp
@@ -333,6 +333,20 @@
 	static Type *getContainedType(Type *vectorType);
 	static Type *getPointerType(Type *elementType);
 	static Type *getPrintfStorageType(Type *valueType);
+
+	// Diagnostic utilities
+	struct OptimizerReport
+	{
+		int allocas = 0;
+		int loads = 0;
+		int stores = 0;
+	};
+
+	using OptimizerCallback = void(const OptimizerReport *report);
+
+	// Sets the callback to be used by the next optimizer invocation (during acquireRoutine),
+	// for reporting stats about the resulting IR code. For testing only.
+	static void setOptimizerCallback(OptimizerCallback *callback);
 };
 
 }  // namespace rr
diff --git a/src/Reactor/Optimizer.cpp b/src/Reactor/Optimizer.cpp
index d5f95e8..8ec7af1 100644
--- a/src/Reactor/Optimizer.cpp
+++ b/src/Reactor/Optimizer.cpp
@@ -24,6 +24,11 @@
 class Optimizer
 {
 public:
+	Optimizer(rr::Nucleus::OptimizerReport *report)
+	    : report(report)
+	{
+	}
+
 	void run(Ice::Cfg *function);
 
 private:
@@ -46,6 +51,8 @@
 	static std::size_t storeSize(const Ice::Inst *instruction);
 	static bool loadTypeMatchesStore(const Ice::Inst *load, const Ice::Inst *store);
 
+	void collectDiagnostics();
+
 	Ice::Cfg *function;
 	Ice::GlobalContext *context;
 
@@ -88,6 +95,8 @@
 	bool hasLoadStoreInsts(Ice::CfgNode *node) const;
 
 	std::vector<Ice::Operand *> operandsWithUses;
+
+	rr::Nucleus::OptimizerReport *report = nullptr;
 };
 
 void Optimizer::run(Ice::Cfg *function)
@@ -115,6 +124,8 @@
 		setUses(operand, nullptr);
 	}
 	operandsWithUses.clear();
+
+	collectDiagnostics();
 }
 
 // Eliminates allocas which store the address of other allocas.
@@ -719,6 +730,38 @@
 	return true;
 }
 
+void Optimizer::collectDiagnostics()
+{
+	if(report)
+	{
+		*report = {};
+
+		for(auto *basicBlock : function->getNodes())
+		{
+			for(auto &inst : basicBlock->getInsts())
+			{
+				if(inst.isDeleted())
+				{
+					continue;
+				}
+
+				if(llvm::isa<Ice::InstAlloca>(inst))
+				{
+					report->allocas++;
+				}
+				else if(isLoad(inst))
+				{
+					report->loads++;
+				}
+				else if(isStore(inst))
+				{
+					report->stores++;
+				}
+			}
+		}
+	}
+}
+
 Optimizer::Uses *Optimizer::getUses(Ice::Operand *operand)
 {
 	Optimizer::Uses *uses = (Optimizer::Uses *)operand->Ice::Operand::getExternalData();
@@ -846,9 +889,9 @@
 
 namespace rr {
 
-void optimize(Ice::Cfg *function)
+void optimize(Ice::Cfg *function, Nucleus::OptimizerReport *report)
 {
-	Optimizer optimizer;
+	Optimizer optimizer(report);
 
 	optimizer.run(function);
 }
diff --git a/src/Reactor/Optimizer.hpp b/src/Reactor/Optimizer.hpp
index b51c74a..7d82a93 100644
--- a/src/Reactor/Optimizer.hpp
+++ b/src/Reactor/Optimizer.hpp
@@ -15,11 +15,13 @@
 #ifndef rr_Optimizer_hpp
 #define rr_Optimizer_hpp
 
+#include "Nucleus.hpp"
+
 #include "src/IceCfg.h"
 
 namespace rr {
 
-void optimize(Ice::Cfg *function);
+void optimize(Ice::Cfg *function, Nucleus::OptimizerReport *report = nullptr);
 
 }  // namespace rr
 
diff --git a/src/Reactor/SubzeroReactor.cpp b/src/Reactor/SubzeroReactor.cpp
index 53377ca..ada1072 100644
--- a/src/Reactor/SubzeroReactor.cpp
+++ b/src/Reactor/SubzeroReactor.cpp
@@ -241,6 +241,9 @@
 
 	return *scheduler;
 }
+
+rr::Nucleus::OptimizerCallback *optimizerCallback = nullptr;
+
 }  // Anonymous namespace
 
 namespace {
@@ -1008,7 +1011,17 @@
 
 		currFunc->setFunctionName(Ice::GlobalString::createWithString(::context, names[i]));
 
-		rr::optimize(currFunc);
+		if(::optimizerCallback)
+		{
+			Nucleus::OptimizerReport report;
+			rr::optimize(currFunc, &report);
+			::optimizerCallback(&report);
+			::optimizerCallback = nullptr;
+		}
+		else
+		{
+			rr::optimize(currFunc);
+		}
 
 		currFunc->computeInOutEdges();
 		ASSERT_MSG(!currFunc->hasError(), "%s", currFunc->getError().c_str());
@@ -2186,6 +2199,11 @@
 	return V(IceConstantData(v, strlen(v) + 1));
 }
 
+void Nucleus::setOptimizerCallback(OptimizerCallback *callback)
+{
+	::optimizerCallback = callback;
+}
+
 Type *Void::type()
 {
 	return T(Ice::IceType_void);
diff --git a/tests/ReactorUnitTests/ReactorUnitTests.cpp b/tests/ReactorUnitTests/ReactorUnitTests.cpp
index 6b85a48..51a7531 100644
--- a/tests/ReactorUnitTests/ReactorUnitTests.cpp
+++ b/tests/ReactorUnitTests/ReactorUnitTests.cpp
@@ -395,7 +395,6 @@
 
 // This test excercises the Optimizer::eliminateLoadsFollowingSingleStore() optimization pass.
 // The three load operations for `y` should get eliminated.
-// TODO(b/180665600): Check that the optimization took place.
 TEST(ReactorUnitTests, EliminateLoadsFollowingSingleStore)
 {
 	FunctionT<int(int)> function;
@@ -415,6 +414,12 @@
 		Return(z);
 	}
 
+	Nucleus::setOptimizerCallback([](const Nucleus::OptimizerReport *report) {
+		EXPECT_EQ(report->allocas, 2);
+		EXPECT_EQ(report->loads, 2);
+		EXPECT_EQ(report->stores, 2);
+	});
+
 	auto routine = function(testName().c_str());
 
 	int result = routine(11);
@@ -423,7 +428,6 @@
 
 // This test excercises the Optimizer::propagateAlloca() optimization pass.
 // The pointer variable should not get stored to / loaded from memory.
-// TODO(b/180665600): Check that the optimization took place.
 TEST(ReactorUnitTests, PropagateAlloca)
 {
 	FunctionT<int(int)> function;
@@ -443,6 +447,12 @@
 		Return(Int(*p));  // TODO(b/179694472): Support Return(*p)
 	}
 
+	Nucleus::setOptimizerCallback([](const Nucleus::OptimizerReport *report) {
+		EXPECT_EQ(report->allocas, 1);
+		EXPECT_EQ(report->loads, 1);
+		EXPECT_EQ(report->stores, 1);
+	});
+
 	auto routine = function(testName().c_str());
 
 	int result = routine(true);