Move Reactor code emission from SpirvShader to EmitState

This change avoids having to pass EmitState into each of the methods
that emit Reactor code. It also prepares for separating EmitState from
SpirvShader so that the latter will only handle SPIR-V parsing.

Bug: b/247020580
Change-Id: Idecf94bf4988029474f188c0fb94c8f059765b65
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/68148
Tested-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 972ec15..2973cdd 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -1260,54 +1260,54 @@
 	}
 }
 
-SIMD::Pointer SpirvShader::WalkExplicitLayoutAccessChain(Object::ID baseId, Object::ID elementId, const Span &indexIds, bool nonUniform, const EmitState *state) const
+SIMD::Pointer SpirvShader::EmitState::WalkExplicitLayoutAccessChain(Object::ID baseId, Object::ID elementId, const Span &indexIds, bool nonUniform) const
 {
 	// Produce a offset into external memory in sizeof(float) units
 
-	auto &baseObject = getObject(baseId);
-	Type::ID typeId = getType(baseObject).element;
-	Decorations d = GetDecorationsForId(baseObject.typeId());
+	auto &baseObject = shader.getObject(baseId);
+	Type::ID typeId = shader.getType(baseObject).element;
+	Decorations d = shader.GetDecorationsForId(baseObject.typeId());
 	SIMD::Int arrayIndex = 0;
 
 	uint32_t start = 0;
 	if(baseObject.kind == Object::Kind::DescriptorSet)
 	{
-		auto type = getType(typeId).definition.opcode();
+		auto type = shader.getType(typeId).definition.opcode();
 		if(type == spv::OpTypeArray || type == spv::OpTypeRuntimeArray)
 		{
-			auto &obj = getObject(indexIds[0]);
+			auto &obj = shader.getObject(indexIds[0]);
 			ASSERT(obj.kind == Object::Kind::Constant || obj.kind == Object::Kind::Intermediate);
 			if(obj.kind == Object::Kind::Constant)
 			{
-				arrayIndex = GetConstScalarInt(indexIds[0]);
+				arrayIndex = shader.GetConstScalarInt(indexIds[0]);
 			}
 			else
 			{
-				nonUniform |= GetDecorationsForId(indexIds[0]).NonUniform;
-				arrayIndex = state->getIntermediate(indexIds[0]).Int(0);
+				nonUniform |= shader.GetDecorationsForId(indexIds[0]).NonUniform;
+				arrayIndex = getIntermediate(indexIds[0]).Int(0);
 			}
 
 			start = 1;
-			typeId = getType(typeId).element;
+			typeId = shader.getType(typeId).element;
 		}
 	}
 
-	auto ptr = GetPointerToData(baseId, arrayIndex, nonUniform, state);
-	OffsetToElement(ptr, elementId, d.ArrayStride, state);
+	auto ptr = GetPointerToData(baseId, arrayIndex, nonUniform);
+	OffsetToElement(ptr, elementId, d.ArrayStride);
 
 	int constantOffset = 0;
 
 	for(uint32_t i = start; i < indexIds.size(); i++)
 	{
-		auto &type = getType(typeId);
-		ApplyDecorationsForId(&d, typeId);
+		auto &type = shader.getType(typeId);
+		shader.ApplyDecorationsForId(&d, typeId);
 
 		switch(type.definition.opcode())
 		{
 		case spv::OpTypeStruct:
 			{
-				int memberIndex = GetConstScalarInt(indexIds[i]);
-				ApplyDecorationsForIdMember(&d, typeId, memberIndex);
+				int memberIndex = shader.GetConstScalarInt(indexIds[i]);
+				shader.ApplyDecorationsForIdMember(&d, typeId, memberIndex);
 				ASSERT(d.HasOffset);
 				constantOffset += d.Offset;
 				typeId = type.definition.word(2u + memberIndex);
@@ -1318,14 +1318,14 @@
 			{
 				// TODO: b/127950082: Check bounds.
 				ASSERT(d.HasArrayStride);
-				auto &obj = getObject(indexIds[i]);
+				auto &obj = shader.getObject(indexIds[i]);
 				if(obj.kind == Object::Kind::Constant)
 				{
-					constantOffset += d.ArrayStride * GetConstScalarInt(indexIds[i]);
+					constantOffset += d.ArrayStride * shader.GetConstScalarInt(indexIds[i]);
 				}
 				else
 				{
-					ptr += SIMD::Int(d.ArrayStride) * state->getIntermediate(indexIds[i]).Int(0);
+					ptr += SIMD::Int(d.ArrayStride) * getIntermediate(indexIds[i]).Int(0);
 				}
 				typeId = type.element;
 			}
@@ -1336,14 +1336,14 @@
 				ASSERT(d.HasMatrixStride);
 				d.InsideMatrix = true;
 				auto columnStride = (d.HasRowMajor && d.RowMajor) ? static_cast<int32_t>(sizeof(float)) : d.MatrixStride;
-				auto &obj = getObject(indexIds[i]);
+				auto &obj = shader.getObject(indexIds[i]);
 				if(obj.kind == Object::Kind::Constant)
 				{
-					constantOffset += columnStride * GetConstScalarInt(indexIds[i]);
+					constantOffset += columnStride * shader.GetConstScalarInt(indexIds[i]);
 				}
 				else
 				{
-					ptr += SIMD::Int(columnStride) * state->getIntermediate(indexIds[i]).Int(0);
+					ptr += SIMD::Int(columnStride) * getIntermediate(indexIds[i]).Int(0);
 				}
 				typeId = type.element;
 			}
@@ -1351,14 +1351,14 @@
 		case spv::OpTypeVector:
 			{
 				auto elemStride = (d.InsideMatrix && d.HasRowMajor && d.RowMajor) ? d.MatrixStride : static_cast<int32_t>(sizeof(float));
-				auto &obj = getObject(indexIds[i]);
+				auto &obj = shader.getObject(indexIds[i]);
 				if(obj.kind == Object::Kind::Constant)
 				{
-					constantOffset += elemStride * GetConstScalarInt(indexIds[i]);
+					constantOffset += elemStride * shader.GetConstScalarInt(indexIds[i]);
 				}
 				else
 				{
-					ptr += SIMD::Int(elemStride) * state->getIntermediate(indexIds[i]).Int(0);
+					ptr += SIMD::Int(elemStride) * getIntermediate(indexIds[i]).Int(0);
 				}
 				typeId = type.element;
 			}
@@ -1372,34 +1372,33 @@
 	return ptr;
 }
 
-SIMD::Pointer SpirvShader::WalkAccessChain(Object::ID baseId, Object::ID elementId, const Span &indexIds, bool nonUniform, const EmitState *state) const
+SIMD::Pointer SpirvShader::EmitState::WalkAccessChain(Object::ID baseId, Object::ID elementId, const Span &indexIds, bool nonUniform) const
 {
 	// TODO: avoid doing per-lane work in some cases if we can?
-	auto routine = state->routine;
-	auto &baseObject = getObject(baseId);
-	Type::ID typeId = getType(baseObject).element;
-	Decorations d = GetDecorationsForId(baseObject.typeId());
-	auto storageClass = getType(baseObject).storageClass;
+	auto &baseObject = shader.getObject(baseId);
+	Type::ID typeId = shader.getType(baseObject).element;
+	Decorations d = shader.GetDecorationsForId(baseObject.typeId());
+	auto storageClass = shader.getType(baseObject).storageClass;
 	bool interleavedByLane = IsStorageInterleavedByLane(storageClass);
 
-	auto ptr = state->getPointer(baseId);
-	OffsetToElement(ptr, elementId, d.ArrayStride, state);
+	auto ptr = getPointer(baseId);
+	OffsetToElement(ptr, elementId, d.ArrayStride);
 
 	int constantOffset = 0;
 
 	for(uint32_t i = 0; i < indexIds.size(); i++)
 	{
-		auto &type = getType(typeId);
+		auto &type = shader.getType(typeId);
 		switch(type.opcode())
 		{
 		case spv::OpTypeStruct:
 			{
-				int memberIndex = GetConstScalarInt(indexIds[i]);
+				int memberIndex = shader.GetConstScalarInt(indexIds[i]);
 				int offsetIntoStruct = 0;
 				for(auto j = 0; j < memberIndex; j++)
 				{
 					auto memberType = type.definition.word(2u + j);
-					offsetIntoStruct += getType(memberType).componentCount * sizeof(float);
+					offsetIntoStruct += shader.getType(memberType).componentCount * sizeof(float);
 				}
 				constantOffset += offsetIntoStruct;
 				typeId = type.definition.word(2u + memberIndex);
@@ -1415,20 +1414,20 @@
 				if(storageClass == spv::StorageClassUniformConstant)
 				{
 					// indexing into an array of descriptors.
-					auto d = descriptorDecorations.at(baseId);
+					auto d = shader.descriptorDecorations.at(baseId);
 					ASSERT(d.DescriptorSet >= 0);
 					ASSERT(d.Binding >= 0);
 					uint32_t descriptorSize = routine->pipelineLayout->getDescriptorSize(d.DescriptorSet, d.Binding);
 
-					auto &obj = getObject(indexIds[i]);
+					auto &obj = shader.getObject(indexIds[i]);
 					if(obj.kind == Object::Kind::Constant)
 					{
-						ptr += descriptorSize * GetConstScalarInt(indexIds[i]);
+						ptr += descriptorSize * shader.GetConstScalarInt(indexIds[i]);
 					}
 					else
 					{
-						nonUniform |= GetDecorationsForId(indexIds[i]).NonUniform;
-						SIMD::Int intermediate = state->getIntermediate(indexIds[i]).Int(0);
+						nonUniform |= shader.GetDecorationsForId(indexIds[i]).NonUniform;
+						SIMD::Int intermediate = getIntermediate(indexIds[i]).Int(0);
 						if(nonUniform)
 						{
 							// NonUniform array data can deal with pointers not bound by a 32-bit address
@@ -1450,20 +1449,20 @@
 				}
 				else
 				{
-					auto stride = getType(type.element).componentCount * static_cast<uint32_t>(sizeof(float));
+					auto stride = shader.getType(type.element).componentCount * static_cast<uint32_t>(sizeof(float));
 
 					if(interleavedByLane)
 					{
 						stride *= SIMD::Width;
 					}
 
-					if(getObject(indexIds[i]).kind == Object::Kind::Constant)
+					if(shader.getObject(indexIds[i]).kind == Object::Kind::Constant)
 					{
-						ptr += stride * GetConstScalarInt(indexIds[i]);
+						ptr += stride * shader.GetConstScalarInt(indexIds[i]);
 					}
 					else
 					{
-						ptr += SIMD::Int(stride) * state->getIntermediate(indexIds[i]).Int(0);
+						ptr += SIMD::Int(stride) * getIntermediate(indexIds[i]).Int(0);
 					}
 				}
 				typeId = type.element;
@@ -1717,7 +1716,7 @@
 	object.definition = insn;
 }
 
-OutOfBoundsBehavior SpirvShader::getOutOfBoundsBehavior(Object::ID pointerId, const EmitState *state) const
+OutOfBoundsBehavior SpirvShader::getOutOfBoundsBehavior(Object::ID pointerId, const vk::PipelineLayout *pipelineLayout) const
 {
 	auto it = descriptorDecorations.find(pointerId);
 	if(it != descriptorDecorations.end())
@@ -1725,7 +1724,7 @@
 		const auto &d = it->second;
 		if((d.DescriptorSet >= 0) && (d.Binding >= 0))
 		{
-			auto descriptorType = state->routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
+			auto descriptorType = pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 			if(descriptorType == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT)
 			{
 				return OutOfBoundsBehavior::UndefinedBehavior;
@@ -1825,7 +1824,7 @@
 
 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(routine, entryPoint, activeLaneMask, storesAndAtomicsMask, descriptorSets, multiSampleCount);
+	EmitState state(*this, routine, entryPoint, activeLaneMask, storesAndAtomicsMask, descriptorSets, multiSampleCount);
 
 	// Emit everything up to the first label
 	// TODO: Separate out dispatch of block from non-block instructions?
@@ -1835,32 +1834,32 @@
 		{
 			break;
 		}
-		EmitInstruction(insn, &state);
+		state.EmitInstruction(insn);
 	}
 
 	// Emit all the blocks starting from entryPoint.
-	EmitBlocks(getFunction(entryPoint).entry, &state);
+	state.EmitBlocks(getFunction(entryPoint).entry);
 }
 
-void SpirvShader::EmitInstructions(InsnIterator begin, InsnIterator end, EmitState *state) const
+void SpirvShader::EmitState::EmitInstructions(InsnIterator begin, InsnIterator end)
 {
 	for(auto insn = begin; insn != end; insn++)
 	{
-		auto res = EmitInstruction(insn, state);
-		switch(res)
+		auto result = EmitInstruction(insn);
+		switch(result)
 		{
 		case EmitResult::Continue:
 			continue;
 		case EmitResult::Terminator:
 			break;
 		default:
-			UNREACHABLE("Unexpected EmitResult %d", int(res));
+			UNREACHABLE("Unexpected EmitResult %d", int(result));
 			break;
 		}
 	}
 }
 
-SpirvShader::EmitResult SpirvShader::EmitInstruction(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitInstruction(InsnIterator insn)
 {
 	auto opcode = insn.opcode();
 
@@ -1941,15 +1940,15 @@
 		return EmitResult::Continue;
 
 	case spv::OpVariable:
-		return EmitVariable(insn, state);
+		return EmitVariable(insn);
 
 	case spv::OpLoad:
 	case spv::OpAtomicLoad:
-		return EmitLoad(insn, state);
+		return EmitLoad(insn);
 
 	case spv::OpStore:
 	case spv::OpAtomicStore:
-		return EmitStore(insn, state);
+		return EmitStore(insn);
 
 	case spv::OpAtomicIAdd:
 	case spv::OpAtomicISub:
@@ -1963,52 +1962,52 @@
 	case spv::OpAtomicIIncrement:
 	case spv::OpAtomicIDecrement:
 	case spv::OpAtomicExchange:
-		return EmitAtomicOp(insn, state);
+		return EmitAtomicOp(insn);
 
 	case spv::OpAtomicCompareExchange:
-		return EmitAtomicCompareExchange(insn, state);
+		return EmitAtomicCompareExchange(insn);
 
 	case spv::OpAccessChain:
 	case spv::OpInBoundsAccessChain:
 	case spv::OpPtrAccessChain:
-		return EmitAccessChain(insn, state);
+		return EmitAccessChain(insn);
 
 	case spv::OpCompositeConstruct:
-		return EmitCompositeConstruct(insn, state);
+		return EmitCompositeConstruct(insn);
 
 	case spv::OpCompositeInsert:
-		return EmitCompositeInsert(insn, state);
+		return EmitCompositeInsert(insn);
 
 	case spv::OpCompositeExtract:
-		return EmitCompositeExtract(insn, state);
+		return EmitCompositeExtract(insn);
 
 	case spv::OpVectorShuffle:
-		return EmitVectorShuffle(insn, state);
+		return EmitVectorShuffle(insn);
 
 	case spv::OpVectorExtractDynamic:
-		return EmitVectorExtractDynamic(insn, state);
+		return EmitVectorExtractDynamic(insn);
 
 	case spv::OpVectorInsertDynamic:
-		return EmitVectorInsertDynamic(insn, state);
+		return EmitVectorInsertDynamic(insn);
 
 	case spv::OpVectorTimesScalar:
 	case spv::OpMatrixTimesScalar:
-		return EmitVectorTimesScalar(insn, state);
+		return EmitVectorTimesScalar(insn);
 
 	case spv::OpMatrixTimesVector:
-		return EmitMatrixTimesVector(insn, state);
+		return EmitMatrixTimesVector(insn);
 
 	case spv::OpVectorTimesMatrix:
-		return EmitVectorTimesMatrix(insn, state);
+		return EmitVectorTimesMatrix(insn);
 
 	case spv::OpMatrixTimesMatrix:
-		return EmitMatrixTimesMatrix(insn, state);
+		return EmitMatrixTimesMatrix(insn);
 
 	case spv::OpOuterProduct:
-		return EmitOuterProduct(insn, state);
+		return EmitOuterProduct(insn);
 
 	case spv::OpTranspose:
-		return EmitTranspose(insn, state);
+		return EmitTranspose(insn);
 
 	case spv::OpNot:
 	case spv::OpBitFieldInsert:
@@ -2036,7 +2035,7 @@
 	case spv::OpDPdyFine:
 	case spv::OpFwidthFine:
 	case spv::OpQuantizeToF16:
-		return EmitUnaryOp(insn, state);
+		return EmitUnaryOp(insn);
 
 	case spv::OpIAdd:
 	case spv::OpISub:
@@ -2088,7 +2087,7 @@
 	case spv::OpSMulExtended:
 	case spv::OpIAddCarry:
 	case spv::OpISubBorrow:
-		return EmitBinaryOp(insn, state);
+		return EmitBinaryOp(insn);
 
 	case spv::OpDot:
 	case spv::OpSDot:
@@ -2097,54 +2096,54 @@
 	case spv::OpSDotAccSat:
 	case spv::OpUDotAccSat:
 	case spv::OpSUDotAccSat:
-		return EmitDot(insn, state);
+		return EmitDot(insn);
 
 	case spv::OpSelect:
-		return EmitSelect(insn, state);
+		return EmitSelect(insn);
 
 	case spv::OpExtInst:
-		return EmitExtendedInstruction(insn, state);
+		return EmitExtendedInstruction(insn);
 
 	case spv::OpAny:
-		return EmitAny(insn, state);
+		return EmitAny(insn);
 
 	case spv::OpAll:
-		return EmitAll(insn, state);
+		return EmitAll(insn);
 
 	case spv::OpBranch:
-		return EmitBranch(insn, state);
+		return EmitBranch(insn);
 
 	case spv::OpPhi:
-		return EmitPhi(insn, state);
+		return EmitPhi(insn);
 
 	case spv::OpSelectionMerge:
 	case spv::OpLoopMerge:
 		return EmitResult::Continue;
 
 	case spv::OpBranchConditional:
-		return EmitBranchConditional(insn, state);
+		return EmitBranchConditional(insn);
 
 	case spv::OpSwitch:
-		return EmitSwitch(insn, state);
+		return EmitSwitch(insn);
 
 	case spv::OpUnreachable:
-		return EmitUnreachable(insn, state);
+		return EmitUnreachable(insn);
 
 	case spv::OpReturn:
-		return EmitReturn(insn, state);
+		return EmitReturn(insn);
 
 	case spv::OpFunctionCall:
-		return EmitFunctionCall(insn, state);
+		return EmitFunctionCall(insn);
 
 	case spv::OpKill:
 	case spv::OpTerminateInvocation:
-		return EmitTerminateInvocation(insn, state);
+		return EmitTerminateInvocation(insn);
 
 	case spv::OpDemoteToHelperInvocation:
-		return EmitDemoteToHelperInvocation(insn, state);
+		return EmitDemoteToHelperInvocation(insn);
 
 	case spv::OpIsHelperInvocationEXT:
-		return EmitIsHelperInvocation(insn, state);
+		return EmitIsHelperInvocation(insn);
 
 	case spv::OpImageSampleImplicitLod:
 	case spv::OpImageSampleExplicitLod:
@@ -2158,47 +2157,47 @@
 	case spv::OpImageDrefGather:
 	case spv::OpImageFetch:
 	case spv::OpImageQueryLod:
-		return EmitImageSample(ImageInstruction(insn, *this, state), state);
+		return EmitImageSample(ImageInstruction(insn, shader, *this));
 
 	case spv::OpImageQuerySizeLod:
-		return EmitImageQuerySizeLod(insn, state);
+		return EmitImageQuerySizeLod(insn);
 
 	case spv::OpImageQuerySize:
-		return EmitImageQuerySize(insn, state);
+		return EmitImageQuerySize(insn);
 
 	case spv::OpImageQueryLevels:
-		return EmitImageQueryLevels(insn, state);
+		return EmitImageQueryLevels(insn);
 
 	case spv::OpImageQuerySamples:
-		return EmitImageQuerySamples(insn, state);
+		return EmitImageQuerySamples(insn);
 
 	case spv::OpImageRead:
-		return EmitImageRead(ImageInstruction(insn, *this, state), state);
+		return EmitImageRead(ImageInstruction(insn, shader, *this));
 
 	case spv::OpImageWrite:
-		return EmitImageWrite(ImageInstruction(insn, *this, state), state);
+		return EmitImageWrite(ImageInstruction(insn, shader, *this));
 
 	case spv::OpImageTexelPointer:
-		return EmitImageTexelPointer(ImageInstruction(insn, *this, state), state);
+		return EmitImageTexelPointer(ImageInstruction(insn, shader, *this));
 
 	case spv::OpSampledImage:
-		return EmitSampledImage(insn, state);
+		return EmitSampledImage(insn);
 
 	case spv::OpImage:
-		return EmitImage(insn, state);
+		return EmitImage(insn);
 
 	case spv::OpCopyObject:
 	case spv::OpCopyLogical:
-		return EmitCopyObject(insn, state);
+		return EmitCopyObject(insn);
 
 	case spv::OpCopyMemory:
-		return EmitCopyMemory(insn, state);
+		return EmitCopyMemory(insn);
 
 	case spv::OpControlBarrier:
-		return EmitControlBarrier(insn, state);
+		return EmitControlBarrier(insn);
 
 	case spv::OpMemoryBarrier:
-		return EmitMemoryBarrier(insn, state);
+		return EmitMemoryBarrier(insn);
 
 	case spv::OpGroupNonUniformElect:
 	case spv::OpGroupNonUniformAll:
@@ -2234,10 +2233,10 @@
 	case spv::OpGroupNonUniformLogicalAnd:
 	case spv::OpGroupNonUniformLogicalOr:
 	case spv::OpGroupNonUniformLogicalXor:
-		return EmitGroupNonUniform(insn, state);
+		return EmitGroupNonUniform(insn);
 
 	case spv::OpArrayLength:
-		return EmitArrayLength(insn, state);
+		return EmitArrayLength(insn);
 
 	default:
 		UNREACHABLE("%s", OpcodeName(opcode));
@@ -2247,27 +2246,27 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitAccessChain(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitAccessChain(InsnIterator insn)
 {
 	Type::ID typeId = insn.word(1);
 	Object::ID resultId = insn.word(2);
-	bool nonUniform = GetDecorationsForId(resultId).NonUniform;
+	bool nonUniform = shader.GetDecorationsForId(resultId).NonUniform;
 	Object::ID baseId = insn.word(3);
-	auto &type = getType(typeId);
+	auto &type = shader.getType(typeId);
 	ASSERT(type.componentCount == 1);
-	ASSERT(getObject(resultId).kind == Object::Kind::Pointer);
+	ASSERT(shader.getObject(resultId).kind == Object::Kind::Pointer);
 
 	Object::ID elementId = (insn.opcode() == spv::OpPtrAccessChain) ? insn.word(4) : 0;
 	int indexId = (insn.opcode() == spv::OpPtrAccessChain) ? 5 : 4;
 	// TODO(b/236280746): Eliminate lookahead by optimizing inside SIMD::Pointer.
-	for(auto it = insn; it != end(); it++)
+	for(auto it = insn; it != shader.end(); it++)
 	{
 		if(it.opcode() == spv::OpLoad)
 		{
 			Object::ID pointerId = it.word(3);
 			if(pointerId.value() == resultId.value())
 			{
-				nonUniform |= GetDecorationsForId(it.word(2)).NonUniform;
+				nonUniform |= shader.GetDecorationsForId(it.word(2)).NonUniform;
 				break;
 			}
 		}
@@ -2278,30 +2277,30 @@
 	   type.storageClass == spv::StorageClassStorageBuffer ||
 	   type.storageClass == spv::StorageClassPhysicalStorageBuffer)
 	{
-		auto ptr = WalkExplicitLayoutAccessChain(baseId, elementId, Span(insn, indexId, insn.wordCount() - indexId), nonUniform, state);
-		state->createPointer(resultId, ptr);
+		auto ptr = WalkExplicitLayoutAccessChain(baseId, elementId, Span(insn, indexId, insn.wordCount() - indexId), nonUniform);
+		createPointer(resultId, ptr);
 	}
 	else
 	{
-		auto ptr = WalkAccessChain(baseId, elementId, Span(insn, indexId, insn.wordCount() - indexId), nonUniform, state);
-		state->createPointer(resultId, ptr);
+		auto ptr = WalkAccessChain(baseId, elementId, Span(insn, indexId, insn.wordCount() - indexId), nonUniform);
+		createPointer(resultId, ptr);
 	}
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitCompositeConstruct(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitCompositeConstruct(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
 	auto offset = 0u;
 
 	for(auto i = 0u; i < insn.wordCount() - 3; i++)
 	{
 		Object::ID srcObjectId = insn.word(3u + i);
-		auto &srcObject = getObject(srcObjectId);
-		auto &srcObjectTy = getType(srcObject);
-		Operand srcObjectAccess(this, state, srcObjectId);
+		auto &srcObject = shader.getObject(srcObjectId);
+		auto &srcObjectTy = shader.getType(srcObject);
+		Operand srcObjectAccess(shader, *this, srcObjectId);
 
 		for(auto j = 0u; j < srcObjectTy.componentCount; j++)
 		{
@@ -2312,17 +2311,17 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitCompositeInsert(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitCompositeInsert(InsnIterator insn)
 {
 	Type::ID resultTypeId = insn.word(1);
-	auto &type = getType(resultTypeId);
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &newPartObject = getObject(insn.word(3));
-	auto &newPartObjectTy = getType(newPartObject);
-	auto firstNewComponent = WalkLiteralAccessChain(resultTypeId, Span(insn, 5, insn.wordCount() - 5));
+	auto &type = shader.getType(resultTypeId);
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &newPartObject = shader.getObject(insn.word(3));
+	auto &newPartObjectTy = shader.getType(newPartObject);
+	auto firstNewComponent = shader.WalkLiteralAccessChain(resultTypeId, Span(insn, 5, insn.wordCount() - 5));
 
-	Operand srcObjectAccess(this, state, insn.word(4));
-	Operand newPartObjectAccess(this, state, insn.word(3));
+	Operand srcObjectAccess(shader, *this, insn.word(4));
+	Operand newPartObjectAccess(shader, *this, insn.word(3));
 
 	// old components before
 	for(auto i = 0u; i < firstNewComponent; i++)
@@ -2343,15 +2342,15 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitCompositeExtract(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitCompositeExtract(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &compositeObject = getObject(insn.word(3));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &compositeObject = shader.getObject(insn.word(3));
 	Type::ID compositeTypeId = compositeObject.definition.word(1);
-	auto firstComponent = WalkLiteralAccessChain(compositeTypeId, Span(insn, 4, insn.wordCount() - 4));
+	auto firstComponent = shader.WalkLiteralAccessChain(compositeTypeId, Span(insn, 4, insn.wordCount() - 4));
 
-	Operand compositeObjectAccess(this, state, insn.word(3));
+	Operand compositeObjectAccess(shader, *this, insn.word(3));
 	for(auto i = 0u; i < type.componentCount; i++)
 	{
 		dst.move(i, compositeObjectAccess.Float(firstComponent + i));
@@ -2360,48 +2359,44 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitVectorShuffle(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitVectorShuffle(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+	// Note: number of components in result, first vector, and second vector are all independent.
+	uint32_t resultSize = shader.getType(insn.resultTypeId()).componentCount;
+	uint32_t firstVectorSize = shader.getObjectType(insn.word(3)).componentCount;
 
-	// Note: number of components in result type, first half type, and second
-	// half type are all independent.
-	auto &firstHalfType = getObjectType(insn.word(3));
+	auto &result = createIntermediate(insn.resultId(), resultSize);
+	Operand firstVector(shader, *this, insn.word(3));
+	Operand secondVector(shader, *this, insn.word(4));
 
-	Operand firstHalfAccess(this, state, insn.word(3));
-	Operand secondHalfAccess(this, state, insn.word(4));
-
-	for(auto i = 0u; i < type.componentCount; i++)
+	for(uint32_t i = 0u; i < resultSize; i++)
 	{
-		auto selector = insn.word(5 + i);
-		if(selector == static_cast<uint32_t>(-1))
+		uint32_t selector = insn.word(5 + i);
+		if(selector == 0xFFFFFFFF)  // Undefined value.
 		{
-			// Undefined value. Until we decide to do real undef values, zero is as good
-			// a value as any
-			dst.move(i, RValue<SIMD::Float>(0.0f));
+			result.move(i, SIMD::Float());
 		}
-		else if(selector < firstHalfType.componentCount)
+		else if(selector < firstVectorSize)
 		{
-			dst.move(i, firstHalfAccess.Float(selector));
+			result.move(i, firstVector.Float(selector));
 		}
 		else
 		{
-			dst.move(i, secondHalfAccess.Float(selector - firstHalfType.componentCount));
+			result.move(i, secondVector.Float(selector - firstVectorSize));
 		}
 	}
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitVectorExtractDynamic(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitVectorExtractDynamic(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &srcType = getObjectType(insn.word(3));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &srcType = shader.getObjectType(insn.word(3));
 
-	Operand src(this, state, insn.word(3));
-	Operand index(this, state, insn.word(4));
+	Operand src(shader, *this, insn.word(3));
+	Operand index(shader, *this, insn.word(4));
 
 	SIMD::UInt v = SIMD::UInt(0);
 
@@ -2414,14 +2409,14 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitVectorInsertDynamic(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitVectorInsertDynamic(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
 
-	Operand src(this, state, insn.word(3));
-	Operand component(this, state, insn.word(4));
-	Operand index(this, state, insn.word(5));
+	Operand src(shader, *this, insn.word(3));
+	Operand component(shader, *this, insn.word(4));
+	Operand index(shader, *this, insn.word(5));
 
 	for(auto i = 0u; i < type.componentCount; i++)
 	{
@@ -2431,11 +2426,11 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitSelect(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitSelect(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto result = getObject(insn.resultId());
-	auto cond = Operand(this, state, insn.word(3));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto result = shader.getObject(insn.resultId());
+	auto cond = Operand(shader, *this, insn.word(3));
 	auto condIsScalar = (cond.componentCount == 1);
 
 	switch(result.kind)
@@ -2445,9 +2440,9 @@
 			ASSERT(condIsScalar);
 			ASSERT(type.storageClass == spv::StorageClassPhysicalStorageBuffer);
 
-			auto &lhs = state->getPointer(insn.word(4));
-			auto &rhs = state->getPointer(insn.word(5));
-			state->createPointer(insn.resultId(), SIMD::Pointer::IfThenElse(cond.Int(0), lhs, rhs));
+			auto &lhs = getPointer(insn.word(4));
+			auto &rhs = getPointer(insn.word(5));
+			createPointer(insn.resultId(), SIMD::Pointer::IfThenElse(cond.Int(0), lhs, rhs));
 
 			SPIRV_SHADER_DBG("{0}: {1}", insn.word(3), cond);
 			SPIRV_SHADER_DBG("{0}: {1}", insn.word(4), lhs);
@@ -2456,9 +2451,9 @@
 		break;
 	default:
 		{
-			auto lhs = Operand(this, state, insn.word(4));
-			auto rhs = Operand(this, state, insn.word(5));
-			auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+			auto lhs = Operand(shader, *this, insn.word(4));
+			auto rhs = Operand(shader, *this, insn.word(5));
+			auto &dst = createIntermediate(insn.resultId(), type.componentCount);
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto sel = cond.Int(condIsScalar ? 0 : i);
@@ -2476,13 +2471,13 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitAny(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitAny(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
+	auto &type = shader.getType(insn.resultTypeId());
 	ASSERT(type.componentCount == 1);
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &srcType = getObjectType(insn.word(3));
-	auto src = Operand(this, state, insn.word(3));
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &srcType = shader.getObjectType(insn.word(3));
+	auto src = Operand(shader, *this, insn.word(3));
 
 	SIMD::UInt result = src.UInt(0);
 
@@ -2495,17 +2490,17 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitAll(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitAll(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
+	auto &type = shader.getType(insn.resultTypeId());
 	ASSERT(type.componentCount == 1);
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &srcType = getObjectType(insn.word(3));
-	auto src = Operand(this, state, insn.word(3));
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &srcType = shader.getObjectType(insn.word(3));
+	auto src = Operand(shader, *this, insn.word(3));
 
 	SIMD::UInt result = src.UInt(0);
 
-	for(auto i = 1u; i < srcType.componentCount; i++)
+	for(uint32_t i = 1; i < srcType.componentCount; i++)
 	{
 		result &= src.UInt(i);
 	}
@@ -2514,22 +2509,22 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitAtomicOp(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitAtomicOp(InsnIterator insn)
 {
-	auto &resultType = getType(Type::ID(insn.word(1)));
+	auto &resultType = shader.getType(Type::ID(insn.word(1)));
 	Object::ID resultId = insn.word(2);
 	Object::ID pointerId = insn.word(3);
 	Object::ID semanticsId = insn.word(5);
-	auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
+	auto memorySemantics = static_cast<spv::MemorySemanticsMask>(shader.getObject(semanticsId).constantValue[0]);
 	auto memoryOrder = MemoryOrder(memorySemantics);
 	// Where no value is provided (increment/decrement) use an implicit value of 1.
-	auto value = (insn.wordCount() == 7) ? Operand(this, state, insn.word(6)).UInt(0) : RValue<SIMD::UInt>(1);
-	auto &dst = state->createIntermediate(resultId, resultType.componentCount);
-	auto ptr = state->getPointer(pointerId);
+	auto value = (insn.wordCount() == 7) ? Operand(shader, *this, insn.word(6)).UInt(0) : RValue<SIMD::UInt>(1);
+	auto &dst = createIntermediate(resultId, resultType.componentCount);
+	auto ptr = getPointer(pointerId);
 
-	SIMD::Int mask = state->activeLaneMask() & state->storesAndAtomicsMask();
+	SIMD::Int mask = activeLaneMask() & storesAndAtomicsMask();
 
-	if((getObject(pointerId).opcode() == spv::OpImageTexelPointer) && ptr.isBasePlusOffset)
+	if((shader.getObject(pointerId).opcode() == spv::OpImageTexelPointer) && ptr.isBasePlusOffset)
 	{
 		mask &= ptr.isInBounds(sizeof(int32_t), OutOfBoundsBehavior::Nullify);
 	}
@@ -2587,24 +2582,24 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitAtomicCompareExchange(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitAtomicCompareExchange(InsnIterator insn)
 {
 	// Separate from EmitAtomicOp due to different instruction encoding
-	auto &resultType = getType(Type::ID(insn.word(1)));
+	auto &resultType = shader.getType(Type::ID(insn.word(1)));
 	Object::ID resultId = insn.word(2);
 
-	auto memorySemanticsEqual = static_cast<spv::MemorySemanticsMask>(getObject(insn.word(5)).constantValue[0]);
+	auto memorySemanticsEqual = static_cast<spv::MemorySemanticsMask>(shader.getObject(insn.word(5)).constantValue[0]);
 	auto memoryOrderEqual = MemoryOrder(memorySemanticsEqual);
-	auto memorySemanticsUnequal = static_cast<spv::MemorySemanticsMask>(getObject(insn.word(6)).constantValue[0]);
+	auto memorySemanticsUnequal = static_cast<spv::MemorySemanticsMask>(shader.getObject(insn.word(6)).constantValue[0]);
 	auto memoryOrderUnequal = MemoryOrder(memorySemanticsUnequal);
 
-	auto value = Operand(this, state, insn.word(7));
-	auto comparator = Operand(this, state, insn.word(8));
-	auto &dst = state->createIntermediate(resultId, resultType.componentCount);
-	auto ptr = state->getPointer(insn.word(3));
+	auto value = Operand(shader, *this, insn.word(7));
+	auto comparator = Operand(shader, *this, insn.word(8));
+	auto &dst = createIntermediate(resultId, resultType.componentCount);
+	auto ptr = getPointer(insn.word(3));
 
 	SIMD::UInt x(0);
-	auto mask = state->activeLaneMask() & state->storesAndAtomicsMask();
+	auto mask = activeLaneMask() & storesAndAtomicsMask();
 	for(int j = 0; j < SIMD::Width; j++)
 	{
 		If(Extract(mask, j) != 0)
@@ -2620,21 +2615,21 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitCopyObject(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitCopyObject(InsnIterator insn)
 {
-	auto src = Operand(this, state, insn.word(3));
+	auto src = Operand(shader, *this, insn.word(3));
 	if(src.isPointer())
 	{
-		state->createPointer(insn.resultId(), src.Pointer());
+		createPointer(insn.resultId(), src.Pointer());
 	}
 	else if(src.isSampledImage())
 	{
-		state->createSampledImage(insn.resultId(), src.SampledImage());
+		createSampledImage(insn.resultId(), src.SampledImage());
 	}
 	else
 	{
-		auto type = getType(insn.resultTypeId());
-		auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+		auto type = shader.getType(insn.resultTypeId());
+		auto &dst = createIntermediate(insn.resultId(), type.componentCount);
 		for(uint32_t i = 0; i < type.componentCount; i++)
 		{
 			dst.move(i, src.Int(i));
@@ -2643,30 +2638,30 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitArrayLength(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitArrayLength(InsnIterator insn)
 {
 	auto structPtrId = Object::ID(insn.word(3));
 	auto arrayFieldIdx = insn.word(4);
 
-	auto &resultType = getType(insn.resultTypeId());
+	auto &resultType = shader.getType(insn.resultTypeId());
 	ASSERT(resultType.componentCount == 1);
 	ASSERT(resultType.definition.opcode() == spv::OpTypeInt);
 
-	auto &structPtrTy = getObjectType(structPtrId);
-	auto &structTy = getType(structPtrTy.element);
+	auto &structPtrTy = shader.getObjectType(structPtrId);
+	auto &structTy = shader.getType(structPtrTy.element);
 	auto arrayId = Type::ID(structTy.definition.word(2 + arrayFieldIdx));
 
-	auto &result = state->createIntermediate(insn.resultId(), 1);
-	auto structBase = GetPointerToData(structPtrId, 0, false, state);
+	auto &result = createIntermediate(insn.resultId(), 1);
+	auto structBase = GetPointerToData(structPtrId, 0, false);
 
 	Decorations structDecorations = {};
-	ApplyDecorationsForIdMember(&structDecorations, structPtrTy.element, arrayFieldIdx);
+	shader.ApplyDecorationsForIdMember(&structDecorations, structPtrTy.element, arrayFieldIdx);
 	ASSERT(structDecorations.HasOffset);
 
 	auto arrayBase = structBase + structDecorations.Offset;
 	auto arraySizeInBytes = SIMD::Int(arrayBase.limit()) - arrayBase.offsets();
 
-	Decorations arrayDecorations = GetDecorationsForId(arrayId);
+	Decorations arrayDecorations = shader.GetDecorationsForId(arrayId);
 	ASSERT(arrayDecorations.HasArrayStride);
 	auto arrayLength = arraySizeInBytes / SIMD::Int(arrayDecorations.ArrayStride);
 
@@ -2675,13 +2670,13 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitExtendedInstruction(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitExtendedInstruction(InsnIterator insn)
 {
-	auto ext = getExtension(insn.word(3));
+	auto ext = shader.getExtension(insn.word(3));
 	switch(ext.name)
 	{
 	case Extension::GLSLstd450:
-		return EmitExtGLSLstd450(insn, state);
+		return EmitExtGLSLstd450(insn);
 	case Extension::NonSemanticInfo:
 		// An extended set name which is prefixed with "NonSemantic." is
 		// guaranteed to contain only non-semantic instructions and all
@@ -2705,26 +2700,22 @@
 {
 	for(auto insn : *this)
 	{
-		switch(insn.opcode())
+		if(insn.opcode() == spv::OpVariable)
 		{
-		case spv::OpVariable:
+			auto &object = getObject(insn.resultId());
+			auto &objectTy = getType(object);
+
+			if(object.kind == Object::Kind::InterfaceVariable && objectTy.storageClass == spv::StorageClassOutput)
 			{
-				auto &object = getObject(insn.resultId());
-				auto &objectTy = getType(object);
-				if(object.kind == Object::Kind::InterfaceVariable && objectTy.storageClass == spv::StorageClassOutput)
-				{
-					auto &dst = routine->getVariable(insn.resultId());
-					int offset = 0;
-					VisitInterface(insn.resultId(),
-					               [&](const Decorations &d, AttribType type) {
-						               auto scalarSlot = d.Location << 2 | d.Component;
-						               routine->outputs[scalarSlot] = dst[offset++];
-					               });
-				}
+				auto &dst = routine->getVariable(insn.resultId());
+				int offset = 0;
+
+				VisitInterface(insn.resultId(),
+				               [&](const Decorations &d, AttribType type) {
+					               auto scalarSlot = d.Location << 2 | d.Component;
+					               routine->outputs[scalarSlot] = dst[offset++];
+				               });
 			}
-			break;
-		default:
-			break;
 		}
 	}
 }
@@ -2763,25 +2754,22 @@
 	}
 }
 
-SpirvShader::Operand::Operand(const SpirvShader *shader, const EmitState *state, SpirvShader::Object::ID objectId)
-    : Operand(state, shader->getObject(objectId))
+SpirvShader::Operand::Operand(const SpirvShader &shader, const EmitState &state, SpirvShader::Object::ID objectId)
+    : Operand(state, shader.getObject(objectId))
 {}
 
-SpirvShader::Operand::Operand(const EmitState *state, const Object &object)
+SpirvShader::Operand::Operand(const EmitState &state, const Object &object)
     : constant(object.kind == SpirvShader::Object::Kind::Constant ? object.constantValue.data() : nullptr)
-    , intermediate(object.kind == SpirvShader::Object::Kind::Intermediate ? &state->getIntermediate(object.id()) : nullptr)
-    , pointer(object.kind == SpirvShader::Object::Kind::Pointer ? &state->getPointer(object.id()) : nullptr)
-    , sampledImage(object.kind == SpirvShader::Object::Kind::SampledImage ? &state->getSampledImage(object.id()) : nullptr)
+    , intermediate(object.kind == SpirvShader::Object::Kind::Intermediate ? &state.getIntermediate(object.id()) : nullptr)
+    , pointer(object.kind == SpirvShader::Object::Kind::Pointer ? &state.getPointer(object.id()) : nullptr)
+    , sampledImage(object.kind == SpirvShader::Object::Kind::SampledImage ? &state.getSampledImage(object.id()) : nullptr)
     , componentCount(intermediate ? intermediate->componentCount : object.constantValue.size())
 {
 	ASSERT(intermediate || constant || pointer || sampledImage);
 }
 
 SpirvShader::Operand::Operand(const Intermediate &value)
-    : constant(nullptr)
-    , intermediate(&value)
-    , pointer(nullptr)
-    , sampledImage(nullptr)
+    : intermediate(&value)
     , componentCount(value.componentCount)
 {
 }
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index f4b7d62..51b9fe9 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -615,7 +615,7 @@
 
 	struct ImageInstruction : public ImageInstructionSignature
 	{
-		ImageInstruction(InsnIterator insn, const SpirvShader &spirv, EmitState *state);
+		ImageInstruction(InsnIterator insn, const SpirvShader &spirv, const EmitState &state);
 
 		const uint32_t position;
 
@@ -1063,148 +1063,6 @@
 	void ProcessInterfaceVariable(Object &object);
 
 public:
-	// EmitState holds control-flow state for the emit() pass.
-	class EmitState
-	{
-	public:
-		EmitState(SpirvRoutine *routine,
-		          Function::ID function,
-		          RValue<SIMD::Int> activeLaneMask,
-		          RValue<SIMD::Int> storesAndAtomicsMask,
-		          const vk::DescriptorSet::Bindings &descriptorSets,
-		          unsigned int multiSampleCount)
-		    : routine(routine)
-		    , function(function)
-		    , activeLaneMaskValue(activeLaneMask.value())
-		    , storesAndAtomicsMaskValue(storesAndAtomicsMask.value())
-		    , descriptorSets(descriptorSets)
-		    , multiSampleCount(multiSampleCount)
-		{
-		}
-
-		// Returns the mask describing the active lanes as updated by dynamic
-		// control flow. Active lanes include helper invocations, used for
-		// calculating fragment derivitives, which must not perform memory
-		// stores or atomic writes.
-		//
-		// Use activeStoresAndAtomicsMask() to consider both control flow and
-		// lanes which are permitted to perform memory stores and atomic
-		// operations
-		RValue<SIMD::Int> activeLaneMask() const
-		{
-			ASSERT(activeLaneMaskValue != nullptr);
-			return RValue<SIMD::Int>(activeLaneMaskValue);
-		}
-
-		// Returns the immutable lane mask that describes which lanes are
-		// permitted to perform memory stores and atomic operations.
-		// Note that unlike activeStoresAndAtomicsMask() this mask *does not*
-		// consider lanes that have been made inactive due to control flow.
-		RValue<SIMD::Int> storesAndAtomicsMask() const
-		{
-			ASSERT(storesAndAtomicsMaskValue != nullptr);
-			return RValue<SIMD::Int>(storesAndAtomicsMaskValue);
-		}
-
-		// Returns a lane mask that describes which lanes are permitted to
-		// perform memory stores and atomic operations, considering lanes that
-		// may have been made inactive due to control flow.
-		RValue<SIMD::Int> activeStoresAndAtomicsMask() const
-		{
-			return activeLaneMask() & storesAndAtomicsMask();
-		}
-
-		// Add a new active lane mask edge from the current block to out.
-		// The edge mask value will be (mask AND activeLaneMaskValue).
-		// If multiple active lane masks are added for the same edge, then
-		// they will be ORed together.
-		void addOutputActiveLaneMaskEdge(Block::ID out, RValue<SIMD::Int> mask);
-
-		// Add a new active lane mask for the edge from -> to.
-		// If multiple active lane masks are added for the same edge, then
-		// they will be ORed together.
-		void addActiveLaneMaskEdge(Block::ID from, Block::ID to, RValue<SIMD::Int> mask);
-
-		SpirvRoutine *routine = nullptr;                 // The current routine being built.
-		Function::ID function;                           // The current function being built.
-		Block::ID block;                                 // The current block being built.
-		rr::Value *activeLaneMaskValue = nullptr;        // The current active lane mask.
-		rr::Value *storesAndAtomicsMaskValue = nullptr;  // The current atomics mask.
-		Block::Set visited;                              // Blocks already built.
-		std::unordered_map<Block::Edge, RValue<SIMD::Int>, Block::Edge::Hash> edgeActiveLaneMasks;
-		std::deque<Block::ID> *pending;
-
-		const vk::DescriptorSet::Bindings &descriptorSets;
-
-		unsigned int getMultiSampleCount() const { return multiSampleCount; }
-
-		Intermediate &createIntermediate(Object::ID id, uint32_t componentCount)
-		{
-			auto it = intermediates.emplace(std::piecewise_construct,
-			                                std::forward_as_tuple(id),
-			                                std::forward_as_tuple(componentCount));
-			ASSERT_MSG(it.second, "Intermediate %d created twice", id.value());
-			return it.first->second;
-		}
-
-		const Intermediate &getIntermediate(Object::ID id) const
-		{
-			auto it = intermediates.find(id);
-			ASSERT_MSG(it != intermediates.end(), "Unknown intermediate %d", id.value());
-			return it->second;
-		}
-
-		void createPointer(Object::ID id, SIMD::Pointer ptr)
-		{
-			bool added = pointers.emplace(id, ptr).second;
-			ASSERT_MSG(added, "Pointer %d created twice", id.value());
-		}
-
-		const SIMD::Pointer &getPointer(Object::ID id) const
-		{
-			auto it = pointers.find(id);
-			ASSERT_MSG(it != pointers.end(), "Unknown pointer %d", id.value());
-			return it->second;
-		}
-
-		void createSampledImage(Object::ID id, SampledImagePointer ptr)
-		{
-			bool added = sampledImages.emplace(id, ptr).second;
-			ASSERT_MSG(added, "Sampled image %d created twice", id.value());
-		}
-
-		const SampledImagePointer &getSampledImage(Object::ID id) const
-		{
-			auto it = sampledImages.find(id);
-			ASSERT_MSG(it != sampledImages.end(), "Unknown sampled image %d", id.value());
-			return it->second;
-		}
-
-		bool isSampledImage(Object::ID id) const
-		{
-			return sampledImages.find(id) != sampledImages.end();
-		}
-
-		const SIMD::Pointer &getImage(Object::ID id) const
-		{
-			return isSampledImage(id) ? getSampledImage(id) : getPointer(id);
-		}
-
-	private:
-		std::unordered_map<Object::ID, Intermediate> intermediates;
-		std::unordered_map<Object::ID, SIMD::Pointer> pointers;
-		std::unordered_map<Object::ID, SampledImagePointer> sampledImages;
-
-		const unsigned int multiSampleCount;
-	};
-
-	// EmitResult is an enumerator of result values from the Emit functions.
-	enum class EmitResult
-	{
-		Continue,    // No termination instructions.
-		Terminator,  // Reached a termination instruction.
-	};
-
 	// Generic wrapper over either per-lane intermediate value, or a constant.
 	// Constants are transparently widened to per-lane values in operator[].
 	// This is appropriate in most cases -- if we're not going to do something
@@ -1212,7 +1070,7 @@
 	class Operand
 	{
 	public:
-		Operand(const SpirvShader *shader, const EmitState *state, SpirvShader::Object::ID objectId);
+		Operand(const SpirvShader &shader, const EmitState &state, SpirvShader::Object::ID objectId);
 		Operand(const Intermediate &value);
 
 		RValue<SIMD::Float> Float(uint32_t i) const
@@ -1276,12 +1134,12 @@
 		RR_PRINT_ONLY(friend struct rr::PrintValue::Ty<Operand>;)
 
 		// Delegate constructor
-		Operand(const EmitState *state, const Object &object);
+		Operand(const EmitState &state, const Object &object);
 
-		const uint32_t *constant;
-		const Intermediate *intermediate;
-		const SIMD::Pointer *pointer;
-		const SampledImagePointer *sampledImage;
+		const uint32_t *constant = nullptr;
+		const Intermediate *intermediate = nullptr;
+		const SIMD::Pointer *pointer = nullptr;
+		const SampledImagePointer *sampledImage = nullptr;
 
 	public:
 		const uint32_t componentCount;
@@ -1289,6 +1147,306 @@
 
 	RR_PRINT_ONLY(friend struct rr::PrintValue::Ty<Operand>;)
 
+	// EmitResult is an enumerator of result values from the Emit functions.
+	enum class EmitResult
+	{
+		Continue,    // No termination instructions.
+		Terminator,  // Reached a termination instruction.
+	};
+
+	// EmitState holds control-flow state for the emit() pass.
+	class EmitState
+	{
+	public:
+		EmitState(const SpirvShader &shader,
+		          SpirvRoutine *routine,
+		          Function::ID function,
+		          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)
+		{
+		}
+
+		// Returns the mask describing the active lanes as updated by dynamic
+		// control flow. Active lanes include helper invocations, used for
+		// calculating fragment derivitives, which must not perform memory
+		// stores or atomic writes.
+		//
+		// Use activeStoresAndAtomicsMask() to consider both control flow and
+		// lanes which are permitted to perform memory stores and atomic
+		// operations
+		RValue<SIMD::Int> activeLaneMask() const
+		{
+			ASSERT(activeLaneMaskValue != nullptr);
+			return RValue<SIMD::Int>(activeLaneMaskValue);
+		}
+
+		// Returns the immutable lane mask that describes which lanes are
+		// permitted to perform memory stores and atomic operations.
+		// Note that unlike activeStoresAndAtomicsMask() this mask *does not*
+		// consider lanes that have been made inactive due to control flow.
+		RValue<SIMD::Int> storesAndAtomicsMask() const
+		{
+			ASSERT(storesAndAtomicsMaskValue != nullptr);
+			return RValue<SIMD::Int>(storesAndAtomicsMaskValue);
+		}
+
+		// Returns a lane mask that describes which lanes are permitted to
+		// perform memory stores and atomic operations, considering lanes that
+		// may have been made inactive due to control flow.
+		RValue<SIMD::Int> activeStoresAndAtomicsMask() const
+		{
+			return activeLaneMask() & storesAndAtomicsMask();
+		}
+
+		// Add a new active lane mask edge from the current block to out.
+		// The edge mask value will be (mask AND activeLaneMaskValue).
+		// If multiple active lane masks are added for the same edge, then
+		// they will be ORed together.
+		void addOutputActiveLaneMaskEdge(Block::ID out, RValue<SIMD::Int> mask);
+
+		// Add a new active lane mask for the edge from -> to.
+		// If multiple active lane masks are added for the same edge, then
+		// they will be ORed together.
+		void addActiveLaneMaskEdge(Block::ID from, Block::ID to, RValue<SIMD::Int> mask);
+
+		unsigned int getMultiSampleCount() const { return multiSampleCount; }
+
+		Intermediate &createIntermediate(Object::ID id, uint32_t componentCount)
+		{
+			auto it = intermediates.emplace(std::piecewise_construct,
+			                                std::forward_as_tuple(id),
+			                                std::forward_as_tuple(componentCount));
+			ASSERT_MSG(it.second, "Intermediate %d created twice", id.value());
+			return it.first->second;
+		}
+
+		const Intermediate &getIntermediate(Object::ID id) const
+		{
+			auto it = intermediates.find(id);
+			ASSERT_MSG(it != intermediates.end(), "Unknown intermediate %d", id.value());
+			return it->second;
+		}
+
+		void createPointer(Object::ID id, SIMD::Pointer ptr)
+		{
+			bool added = pointers.emplace(id, ptr).second;
+			ASSERT_MSG(added, "Pointer %d created twice", id.value());
+		}
+
+		const SIMD::Pointer &getPointer(Object::ID id) const
+		{
+			auto it = pointers.find(id);
+			ASSERT_MSG(it != pointers.end(), "Unknown pointer %d", id.value());
+			return it->second;
+		}
+
+		void createSampledImage(Object::ID id, SampledImagePointer ptr)
+		{
+			bool added = sampledImages.emplace(id, ptr).second;
+			ASSERT_MSG(added, "Sampled image %d created twice", id.value());
+		}
+
+		const SampledImagePointer &getSampledImage(Object::ID id) const
+		{
+			auto it = sampledImages.find(id);
+			ASSERT_MSG(it != sampledImages.end(), "Unknown sampled image %d", id.value());
+			return it->second;
+		}
+
+		bool isSampledImage(Object::ID id) const
+		{
+			return sampledImages.find(id) != sampledImages.end();
+		}
+
+		const SIMD::Pointer &getImage(Object::ID id) const
+		{
+			return isSampledImage(id) ? getSampledImage(id) : getPointer(id);
+		}
+
+		EmitResult EmitVariable(InsnIterator insn);
+		EmitResult EmitLoad(InsnIterator insn);
+		EmitResult EmitStore(InsnIterator insn);
+		EmitResult EmitAccessChain(InsnIterator insn);
+		EmitResult EmitCompositeConstruct(InsnIterator insn);
+		EmitResult EmitCompositeInsert(InsnIterator insn);
+		EmitResult EmitCompositeExtract(InsnIterator insn);
+		EmitResult EmitVectorShuffle(InsnIterator insn);
+		EmitResult EmitVectorTimesScalar(InsnIterator insn);
+		EmitResult EmitMatrixTimesVector(InsnIterator insn);
+		EmitResult EmitVectorTimesMatrix(InsnIterator insn);
+		EmitResult EmitMatrixTimesMatrix(InsnIterator insn);
+		EmitResult EmitOuterProduct(InsnIterator insn);
+		EmitResult EmitTranspose(InsnIterator insn);
+		EmitResult EmitVectorExtractDynamic(InsnIterator insn);
+		EmitResult EmitVectorInsertDynamic(InsnIterator insn);
+		EmitResult EmitUnaryOp(InsnIterator insn);
+		EmitResult EmitBinaryOp(InsnIterator insn);
+		EmitResult EmitDot(InsnIterator insn);
+		EmitResult EmitSelect(InsnIterator insn);
+		EmitResult EmitExtendedInstruction(InsnIterator insn);
+		EmitResult EmitExtGLSLstd450(InsnIterator insn);
+		EmitResult EmitAny(InsnIterator insn);
+		EmitResult EmitAll(InsnIterator insn);
+		EmitResult EmitBranch(InsnIterator insn);
+		EmitResult EmitBranchConditional(InsnIterator insn);
+		EmitResult EmitSwitch(InsnIterator insn);
+		EmitResult EmitUnreachable(InsnIterator insn);
+		EmitResult EmitReturn(InsnIterator insn);
+		EmitResult EmitTerminateInvocation(InsnIterator insn);
+		EmitResult EmitDemoteToHelperInvocation(InsnIterator insn);
+		EmitResult EmitIsHelperInvocation(InsnIterator insn);
+		EmitResult EmitFunctionCall(InsnIterator insn);
+		EmitResult EmitPhi(InsnIterator insn);
+		EmitResult EmitImageSample(const ImageInstruction &instruction);
+		EmitResult EmitImageQuerySizeLod(InsnIterator insn);
+		EmitResult EmitImageQuerySize(InsnIterator insn);
+		EmitResult EmitImageQueryLevels(InsnIterator insn);
+		EmitResult EmitImageQuerySamples(InsnIterator insn);
+		EmitResult EmitImageRead(const ImageInstruction &instruction);
+		EmitResult EmitImageWrite(const ImageInstruction &instruction) const;
+		EmitResult EmitImageTexelPointer(const ImageInstruction &instruction);
+		EmitResult EmitAtomicOp(InsnIterator insn);
+		EmitResult EmitAtomicCompareExchange(InsnIterator insn);
+		EmitResult EmitSampledImage(InsnIterator insn);
+		EmitResult EmitImage(InsnIterator insn);
+		EmitResult EmitCopyObject(InsnIterator insn);
+		EmitResult EmitCopyMemory(InsnIterator insn);
+		EmitResult EmitControlBarrier(InsnIterator insn) const;
+		EmitResult EmitMemoryBarrier(InsnIterator insn);
+		EmitResult EmitGroupNonUniform(InsnIterator insn);
+		EmitResult EmitArrayLength(InsnIterator insn);
+		EmitResult EmitBitcastPointer(Object::ID resultID, Operand &src);
+
+		enum InterpolationType
+		{
+			Centroid,
+			AtSample,
+			AtOffset,
+		};
+		SIMD::Float EmitInterpolate(const SIMD::Pointer &ptr, int32_t location, Object::ID paramId,
+		                            uint32_t component, InterpolationType type) const;
+
+		SIMD::Pointer WalkExplicitLayoutAccessChain(Object::ID id, Object::ID elementId, const Span &indexIds, bool nonUniform) const;
+		SIMD::Pointer WalkAccessChain(Object::ID id, Object::ID elementId, const Span &indexIds, bool nonUniform) const;
+
+		// Returns a SIMD::Pointer to the underlying data for the given pointer
+		// object.
+		// Handles objects of the following kinds:
+		//  - DescriptorSet
+		//  - Pointer
+		//  - InterfaceVariable
+		// Calling GetPointerToData with objects of any other kind will assert.
+		SIMD::Pointer GetPointerToData(Object::ID id, SIMD::Int arrayIndex, bool nonUniform) const;
+		void OffsetToElement(SIMD::Pointer &ptr, Object::ID elementId, int32_t arrayStride) const;
+
+		/* image istructions */
+
+		// Emits code to sample an image, regardless of whether any SIMD lanes are active.
+		void EmitImageSampleUnconditional(Array<SIMD::Float> &out, const ImageInstruction &instruction) const;
+
+		Pointer<Byte> getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction) const;
+		Pointer<Byte> getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, int laneIdx) const;
+		Pointer<Byte> lookupSamplerFunction(Pointer<Byte> imageDescriptor, Pointer<Byte> samplerDescriptor, const ImageInstruction &instruction) const;
+		void callSamplerFunction(Pointer<Byte> samplerFunction, Array<SIMD::Float> &out, Pointer<Byte> imageDescriptor, const ImageInstruction &instruction) const;
+
+		void GetImageDimensions(const Type &resultTy, Object::ID imageId, Object::ID lodId, Intermediate &dst) const;
+		struct TexelAddressData
+		{
+			bool isArrayed;
+			spv::Dim dim;
+			int dims, texelSize;
+			SIMD::Int u, v, w, ptrOffset;
+		};
+		static TexelAddressData setupTexelAddressData(SIMD::Int rowPitch, SIMD::Int slicePitch, SIMD::Int samplePitch, ImageInstructionSignature instruction, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, const SpirvRoutine *routine);
+		static SIMD::Pointer GetNonUniformTexelAddress(ImageInstructionSignature instruction, SIMD::Pointer descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, SIMD::Int activeLaneMask, const SpirvRoutine *routine);
+		static SIMD::Pointer GetTexelAddress(ImageInstructionSignature instruction, Pointer<Byte> descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, const SpirvRoutine *routine);
+		static void WriteImage(ImageInstructionSignature instruction, Pointer<Byte> descriptor, const Pointer<SIMD::Int> &coord, const Pointer<SIMD::Int> &texelAndMask, vk::Format imageFormat);
+
+		/* control flow */
+
+		// Lookup the active lane mask for the edge from -> to.
+		// If from is unreachable, then a mask of all zeros is returned.
+		// Asserts if from is reachable and the edge does not exist.
+		RValue<SIMD::Int> GetActiveLaneMaskEdge(Block::ID from, Block::ID to) const;
+
+		// Updates the current active lane mask.
+		void SetActiveLaneMask(RValue<SIMD::Int> mask);
+		void SetStoresAndAtomicsMask(RValue<SIMD::Int> mask);
+
+		// Emit all the unvisited blocks (except for ignore) in DFS order,
+		// starting with id.
+		void EmitBlocks(Block::ID id, Block::ID ignore = 0);
+		void EmitNonLoop();
+		void EmitLoop();
+
+		void EmitInstructions(InsnIterator begin, InsnIterator end);
+		EmitResult EmitInstruction(InsnIterator insn);
+
+		// Helper for implementing OpStore, which doesn't take an InsnIterator so it
+		// can also store independent operands.
+		void Store(Object::ID pointerId, const Operand &value, bool atomic, std::memory_order memoryOrder) const;
+
+		// LoadPhi loads the phi values from the alloca storage and places the
+		// load values into the intermediate with the phi's result id.
+		void LoadPhi(InsnIterator insn);
+
+		// 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<SpirvShader::Block::ID> &filter) const;
+
+		// 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.
+		void Yield(YieldResult res) const;
+
+		// Helper as we often need to take dot products as part of doing other things.
+		static SIMD::Float FDot(unsigned numComponents, const Operand &x, const Operand &y);
+		static SIMD::Int SDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum);
+		static SIMD::UInt UDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum);
+		static SIMD::Int SUDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum);
+		static SIMD::Int AddSat(RValue<SIMD::Int> a, RValue<SIMD::Int> b);
+		static SIMD::UInt AddSat(RValue<SIMD::UInt> a, RValue<SIMD::UInt> b);
+
+		static ImageSampler *getImageSampler(const vk::Device *device, uint32_t signature, uint32_t samplerId, uint32_t imageViewId);
+		static std::shared_ptr<rr::Routine> emitSamplerRoutine(ImageInstructionSignature instruction, const Sampler &samplerState);
+		static std::shared_ptr<rr::Routine> emitWriteRoutine(ImageInstructionSignature instruction, const Sampler &samplerState);
+
+		// TODO(b/129523279): Eliminate conversion and use vk::Sampler members directly.
+		static sw::FilterType convertFilterMode(const vk::SamplerState *samplerState, VkImageViewType imageViewType, SamplerMethod samplerMethod);
+		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.
+		Function::ID function;                           // The current function being built.
+		Block::ID block;                                 // The current block being built.
+		rr::Value *activeLaneMaskValue = nullptr;        // The current active lane mask.
+		rr::Value *storesAndAtomicsMaskValue = nullptr;  // The current atomics mask.
+		Block::Set visited;                              // Blocks already built.
+		std::unordered_map<Block::Edge, RValue<SIMD::Int>, Block::Edge::Hash> edgeActiveLaneMasks;
+		std::deque<Block::ID> *pending;
+
+		const vk::DescriptorSet::Bindings &descriptorSets;
+
+		std::unordered_map<Object::ID, Intermediate> intermediates;
+		std::unordered_map<Object::ID, SIMD::Pointer> pointers;
+		std::unordered_map<Object::ID, SampledImagePointer> sampledImages;
+
+		const unsigned int multiSampleCount;
+	};
+
 	const Type &getType(Type::ID id) const
 	{
 		auto it = types.find(id);
@@ -1335,118 +1493,11 @@
 		return it->second;
 	}
 
-	// Returns a SIMD::Pointer to the underlying data for the given pointer
-	// object.
-	// Handles objects of the following kinds:
-	//  - DescriptorSet
-	//  - Pointer
-	//  - InterfaceVariable
-	// Calling GetPointerToData with objects of any other kind will assert.
-	SIMD::Pointer GetPointerToData(Object::ID id, SIMD::Int arrayIndex, bool nonUniform, const EmitState *state) const;
-	void OffsetToElement(SIMD::Pointer &ptr, Object::ID elementId, int32_t arrayStride, const EmitState *state) const;
-
-	OutOfBoundsBehavior getOutOfBoundsBehavior(Object::ID pointerId, const EmitState *state) const;
-
-	SIMD::Pointer WalkExplicitLayoutAccessChain(Object::ID id, Object::ID elementId, const Span &indexIds, bool nonUniform, const EmitState *state) const;
-	SIMD::Pointer WalkAccessChain(Object::ID id, Object::ID elementId, const Span &indexIds, bool nonUniform, const EmitState *state) const;
+	OutOfBoundsBehavior getOutOfBoundsBehavior(Object::ID pointerId, const vk::PipelineLayout *pipelineLayout) const;
 
 	// Returns the *component* offset in the literal for the given access chain.
 	uint32_t WalkLiteralAccessChain(Type::ID id, const Span &indexes) const;
 
-	// Lookup the active lane mask for the edge from -> to.
-	// If from is unreachable, then a mask of all zeros is returned.
-	// Asserts if from is reachable and the edge does not exist.
-	RValue<SIMD::Int> GetActiveLaneMaskEdge(EmitState *state, Block::ID from, Block::ID to) const;
-
-	// Updates the current active lane mask.
-	void SetActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const;
-	void SetStoresAndAtomicsMask(RValue<SIMD::Int> mask, EmitState *state) const;
-
-	// Emit all the unvisited blocks (except for ignore) in DFS order,
-	// starting with id.
-	void EmitBlocks(Block::ID id, EmitState *state, Block::ID ignore = 0) const;
-	void EmitNonLoop(EmitState *state) const;
-	void EmitLoop(EmitState *state) const;
-
-	void EmitInstructions(InsnIterator begin, InsnIterator end, EmitState *state) const;
-	EmitResult EmitInstruction(InsnIterator insn, EmitState *state) const;
-
-	// Emit pass instructions:
-	EmitResult EmitVariable(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitLoad(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitStore(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitAccessChain(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitCompositeConstruct(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitCompositeInsert(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitCompositeExtract(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitVectorShuffle(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitVectorTimesScalar(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitMatrixTimesVector(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitVectorTimesMatrix(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitMatrixTimesMatrix(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitOuterProduct(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitTranspose(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitVectorExtractDynamic(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitVectorInsertDynamic(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitUnaryOp(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitBinaryOp(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitDot(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitSelect(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitExtendedInstruction(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitExtGLSLstd450(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitLine(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitAny(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitAll(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitBranch(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitBranchConditional(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitSwitch(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitUnreachable(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitReturn(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitTerminateInvocation(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitDemoteToHelperInvocation(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitIsHelperInvocation(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitFunctionCall(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitPhi(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitImageSample(const ImageInstruction &instruction, EmitState *state) const;
-	EmitResult EmitImageQuerySizeLod(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitImageQuerySize(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitImageQueryLevels(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitImageQuerySamples(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitImageRead(const ImageInstruction &instruction, EmitState *state) const;
-	EmitResult EmitImageWrite(const ImageInstruction &instruction, EmitState *state) const;
-	EmitResult EmitImageTexelPointer(const ImageInstruction &instruction, EmitState *state) const;
-	EmitResult EmitAtomicOp(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitAtomicCompareExchange(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitSampledImage(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitImage(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitCopyObject(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitCopyMemory(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitControlBarrier(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitMemoryBarrier(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitGroupNonUniform(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitArrayLength(InsnIterator insn, EmitState *state) const;
-	EmitResult EmitPointerBitCast(Object::ID resultID, Operand &src, EmitState *state) const;
-
-	// Emits code to sample an image, regardless of whether any SIMD lanes are active.
-	void EmitImageSampleUnconditional(Array<SIMD::Float> &out, const ImageInstruction &instruction, EmitState *state) const;
-
-	Pointer<Byte> getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, EmitState *state) const;
-	Pointer<Byte> getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, int laneIdx, EmitState *state) const;
-	Pointer<Byte> lookupSamplerFunction(Pointer<Byte> imageDescriptor, Pointer<Byte> samplerDescriptor, const ImageInstruction &instruction, EmitState *state) const;
-	void callSamplerFunction(Pointer<Byte> samplerFunction, Array<SIMD::Float> &out, Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, EmitState *state) const;
-
-	void GetImageDimensions(const EmitState *state, const Type &resultTy, Object::ID imageId, Object::ID lodId, Intermediate &dst) const;
-	struct TexelAddressData
-	{
-		bool isArrayed;
-		spv::Dim dim;
-		int dims, texelSize;
-		SIMD::Int u, v, w, ptrOffset;
-	};
-	static TexelAddressData setupTexelAddressData(SIMD::Int rowPitch, SIMD::Int slicePitch, SIMD::Int samplePitch, ImageInstructionSignature instruction, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, const EmitState *state);
-	static SIMD::Pointer GetNonUniformTexelAddress(ImageInstructionSignature instruction, SIMD::Pointer descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, const EmitState *state);
-	static SIMD::Pointer GetTexelAddress(ImageInstructionSignature instruction, Pointer<Byte> descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, const EmitState *state);
-	static void WriteImage(ImageInstructionSignature instruction, Pointer<Byte> descriptor, const Pointer<SIMD::Int> &coord, const Pointer<SIMD::Int> &texelAndMask, vk::Format imageFormat);
 	uint32_t GetConstScalarInt(Object::ID id) const;
 	void EvalSpecConstantOp(InsnIterator insn);
 	void EvalSpecConstantUnaryOp(InsnIterator insn);
@@ -1455,33 +1506,6 @@
 	// Fragment input interpolation functions
 	uint32_t GetNumInputComponents(int32_t location) const;
 	uint32_t GetPackedInterpolant(int32_t location) const;
-	enum InterpolationType
-	{
-		Centroid,
-		AtSample,
-		AtOffset,
-	};
-	SIMD::Float EmitInterpolate(const SIMD::Pointer &ptr, int32_t location, Object::ID paramId,
-	                            uint32_t component, EmitState *state, InterpolationType type) const;
-
-	// Helper for implementing OpStore, which doesn't take an InsnIterator so it
-	// can also store independent operands.
-	void Store(Object::ID pointerId, const Operand &value, bool atomic, std::memory_order memoryOrder, EmitState *state) const;
-
-	// LoadPhi loads the phi values from the alloca storage and places the
-	// load values into the intermediate with the phi's result id.
-	void LoadPhi(InsnIterator insn, EmitState *state) const;
-
-	// 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, EmitState *state, const std::unordered_set<SpirvShader::Block::ID> &filter) const;
-
-	// 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.
-	void Yield(YieldResult res) const;
 
 	// WriteCFGGraphVizDotFile() writes a graphviz dot file of the shader's
 	// control flow to the given file path.
@@ -1501,23 +1525,6 @@
 	// has a result type ID and result ID, i.e. defines an Object.
 	static bool HasTypeAndResult(spv::Op op);
 
-	// Helper as we often need to take dot products as part of doing other things.
-	static SIMD::Float FDot(unsigned numComponents, const Operand &x, const Operand &y);
-	static SIMD::Int SDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum);
-	static SIMD::UInt UDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum);
-	static SIMD::Int SUDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum);
-	static SIMD::Int AddSat(RValue<SIMD::Int> a, RValue<SIMD::Int> b);
-	static SIMD::UInt AddSat(RValue<SIMD::UInt> a, RValue<SIMD::UInt> b);
-
-	static ImageSampler *getImageSampler(const vk::Device *device, uint32_t signature, uint32_t samplerId, uint32_t imageViewId);
-	static std::shared_ptr<rr::Routine> emitSamplerRoutine(ImageInstructionSignature instruction, const Sampler &samplerState);
-	static std::shared_ptr<rr::Routine> emitWriteRoutine(ImageInstructionSignature instruction, const Sampler &samplerState);
-
-	// TODO(b/129523279): Eliminate conversion and use vk::Sampler members directly.
-	static sw::FilterType convertFilterMode(const vk::SamplerState *samplerState, VkImageViewType imageViewType, SamplerMethod samplerMethod);
-	static sw::MipmapType convertMipmapMode(const vk::SamplerState *samplerState);
-	static sw::AddressingMode convertAddressingMode(int coordinateIndex, const vk::SamplerState *samplerState, VkImageViewType imageViewType);
-
 	// Returns 0 when invalid.
 	static VkShaderStageFlagBits executionModelToStage(spv::ExecutionModel model);
 };
diff --git a/src/Pipeline/SpirvShaderArithmetic.cpp b/src/Pipeline/SpirvShaderArithmetic.cpp
index e26a955..8eee57b 100644
--- a/src/Pipeline/SpirvShaderArithmetic.cpp
+++ b/src/Pipeline/SpirvShaderArithmetic.cpp
@@ -23,12 +23,12 @@
 
 namespace sw {
 
-SpirvShader::EmitResult SpirvShader::EmitVectorTimesScalar(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitVectorTimesScalar(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	for(auto i = 0u; i < type.componentCount; i++)
 	{
@@ -38,12 +38,12 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitMatrixTimesVector(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitState::EmitMatrixTimesVector(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	for(auto i = 0u; i < type.componentCount; i++)
 	{
@@ -58,12 +58,12 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitVectorTimesMatrix(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitVectorTimesMatrix(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	for(auto i = 0u; i < type.componentCount; i++)
 	{
@@ -78,16 +78,16 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitMatrixTimesMatrix(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitMatrixTimesMatrix(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	auto numColumns = type.definition.word(3);
-	auto numRows = getType(type.definition.word(2)).definition.word(3);
-	auto numAdds = getObjectType(insn.word(3)).definition.word(3);
+	auto numRows = shader.getType(type.definition.word(2)).definition.word(3);
+	auto numAdds = shader.getObjectType(insn.word(3)).definition.word(3);
 
 	for(auto row = 0u; row < numRows; row++)
 	{
@@ -105,12 +105,12 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitOuterProduct(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitOuterProduct(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	auto numRows = lhs.componentCount;
 	auto numCols = rhs.componentCount;
@@ -126,14 +126,14 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitTranspose(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitState::EmitTranspose(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto mat = Operand(this, state, insn.word(3));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto mat = Operand(shader, *this, insn.word(3));
 
 	auto numCols = type.definition.word(3);
-	auto numRows = getType(type.definition.word(2)).componentCount;
+	auto numRows = shader.getType(type.definition.word(2)).componentCount;
 
 	for(auto col = 0u; col < numCols; col++)
 	{
@@ -146,7 +146,7 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitPointerBitCast(Object::ID resultID, Operand &src, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitBitcastPointer(Object::ID resultID, Operand &src)
 {
 	if(src.isPointer())  // Pointer -> Integer bits
 	{
@@ -155,7 +155,7 @@
 			SIMD::UInt bits;
 			src.Pointer().castTo(bits);
 
-			auto &dst = state->createIntermediate(resultID, 1);
+			auto &dst = createIntermediate(resultID, 1);
 			dst.move(0, bits);
 		}
 		else  // 64-bit pointers
@@ -166,7 +166,7 @@
 			SIMD::UInt lowerBits, upperBits;
 			ptr.castTo(lowerBits, upperBits);
 
-			auto &dst = state->createIntermediate(resultID, 2);
+			auto &dst = createIntermediate(resultID, 2);
 			dst.move(0, lowerBits);
 			dst.move(1, upperBits);
 		}
@@ -175,35 +175,35 @@
 	{
 		if(sizeof(void *) == 4)  // 32-bit pointers
 		{
-			state->createPointer(resultID, SIMD::Pointer(src.UInt(0)));
+			createPointer(resultID, SIMD::Pointer(src.UInt(0)));
 		}
 		else  // 64-bit pointers
 		{
 			ASSERT(sizeof(void *) == 8);
 			// Casting two 32-bit integers into a 64-bit pointer
-			state->createPointer(resultID, SIMD::Pointer(src.UInt(0), src.UInt(1)));
+			createPointer(resultID, SIMD::Pointer(src.UInt(0), src.UInt(1)));
 		}
 	}
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitUnaryOp(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitUnaryOp(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto src = Operand(this, state, insn.word(3));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto src = Operand(shader, *this, insn.word(3));
 
-	bool dstIsPointer = getObject(insn.resultId()).kind == Object::Kind::Pointer;
+	bool dstIsPointer = shader.getObject(insn.resultId()).kind == Object::Kind::Pointer;
 	bool srcIsPointer = src.isPointer();
 	if(srcIsPointer || dstIsPointer)
 	{
 		ASSERT(insn.opcode() == spv::OpBitcast);
-		ASSERT((srcIsPointer || (type.componentCount == 1)));  // When the ouput is a pointer, it's a single pointer
+		ASSERT(srcIsPointer || (type.componentCount == 1));  // When the ouput is a pointer, it's a single pointer
 
-		return EmitPointerBitCast(insn.resultId(), src, state);
+		return EmitBitcastPointer(insn.resultId(), src);
 	}
 
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
 
 	for(auto i = 0u; i < type.componentCount; i++)
 	{
@@ -215,9 +215,9 @@
 			break;
 		case spv::OpBitFieldInsert:
 			{
-				auto insert = Operand(this, state, insn.word(4)).UInt(i);
-				auto offset = Operand(this, state, insn.word(5)).UInt(0);
-				auto count = Operand(this, state, insn.word(6)).UInt(0);
+				auto insert = Operand(shader, *this, insn.word(4)).UInt(i);
+				auto offset = Operand(shader, *this, insn.word(5)).UInt(0);
+				auto count = Operand(shader, *this, insn.word(6)).UInt(0);
 				auto one = SIMD::UInt(1);
 				auto v = src.UInt(i);
 				auto mask = Bitmask32(offset + count) ^ Bitmask32(offset);
@@ -227,8 +227,8 @@
 		case spv::OpBitFieldSExtract:
 		case spv::OpBitFieldUExtract:
 			{
-				auto offset = Operand(this, state, insn.word(4)).UInt(0);
-				auto count = Operand(this, state, insn.word(5)).UInt(0);
+				auto offset = Operand(shader, *this, insn.word(4)).UInt(0);
+				auto count = Operand(shader, *this, insn.word(5)).UInt(0);
 				auto one = SIMD::UInt(1);
 				auto v = src.UInt(i);
 				SIMD::UInt out = (v >> offset) & Bitmask32(count);
@@ -361,13 +361,13 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitBinaryOp(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitBinaryOp(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &lhsType = getObjectType(insn.word(3));
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &lhsType = shader.getObjectType(insn.word(3));
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	for(auto i = 0u; i < lhsType.componentCount; i++)
 	{
@@ -574,14 +574,14 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitDot(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitDot(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
+	auto &type = shader.getType(insn.resultTypeId());
 	ASSERT(type.componentCount == 1);
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	auto &lhsType = getObjectType(insn.word(3));
-	auto lhs = Operand(this, state, insn.word(3));
-	auto rhs = Operand(this, state, insn.word(4));
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	auto &lhsType = shader.getObjectType(insn.word(3));
+	auto lhs = Operand(shader, *this, insn.word(3));
+	auto rhs = Operand(shader, *this, insn.word(4));
 
 	auto opcode = insn.opcode();
 	switch(opcode)
@@ -600,19 +600,19 @@
 		break;
 	case spv::OpSDotAccSat:
 		{
-			auto accum = Operand(this, state, insn.word(5));
+			auto accum = Operand(shader, *this, insn.word(5));
 			dst.move(0, SDot(lhsType.componentCount, lhs, rhs, &accum));
 		}
 		break;
 	case spv::OpUDotAccSat:
 		{
-			auto accum = Operand(this, state, insn.word(5));
+			auto accum = Operand(shader, *this, insn.word(5));
 			dst.move(0, UDot(lhsType.componentCount, lhs, rhs, &accum));
 		}
 		break;
 	case spv::OpSUDotAccSat:
 		{
-			auto accum = Operand(this, state, insn.word(5));
+			auto accum = Operand(shader, *this, insn.word(5));
 			dst.move(0, SUDot(lhsType.componentCount, lhs, rhs, &accum));
 		}
 		break;
@@ -628,7 +628,7 @@
 	return EmitResult::Continue;
 }
 
-SIMD::Float SpirvShader::FDot(unsigned numComponents, const Operand &x, const Operand &y)
+SIMD::Float SpirvShader::EmitState::FDot(unsigned numComponents, const Operand &x, const Operand &y)
 {
 	SIMD::Float d = x.Float(0) * y.Float(0);
 
@@ -640,7 +640,7 @@
 	return d;
 }
 
-SIMD::Int SpirvShader::SDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum)
+SIMD::Int SpirvShader::EmitState::SDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum)
 {
 	SIMD::Int d(0);
 
@@ -676,7 +676,7 @@
 	return d;
 }
 
-SIMD::UInt SpirvShader::UDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum)
+SIMD::UInt SpirvShader::EmitState::UDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum)
 {
 	SIMD::UInt d(0);
 
@@ -712,7 +712,7 @@
 	return d;
 }
 
-SIMD::Int SpirvShader::SUDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum)
+SIMD::Int SpirvShader::EmitState::SUDot(unsigned numComponents, const Operand &x, const Operand &y, const Operand *accum)
 {
 	SIMD::Int d(0);
 
@@ -748,7 +748,7 @@
 	return d;
 }
 
-SIMD::Int SpirvShader::AddSat(RValue<SIMD::Int> a, RValue<SIMD::Int> b)
+SIMD::Int SpirvShader::EmitState::AddSat(RValue<SIMD::Int> a, RValue<SIMD::Int> b)
 {
 	SIMD::Int sum = a + b;
 	SIMD::Int sSign = sum >> 31;
@@ -765,7 +765,7 @@
 	       (~oob & sum);
 }
 
-SIMD::UInt SpirvShader::AddSat(RValue<SIMD::UInt> a, RValue<SIMD::UInt> b)
+SIMD::UInt SpirvShader::EmitState::AddSat(RValue<SIMD::UInt> a, RValue<SIMD::UInt> b)
 {
 	SIMD::UInt sum = a + b;
 
diff --git a/src/Pipeline/SpirvShaderControlFlow.cpp b/src/Pipeline/SpirvShaderControlFlow.cpp
index 4d84ebc..e19f1b3 100644
--- a/src/Pipeline/SpirvShaderControlFlow.cpp
+++ b/src/Pipeline/SpirvShaderControlFlow.cpp
@@ -216,21 +216,21 @@
 	}
 }
 
-RValue<SIMD::Int> SpirvShader::GetActiveLaneMaskEdge(EmitState *state, Block::ID from, Block::ID to) const
+RValue<SIMD::Int> SpirvShader::EmitState::GetActiveLaneMaskEdge(Block::ID from, Block::ID to) const
 {
 	auto edge = Block::Edge{ from, to };
-	auto it = state->edgeActiveLaneMasks.find(edge);
-	ASSERT_MSG(it != state->edgeActiveLaneMasks.end(), "Could not find edge %d -> %d", from.value(), to.value());
+	auto it = edgeActiveLaneMasks.find(edge);
+	ASSERT_MSG(it != edgeActiveLaneMasks.end(), "Could not find edge %d -> %d", from.value(), to.value());
 	return it->second;
 }
 
-void SpirvShader::EmitBlocks(Block::ID id, EmitState *state, Block::ID ignore /* = 0 */) const
+void SpirvShader::EmitState::EmitBlocks(Block::ID id, Block::ID ignore /* = 0 */)
 {
-	auto oldPending = state->pending;
-	auto &function = getFunction(state->function);
+	auto oldPending = this->pending;
+	auto &function = shader.getFunction(this->function);
 
 	std::deque<Block::ID> pending;
-	state->pending = &pending;
+	this->pending = &pending;
 	pending.push_front(id);
 	while(pending.size() > 0)
 	{
@@ -246,9 +246,9 @@
 		// Ensure all dependency blocks have been generated.
 		auto depsDone = true;
 		function.ForeachBlockDependency(id, [&](Block::ID dep) {
-			if(state->visited.count(dep) == 0)
+			if(visited.count(dep) == 0)
 			{
-				state->pending->push_front(dep);
+				this->pending->push_front(dep);
 				depsDone = false;
 			}
 		});
@@ -260,7 +260,7 @@
 
 		pending.pop_front();
 
-		state->block = id;
+		this->block = id;
 
 		switch(block.kind)
 		{
@@ -269,11 +269,11 @@
 		case Block::UnstructuredBranchConditional:
 		case Block::StructuredSwitch:
 		case Block::UnstructuredSwitch:
-			EmitNonLoop(state);
+			EmitNonLoop();
 			break;
 
 		case Block::Loop:
-			EmitLoop(state);
+			EmitLoop();
 			break;
 
 		default:
@@ -281,16 +281,16 @@
 		}
 	}
 
-	state->pending = oldPending;
+	this->pending = oldPending;
 }
 
-void SpirvShader::EmitNonLoop(EmitState *state) const
+void SpirvShader::EmitState::EmitNonLoop()
 {
-	auto &function = getFunction(state->function);
-	auto blockId = state->block;
+	auto &function = shader.getFunction(this->function);
+	auto blockId = block;
 	auto block = function.getBlock(blockId);
 
-	if(!state->visited.emplace(blockId).second)
+	if(!visited.emplace(blockId).second)
 	{
 		return;  // Already generated this block.
 	}
@@ -301,36 +301,36 @@
 		SIMD::Int activeLaneMask(0);
 		for(auto in : block.ins)
 		{
-			auto inMask = GetActiveLaneMaskEdge(state, in, blockId);
+			auto inMask = GetActiveLaneMaskEdge(in, blockId);
 			SPIRV_SHADER_DBG("Block {0} -> {1} mask: {2}", in, blockId, inMask);
 			activeLaneMask |= inMask;
 		}
 		SPIRV_SHADER_DBG("Block {0} mask: {1}", blockId, activeLaneMask);
-		SetActiveLaneMask(activeLaneMask, state);
+		SetActiveLaneMask(activeLaneMask);
 	}
 
-	EmitInstructions(block.begin(), block.end(), state);
+	EmitInstructions(block.begin(), block.end());
 
 	for(auto out : block.outs)
 	{
-		if(state->visited.count(out) == 0)
+		if(visited.count(out) == 0)
 		{
-			state->pending->push_back(out);
+			pending->push_back(out);
 		}
 	}
 
 	SPIRV_SHADER_DBG("Block {0} done", blockId);
 }
 
-void SpirvShader::EmitLoop(EmitState *state) const
+void SpirvShader::EmitState::EmitLoop()
 {
-	auto &function = getFunction(state->function);
-	auto blockId = state->block;
+	auto &function = shader.getFunction(this->function);
+	auto blockId = block;
 	auto &block = function.getBlock(blockId);
 	auto mergeBlockId = block.mergeBlock;
 	auto &mergeBlock = function.getBlock(mergeBlockId);
 
-	if(!state->visited.emplace(blockId).second)
+	if(!visited.emplace(blockId).second)
 	{
 		return;  // Already emitted this loop.
 	}
@@ -358,7 +358,7 @@
 	{
 		if(insn.opcode() == spv::OpPhi)
 		{
-			StorePhi(blockId, insn, state, incomingBlocks);
+			StorePhi(blockId, insn, incomingBlocks);
 		}
 	}
 
@@ -367,7 +367,7 @@
 	SIMD::Int loopActiveLaneMask = SIMD::Int(0);
 	for(auto in : incomingBlocks)
 	{
-		loopActiveLaneMask |= GetActiveLaneMaskEdge(state, in, blockId);
+		loopActiveLaneMask |= GetActiveLaneMaskEdge(in, blockId);
 	}
 
 	// mergeActiveLaneMasks contains edge lane masks for the merge block.
@@ -389,18 +389,18 @@
 	SPIRV_SHADER_DBG("*** LOOP START (mask: {0}) ***", loopActiveLaneMask);
 
 	// Load the active lane mask.
-	SetActiveLaneMask(loopActiveLaneMask, state);
+	SetActiveLaneMask(loopActiveLaneMask);
 
 	// Emit the non-phi loop header block's instructions.
 	for(auto insn = block.begin(); insn != block.end(); insn++)
 	{
 		if(insn.opcode() == spv::OpPhi)
 		{
-			LoadPhi(insn, state);
+			LoadPhi(insn);
 		}
 		else
 		{
-			EmitInstruction(insn, state);
+			EmitInstruction(insn);
 		}
 	}
 
@@ -408,11 +408,11 @@
 	// don't emit the merge block yet.
 	for(auto out : block.outs)
 	{
-		EmitBlocks(out, state, mergeBlockId);
+		EmitBlocks(out, mergeBlockId);
 	}
 
 	// Restore current block id after emitting loop blocks.
-	state->block = blockId;
+	this->block = blockId;
 
 	// Rebuild the loopActiveLaneMask from the loop back edges.
 	loopActiveLaneMask = SIMD::Int(0);
@@ -420,7 +420,7 @@
 	{
 		if(function.ExistsPath(blockId, in, mergeBlockId))
 		{
-			loopActiveLaneMask |= GetActiveLaneMaskEdge(state, in, blockId);
+			loopActiveLaneMask |= GetActiveLaneMaskEdge(in, blockId);
 		}
 	}
 
@@ -428,8 +428,8 @@
 	for(auto in : function.getBlock(mergeBlockId).ins)
 	{
 		auto edge = Block::Edge{ in, mergeBlockId };
-		auto it = state->edgeActiveLaneMasks.find(edge);
-		if(it != state->edgeActiveLaneMasks.end())
+		auto it = edgeActiveLaneMasks.find(edge);
+		if(it != edgeActiveLaneMasks.end())
 		{
 			mergeActiveLaneMasks[in] |= it->second;
 		}
@@ -440,7 +440,7 @@
 	{
 		if(insn.opcode() == spv::OpPhi)
 		{
-			StorePhi(blockId, insn, state, loopBlocks);
+			StorePhi(blockId, insn, loopBlocks);
 		}
 	}
 
@@ -474,7 +474,7 @@
 	{
 		if(insn.opcode() == spv::OpPhi)
 		{
-			StorePhi(mergeBlockId, insn, state, loopBlocks);
+			StorePhi(mergeBlockId, insn, loopBlocks);
 		}
 	}
 
@@ -485,50 +485,50 @@
 
 	// Continue emitting from the merge block.
 	Nucleus::setInsertBlock(mergeBasicBlock);
-	state->pending->push_back(mergeBlockId);
+	pending->push_back(mergeBlockId);
 	for(const auto &it : mergeActiveLaneMasks)
 	{
-		state->addActiveLaneMaskEdge(it.first, mergeBlockId, it.second);
+		addActiveLaneMaskEdge(it.first, mergeBlockId, it.second);
 	}
 }
 
-SpirvShader::EmitResult SpirvShader::EmitBranch(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitBranch(InsnIterator insn)
 {
 	auto target = Block::ID(insn.word(1));
-	state->addActiveLaneMaskEdge(state->block, target, state->activeLaneMask());
+	addActiveLaneMaskEdge(block, target, activeLaneMask());
 	return EmitResult::Terminator;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitBranchConditional(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitBranchConditional(InsnIterator insn)
 {
-	auto &function = getFunction(state->function);
-	auto block = function.getBlock(state->block);
+	auto &function = shader.getFunction(this->function);
+	auto block = function.getBlock(this->block);
 	ASSERT(block.branchInstruction == insn);
 
 	auto condId = Object::ID(block.branchInstruction.word(1));
 	auto trueBlockId = Block::ID(block.branchInstruction.word(2));
 	auto falseBlockId = Block::ID(block.branchInstruction.word(3));
 
-	auto cond = Operand(this, state, condId);
-	ASSERT_MSG(getObjectType(condId).componentCount == 1, "Condition must be a Boolean type scalar");
+	auto cond = Operand(shader, *this, condId);
+	ASSERT_MSG(shader.getObjectType(condId).componentCount == 1, "Condition must be a Boolean type scalar");
 
 	// TODO: Optimize for case where all lanes take same path.
 
-	state->addOutputActiveLaneMaskEdge(trueBlockId, cond.Int(0));
-	state->addOutputActiveLaneMaskEdge(falseBlockId, ~cond.Int(0));
+	addOutputActiveLaneMaskEdge(trueBlockId, cond.Int(0));
+	addOutputActiveLaneMaskEdge(falseBlockId, ~cond.Int(0));
 
 	return EmitResult::Terminator;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitSwitch(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitSwitch(InsnIterator insn)
 {
-	auto &function = getFunction(state->function);
-	auto block = function.getBlock(state->block);
+	auto &function = shader.getFunction(this->function);
+	auto block = function.getBlock(this->block);
 	ASSERT(block.branchInstruction == insn);
 
 	auto selId = Object::ID(block.branchInstruction.word(1));
 
-	auto sel = Operand(this, state, selId);
+	auto sel = Operand(shader, *this, selId);
 	ASSERT_MSG(sel.componentCount == 1, "Selector must be a scalar");
 	SPIRV_SHADER_DBG("switch({0})", sel);
 
@@ -536,7 +536,7 @@
 
 	// TODO: Optimize for case where all lanes take same path.
 
-	SIMD::Int defaultLaneMask = state->activeLaneMask();
+	SIMD::Int defaultLaneMask = activeLaneMask();
 
 	// Gather up the case label matches and calculate defaultLaneMask.
 	std::vector<RValue<SIMD::Int>> caseLabelMatches;
@@ -546,59 +546,59 @@
 		auto label = block.branchInstruction.word(i * 2 + 3);
 		auto caseBlockId = Block::ID(block.branchInstruction.word(i * 2 + 4));
 		auto caseLabelMatch = CmpEQ(sel.Int(0), SIMD::Int(label));
-		SPIRV_SHADER_DBG("case {0}: {1}", label, caseLabelMatch & state->activeLaneMask());
-		state->addOutputActiveLaneMaskEdge(caseBlockId, caseLabelMatch);
+		SPIRV_SHADER_DBG("case {0}: {1}", label, caseLabelMatch & activeLaneMask());
+		addOutputActiveLaneMaskEdge(caseBlockId, caseLabelMatch);
 		defaultLaneMask &= ~caseLabelMatch;
 	}
 
 	auto defaultBlockId = Block::ID(block.branchInstruction.word(2));
 	SPIRV_SHADER_DBG("default: {0}", defaultLaneMask);
-	state->addOutputActiveLaneMaskEdge(defaultBlockId, defaultLaneMask);
+	addOutputActiveLaneMaskEdge(defaultBlockId, defaultLaneMask);
 
 	return EmitResult::Terminator;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitUnreachable(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitUnreachable(InsnIterator insn)
 {
 	// TODO: Log something in this case?
-	SetActiveLaneMask(SIMD::Int(0), state);
+	SetActiveLaneMask(SIMD::Int(0));
 	return EmitResult::Terminator;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitReturn(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitReturn(InsnIterator insn)
 {
-	SetActiveLaneMask(SIMD::Int(0), state);
+	SetActiveLaneMask(SIMD::Int(0));
 	return EmitResult::Terminator;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitTerminateInvocation(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitTerminateInvocation(InsnIterator insn)
 {
-	state->routine->discardMask |= SignMask(state->activeLaneMask());
-	SetActiveLaneMask(SIMD::Int(0), state);
+	routine->discardMask |= SignMask(activeLaneMask());
+	SetActiveLaneMask(SIMD::Int(0));
 	return EmitResult::Terminator;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitDemoteToHelperInvocation(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitDemoteToHelperInvocation(InsnIterator insn)
 {
-	state->routine->helperInvocation |= state->activeLaneMask();
-	state->routine->discardMask |= SignMask(state->activeLaneMask());
-	SetStoresAndAtomicsMask(state->storesAndAtomicsMask() & ~state->activeLaneMask(), state);
+	routine->helperInvocation |= activeLaneMask();
+	routine->discardMask |= SignMask(activeLaneMask());
+	SetStoresAndAtomicsMask(storesAndAtomicsMask() & ~activeLaneMask());
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitIsHelperInvocation(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitIsHelperInvocation(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
-	dst.move(0, state->routine->helperInvocation);
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
+	dst.move(0, routine->helperInvocation);
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitFunctionCall(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitFunctionCall(InsnIterator insn)
 {
 	auto functionId = Function::ID(insn.word(3));
-	const auto &functionIt = functions.find(functionId);
-	ASSERT(functionIt != functions.end());
+	const auto &functionIt = shader.functions.find(functionId);
+	ASSERT(functionIt != shader.functions.end());
 	auto &function = functionIt->second;
 
 	// TODO(b/141246700): Add full support for spv::OpFunctionCall
@@ -626,7 +626,7 @@
 
 			if(blockInsn.opcode() == spv::OpKill)
 			{
-				EmitInstruction(blockInsn, state);
+				EmitInstruction(blockInsn);
 			}
 		}
 	}
@@ -634,10 +634,10 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitControlBarrier(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitControlBarrier(InsnIterator insn) const
 {
-	auto executionScope = spv::Scope(GetConstScalarInt(insn.word(1)));
-	auto semantics = spv::MemorySemanticsMask(GetConstScalarInt(insn.word(3)));
+	auto executionScope = spv::Scope(shader.GetConstScalarInt(insn.word(1)));
+	auto semantics = spv::MemorySemanticsMask(shader.GetConstScalarInt(insn.word(3)));
 	// TODO(b/176819536): We probably want to consider the memory scope here.
 	// For now, just always emit the full fence.
 	Fence(semantics);
@@ -658,32 +658,32 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitPhi(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitPhi(InsnIterator insn)
 {
-	auto &function = getFunction(state->function);
-	auto currentBlock = function.getBlock(state->block);
+	auto &function = shader.getFunction(this->function);
+	auto currentBlock = function.getBlock(block);
 	if(!currentBlock.isLoopMerge)
 	{
 		// If this is a loop merge block, then don't attempt to update the
 		// phi values from the ins. EmitLoop() has had to take special care
 		// of this phi in order to correctly deal with divergent lanes.
-		StorePhi(state->block, insn, state, currentBlock.ins);
+		StorePhi(block, insn, currentBlock.ins);
 	}
-	LoadPhi(insn, state);
+	LoadPhi(insn);
 	return EmitResult::Continue;
 }
 
-void SpirvShader::LoadPhi(InsnIterator insn, EmitState *state) const
+void SpirvShader::EmitState::LoadPhi(InsnIterator insn)
 {
 	auto typeId = Type::ID(insn.word(1));
-	auto type = getType(typeId);
+	auto type = shader.getType(typeId);
 	auto objectId = Object::ID(insn.word(2));
 
-	auto storageIt = state->routine->phis.find(objectId);
-	ASSERT(storageIt != state->routine->phis.end());
+	auto storageIt = routine->phis.find(objectId);
+	ASSERT(storageIt != routine->phis.end());
 	auto &storage = storageIt->second;
 
-	auto &dst = state->createIntermediate(objectId, type.componentCount);
+	auto &dst = createIntermediate(objectId, type.componentCount);
 	for(uint32_t i = 0; i < type.componentCount; i++)
 	{
 		dst.move(i, storage[i]);
@@ -691,14 +691,14 @@
 	}
 }
 
-void SpirvShader::StorePhi(Block::ID currentBlock, InsnIterator insn, EmitState *state, const std::unordered_set<SpirvShader::Block::ID> &filter) const
+void SpirvShader::EmitState::StorePhi(Block::ID currentBlock, InsnIterator insn, const std::unordered_set<SpirvShader::Block::ID> &filter) const
 {
 	auto typeId = Type::ID(insn.word(1));
-	auto type = getType(typeId);
+	auto type = shader.getType(typeId);
 	auto objectId = Object::ID(insn.word(2));
 
-	auto storageIt = state->routine->phis.find(objectId);
-	ASSERT(storageIt != state->routine->phis.end());
+	auto storageIt = routine->phis.find(objectId);
+	ASSERT(storageIt != routine->phis.end());
 	auto &storage = storageIt->second;
 
 	for(uint32_t w = 3; w < insn.wordCount(); w += 2)
@@ -711,8 +711,8 @@
 			continue;
 		}
 
-		auto mask = GetActiveLaneMaskEdge(state, blockId, currentBlock);
-		auto in = Operand(this, state, varId);
+		auto mask = GetActiveLaneMaskEdge(blockId, currentBlock);
+		auto in = Operand(shader, *this, varId);
 
 		for(uint32_t i = 0; i < type.componentCount; i++)
 		{
@@ -728,19 +728,19 @@
 	}
 }
 
-void SpirvShader::Yield(YieldResult res) const
+void SpirvShader::EmitState::Yield(YieldResult res) const
 {
 	rr::Yield(RValue<Int>(int(res)));
 }
 
-void SpirvShader::SetActiveLaneMask(RValue<SIMD::Int> mask, EmitState *state) const
+void SpirvShader::EmitState::SetActiveLaneMask(RValue<SIMD::Int> mask)
 {
-	state->activeLaneMaskValue = mask.value();
+	activeLaneMaskValue = mask.value();
 }
 
-void SpirvShader::SetStoresAndAtomicsMask(RValue<SIMD::Int> mask, EmitState *state) const
+void SpirvShader::EmitState::SetStoresAndAtomicsMask(RValue<SIMD::Int> mask)
 {
-	state->storesAndAtomicsMaskValue = mask.value();
+	storesAndAtomicsMaskValue = mask.value();
 }
 
 void SpirvShader::WriteCFGGraphVizDotFile(const char *path) const
diff --git a/src/Pipeline/SpirvShaderGLSLstd450.cpp b/src/Pipeline/SpirvShaderGLSLstd450.cpp
index 61c2e42..672afd8 100644
--- a/src/Pipeline/SpirvShaderGLSLstd450.cpp
+++ b/src/Pipeline/SpirvShaderGLSLstd450.cpp
@@ -25,17 +25,17 @@
 
 static constexpr float PI = 3.141592653589793f;
 
-SpirvShader::EmitResult SpirvShader::EmitExtGLSLstd450(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitExtGLSLstd450(InsnIterator insn)
 {
-	auto &type = getType(insn.resultTypeId());
-	auto &dst = state->createIntermediate(insn.resultId(), type.componentCount);
+	auto &type = shader.getType(insn.resultTypeId());
+	auto &dst = createIntermediate(insn.resultId(), type.componentCount);
 	auto extInstIndex = static_cast<GLSLstd450>(insn.word(4));
 
 	switch(extInstIndex)
 	{
 	case GLSLstd450FAbs:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Abs(src.Float(i)));
@@ -44,7 +44,7 @@
 		break;
 	case GLSLstd450SAbs:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Abs(src.Int(i)));
@@ -53,8 +53,8 @@
 		break;
 	case GLSLstd450Cross:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			dst.move(0, lhs.Float(1) * rhs.Float(2) - rhs.Float(1) * lhs.Float(2));
 			dst.move(1, lhs.Float(2) * rhs.Float(0) - rhs.Float(2) * lhs.Float(0));
 			dst.move(2, lhs.Float(0) * rhs.Float(1) - rhs.Float(0) * lhs.Float(1));
@@ -62,7 +62,7 @@
 		break;
 	case GLSLstd450Floor:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Floor(src.Float(i)));
@@ -71,7 +71,7 @@
 		break;
 	case GLSLstd450Trunc:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Trunc(src.Float(i)));
@@ -80,7 +80,7 @@
 		break;
 	case GLSLstd450Ceil:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Ceil(src.Float(i)));
@@ -89,7 +89,7 @@
 		break;
 	case GLSLstd450Fract:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Frac(src.Float(i)));
@@ -98,7 +98,7 @@
 		break;
 	case GLSLstd450Round:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Round(src.Float(i)));
@@ -107,7 +107,7 @@
 		break;
 	case GLSLstd450RoundEven:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto x = Round(src.Float(i));
@@ -119,8 +119,8 @@
 		break;
 	case GLSLstd450FMin:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Min(lhs.Float(i), rhs.Float(i)));
@@ -129,8 +129,8 @@
 		break;
 	case GLSLstd450FMax:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Max(lhs.Float(i), rhs.Float(i)));
@@ -139,8 +139,8 @@
 		break;
 	case GLSLstd450SMin:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Min(lhs.Int(i), rhs.Int(i)));
@@ -149,8 +149,8 @@
 		break;
 	case GLSLstd450SMax:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Max(lhs.Int(i), rhs.Int(i)));
@@ -159,8 +159,8 @@
 		break;
 	case GLSLstd450UMin:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Min(lhs.UInt(i), rhs.UInt(i)));
@@ -169,8 +169,8 @@
 		break;
 	case GLSLstd450UMax:
 		{
-			auto lhs = Operand(this, state, insn.word(5));
-			auto rhs = Operand(this, state, insn.word(6));
+			auto lhs = Operand(shader, *this, insn.word(5));
+			auto rhs = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Max(lhs.UInt(i), rhs.UInt(i)));
@@ -179,8 +179,8 @@
 		break;
 	case GLSLstd450Step:
 		{
-			auto edge = Operand(this, state, insn.word(5));
-			auto x = Operand(this, state, insn.word(6));
+			auto edge = Operand(shader, *this, insn.word(5));
+			auto x = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, CmpNLT(x.Float(i), edge.Float(i)) & As<SIMD::Int>(SIMD::Float(1.0f)));
@@ -189,9 +189,9 @@
 		break;
 	case GLSLstd450SmoothStep:
 		{
-			auto edge0 = Operand(this, state, insn.word(5));
-			auto edge1 = Operand(this, state, insn.word(6));
-			auto x = Operand(this, state, insn.word(7));
+			auto edge0 = Operand(shader, *this, insn.word(5));
+			auto edge1 = Operand(shader, *this, insn.word(6));
+			auto x = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto tx = Min(Max((x.Float(i) - edge0.Float(i)) / (edge1.Float(i) - edge0.Float(i)), 0.0f), 1.0f);
@@ -201,9 +201,9 @@
 		break;
 	case GLSLstd450FMix:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto y = Operand(this, state, insn.word(6));
-			auto a = Operand(this, state, insn.word(7));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto y = Operand(shader, *this, insn.word(6));
+			auto a = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, a.Float(i) * (y.Float(i) - x.Float(i)) + x.Float(i));
@@ -212,9 +212,9 @@
 		break;
 	case GLSLstd450FClamp:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto minVal = Operand(this, state, insn.word(6));
-			auto maxVal = Operand(this, state, insn.word(7));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto minVal = Operand(shader, *this, insn.word(6));
+			auto maxVal = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Min(Max(x.Float(i), minVal.Float(i)), maxVal.Float(i)));
@@ -223,9 +223,9 @@
 		break;
 	case GLSLstd450SClamp:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto minVal = Operand(this, state, insn.word(6));
-			auto maxVal = Operand(this, state, insn.word(7));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto minVal = Operand(shader, *this, insn.word(6));
+			auto maxVal = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Min(Max(x.Int(i), minVal.Int(i)), maxVal.Int(i)));
@@ -234,9 +234,9 @@
 		break;
 	case GLSLstd450UClamp:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto minVal = Operand(this, state, insn.word(6));
-			auto maxVal = Operand(this, state, insn.word(7));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto minVal = Operand(shader, *this, insn.word(6));
+			auto maxVal = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, Min(Max(x.UInt(i), minVal.UInt(i)), maxVal.UInt(i)));
@@ -245,7 +245,7 @@
 		break;
 	case GLSLstd450FSign:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto neg = As<SIMD::Int>(CmpLT(src.Float(i), SIMD::Float(-0.0f))) & As<SIMD::Int>(SIMD::Float(-1.0f));
@@ -256,7 +256,7 @@
 		break;
 	case GLSLstd450SSign:
 		{
-			auto src = Operand(this, state, insn.word(5));
+			auto src = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto neg = CmpLT(src.Int(i), SIMD::Int(0)) & SIMD::Int(-1);
@@ -267,8 +267,8 @@
 		break;
 	case GLSLstd450Reflect:
 		{
-			auto I = Operand(this, state, insn.word(5));
-			auto N = Operand(this, state, insn.word(6));
+			auto I = Operand(shader, *this, insn.word(5));
+			auto N = Operand(shader, *this, insn.word(6));
 
 			SIMD::Float d = FDot(type.componentCount, I, N);
 
@@ -280,10 +280,10 @@
 		break;
 	case GLSLstd450Refract:
 		{
-			auto I = Operand(this, state, insn.word(5));
-			auto N = Operand(this, state, insn.word(6));
-			auto eta = Operand(this, state, insn.word(7));
-			Decorations r = GetDecorationsForId(insn.resultId());
+			auto I = Operand(shader, *this, insn.word(5));
+			auto N = Operand(shader, *this, insn.word(6));
+			auto eta = Operand(shader, *this, insn.word(7));
+			Decorations r = shader.GetDecorationsForId(insn.resultId());
 
 			SIMD::Float d = FDot(type.componentCount, I, N);
 			SIMD::Float k = SIMD::Float(1.0f) - eta.Float(0) * eta.Float(0) * (SIMD::Float(1.0f) - d * d);
@@ -298,9 +298,9 @@
 		break;
 	case GLSLstd450FaceForward:
 		{
-			auto N = Operand(this, state, insn.word(5));
-			auto I = Operand(this, state, insn.word(6));
-			auto Nref = Operand(this, state, insn.word(7));
+			auto N = Operand(shader, *this, insn.word(5));
+			auto I = Operand(shader, *this, insn.word(6));
+			auto Nref = Operand(shader, *this, insn.word(7));
 
 			SIMD::Float d = FDot(type.componentCount, I, Nref);
 			SIMD::Int neg = CmpLT(d, SIMD::Float(0.0f));
@@ -314,19 +314,19 @@
 		break;
 	case GLSLstd450Length:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			SIMD::Float d = FDot(getObjectType(insn.word(5)).componentCount, x, x);
-			Decorations r = GetDecorationsForId(insn.resultId());
+			auto x = Operand(shader, *this, insn.word(5));
+			SIMD::Float d = FDot(shader.getObjectType(insn.word(5)).componentCount, x, x);
+			Decorations r = shader.GetDecorationsForId(insn.resultId());
 
 			dst.move(0, Sqrt(d, r.RelaxedPrecision));
 		}
 		break;
 	case GLSLstd450Normalize:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			Decorations r = GetDecorationsForId(insn.resultId());
+			auto x = Operand(shader, *this, insn.word(5));
+			Decorations r = shader.GetDecorationsForId(insn.resultId());
 
-			SIMD::Float d = FDot(getObjectType(insn.word(5)).componentCount, x, x);
+			SIMD::Float d = FDot(shader.getObjectType(insn.word(5)).componentCount, x, x);
 			SIMD::Float invLength = SIMD::Float(1.0f) / Sqrt(d, r.RelaxedPrecision);
 
 			for(auto i = 0u; i < type.componentCount; i++)
@@ -337,9 +337,9 @@
 		break;
 	case GLSLstd450Distance:
 		{
-			auto p0 = Operand(this, state, insn.word(5));
-			auto p1 = Operand(this, state, insn.word(6));
-			Decorations r = GetDecorationsForId(insn.resultId());
+			auto p0 = Operand(shader, *this, insn.word(5));
+			auto p1 = Operand(shader, *this, insn.word(6));
+			Decorations r = shader.GetDecorationsForId(insn.resultId());
 
 			// sqrt(dot(p0-p1, p0-p1))
 			SIMD::Float d = (p0.Float(0) - p1.Float(0)) * (p0.Float(0) - p1.Float(0));
@@ -354,7 +354,7 @@
 		break;
 	case GLSLstd450Modf:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			auto ptrId = Object::ID(insn.word(6));
 
 			Intermediate whole(type.componentCount);
@@ -366,12 +366,12 @@
 				whole.move(i, wholeAndFrac.first);
 			}
 
-			Store(ptrId, whole, false, std::memory_order_relaxed, state);
+			Store(ptrId, whole, false, std::memory_order_relaxed);
 		}
 		break;
 	case GLSLstd450ModfStruct:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 
 			for(auto i = 0u; i < val.componentCount; i++)
 			{
@@ -383,7 +383,7 @@
 		break;
 	case GLSLstd450PackSnorm4x8:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, (SIMD::Int(Round(Min(Max(val.Float(0), SIMD::Float(-1.0f)), SIMD::Float(1.0f)) * SIMD::Float(127.0f))) &
 			             SIMD::Int(0xFF)) |
 			                ((SIMD::Int(Round(Min(Max(val.Float(1), SIMD::Float(-1.0f)), SIMD::Float(1.0f)) * SIMD::Float(127.0f))) &
@@ -399,7 +399,7 @@
 		break;
 	case GLSLstd450PackUnorm4x8:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, (SIMD::UInt(Round(Min(Max(val.Float(0), SIMD::Float(0.0f)), SIMD::Float(1.0f)) * SIMD::Float(255.0f)))) |
 			                ((SIMD::UInt(Round(Min(Max(val.Float(1), SIMD::Float(0.0f)), SIMD::Float(1.0f)) * SIMD::Float(255.0f)))) << 8) |
 			                ((SIMD::UInt(Round(Min(Max(val.Float(2), SIMD::Float(0.0f)), SIMD::Float(1.0f)) * SIMD::Float(255.0f)))) << 16) |
@@ -408,7 +408,7 @@
 		break;
 	case GLSLstd450PackSnorm2x16:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, (SIMD::Int(Round(Min(Max(val.Float(0), SIMD::Float(-1.0f)), SIMD::Float(1.0f)) * SIMD::Float(32767.0f))) &
 			             SIMD::Int(0xFFFF)) |
 			                ((SIMD::Int(Round(Min(Max(val.Float(1), SIMD::Float(-1.0f)), SIMD::Float(1.0f)) * SIMD::Float(32767.0f))) &
@@ -418,7 +418,7 @@
 		break;
 	case GLSLstd450PackUnorm2x16:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, (SIMD::UInt(Round(Min(Max(val.Float(0), SIMD::Float(0.0f)), SIMD::Float(1.0f)) * SIMD::Float(65535.0f))) &
 			             SIMD::UInt(0xFFFF)) |
 			                ((SIMD::UInt(Round(Min(Max(val.Float(1), SIMD::Float(0.0f)), SIMD::Float(1.0f)) * SIMD::Float(65535.0f))) &
@@ -428,13 +428,13 @@
 		break;
 	case GLSLstd450PackHalf2x16:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, floatToHalfBits(val.UInt(0), false) | floatToHalfBits(val.UInt(1), true));
 		}
 		break;
 	case GLSLstd450UnpackSnorm4x8:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, Min(Max(SIMD::Float(((val.Int(0) << 24) & SIMD::Int(0xFF000000))) * SIMD::Float(1.0f / float(0x7f000000)), SIMD::Float(-1.0f)), SIMD::Float(1.0f)));
 			dst.move(1, Min(Max(SIMD::Float(((val.Int(0) << 16) & SIMD::Int(0xFF000000))) * SIMD::Float(1.0f / float(0x7f000000)), SIMD::Float(-1.0f)), SIMD::Float(1.0f)));
 			dst.move(2, Min(Max(SIMD::Float(((val.Int(0) << 8) & SIMD::Int(0xFF000000))) * SIMD::Float(1.0f / float(0x7f000000)), SIMD::Float(-1.0f)), SIMD::Float(1.0f)));
@@ -443,7 +443,7 @@
 		break;
 	case GLSLstd450UnpackUnorm4x8:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, SIMD::Float((val.UInt(0) & SIMD::UInt(0xFF))) * SIMD::Float(1.0f / 255.f));
 			dst.move(1, SIMD::Float(((val.UInt(0) >> 8) & SIMD::UInt(0xFF))) * SIMD::Float(1.0f / 255.f));
 			dst.move(2, SIMD::Float(((val.UInt(0) >> 16) & SIMD::UInt(0xFF))) * SIMD::Float(1.0f / 255.f));
@@ -452,7 +452,7 @@
 		break;
 	case GLSLstd450UnpackSnorm2x16:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			// clamp(f / 32767.0, -1.0, 1.0)
 			dst.move(0, Min(Max(SIMD::Float(As<SIMD::Int>((val.UInt(0) & SIMD::UInt(0x0000FFFF)) << 16)) *
 			                        SIMD::Float(1.0f / float(0x7FFF0000)),
@@ -465,7 +465,7 @@
 		break;
 	case GLSLstd450UnpackUnorm2x16:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			// f / 65535.0
 			dst.move(0, SIMD::Float((val.UInt(0) & SIMD::UInt(0x0000FFFF)) << 16) * SIMD::Float(1.0f / float(0xFFFF0000)));
 			dst.move(1, SIMD::Float(val.UInt(0) & SIMD::UInt(0xFFFF0000)) * SIMD::Float(1.0f / float(0xFFFF0000)));
@@ -473,16 +473,16 @@
 		break;
 	case GLSLstd450UnpackHalf2x16:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			dst.move(0, halfToFloatBits(val.UInt(0) & SIMD::UInt(0x0000FFFF)));
 			dst.move(1, halfToFloatBits((val.UInt(0) & SIMD::UInt(0xFFFF0000)) >> 16));
 		}
 		break;
 	case GLSLstd450Fma:
 		{
-			auto a = Operand(this, state, insn.word(5));
-			auto b = Operand(this, state, insn.word(6));
-			auto c = Operand(this, state, insn.word(7));
+			auto a = Operand(shader, *this, insn.word(5));
+			auto b = Operand(shader, *this, insn.word(6));
+			auto c = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, MulAdd(a.Float(i), b.Float(i), c.Float(i)));
@@ -491,7 +491,7 @@
 		break;
 	case GLSLstd450Frexp:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			auto ptrId = Object::ID(insn.word(6));
 
 			Intermediate exp(type.componentCount);
@@ -503,12 +503,12 @@
 				exp.move(i, significandAndExponent.second);
 			}
 
-			Store(ptrId, exp, false, std::memory_order_relaxed, state);
+			Store(ptrId, exp, false, std::memory_order_relaxed);
 		}
 		break;
 	case GLSLstd450FrexpStruct:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 
 			for(auto i = 0u; i < val.componentCount; i++)
 			{
@@ -520,8 +520,8 @@
 		break;
 	case GLSLstd450Ldexp:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto exp = Operand(this, state, insn.word(6));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto exp = Operand(shader, *this, insn.word(6));
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -531,7 +531,7 @@
 		break;
 	case GLSLstd450Radians:
 		{
-			auto degrees = Operand(this, state, insn.word(5));
+			auto degrees = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, degrees.Float(i) * SIMD::Float(PI / 180.0f));
@@ -540,7 +540,7 @@
 		break;
 	case GLSLstd450Degrees:
 		{
-			auto radians = Operand(this, state, insn.word(5));
+			auto radians = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, radians.Float(i) * SIMD::Float(180.0f / PI));
@@ -549,8 +549,8 @@
 		break;
 	case GLSLstd450Sin:
 		{
-			auto radians = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto radians = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -560,8 +560,8 @@
 		break;
 	case GLSLstd450Cos:
 		{
-			auto radians = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto radians = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -571,8 +571,8 @@
 		break;
 	case GLSLstd450Tan:
 		{
-			auto radians = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto radians = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -582,8 +582,8 @@
 		break;
 	case GLSLstd450Asin:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -593,8 +593,8 @@
 		break;
 	case GLSLstd450Acos:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -604,8 +604,8 @@
 		break;
 	case GLSLstd450Atan:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -615,8 +615,8 @@
 		break;
 	case GLSLstd450Sinh:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -626,8 +626,8 @@
 		break;
 	case GLSLstd450Cosh:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -637,8 +637,8 @@
 		break;
 	case GLSLstd450Tanh:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -648,8 +648,8 @@
 		break;
 	case GLSLstd450Asinh:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -659,8 +659,8 @@
 		break;
 	case GLSLstd450Acosh:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -670,8 +670,8 @@
 		break;
 	case GLSLstd450Atanh:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -681,9 +681,9 @@
 		break;
 	case GLSLstd450Atan2:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto y = Operand(this, state, insn.word(6));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto x = Operand(shader, *this, insn.word(5));
+			auto y = Operand(shader, *this, insn.word(6));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -693,9 +693,9 @@
 		break;
 	case GLSLstd450Pow:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto y = Operand(this, state, insn.word(6));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto x = Operand(shader, *this, insn.word(5));
+			auto y = Operand(shader, *this, insn.word(6));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -705,8 +705,8 @@
 		break;
 	case GLSLstd450Exp:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -716,8 +716,8 @@
 		break;
 	case GLSLstd450Log:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -727,8 +727,8 @@
 		break;
 	case GLSLstd450Exp2:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -738,8 +738,8 @@
 		break;
 	case GLSLstd450Log2:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -749,8 +749,8 @@
 		break;
 	case GLSLstd450Sqrt:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -760,8 +760,8 @@
 		break;
 	case GLSLstd450InverseSqrt:
 		{
-			auto val = Operand(this, state, insn.word(5));
-			Decorations d = GetDecorationsForId(insn.resultId());
+			auto val = Operand(shader, *this, insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.resultId());
 
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -771,7 +771,7 @@
 		break;
 	case GLSLstd450Determinant:
 		{
-			auto mat = Operand(this, state, insn.word(5));
+			auto mat = Operand(shader, *this, insn.word(5));
 
 			switch(mat.componentCount)
 			{
@@ -800,7 +800,7 @@
 		break;
 	case GLSLstd450MatrixInverse:
 		{
-			auto mat = Operand(this, state, insn.word(5));
+			auto mat = Operand(shader, *this, insn.word(5));
 
 			switch(mat.componentCount)
 			{
@@ -862,7 +862,7 @@
 		break;
 	case GLSLstd450FindILsb:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto v = val.UInt(i);
@@ -872,7 +872,7 @@
 		break;
 	case GLSLstd450FindSMsb:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto v = val.UInt(i) ^ As<SIMD::UInt>(CmpLT(val.Int(i), SIMD::Int(0)));
@@ -882,7 +882,7 @@
 		break;
 	case GLSLstd450FindUMsb:
 		{
-			auto val = Operand(this, state, insn.word(5));
+			auto val = Operand(shader, *this, insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, SIMD::UInt(31) - Ctlz(val.UInt(i), false));
@@ -891,38 +891,38 @@
 		break;
 	case GLSLstd450InterpolateAtCentroid:
 		{
-			Decorations d = GetDecorationsForId(insn.word(5));
-			auto ptr = state->getPointer(insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.word(5));
+			auto ptr = getPointer(insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
-				dst.move(i, EmitInterpolate(ptr, d.Location, 0, i, state, SpirvShader::Centroid));
+				dst.move(i, EmitInterpolate(ptr, d.Location, 0, i, Centroid));
 			}
 		}
 		break;
 	case GLSLstd450InterpolateAtSample:
 		{
-			Decorations d = GetDecorationsForId(insn.word(5));
-			auto ptr = state->getPointer(insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.word(5));
+			auto ptr = getPointer(insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
-				dst.move(i, EmitInterpolate(ptr, d.Location, insn.word(6), i, state, SpirvShader::AtSample));
+				dst.move(i, EmitInterpolate(ptr, d.Location, insn.word(6), i, AtSample));
 			}
 		}
 		break;
 	case GLSLstd450InterpolateAtOffset:
 		{
-			Decorations d = GetDecorationsForId(insn.word(5));
-			auto ptr = state->getPointer(insn.word(5));
+			Decorations d = shader.GetDecorationsForId(insn.word(5));
+			auto ptr = getPointer(insn.word(5));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
-				dst.move(i, EmitInterpolate(ptr, d.Location, insn.word(6), i, state, SpirvShader::AtOffset));
+				dst.move(i, EmitInterpolate(ptr, d.Location, insn.word(6), i, AtOffset));
 			}
 		}
 		break;
 	case GLSLstd450NMin:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto y = Operand(this, state, insn.word(6));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto y = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, NMin(x.Float(i), y.Float(i)));
@@ -931,8 +931,8 @@
 		break;
 	case GLSLstd450NMax:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto y = Operand(this, state, insn.word(6));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto y = Operand(shader, *this, insn.word(6));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				dst.move(i, NMax(x.Float(i), y.Float(i)));
@@ -941,9 +941,9 @@
 		break;
 	case GLSLstd450NClamp:
 		{
-			auto x = Operand(this, state, insn.word(5));
-			auto minVal = Operand(this, state, insn.word(6));
-			auto maxVal = Operand(this, state, insn.word(7));
+			auto x = Operand(shader, *this, insn.word(5));
+			auto minVal = Operand(shader, *this, insn.word(6));
+			auto maxVal = Operand(shader, *this, insn.word(7));
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				auto clamp = NMin(NMax(x.Float(i), minVal.Float(i)), maxVal.Float(i));
@@ -978,23 +978,23 @@
 	return interpolant;
 }
 
-SIMD::Float SpirvShader::EmitInterpolate(const SIMD::Pointer &ptr, int32_t location, Object::ID paramId,
-                                         uint32_t component, EmitState *state, InterpolationType type) const
+SIMD::Float SpirvShader::EmitState::EmitInterpolate(const SIMD::Pointer &ptr, int32_t location, Object::ID paramId,
+                                                    uint32_t component, InterpolationType type) const
 {
 	uint32_t interpolant = (location * 4);
-	uint32_t components_per_row = GetNumInputComponents(location);
-	if((location < 0) || (interpolant >= inputs.size()) || (components_per_row == 0))
+	uint32_t components_per_row = shader.GetNumInputComponents(location);
+	if((location < 0) || (interpolant >= shader.inputs.size()) || (components_per_row == 0))
 	{
 		return SIMD::Float(0.0f);
 	}
 
-	const auto &interpolationData = state->routine->interpolationData;
+	const auto &interpolationData = routine->interpolationData;
 
 	SIMD::Float x;
 	SIMD::Float y;
 	SIMD::Float rhw;
 
-	bool multisample = (state->getMultiSampleCount() > 1);
+	bool multisample = (getMultiSampleCount() > 1);
 	switch(type)
 	{
 	case Centroid:
@@ -1018,9 +1018,9 @@
 		if(multisample)
 		{
 			static constexpr int NUM_SAMPLES = 4;
-			ASSERT(state->getMultiSampleCount() == NUM_SAMPLES);
+			ASSERT(getMultiSampleCount() == NUM_SAMPLES);
 
-			auto sampleOperand = Operand(this, state, paramId);
+			auto sampleOperand = Operand(shader, *this, paramId);
 			ASSERT(sampleOperand.componentCount == 1);
 
 			// If sample does not exist, the position used to interpolate the
@@ -1030,8 +1030,8 @@
 			for(int i = 0; i < SIMD::Width; i++)
 			{
 				Int sample = Extract(samples, i);
-				x = Insert(x, *Pointer<Float>(state->routine->constants + OFFSET(Constants, SampleLocationsX) + sample * sizeof(float)), i);
-				y = Insert(y, *Pointer<Float>(state->routine->constants + OFFSET(Constants, SampleLocationsY) + sample * sizeof(float)), i);
+				x = Insert(x, *Pointer<Float>(routine->constants + OFFSET(Constants, SampleLocationsX) + sample * sizeof(float)), i);
+				y = Insert(y, *Pointer<Float>(routine->constants + OFFSET(Constants, SampleLocationsY) + sample * sizeof(float)), i);
 			}
 		}
 
@@ -1042,7 +1042,7 @@
 	case AtOffset:
 		{
 			//  An offset of (0, 0) identifies the center of the pixel.
-			auto offset = Operand(this, state, paramId);
+			auto offset = Operand(shader, *this, paramId);
 			ASSERT(offset.componentCount == 2);
 
 			x = interpolationData.x + offset.Float(0);
@@ -1055,7 +1055,7 @@
 		return SIMD::Float(0.0f);
 	}
 
-	uint32_t packedInterpolant = GetPackedInterpolant(location);
+	uint32_t packedInterpolant = shader.GetPackedInterpolant(location);
 	Pointer<Byte> planeEquation = interpolationData.primitive + OFFSET(Primitive, V[packedInterpolant]);
 
 	// The pointer's offsets index into the input variable array, which are SIMD::Float vectors.
@@ -1078,21 +1078,21 @@
 			C = Insert(C, *Pointer<Float>(planeEquationI + OFFSET(PlaneEquation, C)), i);
 		}
 
-		return Interpolate(x, y, rhw, A, B, C, state->routine->inputsInterpolation[packedInterpolant]);
+		return Interpolate(x, y, rhw, A, B, C, routine->inputsInterpolation[packedInterpolant]);
 	}
 	else
 	{
 		ASSERT(ptr.hasStaticEqualOffsets());
 
 		uint32_t offset = (ptr.staticOffsets[0] >> offsetShift) + component;
-		if((interpolant + offset) >= inputs.size())
+		if((interpolant + offset) >= shader.inputs.size())
 		{
 			return SIMD::Float(0.0f);
 		}
 		planeEquation += offset * sizeof(PlaneEquation);
 	}
 
-	return SpirvRoutine::interpolateAtXY(x, y, rhw, planeEquation, state->routine->inputsInterpolation[packedInterpolant]);
+	return SpirvRoutine::interpolateAtXY(x, y, rhw, planeEquation, routine->inputsInterpolation[packedInterpolant]);
 }
 
 SIMD::Float SpirvRoutine::interpolateAtXY(const SIMD::Float &x, const SIMD::Float &y, const SIMD::Float &rhw, Pointer<Byte> planeEquation, Interpolation interpolation)
diff --git a/src/Pipeline/SpirvShaderGroup.cpp b/src/Pipeline/SpirvShaderGroup.cpp
index a081437..86687c8 100644
--- a/src/Pipeline/SpirvShaderGroup.cpp
+++ b/src/Pipeline/SpirvShaderGroup.cpp
@@ -64,16 +64,16 @@
 	}
 }
 
-SpirvShader::EmitResult SpirvShader::EmitGroupNonUniform(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitGroupNonUniform(InsnIterator insn)
 {
 	ASSERT(SIMD::Width == 4);  // EmitGroupNonUniform makes many assumptions that the SIMD vector width is 4
 
-	auto &type = getType(Type::ID(insn.word(1)));
+	auto &type = shader.getType(Type::ID(insn.word(1)));
 	Object::ID resultId = insn.word(2);
-	auto scope = spv::Scope(GetConstScalarInt(insn.word(3)));
+	auto scope = spv::Scope(shader.GetConstScalarInt(insn.word(3)));
 	ASSERT_MSG(scope == spv::ScopeSubgroup, "Scope for Non Uniform Group Operations must be Subgroup for Vulkan 1.1");
 
-	auto &dst = state->createIntermediate(resultId, type.componentCount);
+	auto &dst = createIntermediate(resultId, type.componentCount);
 
 	switch(insn.opcode())
 	{
@@ -81,7 +81,7 @@
 		{
 			// Result is true only in the active invocation with the lowest id
 			// in the group, otherwise result is false.
-			SIMD::Int active = state->activeLaneMask();  // Considers helper invocations active. See b/151137030
+			SIMD::Int active = activeLaneMask();  // Considers helper invocations active. See b/151137030
 			// TODO: Would be nice if we could write this as:
 			//   elect = active & ~(active.Oxyz | active.OOxy | active.OOOx)
 			auto v0111 = SIMD::Int(0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
@@ -92,23 +92,23 @@
 
 	case spv::OpGroupNonUniformAll:
 		{
-			Operand predicate(this, state, insn.word(4));
-			dst.move(0, AndAll(predicate.UInt(0) | ~As<SIMD::UInt>(state->activeLaneMask())));  // Considers helper invocations active. See b/151137030
+			Operand predicate(shader, *this, insn.word(4));
+			dst.move(0, AndAll(predicate.UInt(0) | ~As<SIMD::UInt>(activeLaneMask())));  // Considers helper invocations active. See b/151137030
 		}
 		break;
 
 	case spv::OpGroupNonUniformAny:
 		{
-			Operand predicate(this, state, insn.word(4));
-			dst.move(0, OrAll(predicate.UInt(0) & As<SIMD::UInt>(state->activeLaneMask())));  // Considers helper invocations active. See b/151137030
+			Operand predicate(shader, *this, insn.word(4));
+			dst.move(0, OrAll(predicate.UInt(0) & As<SIMD::UInt>(activeLaneMask())));  // Considers helper invocations active. See b/151137030
 		}
 		break;
 
 	case spv::OpGroupNonUniformAllEqual:
 		{
-			Operand value(this, state, insn.word(4));
+			Operand value(shader, *this, insn.word(4));
 			auto res = SIMD::UInt(0xffffffff);
-			SIMD::UInt active = As<SIMD::UInt>(state->activeLaneMask());  // Considers helper invocations active. See b/151137030
+			SIMD::UInt active = As<SIMD::UInt>(activeLaneMask());  // Considers helper invocations active. See b/151137030
 			SIMD::UInt inactive = ~active;
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
@@ -128,13 +128,13 @@
 		{
 			auto valueId = Object::ID(insn.word(4));
 			auto idId = Object::ID(insn.word(5));
-			Operand value(this, state, valueId);
+			Operand value(shader, *this, valueId);
 
 			// Decide between the fast path for constants and the slow path for
 			// intermediates.
-			if(getObject(idId).kind == SpirvShader::Object::Kind::Constant)
+			if(shader.getObject(idId).kind == SpirvShader::Object::Kind::Constant)
 			{
-				auto id = SIMD::Int(GetConstScalarInt(insn.word(5)));
+				auto id = SIMD::Int(shader.GetConstScalarInt(insn.word(5)));
 				auto mask = CmpEQ(id, SIMD::Int(0, 1, 2, 3));
 				for(auto i = 0u; i < type.componentCount; i++)
 				{
@@ -143,9 +143,9 @@
 			}
 			else
 			{
-				Operand id(this, state, idId);
+				Operand id(shader, *this, idId);
 
-				SIMD::UInt active = As<SIMD::UInt>(state->activeLaneMask());  // Considers helper invocations active. See b/151137030
+				SIMD::UInt active = As<SIMD::UInt>(activeLaneMask());  // Considers helper invocations active. See b/151137030
 				SIMD::UInt inactive = ~active;
 				SIMD::UInt filled = id.UInt(0) & active;
 
@@ -167,10 +167,10 @@
 	case spv::OpGroupNonUniformBroadcastFirst:
 		{
 			auto valueId = Object::ID(insn.word(4));
-			Operand value(this, state, valueId);
+			Operand value(shader, *this, valueId);
 			// Result is true only in the active invocation with the lowest id
 			// in the group, otherwise result is false.
-			SIMD::Int active = state->activeLaneMask();  // Considers helper invocations active. See b/151137030
+			SIMD::Int active = activeLaneMask();  // Considers helper invocations active. See b/151137030
 			// TODO: Would be nice if we could write this as:
 			//   elect = active & ~(active.Oxyz | active.OOxy | active.OOOx)
 			auto v0111 = SIMD::Int(0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
@@ -185,13 +185,13 @@
 	case spv::OpGroupNonUniformQuadBroadcast:
 		{
 			auto valueId = Object::ID(insn.word(4));
-			Operand value(this, state, valueId);
+			Operand value(shader, *this, valueId);
 
-			ASSERT(getType(getObject(insn.word(5))).componentCount == 1);
+			ASSERT(shader.getType(shader.getObject(insn.word(5))).componentCount == 1);
 			auto indexId = Object::ID(insn.word(5));
-			SIMD::Int index = Operand(this, state, indexId).Int(0);
+			SIMD::Int index = Operand(shader, *this, indexId).Int(0);
 
-			SIMD::Int active = state->activeLaneMask();
+			SIMD::Int active = activeLaneMask();
 			// Populate all lanes in index with the same value. Index is required to be
 			// uniform per the SPIR-V spec, so all active lanes should be identical.
 			index = OrAll(active & index);
@@ -208,9 +208,9 @@
 		{
 			auto valueId = Object::ID(insn.word(4));
 			// SPIR-V spec: Drection must be a scalar of integer type and come from a constant instruction
-			int direction = GetConstScalarInt(insn.word(5));
+			int direction = shader.GetConstScalarInt(insn.word(5));
 
-			Operand value(this, state, valueId);
+			Operand value(shader, *this, valueId);
 			for(auto i = 0u; i < type.componentCount; i++)
 			{
 				SIMD::Int v = value.Int(i);
@@ -238,8 +238,8 @@
 	case spv::OpGroupNonUniformBallot:
 		{
 			ASSERT(type.componentCount == 4);
-			Operand predicate(this, state, insn.word(4));
-			dst.move(0, SIMD::Int(SignMask(state->activeLaneMask() & predicate.Int(0))));  // Considers helper invocations active. See b/151137030
+			Operand predicate(shader, *this, insn.word(4));
+			dst.move(0, SIMD::Int(SignMask(activeLaneMask() & predicate.Int(0))));  // Considers helper invocations active. See b/151137030
 			dst.move(1, SIMD::Int(0));
 			dst.move(2, SIMD::Int(0));
 			dst.move(3, SIMD::Int(0));
@@ -250,8 +250,8 @@
 		{
 			auto valueId = Object::ID(insn.word(4));
 			ASSERT(type.componentCount == 1);
-			ASSERT(getObjectType(valueId).componentCount == 4);
-			Operand value(this, state, valueId);
+			ASSERT(shader.getObjectType(valueId).componentCount == 4);
+			Operand value(shader, *this, valueId);
 			auto bit = (value.Int(0) >> SIMD::Int(0, 1, 2, 3)) & SIMD::Int(1);
 			dst.move(0, -bit);
 		}
@@ -262,10 +262,10 @@
 			auto valueId = Object::ID(insn.word(4));
 			auto indexId = Object::ID(insn.word(5));
 			ASSERT(type.componentCount == 1);
-			ASSERT(getObjectType(valueId).componentCount == 4);
-			ASSERT(getObjectType(indexId).componentCount == 1);
-			Operand value(this, state, valueId);
-			Operand index(this, state, indexId);
+			ASSERT(shader.getObjectType(valueId).componentCount == 4);
+			ASSERT(shader.getObjectType(indexId).componentCount == 1);
+			Operand value(shader, *this, valueId);
+			Operand index(shader, *this, indexId);
 			auto vecIdx = index.Int(0) / SIMD::Int(32);
 			auto bitIdx = index.Int(0) & SIMD::Int(31);
 			auto bits = (value.Int(0) & CmpEQ(vecIdx, SIMD::Int(0))) |
@@ -281,8 +281,8 @@
 			auto operation = spv::GroupOperation(insn.word(4));
 			auto valueId = Object::ID(insn.word(5));
 			ASSERT(type.componentCount == 1);
-			ASSERT(getObjectType(valueId).componentCount == 4);
-			Operand value(this, state, valueId);
+			ASSERT(shader.getObjectType(valueId).componentCount == 4);
+			Operand value(shader, *this, valueId);
 			switch(operation)
 			{
 			case spv::GroupOperationReduce:
@@ -304,8 +304,8 @@
 		{
 			auto valueId = Object::ID(insn.word(4));
 			ASSERT(type.componentCount == 1);
-			ASSERT(getObjectType(valueId).componentCount == 4);
-			Operand value(this, state, valueId);
+			ASSERT(shader.getObjectType(valueId).componentCount == 4);
+			Operand value(shader, *this, valueId);
 			dst.move(0, Cttz(value.UInt(0) & SIMD::UInt(15), true));
 		}
 		break;
@@ -314,16 +314,16 @@
 		{
 			auto valueId = Object::ID(insn.word(4));
 			ASSERT(type.componentCount == 1);
-			ASSERT(getObjectType(valueId).componentCount == 4);
-			Operand value(this, state, valueId);
+			ASSERT(shader.getObjectType(valueId).componentCount == 4);
+			Operand value(shader, *this, valueId);
 			dst.move(0, SIMD::UInt(31) - Ctlz(value.UInt(0) & SIMD::UInt(15), false));
 		}
 		break;
 
 	case spv::OpGroupNonUniformShuffle:
 		{
-			Operand value(this, state, insn.word(4));
-			Operand id(this, state, insn.word(5));
+			Operand value(shader, *this, insn.word(4));
+			Operand id(shader, *this, insn.word(5));
 			auto x = CmpEQ(SIMD::Int(0), id.Int(0));
 			auto y = CmpEQ(SIMD::Int(1), id.Int(0));
 			auto z = CmpEQ(SIMD::Int(2), id.Int(0));
@@ -338,8 +338,8 @@
 
 	case spv::OpGroupNonUniformShuffleXor:
 		{
-			Operand value(this, state, insn.word(4));
-			Operand mask(this, state, insn.word(5));
+			Operand value(shader, *this, insn.word(4));
+			Operand mask(shader, *this, insn.word(5));
 			auto x = CmpEQ(SIMD::Int(0), SIMD::Int(0, 1, 2, 3) ^ mask.Int(0));
 			auto y = CmpEQ(SIMD::Int(1), SIMD::Int(0, 1, 2, 3) ^ mask.Int(0));
 			auto z = CmpEQ(SIMD::Int(2), SIMD::Int(0, 1, 2, 3) ^ mask.Int(0));
@@ -354,8 +354,8 @@
 
 	case spv::OpGroupNonUniformShuffleUp:
 		{
-			Operand value(this, state, insn.word(4));
-			Operand delta(this, state, insn.word(5));
+			Operand value(shader, *this, insn.word(4));
+			Operand delta(shader, *this, insn.word(5));
 			auto d0 = CmpEQ(SIMD::Int(0), delta.Int(0));
 			auto d1 = CmpEQ(SIMD::Int(1), delta.Int(0));
 			auto d2 = CmpEQ(SIMD::Int(2), delta.Int(0));
@@ -370,8 +370,8 @@
 
 	case spv::OpGroupNonUniformShuffleDown:
 		{
-			Operand value(this, state, insn.word(4));
-			Operand delta(this, state, insn.word(5));
+			Operand value(shader, *this, insn.word(4));
+			Operand delta(shader, *this, insn.word(5));
 			auto d0 = CmpEQ(SIMD::Int(0), delta.Int(0));
 			auto d1 = CmpEQ(SIMD::Int(1), delta.Int(0));
 			auto d2 = CmpEQ(SIMD::Int(2), delta.Int(0));
@@ -386,10 +386,10 @@
 
 	// The remaining instructions are GroupNonUniformArithmetic operations
 	default:
-		auto &type = getType(SpirvShader::Type::ID(insn.word(1)));
+		auto &type = shader.getType(SpirvShader::Type::ID(insn.word(1)));
 		auto operation = static_cast<spv::GroupOperation>(insn.word(4));
-		SpirvShader::Operand value(this, state, insn.word(5));
-		auto mask = As<SIMD::UInt>(state->activeLaneMask());  // Considers helper invocations active. See b/151137030
+		SpirvShader::Operand value(shader, *this, insn.word(5));
+		auto mask = As<SIMD::UInt>(activeLaneMask());  // Considers helper invocations active. See b/151137030
 
 		for(uint32_t i = 0; i < type.componentCount; i++)
 		{
diff --git a/src/Pipeline/SpirvShaderImage.cpp b/src/Pipeline/SpirvShaderImage.cpp
index 04e5873..ec9662f 100644
--- a/src/Pipeline/SpirvShaderImage.cpp
+++ b/src/Pipeline/SpirvShaderImage.cpp
@@ -74,7 +74,7 @@
 	}
 }
 
-SpirvShader::ImageInstruction::ImageInstruction(InsnIterator insn, const SpirvShader &spirv, EmitState *state)
+SpirvShader::ImageInstruction::ImageInstruction(InsnIterator insn, const SpirvShader &spirv, const EmitState &state)
     : ImageInstructionSignature(parseVariantAndMethod(insn))
     , position(insn.distanceFrom(spirv.begin()))
 {
@@ -99,9 +99,9 @@
 			// an externally combined sampler and image.
 			Object::ID sampledImageId = insn.word(3);
 
-			if(state->isSampledImage(sampledImageId))  // Result of an OpSampledImage instruction
+			if(state.isSampledImage(sampledImageId))  // Result of an OpSampledImage instruction
 			{
-				const SampledImagePointer &sampledImage = state->getSampledImage(sampledImageId);
+				const SampledImagePointer &sampledImage = state.getSampledImage(sampledImageId);
 				imageId = spirv.getObject(sampledImageId).definition.word(3);
 				samplerId = sampledImage.samplerId;
 			}
@@ -329,18 +329,18 @@
 	return (operandsIndex != 0) ? insn.word(operandsIndex) : 0;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageSample(const ImageInstruction &instruction, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageSample(const ImageInstruction &instruction)
 {
-	auto &resultType = getType(instruction.resultTypeId);
-	auto &result = state->createIntermediate(instruction.resultId, resultType.componentCount);
+	auto &resultType = shader.getType(instruction.resultTypeId);
+	auto &result = createIntermediate(instruction.resultId, resultType.componentCount);
 	Array<SIMD::Float> out(4);
 
 	// TODO(b/153380916): When we're in a code path that is always executed,
 	// i.e. post-dominators of the entry block, we don't have to dynamically
 	// check whether any lanes are active, and can elide the jump.
-	If(AnyTrue(state->activeLaneMask()))
+	If(AnyTrue(activeLaneMask()))
 	{
-		EmitImageSampleUnconditional(out, instruction, state);
+		EmitImageSampleUnconditional(out, instruction);
 	}
 
 	for(auto i = 0u; i < resultType.componentCount; i++) { result.move(i, out[i]); }
@@ -348,14 +348,14 @@
 	return EmitResult::Continue;
 }
 
-void SpirvShader::EmitImageSampleUnconditional(Array<SIMD::Float> &out, const ImageInstruction &instruction, EmitState *state) const
+void SpirvShader::EmitState::EmitImageSampleUnconditional(Array<SIMD::Float> &out, const ImageInstruction &instruction) const
 {
-	auto decorations = GetDecorationsForId(instruction.imageId);
+	auto decorations = shader.GetDecorationsForId(instruction.imageId);
 
 	if(decorations.NonUniform)
 	{
-		SIMD::Int activeLaneMask = state->activeLaneMask();
-		SIMD::Pointer imagePointer = state->getImage(instruction.imageId);
+		SIMD::Int activeLaneMask = this->activeLaneMask();
+		SIMD::Pointer imagePointer = getImage(instruction.imageId);
 		// PerLane output
 		for(int laneIdx = 0; laneIdx < SIMD::Width; laneIdx++)
 		{
@@ -363,11 +363,11 @@
 			If(Extract(activeLaneMask, laneIdx) != 0)
 			{
 				Pointer<Byte> imageDescriptor = imagePointer.getPointerForLane(laneIdx);  // vk::SampledImageDescriptor*
-				Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction, laneIdx, state);
+				Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction, laneIdx);
 
-				Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction, state);
+				Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction);
 
-				callSamplerFunction(samplerFunction, laneOut, imageDescriptor, instruction, state);
+				callSamplerFunction(samplerFunction, laneOut, imageDescriptor, instruction);
 			}
 
 			for(int outIdx = 0; outIdx < out.getArraySize(); outIdx++)
@@ -378,36 +378,36 @@
 	}
 	else
 	{
-		Pointer<Byte> imageDescriptor = state->getImage(instruction.imageId).getUniformPointer();  // vk::SampledImageDescriptor*
-		Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction, state);
+		Pointer<Byte> imageDescriptor = getImage(instruction.imageId).getUniformPointer();  // vk::SampledImageDescriptor*
+		Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction);
 
-		Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction, state);
+		Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction);
 
-		callSamplerFunction(samplerFunction, out, imageDescriptor, instruction, state);
+		callSamplerFunction(samplerFunction, out, imageDescriptor, instruction);
 	}
 }
 
-Pointer<Byte> SpirvShader::getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, EmitState *state) const
+Pointer<Byte> SpirvShader::EmitState::getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction) const
 {
-	return ((instruction.samplerId == instruction.imageId) || (instruction.samplerId == 0)) ? imageDescriptor : state->getImage(instruction.samplerId).getUniformPointer();
+	return ((instruction.samplerId == instruction.imageId) || (instruction.samplerId == 0)) ? imageDescriptor : getImage(instruction.samplerId).getUniformPointer();
 }
 
-Pointer<Byte> SpirvShader::getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, int laneIdx, EmitState *state) const
+Pointer<Byte> SpirvShader::EmitState::getSamplerDescriptor(Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, int laneIdx) const
 {
-	return ((instruction.samplerId == instruction.imageId) || (instruction.samplerId == 0)) ? imageDescriptor : state->getImage(instruction.samplerId).getPointerForLane(laneIdx);
+	return ((instruction.samplerId == instruction.imageId) || (instruction.samplerId == 0)) ? imageDescriptor : getImage(instruction.samplerId).getPointerForLane(laneIdx);
 }
 
-Pointer<Byte> SpirvShader::lookupSamplerFunction(Pointer<Byte> imageDescriptor, Pointer<Byte> samplerDescriptor, const ImageInstruction &instruction, EmitState *state) const
+Pointer<Byte> SpirvShader::EmitState::lookupSamplerFunction(Pointer<Byte> imageDescriptor, Pointer<Byte> samplerDescriptor, const ImageInstruction &instruction) const
 {
 	Int samplerId = (instruction.samplerId != 0) ? *Pointer<rr::Int>(samplerDescriptor + OFFSET(vk::SampledImageDescriptor, samplerId)) : Int(0);
 
-	auto &cache = state->routine->samplerCache.at(instruction.position);
+	auto &cache = routine->samplerCache.at(instruction.position);
 	Bool cacheHit = (cache.imageDescriptor == imageDescriptor) && (cache.samplerId == samplerId);  // TODO(b/205566405): Skip sampler ID check for samplerless instructions.
 
 	If(!cacheHit)
 	{
 		rr::Int imageViewId = *Pointer<rr::Int>(imageDescriptor + OFFSET(vk::ImageDescriptor, imageViewId));
-		cache.function = Call(getImageSampler, state->routine->device, instruction.signature, samplerId, imageViewId);
+		cache.function = Call(getImageSampler, routine->device, instruction.signature, samplerId, imageViewId);
 		cache.imageDescriptor = imageDescriptor;
 		cache.samplerId = samplerId;
 	}
@@ -415,11 +415,11 @@
 	return cache.function;
 }
 
-void SpirvShader::callSamplerFunction(Pointer<Byte> samplerFunction, Array<SIMD::Float> &out, Pointer<Byte> imageDescriptor, const ImageInstruction &instruction, EmitState *state) const
+void SpirvShader::EmitState::callSamplerFunction(Pointer<Byte> samplerFunction, Array<SIMD::Float> &out, Pointer<Byte> imageDescriptor, const ImageInstruction &instruction) const
 {
 	Array<SIMD::Float> in(16);  // Maximum 16 input parameter components.
 
-	auto coordinate = Operand(this, state, instruction.coordinateId);
+	auto coordinate = Operand(shader, *this, instruction.coordinateId);
 
 	uint32_t i = 0;
 	for(; i < instruction.coordinates; i++)
@@ -436,7 +436,7 @@
 
 	if(instruction.isDref())
 	{
-		auto drefValue = Operand(this, state, instruction.drefId);
+		auto drefValue = Operand(shader, *this, instruction.drefId);
 
 		if(instruction.isProj())
 		{
@@ -452,14 +452,14 @@
 
 	if(instruction.lodOrBiasId != 0)
 	{
-		auto lodValue = Operand(this, state, instruction.lodOrBiasId);
+		auto lodValue = Operand(shader, *this, instruction.lodOrBiasId);
 		in[i] = lodValue.Float(0);
 		i++;
 	}
 	else if(instruction.gradDxId != 0)
 	{
-		auto dxValue = Operand(this, state, instruction.gradDxId);
-		auto dyValue = Operand(this, state, instruction.gradDyId);
+		auto dxValue = Operand(shader, *this, instruction.gradDxId);
+		auto dyValue = Operand(shader, *this, instruction.gradDyId);
 		ASSERT(dxValue.componentCount == dxValue.componentCount);
 
 		for(uint32_t j = 0; j < dxValue.componentCount; j++, i++)
@@ -483,7 +483,7 @@
 
 	if(instruction.offsetId != 0)
 	{
-		auto offsetValue = Operand(this, state, instruction.offsetId);
+		auto offsetValue = Operand(shader, *this, instruction.offsetId);
 
 		for(uint32_t j = 0; j < offsetValue.componentCount; j++, i++)
 		{
@@ -493,53 +493,52 @@
 
 	if(instruction.sample)
 	{
-		auto sampleValue = Operand(this, state, instruction.sampleId);
+		auto sampleValue = Operand(shader, *this, instruction.sampleId);
 		in[i] = As<SIMD::Float>(sampleValue.Int(0));
 	}
 
 	Pointer<Byte> texture = imageDescriptor + OFFSET(vk::SampledImageDescriptor, texture);  // sw::Texture*
 
-	Call<ImageSampler>(samplerFunction, texture, &in, &out, state->routine->constants);
+	Call<ImageSampler>(samplerFunction, texture, &in, &out, routine->constants);
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageQuerySizeLod(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageQuerySizeLod(InsnIterator insn)
 {
-	auto &resultTy = getType(insn.resultTypeId());
+	auto &resultTy = shader.getType(insn.resultTypeId());
 	auto imageId = Object::ID(insn.word(3));
 	auto lodId = Object::ID(insn.word(4));
 
-	auto &dst = state->createIntermediate(insn.resultId(), resultTy.componentCount);
-	GetImageDimensions(state, resultTy, imageId, lodId, dst);
+	auto &dst = createIntermediate(insn.resultId(), resultTy.componentCount);
+	GetImageDimensions(resultTy, imageId, lodId, dst);
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageQuerySize(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageQuerySize(InsnIterator insn)
 {
-	auto &resultTy = getType(insn.resultTypeId());
+	auto &resultTy = shader.getType(insn.resultTypeId());
 	auto imageId = Object::ID(insn.word(3));
 	auto lodId = Object::ID(0);
 
-	auto &dst = state->createIntermediate(insn.resultId(), resultTy.componentCount);
-	GetImageDimensions(state, resultTy, imageId, lodId, dst);
+	auto &dst = createIntermediate(insn.resultId(), resultTy.componentCount);
+	GetImageDimensions(resultTy, imageId, lodId, dst);
 
 	return EmitResult::Continue;
 }
 
-void SpirvShader::GetImageDimensions(const EmitState *state, const Type &resultTy, Object::ID imageId, Object::ID lodId, Intermediate &dst) const
+void SpirvShader::EmitState::GetImageDimensions(const Type &resultTy, Object::ID imageId, Object::ID lodId, Intermediate &dst) const
 {
-	auto routine = state->routine;
-	auto &image = getObject(imageId);
-	auto &imageType = getType(image);
+	auto &image = shader.getObject(imageId);
+	auto &imageType = shader.getType(image);
 
 	ASSERT(imageType.definition.opcode() == spv::OpTypeImage);
 	bool isArrayed = imageType.definition.word(5) != 0;
 	uint32_t dimensions = resultTy.componentCount - (isArrayed ? 1 : 0);
 
-	const DescriptorDecorations &d = descriptorDecorations.at(imageId);
+	const DescriptorDecorations &d = shader.descriptorDecorations.at(imageId);
 	auto descriptorType = routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 
-	Pointer<Byte> descriptor = state->getPointer(imageId).getUniformPointer();
+	Pointer<Byte> descriptor = getPointer(imageId).getUniformPointer();
 
 	Int width;
 	Int height;
@@ -566,7 +565,7 @@
 
 	if(lodId != 0)
 	{
-		auto lodVal = Operand(this, state, lodId);
+		auto lodVal = Operand(shader, *this, lodId);
 		ASSERT(lodVal.componentCount == 1);
 		auto lod = lodVal.Int(0);
 		auto one = SIMD::Int(1);
@@ -589,16 +588,16 @@
 	}
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageQueryLevels(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageQueryLevels(InsnIterator insn)
 {
-	auto &resultTy = getType(insn.resultTypeId());
+	auto &resultTy = shader.getType(insn.resultTypeId());
 	ASSERT(resultTy.componentCount == 1);
 	auto imageId = Object::ID(insn.word(3));
 
-	const DescriptorDecorations &d = descriptorDecorations.at(imageId);
-	auto descriptorType = state->routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
+	const DescriptorDecorations &d = shader.descriptorDecorations.at(imageId);
+	auto descriptorType = routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 
-	Pointer<Byte> descriptor = state->getPointer(imageId).getUniformPointer();
+	Pointer<Byte> descriptor = getPointer(imageId).getUniformPointer();
 	Int mipLevels = 0;
 	switch(descriptorType)
 	{
@@ -611,26 +610,26 @@
 		UNREACHABLE("Image descriptorType: %d", int(descriptorType));
 	}
 
-	auto &dst = state->createIntermediate(insn.resultId(), 1);
+	auto &dst = createIntermediate(insn.resultId(), 1);
 	dst.move(0, SIMD::Int(mipLevels));
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageQuerySamples(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageQuerySamples(InsnIterator insn)
 {
-	auto &resultTy = getType(insn.resultTypeId());
+	auto &resultTy = shader.getType(insn.resultTypeId());
 	ASSERT(resultTy.componentCount == 1);
 	auto imageId = Object::ID(insn.word(3));
-	auto imageTy = getObjectType(imageId);
+	auto imageTy = shader.getObjectType(imageId);
 	ASSERT(imageTy.definition.opcode() == spv::OpTypeImage);
 	ASSERT(imageTy.definition.word(3) == spv::Dim2D);
 	ASSERT(imageTy.definition.word(6 /* MS */) == 1);
 
-	const DescriptorDecorations &d = descriptorDecorations.at(imageId);
-	auto descriptorType = state->routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
+	const DescriptorDecorations &d = shader.descriptorDecorations.at(imageId);
+	auto descriptorType = routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 
-	Pointer<Byte> descriptor = state->getPointer(imageId).getUniformPointer();
+	Pointer<Byte> descriptor = getPointer(imageId).getUniformPointer();
 	Int sampleCount = 0;
 	switch(descriptorType)
 	{
@@ -646,13 +645,13 @@
 		UNREACHABLE("Image descriptorType: %d", int(descriptorType));
 	}
 
-	auto &dst = state->createIntermediate(insn.resultId(), 1);
+	auto &dst = createIntermediate(insn.resultId(), 1);
 	dst.move(0, SIMD::Int(sampleCount));
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::TexelAddressData SpirvShader::setupTexelAddressData(SIMD::Int rowPitch, SIMD::Int slicePitch, SIMD::Int samplePitch, ImageInstructionSignature instruction, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, const EmitState *state)
+SpirvShader::EmitState::TexelAddressData SpirvShader::EmitState::setupTexelAddressData(SIMD::Int rowPitch, SIMD::Int slicePitch, SIMD::Int samplePitch, ImageInstructionSignature instruction, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, const SpirvRoutine *routine)
 {
 	TexelAddressData data;
 
@@ -671,8 +670,8 @@
 
 	if(data.dim == spv::DimSubpassData)
 	{
-		data.u += state->routine->windowSpacePosition[0];
-		data.v += state->routine->windowSpacePosition[1];
+		data.u += routine->windowSpacePosition[0];
+		data.v += routine->windowSpacePosition[1];
 	}
 
 	data.ptrOffset = data.u * SIMD::Int(data.texelSize);
@@ -701,7 +700,7 @@
 	if(data.dim == spv::DimSubpassData)
 	{
 		// Multiview input attachment access is to the layer corresponding to the current view
-		data.ptrOffset += SIMD::Int(state->routine->layer) * slicePitch;
+		data.ptrOffset += SIMD::Int(routine->layer) * slicePitch;
 	}
 
 	if(instruction.sample)
@@ -712,48 +711,47 @@
 	return data;
 }
 
-SIMD::Pointer SpirvShader::GetNonUniformTexelAddress(ImageInstructionSignature instruction, SIMD::Pointer descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, const EmitState *state)
+SIMD::Pointer SpirvShader::EmitState::GetNonUniformTexelAddress(ImageInstructionSignature instruction, SIMD::Pointer descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, SIMD::Int activeLaneMask, const SpirvRoutine *routine)
 {
 	const bool useStencilAspect = (imageFormat == VK_FORMAT_S8_UINT);
 	auto rowPitch = (descriptor + (useStencilAspect
 	                                   ? OFFSET(vk::StorageImageDescriptor, stencilRowPitchBytes)
 	                                   : OFFSET(vk::StorageImageDescriptor, rowPitchBytes)))
-	                    .Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask());
-
+	                    .Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask);
 	auto slicePitch = (descriptor + (useStencilAspect
 	                                     ? OFFSET(vk::StorageImageDescriptor, stencilSlicePitchBytes)
 	                                     : OFFSET(vk::StorageImageDescriptor, slicePitchBytes)))
-	                      .Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask());
+	                      .Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask);
 	auto samplePitch = (descriptor + (useStencilAspect
 	                                      ? OFFSET(vk::StorageImageDescriptor, stencilSamplePitchBytes)
 	                                      : OFFSET(vk::StorageImageDescriptor, samplePitchBytes)))
-	                       .Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask());
+	                       .Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask);
 
-	auto texelData = setupTexelAddressData(rowPitch, slicePitch, samplePitch, instruction, coordinate, sample, imageFormat, state);
+	auto texelData = setupTexelAddressData(rowPitch, slicePitch, samplePitch, instruction, coordinate, sample, imageFormat, routine);
 
 	// If the out-of-bounds behavior is set to nullify, then each coordinate must be tested individually.
 	// Other out-of-bounds behaviors work properly by just comparing the offset against the total size.
 	if(outOfBoundsBehavior == OutOfBoundsBehavior::Nullify)
 	{
-		SIMD::UInt width = (descriptor + OFFSET(vk::StorageImageDescriptor, width)).Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask());
+		SIMD::UInt width = (descriptor + OFFSET(vk::StorageImageDescriptor, width)).Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask);
 		SIMD::Int oobMask = As<SIMD::Int>(CmpNLT(As<SIMD::UInt>(texelData.u), width));
 
 		if(texelData.dims > 1)
 		{
-			SIMD::UInt height = As<SIMD::UInt>((descriptor + OFFSET(vk::StorageImageDescriptor, height)).Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask()));
+			SIMD::UInt height = As<SIMD::UInt>((descriptor + OFFSET(vk::StorageImageDescriptor, height)).Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask));
 			oobMask |= As<SIMD::Int>(CmpNLT(As<SIMD::UInt>(texelData.v), height));
 		}
 
 		if((texelData.dims > 2) || texelData.isArrayed)
 		{
-			SIMD::UInt depth = As<SIMD::UInt>((descriptor + OFFSET(vk::StorageImageDescriptor, depth)).Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask()));
+			SIMD::UInt depth = As<SIMD::UInt>((descriptor + OFFSET(vk::StorageImageDescriptor, depth)).Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask));
 			if(texelData.dim == spv::DimCube) { depth *= 6; }
 			oobMask |= As<SIMD::Int>(CmpNLT(As<SIMD::UInt>(texelData.w), depth));
 		}
 
 		if(instruction.sample)
 		{
-			SIMD::UInt sampleCount = As<SIMD::UInt>((descriptor + OFFSET(vk::StorageImageDescriptor, sampleCount)).Load<SIMD::Int>(outOfBoundsBehavior, state->activeLaneMask()));
+			SIMD::UInt sampleCount = As<SIMD::UInt>((descriptor + OFFSET(vk::StorageImageDescriptor, sampleCount)).Load<SIMD::Int>(outOfBoundsBehavior, activeLaneMask));
 			oobMask |= As<SIMD::Int>(CmpNLT(As<SIMD::UInt>(sample), sampleCount));
 		}
 
@@ -774,7 +772,7 @@
 	return SIMD::Pointer(imageBase) + texelData.ptrOffset;
 }
 
-SIMD::Pointer SpirvShader::GetTexelAddress(ImageInstructionSignature instruction, Pointer<Byte> descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, const EmitState *state)
+SIMD::Pointer SpirvShader::EmitState::GetTexelAddress(ImageInstructionSignature instruction, Pointer<Byte> descriptor, SIMD::Int coordinate[], SIMD::Int sample, vk::Format imageFormat, OutOfBoundsBehavior outOfBoundsBehavior, const SpirvRoutine *routine)
 {
 	const bool useStencilAspect = (imageFormat == VK_FORMAT_S8_UINT);
 	auto rowPitch = SIMD::Int(*Pointer<Int>(descriptor + (useStencilAspect
@@ -789,7 +787,7 @@
 	                                    ? OFFSET(vk::StorageImageDescriptor, stencilSamplePitchBytes)
 	                                    : OFFSET(vk::StorageImageDescriptor, samplePitchBytes))));
 
-	auto texelData = setupTexelAddressData(rowPitch, slicePitch, samplePitch, instruction, coordinate, sample, imageFormat, state);
+	auto texelData = setupTexelAddressData(rowPitch, slicePitch, samplePitch, instruction, coordinate, sample, imageFormat, routine);
 
 	// If the out-of-bounds behavior is set to nullify, then each coordinate must be tested individually.
 	// Other out-of-bounds behaviors work properly by just comparing the offset against the total size.
@@ -832,36 +830,36 @@
 	return SIMD::Pointer(imageBase, imageSizeInBytes, texelData.ptrOffset);
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageRead(const ImageInstruction &instruction, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageRead(const ImageInstruction &instruction)
 {
-	auto &resultType = getObjectType(instruction.resultId);
-	auto &image = getObject(instruction.imageId);
-	auto &imageType = getType(image);
+	auto &resultType = shader.getObjectType(instruction.resultId);
+	auto &image = shader.getObject(instruction.imageId);
+	auto &imageType = shader.getType(image);
 
 	ASSERT(imageType.definition.opcode() == spv::OpTypeImage);
 	auto dim = static_cast<spv::Dim>(instruction.dim);
 
-	auto coordinate = Operand(this, state, instruction.coordinateId);
-	const DescriptorDecorations &d = descriptorDecorations.at(instruction.imageId);
+	auto coordinate = Operand(shader, *this, instruction.coordinateId);
+	const DescriptorDecorations &d = shader.descriptorDecorations.at(instruction.imageId);
 
 	// For subpass data, format in the instruction is spv::ImageFormatUnknown. Get it from
 	// the renderpass data instead. In all other cases, we can use the format in the instruction.
 	vk::Format imageFormat = (dim == spv::DimSubpassData)
-	                             ? inputAttachmentFormats[d.InputAttachmentIndex]
+	                             ? shader.inputAttachmentFormats[d.InputAttachmentIndex]
 	                             : SpirvFormatToVulkanFormat(static_cast<spv::ImageFormat>(instruction.imageFormat));
 
 	// Depth+Stencil image attachments select aspect based on the Sampled Type of the
 	// OpTypeImage. If float, then we want the depth aspect. If int, we want the stencil aspect.
 	bool useStencilAspect = (imageFormat == VK_FORMAT_D32_SFLOAT_S8_UINT &&
-	                         getType(imageType.definition.word(2)).opcode() == spv::OpTypeInt);
+	                         shader.getType(imageType.definition.word(2)).opcode() == spv::OpTypeInt);
 
 	if(useStencilAspect)
 	{
 		imageFormat = VK_FORMAT_S8_UINT;
 	}
 
-	auto &dst = state->createIntermediate(instruction.resultId, resultType.componentCount);
-	SIMD::Pointer ptr = state->getPointer(instruction.imageId);
+	auto &dst = createIntermediate(instruction.resultId, resultType.componentCount);
+	SIMD::Pointer ptr = getPointer(instruction.imageId);
 
 	SIMD::Int uvwa[4];
 	SIMD::Int sample;
@@ -876,7 +874,7 @@
 	}
 	if(instruction.sample)
 	{
-		sample = Operand(this, state, instruction.sampleId).Int(0);
+		sample = Operand(shader, *this, instruction.sampleId).Int(0);
 	}
 
 	// Gather packed texel data. Texels larger than 4 bytes occupy multiple SIMD::Int elements.
@@ -884,19 +882,19 @@
 	SIMD::Int packed[4];
 
 	SIMD::Pointer texelPtr = ptr.isBasePlusOffset
-	                             ? GetTexelAddress(instruction, ptr.getUniformPointer(), uvwa, sample, imageFormat, robustness, state)
-	                             : GetNonUniformTexelAddress(instruction, ptr, uvwa, sample, imageFormat, robustness, state);
+	                             ? GetTexelAddress(instruction, ptr.getUniformPointer(), uvwa, sample, imageFormat, robustness, routine)
+	                             : GetNonUniformTexelAddress(instruction, ptr, uvwa, sample, imageFormat, robustness, activeLaneMask(), routine);
 	if(texelSize == 4 || texelSize == 8 || texelSize == 16)
 	{
 		for(auto i = 0; i < texelSize / 4; i++)
 		{
-			packed[i] = texelPtr.Load<SIMD::Int>(robustness, state->activeLaneMask());
+			packed[i] = texelPtr.Load<SIMD::Int>(robustness, activeLaneMask());
 			texelPtr += sizeof(float);
 		}
 	}
 	else if(texelSize == 2)
 	{
-		SIMD::Int mask = state->activeLaneMask() & texelPtr.isInBounds(2, robustness);
+		SIMD::Int mask = activeLaneMask() & texelPtr.isInBounds(2, robustness);
 
 		for(int i = 0; i < SIMD::Width; i++)
 		{
@@ -908,7 +906,7 @@
 	}
 	else if(texelSize == 1)
 	{
-		SIMD::Int mask = state->activeLaneMask() & texelPtr.isInBounds(1, robustness);
+		SIMD::Int mask = activeLaneMask() & texelPtr.isInBounds(1, robustness);
 		for(int i = 0; i < SIMD::Width; i++)
 		{
 			If(Extract(mask, i) != 0)
@@ -1247,16 +1245,16 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageWrite(const ImageInstruction &instruction, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageWrite(const ImageInstruction &instruction) const
 {
-	auto &image = getObject(instruction.imageId);
-	auto &imageType = getType(image);
+	auto &image = shader.getObject(instruction.imageId);
+	auto &imageType = shader.getType(image);
 
 	ASSERT(imageType.definition.opcode() == spv::OpTypeImage);
 	ASSERT(static_cast<spv::Dim>(instruction.dim) != spv::DimSubpassData);  // "Its Dim operand must not be SubpassData."
 
-	auto coordinate = Operand(this, state, instruction.coordinateId);
-	auto texel = Operand(this, state, instruction.texelId);
+	auto coordinate = Operand(shader, *this, instruction.coordinateId);
+	auto texel = Operand(shader, *this, instruction.texelId);
 
 	Array<SIMD::Int> coord(5);  // uvwa & sample
 
@@ -1268,7 +1266,7 @@
 
 	if(instruction.sample)
 	{
-		coord[i] = Operand(this, state, instruction.sampleId).Int(0);
+		coord[i] = Operand(shader, *this, instruction.sampleId).Int(0);
 	}
 
 	Array<SIMD::Int> texelAndMask(5);
@@ -1276,21 +1274,21 @@
 	texelAndMask[1] = texel.Int(1);
 	texelAndMask[2] = texel.Int(2);
 	texelAndMask[3] = texel.Int(3);
-	texelAndMask[4] = state->activeStoresAndAtomicsMask();
+	texelAndMask[4] = activeStoresAndAtomicsMask();
 
 	vk::Format imageFormat = SpirvFormatToVulkanFormat(static_cast<spv::ImageFormat>(instruction.imageFormat));
 
-	SIMD::Pointer ptr = state->getPointer(instruction.imageId);
+	SIMD::Pointer ptr = getPointer(instruction.imageId);
 	if(ptr.isBasePlusOffset)
 	{
 		Pointer<Byte> imageDescriptor = ptr.getUniformPointer();  // vk::StorageImageDescriptor* or vk::SampledImageDescriptor*
-		Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction, state);
+		Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction);
 
 		if(imageFormat == VK_FORMAT_UNDEFINED)  // spv::ImageFormatUnknown
 		{
-			Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction, state);
+			Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction);
 
-			Call<ImageSampler>(samplerFunction, imageDescriptor, &coord, &texelAndMask, state->routine->constants);
+			Call<ImageSampler>(samplerFunction, imageDescriptor, &coord, &texelAndMask, routine->constants);
 		}
 		else
 		{
@@ -1303,14 +1301,15 @@
 		{
 			SIMD::Int singleLaneMask = 0;
 			singleLaneMask = Insert(singleLaneMask, 0xffffffff, j);
-			texelAndMask[4] = state->activeStoresAndAtomicsMask() & singleLaneMask;
+			texelAndMask[4] = activeStoresAndAtomicsMask() & singleLaneMask;
 			Pointer<Byte> imageDescriptor = ptr.getPointerForLane(j);
-			Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction, j, state);
+			Pointer<Byte> samplerDescriptor = getSamplerDescriptor(imageDescriptor, instruction, j);
+
 			if(imageFormat == VK_FORMAT_UNDEFINED)  // spv::ImageFormatUnknown
 			{
-				Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction, state);
+				Pointer<Byte> samplerFunction = lookupSamplerFunction(imageDescriptor, samplerDescriptor, instruction);
 
-				Call<ImageSampler>(samplerFunction, imageDescriptor, &coord, &texelAndMask, state->routine->constants);
+				Call<ImageSampler>(samplerFunction, imageDescriptor, &coord, &texelAndMask, routine->constants);
 			}
 			else
 			{
@@ -1322,7 +1321,7 @@
 	return EmitResult::Continue;
 }
 
-void SpirvShader::WriteImage(ImageInstructionSignature instruction, Pointer<Byte> descriptor, const Pointer<SIMD::Int> &coord, const Pointer<SIMD::Int> &texelAndMask, vk::Format imageFormat)
+void SpirvShader::EmitState::WriteImage(ImageInstructionSignature instruction, Pointer<Byte> descriptor, const Pointer<SIMD::Int> &coord, const Pointer<SIMD::Int> &texelAndMask, vk::Format imageFormat)
 {
 	SIMD::Int texel[4];
 	texel[0] = texelAndMask[0];
@@ -1493,8 +1492,8 @@
 	//  validation. If the texel fails integer texel coordinate validation, then the write has no effect."
 	// - https://www.khronos.org/registry/vulkan/specs/1.2/html/chap16.html#textures-output-coordinate-validation
 	auto robustness = OutOfBoundsBehavior::Nullify;
-	// GetTexelAddress() only needs the EmitState* for SubpassData accesses (i.e. input attachments).
-	const EmitState *state = nullptr;
+	// GetTexelAddress() only needs the SpirvRoutine* for SubpassData accesses (i.e. input attachments).
+	const SpirvRoutine *routine = nullptr;
 
 	SIMD::Int uvwa[4];
 	SIMD::Int sample;
@@ -1510,7 +1509,7 @@
 		sample = As<SIMD::Int>(coord[i]);
 	}
 
-	auto texelPtr = GetTexelAddress(instruction, descriptor, uvwa, sample, imageFormat, robustness, state);
+	auto texelPtr = GetTexelAddress(instruction, descriptor, uvwa, sample, imageFormat, robustness, routine);
 
 	const int texelSize = imageFormat.bytes();
 
@@ -1552,11 +1551,11 @@
 		UNREACHABLE("texelSize: %d", int(texelSize));
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImageTexelPointer(const ImageInstruction &instruction, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImageTexelPointer(const ImageInstruction &instruction)
 {
-	auto coordinate = Operand(this, state, instruction.coordinateId);
+	auto coordinate = Operand(shader, *this, instruction.coordinateId);
 
-	SIMD::Pointer ptr = state->getPointer(instruction.imageId);
+	SIMD::Pointer ptr = getPointer(instruction.imageId);
 
 	// VK_EXT_image_robustness requires checking for out-of-bounds accesses.
 	// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
@@ -1570,36 +1569,36 @@
 		uvwa[i] = coordinate.Int(i);
 	}
 
-	SIMD::Int sample = Operand(this, state, instruction.sampleId).Int(0);
+	SIMD::Int sample = Operand(shader, *this, instruction.sampleId).Int(0);
 
 	auto texelPtr = ptr.isBasePlusOffset
-	                    ? GetTexelAddress(instruction, ptr.getUniformPointer(), uvwa, sample, imageFormat, robustness, state)
-	                    : GetNonUniformTexelAddress(instruction, ptr, uvwa, sample, imageFormat, robustness, state);
+	                    ? GetTexelAddress(instruction, ptr.getUniformPointer(), uvwa, sample, imageFormat, robustness, routine)
+	                    : GetNonUniformTexelAddress(instruction, ptr, uvwa, sample, imageFormat, robustness, activeLaneMask(), routine);
 
-	state->createPointer(instruction.resultId, texelPtr);
+	createPointer(instruction.resultId, texelPtr);
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitSampledImage(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitSampledImage(InsnIterator insn)
 {
 	Object::ID resultId = insn.word(2);
 	Object::ID imageId = insn.word(3);
 	Object::ID samplerId = insn.word(4);
 
 	// Create a sampled image, containing both a sampler and an image
-	state->createSampledImage(resultId, { state->getPointer(imageId), samplerId });
+	createSampledImage(resultId, { getPointer(imageId), samplerId });
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitImage(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitImage(InsnIterator insn)
 {
 	Object::ID resultId = insn.word(2);
 	Object::ID imageId = insn.word(3);
 
 	// Extract the image from a sampled image.
-	state->createPointer(resultId, state->getImage(imageId));
+	createPointer(resultId, getImage(imageId));
 
 	return EmitResult::Continue;
 }
diff --git a/src/Pipeline/SpirvShaderMemory.cpp b/src/Pipeline/SpirvShaderMemory.cpp
index 90818a4..5921e6e 100644
--- a/src/Pipeline/SpirvShaderMemory.cpp
+++ b/src/Pipeline/SpirvShaderMemory.cpp
@@ -23,65 +23,65 @@
 
 namespace sw {
 
-SpirvShader::EmitResult SpirvShader::EmitLoad(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitLoad(InsnIterator insn)
 {
 	bool atomic = (insn.opcode() == spv::OpAtomicLoad);
 	Object::ID resultId = insn.word(2);
 	Object::ID pointerId = insn.word(3);
-	auto &result = getObject(resultId);
-	auto &resultTy = getType(result);
-	auto &pointer = getObject(pointerId);
-	auto &pointerTy = getType(pointer);
+	auto &result = shader.getObject(resultId);
+	auto &resultTy = shader.getType(result);
+	auto &pointer = shader.getObject(pointerId);
+	auto &pointerTy = shader.getType(pointer);
 	std::memory_order memoryOrder = std::memory_order_relaxed;
 
-	ASSERT(getType(pointer).element == result.typeId());
+	ASSERT(shader.getType(pointer).element == result.typeId());
 	ASSERT(Type::ID(insn.word(1)) == result.typeId());
-	ASSERT(!atomic || getType(getType(pointer).element).opcode() == spv::OpTypeInt);  // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
+	ASSERT(!atomic || shader.getType(shader.getType(pointer).element).opcode() == spv::OpTypeInt);  // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
 
 	if(pointerTy.storageClass == spv::StorageClassUniformConstant)
 	{
 		// Just propagate the pointer.
-		auto &ptr = state->getPointer(pointerId);
-		state->createPointer(resultId, ptr);
+		auto &ptr = getPointer(pointerId);
+		createPointer(resultId, ptr);
 		return EmitResult::Continue;
 	}
 
 	if(atomic)
 	{
 		Object::ID semanticsId = insn.word(5);
-		auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
+		auto memorySemantics = static_cast<spv::MemorySemanticsMask>(shader.getObject(semanticsId).constantValue[0]);
 		memoryOrder = MemoryOrder(memorySemantics);
 	}
 
-	auto ptr = GetPointerToData(pointerId, 0, false, state);
+	auto ptr = GetPointerToData(pointerId, 0, false);
 	bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
-	auto robustness = getOutOfBoundsBehavior(pointerId, state);
+	auto robustness = shader.getOutOfBoundsBehavior(pointerId, routine->pipelineLayout);
 
 	if(result.kind == Object::Kind::Pointer)
 	{
-		VisitMemoryObject(pointerId, true, [&](const MemoryElement &el) {
+		shader.VisitMemoryObject(pointerId, true, [&](const MemoryElement &el) {
 			ASSERT(el.index == 0);
 			auto p = GetElementPointer(ptr, el.offset, interleavedByLane);
-			state->createPointer(resultId, p.Load<SIMD::Pointer>(robustness, state->activeLaneMask(), atomic, memoryOrder, sizeof(void *)));
+			createPointer(resultId, p.Load<SIMD::Pointer>(robustness, activeLaneMask(), atomic, memoryOrder, sizeof(void *)));
 		});
 
-		SPIRV_SHADER_DBG("Load(atomic: {0}, order: {1}, ptr: {2}, mask: {3})", atomic, int(memoryOrder), ptr, state->activeLaneMask());
+		SPIRV_SHADER_DBG("Load(atomic: {0}, order: {1}, ptr: {2}, mask: {3})", atomic, int(memoryOrder), ptr, activeLaneMask());
 	}
 	else
 	{
-		auto &dst = state->createIntermediate(resultId, resultTy.componentCount);
-		VisitMemoryObject(pointerId, false, [&](const MemoryElement &el) {
+		auto &dst = createIntermediate(resultId, resultTy.componentCount);
+		shader.VisitMemoryObject(pointerId, false, [&](const MemoryElement &el) {
 			auto p = GetElementPointer(ptr, el.offset, interleavedByLane);
-			dst.move(el.index, p.Load<SIMD::Float>(robustness, state->activeLaneMask(), atomic, memoryOrder));
+			dst.move(el.index, p.Load<SIMD::Float>(robustness, activeLaneMask(), atomic, memoryOrder));
 		});
 
-		SPIRV_SHADER_DBG("Load(atomic: {0}, order: {1}, ptr: {2}, val: {3}, mask: {4})", atomic, int(memoryOrder), ptr, dst, state->activeLaneMask());
+		SPIRV_SHADER_DBG("Load(atomic: {0}, order: {1}, ptr: {2}, val: {3}, mask: {4})", atomic, int(memoryOrder), ptr, dst, activeLaneMask());
 	}
 
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitStore(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitStore(InsnIterator insn)
 {
 	bool atomic = (insn.opcode() == spv::OpAtomicStore);
 	Object::ID pointerId = insn.word(1);
@@ -91,40 +91,40 @@
 	if(atomic)
 	{
 		Object::ID semanticsId = insn.word(3);
-		auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
+		auto memorySemantics = static_cast<spv::MemorySemanticsMask>(shader.getObject(semanticsId).constantValue[0]);
 		memoryOrder = MemoryOrder(memorySemantics);
 	}
 
-	const auto &value = Operand(this, state, objectId);
+	const auto &value = Operand(shader, *this, objectId);
 
-	Store(pointerId, value, atomic, memoryOrder, state);
+	Store(pointerId, value, atomic, memoryOrder);
 
 	return EmitResult::Continue;
 }
 
-void SpirvShader::Store(Object::ID pointerId, const Operand &value, bool atomic, std::memory_order memoryOrder, EmitState *state) const
+void SpirvShader::EmitState::Store(Object::ID pointerId, const Operand &value, bool atomic, std::memory_order memoryOrder) const
 {
-	auto &pointer = getObject(pointerId);
-	auto &pointerTy = getType(pointer);
-	auto &elementTy = getType(pointerTy.element);
+	auto &pointer = shader.getObject(pointerId);
+	auto &pointerTy = shader.getType(pointer);
+	auto &elementTy = shader.getType(pointerTy.element);
 
 	ASSERT(!atomic || elementTy.opcode() == spv::OpTypeInt);  // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
 
-	auto ptr = GetPointerToData(pointerId, 0, false, state);
+	auto ptr = GetPointerToData(pointerId, 0, false);
 	bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
-	auto robustness = getOutOfBoundsBehavior(pointerId, state);
+	auto robustness = shader.getOutOfBoundsBehavior(pointerId, routine->pipelineLayout);
 
-	SIMD::Int mask = state->activeLaneMask();
+	SIMD::Int mask = activeLaneMask();
 	if(!StoresInHelperInvocation(pointerTy.storageClass))
 	{
-		mask = mask & state->storesAndAtomicsMask();
+		mask = mask & storesAndAtomicsMask();
 	}
 
 	SPIRV_SHADER_DBG("Store(atomic: {0}, order: {1}, ptr: {2}, val: {3}, mask: {4}", atomic, int(memoryOrder), ptr, value, mask);
 
 	if(value.isPointer())
 	{
-		VisitMemoryObject(pointerId, true, [&](const MemoryElement &el) {
+		shader.VisitMemoryObject(pointerId, true, [&](const MemoryElement &el) {
 			ASSERT(el.index == 0);
 			auto p = GetElementPointer(ptr, el.offset, interleavedByLane);
 			p.Store(value.Pointer(), robustness, mask, atomic, memoryOrder);
@@ -132,19 +132,18 @@
 	}
 	else
 	{
-		VisitMemoryObject(pointerId, false, [&](const MemoryElement &el) {
+		shader.VisitMemoryObject(pointerId, false, [&](const MemoryElement &el) {
 			auto p = GetElementPointer(ptr, el.offset, interleavedByLane);
 			p.Store(value.Float(el.index), robustness, mask, atomic, memoryOrder);
 		});
 	}
 }
 
-SpirvShader::EmitResult SpirvShader::EmitVariable(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitVariable(InsnIterator insn)
 {
-	auto routine = state->routine;
 	Object::ID resultId = insn.word(2);
-	auto &object = getObject(resultId);
-	auto &objectTy = getType(object);
+	auto &object = shader.getObject(resultId);
+	auto &objectTy = shader.getType(object);
 
 	switch(objectTy.storageClass)
 	{
@@ -154,17 +153,17 @@
 		{
 			ASSERT(objectTy.opcode() == spv::OpTypePointer);
 			auto base = &routine->getVariable(resultId)[0];
-			auto elementTy = getType(objectTy.element);
+			auto elementTy = shader.getType(objectTy.element);
 			auto size = elementTy.componentCount * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
-			state->createPointer(resultId, SIMD::Pointer(base, size));
+			createPointer(resultId, SIMD::Pointer(base, size));
 		}
 		break;
 	case spv::StorageClassWorkgroup:
 		{
 			ASSERT(objectTy.opcode() == spv::OpTypePointer);
 			auto base = &routine->workgroupMemory[0];
-			auto size = workgroupMemory.size();
-			state->createPointer(resultId, SIMD::Pointer(base, size, workgroupMemory.offsetOf(resultId)));
+			auto size = shader.workgroupMemory.size();
+			createPointer(resultId, SIMD::Pointer(base, size, shader.workgroupMemory.offsetOf(resultId)));
 		}
 		break;
 	case spv::StorageClassInput:
@@ -173,22 +172,22 @@
 			{
 				auto &dst = routine->getVariable(resultId);
 				int offset = 0;
-				VisitInterface(resultId,
-				               [&](const Decorations &d, AttribType type) {
-					               auto scalarSlot = d.Location << 2 | d.Component;
-					               dst[offset++] = routine->inputs[scalarSlot];
-				               });
+				shader.VisitInterface(resultId,
+				                      [&](const Decorations &d, AttribType type) {
+					                      auto scalarSlot = d.Location << 2 | d.Component;
+					                      dst[offset++] = routine->inputs[scalarSlot];
+				                      });
 			}
 			ASSERT(objectTy.opcode() == spv::OpTypePointer);
 			auto base = &routine->getVariable(resultId)[0];
-			auto elementTy = getType(objectTy.element);
+			auto elementTy = shader.getType(objectTy.element);
 			auto size = elementTy.componentCount * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
-			state->createPointer(resultId, SIMD::Pointer(base, size));
+			createPointer(resultId, SIMD::Pointer(base, size));
 		}
 		break;
 	case spv::StorageClassUniformConstant:
 		{
-			const auto &d = descriptorDecorations.at(resultId);
+			const auto &d = shader.descriptorDecorations.at(resultId);
 			ASSERT(d.DescriptorSet >= 0);
 			ASSERT(d.Binding >= 0);
 
@@ -196,14 +195,14 @@
 			Pointer<Byte> set = routine->descriptorSets[d.DescriptorSet];  // DescriptorSet*
 			Pointer<Byte> binding = Pointer<Byte>(set + bindingOffset);    // vk::SampledImageDescriptor*
 			auto size = 0;                                                 // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
-			state->createPointer(resultId, SIMD::Pointer(binding, size));
+			createPointer(resultId, SIMD::Pointer(binding, size));
 		}
 		break;
 	case spv::StorageClassUniform:
 	case spv::StorageClassStorageBuffer:
 	case spv::StorageClassPhysicalStorageBuffer:
 		{
-			const auto &d = descriptorDecorations.at(resultId);
+			const auto &d = shader.descriptorDecorations.at(resultId);
 			ASSERT(d.DescriptorSet >= 0);
 			auto size = 0;  // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
 			// Note: the module may contain descriptor set references that are not suitable for this implementation -- using a set index higher than the number
@@ -211,17 +210,17 @@
 			// is valid. In this case make the value nullptr to make it easier to diagnose an attempt to dereference it.
 			if(static_cast<uint32_t>(d.DescriptorSet) < vk::MAX_BOUND_DESCRIPTOR_SETS)
 			{
-				state->createPointer(resultId, SIMD::Pointer(routine->descriptorSets[d.DescriptorSet], size));
+				createPointer(resultId, SIMD::Pointer(routine->descriptorSets[d.DescriptorSet], size));
 			}
 			else
 			{
-				state->createPointer(resultId, SIMD::Pointer(nullptr, 0));
+				createPointer(resultId, SIMD::Pointer(nullptr, 0));
 			}
 		}
 		break;
 	case spv::StorageClassPushConstant:
 		{
-			state->createPointer(resultId, SIMD::Pointer(routine->pushConstants, vk::MAX_PUSH_CONSTANT_SIZE));
+			createPointer(resultId, SIMD::Pointer(routine->pushConstants, vk::MAX_PUSH_CONSTANT_SIZE));
 		}
 		break;
 	default:
@@ -232,7 +231,7 @@
 	if(insn.wordCount() > 4)
 	{
 		Object::ID initializerId = insn.word(4);
-		if(getObject(initializerId).kind != Object::Kind::Constant)
+		if(shader.getObject(initializerId).kind != Object::Kind::Constant)
 		{
 			UNIMPLEMENTED("b/148241854: Non-constant initializers not yet implemented");  // FIXME(b/148241854)
 		}
@@ -245,13 +244,15 @@
 		case spv::StorageClassWorkgroup:
 			{
 				bool interleavedByLane = IsStorageInterleavedByLane(objectTy.storageClass);
-				auto ptr = GetPointerToData(resultId, 0, false, state);
-				Operand initialValue(this, state, initializerId);
-				VisitMemoryObject(resultId, false, [&](const MemoryElement &el) {
+				auto ptr = GetPointerToData(resultId, 0, false);
+				Operand initialValue(shader, *this, initializerId);
+
+				shader.VisitMemoryObject(resultId, false, [&](const MemoryElement &el) {
 					auto p = GetElementPointer(ptr, el.offset, interleavedByLane);
 					auto robustness = OutOfBoundsBehavior::UndefinedBehavior;  // Local variables are always within bounds.
-					p.Store(initialValue.Float(el.index), robustness, state->activeLaneMask());
+					p.Store(initialValue.Float(el.index), robustness, activeLaneMask());
 				});
+
 				if(objectTy.storageClass == spv::StorageClassWorkgroup)
 				{
 					// Initialization of workgroup memory is done by each subgroup and requires waiting on a barrier.
@@ -268,24 +269,24 @@
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitCopyMemory(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitCopyMemory(InsnIterator insn)
 {
 	Object::ID dstPtrId = insn.word(1);
 	Object::ID srcPtrId = insn.word(2);
-	auto &dstPtrTy = getObjectType(dstPtrId);
-	auto &srcPtrTy = getObjectType(srcPtrId);
+	auto &dstPtrTy = shader.getObjectType(dstPtrId);
+	auto &srcPtrTy = shader.getObjectType(srcPtrId);
 	ASSERT(dstPtrTy.element == srcPtrTy.element);
 
 	bool dstInterleavedByLane = IsStorageInterleavedByLane(dstPtrTy.storageClass);
 	bool srcInterleavedByLane = IsStorageInterleavedByLane(srcPtrTy.storageClass);
-	auto dstPtr = GetPointerToData(dstPtrId, 0, false, state);
-	auto srcPtr = GetPointerToData(srcPtrId, 0, false, state);
+	auto dstPtr = GetPointerToData(dstPtrId, 0, false);
+	auto srcPtr = GetPointerToData(srcPtrId, 0, false);
 
 	std::unordered_map<uint32_t, uint32_t> srcOffsets;
 
-	VisitMemoryObject(srcPtrId, false, [&](const MemoryElement &el) { srcOffsets[el.index] = el.offset; });
+	shader.VisitMemoryObject(srcPtrId, false, [&](const MemoryElement &el) { srcOffsets[el.index] = el.offset; });
 
-	VisitMemoryObject(dstPtrId, false, [&](const MemoryElement &el) {
+	shader.VisitMemoryObject(dstPtrId, false, [&](const MemoryElement &el) {
 		auto it = srcOffsets.find(el.index);
 		ASSERT(it != srcOffsets.end());
 		auto srcOffset = it->second;
@@ -297,15 +298,15 @@
 		// TODO(b/131224163): Optimize based on src/dst storage classes.
 		auto robustness = OutOfBoundsBehavior::RobustBufferAccess;
 
-		auto value = src.Load<SIMD::Float>(robustness, state->activeLaneMask());
-		dst.Store(value, robustness, state->activeLaneMask());
+		auto value = src.Load<SIMD::Float>(robustness, activeLaneMask());
+		dst.Store(value, robustness, activeLaneMask());
 	});
 	return EmitResult::Continue;
 }
 
-SpirvShader::EmitResult SpirvShader::EmitMemoryBarrier(InsnIterator insn, EmitState *state) const
+SpirvShader::EmitResult SpirvShader::EmitState::EmitMemoryBarrier(InsnIterator insn)
 {
-	auto semantics = spv::MemorySemanticsMask(GetConstScalarInt(insn.word(2)));
+	auto semantics = spv::MemorySemanticsMask(shader.GetConstScalarInt(insn.word(2)));
 	// TODO(b/176819536): We probably want to consider the memory scope here.
 	// For now, just always emit the full fence.
 	Fence(semantics);
@@ -406,19 +407,18 @@
 	}
 }
 
-SIMD::Pointer SpirvShader::GetPointerToData(Object::ID id, SIMD::Int arrayIndices, bool nonUniform, const EmitState *state) const
+SIMD::Pointer SpirvShader::EmitState::GetPointerToData(Object::ID id, SIMD::Int arrayIndices, bool nonUniform) const
 {
-	auto routine = state->routine;
-	auto &object = getObject(id);
+	auto &object = shader.getObject(id);
 	switch(object.kind)
 	{
 	case Object::Kind::Pointer:
 	case Object::Kind::InterfaceVariable:
-		return state->getPointer(id);
+		return getPointer(id);
 
 	case Object::Kind::DescriptorSet:
 		{
-			const auto &d = descriptorDecorations.at(id);
+			const auto &d = shader.descriptorDecorations.at(id);
 			ASSERT(d.DescriptorSet >= 0 && static_cast<uint32_t>(d.DescriptorSet) < vk::MAX_BOUND_DESCRIPTOR_SETS);
 			ASSERT(d.Binding >= 0);
 			ASSERT(routine->pipelineLayout->getDescriptorCount(d.DescriptorSet, d.Binding) != 0);  // "If descriptorCount is zero this binding entry is reserved and the resource must not be accessed from any stage via this binding within any pipeline using the set layout."
@@ -426,11 +426,11 @@
 			uint32_t bindingOffset = routine->pipelineLayout->getBindingOffset(d.DescriptorSet, d.Binding);
 			uint32_t descriptorSize = routine->pipelineLayout->getDescriptorSize(d.DescriptorSet, d.Binding);
 
-			auto set = state->getPointer(id);
+			auto set = getPointer(id);
 			if(nonUniform)
 			{
 				SIMD::Int descriptorOffset = bindingOffset + descriptorSize * arrayIndices;
-				auto robustness = getOutOfBoundsBehavior(id, state);
+				auto robustness = shader.getOutOfBoundsBehavior(id, routine->pipelineLayout);
 				ASSERT(routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding) != VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT);
 
 				std::vector<Pointer<Byte>> pointers(SIMD::Width);
@@ -445,7 +445,7 @@
 				{
 					SIMD::Int dynamicOffsetIndex = SIMD::Int(routine->pipelineLayout->getDynamicOffsetIndex(d.DescriptorSet, d.Binding) + arrayIndices);
 					SIMD::Pointer routineDynamicOffsets = SIMD::Pointer(routine->descriptorDynamicOffsets, 0, sizeof(int) * dynamicOffsetIndex);
-					SIMD::Int dynamicOffsets = routineDynamicOffsets.Load<SIMD::Int>(robustness, state->activeLaneMask());
+					SIMD::Int dynamicOffsets = routineDynamicOffsets.Load<SIMD::Int>(robustness, activeLaneMask());
 					ptr += dynamicOffsets;
 				}
 				return ptr;
@@ -493,24 +493,25 @@
 	}
 }
 
-void SpirvShader::OffsetToElement(SIMD::Pointer &ptr, Object::ID elementId, int32_t arrayStride, const EmitState *state) const
+void SpirvShader::EmitState::OffsetToElement(SIMD::Pointer &ptr, Object::ID elementId, int32_t arrayStride) const
 {
 	if(elementId != 0 && arrayStride != 0)
 	{
-		auto &elementObject = getObject(elementId);
+		auto &elementObject = shader.getObject(elementId);
 		ASSERT(elementObject.kind == Object::Kind::Constant || elementObject.kind == Object::Kind::Intermediate);
+
 		if(elementObject.kind == Object::Kind::Constant)
 		{
-			ptr += GetConstScalarInt(elementId) * arrayStride;
+			ptr += shader.GetConstScalarInt(elementId) * arrayStride;
 		}
 		else
 		{
-			ptr += state->getIntermediate(elementId).Int(0) * arrayStride;
+			ptr += getIntermediate(elementId).Int(0) * arrayStride;
 		}
 	}
 }
 
-void SpirvShader::Fence(spv::MemorySemanticsMask semantics) const
+void SpirvShader::EmitState::Fence(spv::MemorySemanticsMask semantics) const
 {
 	if(semantics != spv::MemorySemanticsMaskNone)
 	{
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index ee4cca3..c2dbf7c 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -30,7 +30,7 @@
 
 namespace sw {
 
-SpirvShader::ImageSampler *SpirvShader::getImageSampler(const vk::Device *device, uint32_t signature, uint32_t samplerId, uint32_t imageViewId)
+SpirvShader::ImageSampler *SpirvShader::EmitState::getImageSampler(const vk::Device *device, uint32_t signature, uint32_t samplerId, uint32_t imageViewId)
 {
 	ImageInstructionSignature instruction(signature);
 	ASSERT(imageViewId != 0 && (samplerId != 0 || instruction.samplerMethod == Fetch || instruction.samplerMethod == Write));
@@ -125,7 +125,7 @@
 	return (ImageSampler *)(routine->getEntry());
 }
 
-std::shared_ptr<rr::Routine> SpirvShader::emitWriteRoutine(ImageInstructionSignature instruction, const Sampler &samplerState)
+std::shared_ptr<rr::Routine> SpirvShader::EmitState::emitWriteRoutine(ImageInstructionSignature instruction, const Sampler &samplerState)
 {
 	// TODO(b/129523279): Hold a separate mutex lock for the sampler being built.
 	rr::Function<Void(Pointer<Byte>, Pointer<SIMD::Float>, Pointer<SIMD::Float>, Pointer<Byte>)> function;
@@ -141,7 +141,7 @@
 	return function("sampler");
 }
 
-std::shared_ptr<rr::Routine> SpirvShader::emitSamplerRoutine(ImageInstructionSignature instruction, const Sampler &samplerState)
+std::shared_ptr<rr::Routine> SpirvShader::EmitState::emitSamplerRoutine(ImageInstructionSignature instruction, const Sampler &samplerState)
 {
 	// TODO(b/129523279): Hold a separate mutex lock for the sampler being built.
 	rr::Function<Void(Pointer<Byte>, Pointer<SIMD::Float>, Pointer<SIMD::Float>, Pointer<Byte>)> function;
@@ -263,7 +263,7 @@
 	return function("sampler");
 }
 
-sw::FilterType SpirvShader::convertFilterMode(const vk::SamplerState *samplerState, VkImageViewType imageViewType, SamplerMethod samplerMethod)
+sw::FilterType SpirvShader::EmitState::convertFilterMode(const vk::SamplerState *samplerState, VkImageViewType imageViewType, SamplerMethod samplerMethod)
 {
 	if(samplerMethod == Gather)
 	{
@@ -316,7 +316,7 @@
 	return FILTER_POINT;
 }
 
-sw::MipmapType SpirvShader::convertMipmapMode(const vk::SamplerState *samplerState)
+sw::MipmapType SpirvShader::EmitState::convertMipmapMode(const vk::SamplerState *samplerState)
 {
 	if(!samplerState)
 	{
@@ -339,7 +339,7 @@
 	}
 }
 
-sw::AddressingMode SpirvShader::convertAddressingMode(int coordinateIndex, const vk::SamplerState *samplerState, VkImageViewType imageViewType)
+sw::AddressingMode SpirvShader::EmitState::convertAddressingMode(int coordinateIndex, const vk::SamplerState *samplerState, VkImageViewType imageViewType)
 {
 	switch(imageViewType)
 	{