Limit SPIR-V phi variable lifetimes

This change moves phi variables from SpirvRoutine to EmitState. The
former class represents the interface of a shader, and phis are not part
of that. EmitState only lives during emission of the actual SPIR-V code
into Reactor code, so it deletes these variables automatically and
prevents unnecessary materialization and reduces compilation overhead.

This also enabled reducing the public interface of EmitState to a single
static emit() function.

Bug: b/178662288
Bug: b/247020580
Change-Id: I88110c02e925699ad9706aaa3dc8779f9bbd3430
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/68729
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Pipeline/ComputeProgram.cpp b/src/Pipeline/ComputeProgram.cpp
index bb05cee..530491a 100644
--- a/src/Pipeline/ComputeProgram.cpp
+++ b/src/Pipeline/ComputeProgram.cpp
@@ -47,7 +47,6 @@
 	shader->emitProlog(&routine);
 	emit(&routine);
 	shader->emitEpilog(&routine);
-	shader->clearPhis(&routine);
 }
 
 void ComputeProgram::setWorkgroupBuiltins(Pointer<Byte> data, SpirvRoutine *routine, Int workgroupID[3])
diff --git a/src/Pipeline/PixelProgram.cpp b/src/Pipeline/PixelProgram.cpp
index 6919cb3..df4f273 100644
--- a/src/Pipeline/PixelProgram.cpp
+++ b/src/Pipeline/PixelProgram.cpp
@@ -199,12 +199,6 @@
 
 	spirvShader->emit(&routine, activeLaneMask, storesAndAtomicsMask, descriptorSets, state.multiSampleCount);
 	spirvShader->emitEpilog(&routine);
-	// At the last invocation of the fragment shader, clear phi data.
-	// TODO(b/178662288): Automatically clear phis through SpirvRoutine lifetime reduction.
-	if(samples[0] == static_cast<int>(state.multiSampleCount - 1))
-	{
-		spirvShader->clearPhis(&routine);
-	}
 
 	for(int i = 0; i < MAX_COLOR_BUFFERS; i++)
 	{
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 06fa405..08ebc52 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -1781,20 +1781,13 @@
 				auto resultPointerType = getType(insn.resultTypeId());
 				auto pointeeType = getType(resultPointerType.element);
 
-				if(pointeeType.componentCount > 0)  // TODO: what to do about zero-slot objects?
+				if(pointeeType.componentCount > 0)
 				{
 					routine->createVariable(insn.resultId(), pointeeType.componentCount);
 				}
 			}
 			break;
 
-		case spv::OpPhi:
-			{
-				auto type = getType(insn.resultTypeId());
-				routine->phis.emplace(insn.resultId(), SpirvRoutine::Variable(type.componentCount));
-			}
-			break;
-
 		case spv::OpImageSampleImplicitLod:
 		case spv::OpImageSampleExplicitLod:
 		case spv::OpImageSampleDrefImplicitLod:
@@ -1824,21 +1817,60 @@
 
 void SpirvShader::emit(SpirvRoutine *routine, const RValue<SIMD::Int> &activeLaneMask, const RValue<SIMD::Int> &storesAndAtomicsMask, const vk::DescriptorSet::Bindings &descriptorSets, unsigned int multiSampleCount) const
 {
-	EmitState state(*this, routine, entryPoint, activeLaneMask, storesAndAtomicsMask, descriptorSets, multiSampleCount);
+	EmitState::emit(*this, routine, entryPoint, activeLaneMask, storesAndAtomicsMask, descriptorSets, multiSampleCount);
+}
+
+EmitState::EmitState(const SpirvShader &shader,
+                     SpirvRoutine *routine,
+                     SpirvShader::Function::ID entryPoint,
+                     RValue<SIMD::Int> activeLaneMask,
+                     RValue<SIMD::Int> storesAndAtomicsMask,
+                     const vk::DescriptorSet::Bindings &descriptorSets,
+                     unsigned int multiSampleCount)
+    : shader(shader)
+    , routine(routine)
+    , function(entryPoint)
+    , activeLaneMaskValue(activeLaneMask.value())
+    , storesAndAtomicsMaskValue(storesAndAtomicsMask.value())
+    , descriptorSets(descriptorSets)
+    , multiSampleCount(multiSampleCount)
+{
+}
+
+void EmitState::emit(const SpirvShader &shader,
+                     SpirvRoutine *routine,
+                     SpirvShader::Function::ID entryPoint,
+                     RValue<SIMD::Int> activeLaneMask,
+                     RValue<SIMD::Int> storesAndAtomicsMask,
+                     const vk::DescriptorSet::Bindings &descriptorSets,
+                     unsigned int multiSampleCount)
+{
+	EmitState state(shader, routine, entryPoint, activeLaneMask, storesAndAtomicsMask, descriptorSets, multiSampleCount);
+
+	// Create phi variables
+	for(auto insn : shader)
+	{
+		if(insn.opcode() == spv::OpPhi)
+		{
+			auto type = shader.getType(insn.resultTypeId());
+			state.phis.emplace(insn.resultId(), Array<SIMD::Float>(type.componentCount));
+		}
+	}
 
 	// Emit everything up to the first label
 	// TODO: Separate out dispatch of block from non-block instructions?
-	for(auto insn : *this)
+	for(auto insn : shader)
 	{
 		if(insn.opcode() == spv::OpLabel)
 		{
 			break;
 		}
+
 		state.EmitInstruction(insn);
 	}
 
 	// Emit all the blocks starting from entryPoint.
-	state.EmitBlocks(getFunction(entryPoint).entry);
+	state.EmitBlocks(shader.getFunction(entryPoint).entry);
 }
 
 void EmitState::EmitInstructions(InsnIterator begin, InsnIterator end)
@@ -2716,15 +2748,6 @@
 	}
 }
 
-void SpirvShader::clearPhis(SpirvRoutine *routine) const
-{
-	// Clear phis that are no longer used. This serves two purposes:
-	// (1) The phi rr::Variables are destructed, preventing pointless
-	//     materialization.
-	// (2) Frees memory that will never be used again.
-	routine->phis.clear();
-}
-
 VkShaderStageFlagBits SpirvShader::executionModelToStage(spv::ExecutionModel model)
 {
 	switch(model)
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 9a3ad64..2a40a53 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -813,10 +813,10 @@
 	std::vector<InterfaceComponent> inputs;
 	std::vector<InterfaceComponent> outputs;
 
+	// TODO(b/247020580): Move to SpirvRoutine
 	void emitProlog(SpirvRoutine *routine) const;
 	void emit(SpirvRoutine *routine, const RValue<SIMD::Int> &activeLaneMask, const RValue<SIMD::Int> &storesAndAtomicsMask, const vk::DescriptorSet::Bindings &descriptorSets, unsigned int multiSampleCount = 0) const;
 	void emitEpilog(SpirvRoutine *routine) const;
-	void clearPhis(SpirvRoutine *routine) const;
 
 	uint32_t getWorkgroupSizeX() const;
 	uint32_t getWorkgroupSizeY() const;
@@ -990,22 +990,28 @@
 	using Span = SpirvShader::Span;
 
 public:
+	static void emit(const SpirvShader &shader,
+	                 SpirvRoutine *routine,
+	                 SpirvShader::Function::ID entryPoint,
+	                 RValue<SIMD::Int> activeLaneMask,
+	                 RValue<SIMD::Int> storesAndAtomicsMask,
+	                 const vk::DescriptorSet::Bindings &descriptorSets,
+	                 unsigned int multiSampleCount);
+
+	// Helper for calling rr::Yield with result cast to an rr::Int.
+	enum class YieldResult
+	{
+		ControlBarrier = 0,
+	};
+
+private:
 	EmitState(const SpirvShader &shader,
 	          SpirvRoutine *routine,
-	          SpirvShader::Function::ID function,
+	          SpirvShader::Function::ID entryPoint,
 	          RValue<SIMD::Int> activeLaneMask,
 	          RValue<SIMD::Int> storesAndAtomicsMask,
 	          const vk::DescriptorSet::Bindings &descriptorSets,
-	          unsigned int multiSampleCount)
-	    : shader(shader)
-	    , routine(routine)
-	    , function(function)
-	    , activeLaneMaskValue(activeLaneMask.value())
-	    , storesAndAtomicsMaskValue(storesAndAtomicsMask.value())
-	    , descriptorSets(descriptorSets)
-	    , multiSampleCount(multiSampleCount)
-	{
-	}
+	          unsigned int multiSampleCount);
 
 	// Returns the mask describing the active lanes as updated by dynamic
 	// control flow. Active lanes include helper invocations, used for
@@ -1481,16 +1487,11 @@
 	// StorePhi updates the phi's alloca storage value using the incoming
 	// values from blocks that are both in the OpPhi instruction and in
 	// filter.
-	void StorePhi(Block::ID blockID, InsnIterator insn, const std::unordered_set<Block::ID> &filter) const;
+	void StorePhi(Block::ID blockID, InsnIterator insn, const std::unordered_set<Block::ID> &filter);
 
 	// Emits a rr::Fence for the given MemorySemanticsMask.
 	void Fence(spv::MemorySemanticsMask semantics) const;
 
-	// Helper for calling rr::Yield with res cast to an rr::Int.
-	enum class YieldResult
-	{
-		ControlBarrier = 0,
-	};
 	void Yield(YieldResult res) const;
 
 	// Helper as we often need to take dot products as part of doing other things.
@@ -1511,7 +1512,6 @@
 	static sw::MipmapType convertMipmapMode(const vk::SamplerState *samplerState);
 	static sw::AddressingMode convertAddressingMode(int coordinateIndex, const vk::SamplerState *samplerState, VkImageViewType imageViewType);
 
-private:
 	const SpirvShader &shader;
 	SpirvRoutine *const routine;                     // The current routine being built.
 	SpirvShader::Function::ID function;              // The current function being built.
@@ -1525,6 +1525,7 @@
 	const vk::DescriptorSet::Bindings &descriptorSets;
 
 	std::unordered_map<Object::ID, Intermediate> intermediates;
+	std::unordered_map<Object::ID, Array<SIMD::Float>> phis;
 	std::unordered_map<Object::ID, SIMD::Pointer> pointers;
 	std::unordered_map<Object::ID, SampledImagePointer> sampledImages;
 
@@ -1639,15 +1640,6 @@
 			f(builtin, getVariable(builtin.Id));
 		}
 	}
-
-private:
-	// The phis are only accessible to SpirvShader
-	// as they are only used and exist between calls to
-	// SpirvShader::emitProlog() and SpirvShader::emitEpilog().
-	friend class SpirvShader;
-
-public:
-	std::unordered_map<Object::ID, Variable> phis;
 };
 
 }  // namespace sw
diff --git a/src/Pipeline/SpirvShaderControlFlow.cpp b/src/Pipeline/SpirvShaderControlFlow.cpp
index e50d4c5..5783724 100644
--- a/src/Pipeline/SpirvShaderControlFlow.cpp
+++ b/src/Pipeline/SpirvShaderControlFlow.cpp
@@ -679,8 +679,8 @@
 	auto type = shader.getType(typeId);
 	auto objectId = Object::ID(insn.word(2));
 
-	auto storageIt = routine->phis.find(objectId);
-	ASSERT(storageIt != routine->phis.end());
+	auto storageIt = phis.find(objectId);
+	ASSERT(storageIt != phis.end());
 	auto &storage = storageIt->second;
 
 	auto &dst = createIntermediate(objectId, type.componentCount);
@@ -691,14 +691,14 @@
 	}
 }
 
-void EmitState::StorePhi(Block::ID currentBlock, InsnIterator insn, const std::unordered_set<Block::ID> &filter) const
+void EmitState::StorePhi(Block::ID currentBlock, InsnIterator insn, const std::unordered_set<Block::ID> &filter)
 {
 	auto typeId = Type::ID(insn.word(1));
 	auto type = shader.getType(typeId);
 	auto objectId = Object::ID(insn.word(2));
 
-	auto storageIt = routine->phis.find(objectId);
-	ASSERT(storageIt != routine->phis.end());
+	auto storageIt = phis.find(objectId);
+	ASSERT(storageIt != phis.end());
 	auto &storage = storageIt->second;
 
 	for(uint32_t w = 3; w < insn.wordCount(); w += 2)
diff --git a/src/Pipeline/VertexProgram.cpp b/src/Pipeline/VertexProgram.cpp
index 9a553cf..3ffa499 100644
--- a/src/Pipeline/VertexProgram.cpp
+++ b/src/Pipeline/VertexProgram.cpp
@@ -85,7 +85,6 @@
 	spirvShader->emit(&routine, activeLaneMask, storesAndAtomicsMask, descriptorSets);
 
 	spirvShader->emitEpilog(&routine);
-	spirvShader->clearPhis(&routine);
 }
 
 }  // namespace sw