Skip image sampling if no SIMD lanes are active

Currently all conditional branches in SPIR-V shaders are 'flattened',
resulting in the execution of all instructions in both code paths, even
in the case where none of the SIMD lanes are active. If a logically
never taken code path contains an image sampling instruction which
accesses a sampler or image array out of bounds, this could lead to
reading invalid descriptor data, or a page fault.

This is fixed by checking if any SIMD lanes are active, and if not,
jumping over the actual sampling operations which access the
descriptor data.

Bug: b/153380916
Change-Id: I8e0cf7ef855e1250ce6dce9ebf7a47e29da7814d
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/43549
Tested-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index b61737d..04ca7c6 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -1208,6 +1208,9 @@
 	EmitResult EmitGroupNonUniform(InsnIterator insn, EmitState *state) const;
 	EmitResult EmitArrayLength(InsnIterator insn, EmitState *state) const;
 
+	// Emits code to sample an image, regardless of whether any SIMD lanes are active.
+	void EmitImageSampleUnconditional(Array<SIMD::Float> &out, ImageInstruction instruction, InsnIterator insn, EmitState *state) const;
+
 	void GetImageDimensions(EmitState const *state, Type const &resultTy, Object::ID imageId, Object::ID lodId, Intermediate &dst) const;
 	SIMD::Pointer GetTexelAddress(EmitState const *state, SIMD::Pointer base, Operand const &coordinate, Type const &imageType, Pointer<Byte> descriptor, int texelSize, Object::ID sampleId, bool useStencilAspect) const;
 	uint32_t GetConstScalarInt(Object::ID id) const;
diff --git a/src/Pipeline/SpirvShaderImage.cpp b/src/Pipeline/SpirvShaderImage.cpp
index 6da2de9..273502a 100644
--- a/src/Pipeline/SpirvShaderImage.cpp
+++ b/src/Pipeline/SpirvShaderImage.cpp
@@ -107,11 +107,28 @@
 
 SpirvShader::EmitResult SpirvShader::EmitImageSample(ImageInstruction instruction, InsnIterator insn, EmitState *state) const
 {
+	auto &resultType = getType(insn.resultTypeId());
+	auto &result = state->createIntermediate(insn.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()))
+	{
+		EmitImageSampleUnconditional(out, instruction, insn, state);
+	}
+
+	for(auto i = 0u; i < resultType.componentCount; i++) { result.move(i, out[i]); }
+
+	return EmitResult::Continue;
+}
+
+void SpirvShader::EmitImageSampleUnconditional(Array<SIMD::Float> &out, ImageInstruction instruction, InsnIterator insn, EmitState *state) const
+{
 	Object::ID sampledImageId = insn.word(3);  // For OpImageFetch this is just an Image, not a SampledImage.
 	Object::ID coordinateId = insn.word(4);
-	auto &resultType = getType(insn.resultTypeId());
 
-	auto &result = state->createIntermediate(insn.resultId(), resultType.componentCount);
 	auto imageDescriptor = state->getPointer(sampledImageId).base;  // vk::SampledImageDescriptor*
 
 	// If using a separate sampler, look through the OpSampledImage instruction to find the sampler descriptor
@@ -297,12 +314,7 @@
 		cache.sampler = sampler;
 	}
 
-	Array<SIMD::Float> out(4);
 	Call<ImageSampler>(cache.function, texture, &in[0], &out[0], state->routine->constants);
-
-	for(auto i = 0u; i < resultType.componentCount; i++) { result.move(i, out[i]); }
-
-	return EmitResult::Continue;
 }
 
 SpirvShader::EmitResult SpirvShader::EmitImageQuerySizeLod(InsnIterator insn, EmitState *state) const
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index f1a44ef..590fc11 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -35,11 +35,10 @@
 	ImageInstruction instruction(inst);
 	const auto samplerId = sampler ? sampler->id : 0;
 	ASSERT(imageDescriptor->imageViewId != 0 && (samplerId != 0 || instruction.samplerMethod == Fetch));
+	ASSERT(imageDescriptor->device);
 
 	vk::Device::SamplingRoutineCache::Key key = { inst, imageDescriptor->imageViewId, samplerId };
 
-	ASSERT(imageDescriptor->device);
-
 	vk::Device::SamplingRoutineCache *cache = imageDescriptor->device->getSamplingRoutineCache();
 
 	auto createSamplingRoutine = [&](const vk::Device::SamplingRoutineCache::Key &key) {