SpirvShader: Implement OpGroupNonUniformElect

The only elect function we'll need to implement for Pastel 1.0.

Used by memory barrier tests, but not tested on its own.

Bug: b/131667233
Change-Id: I5309fa6b49c49091226fbaaca4e46363f6dff534
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/30208
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Presubmit-Ready: Ben Clayton <bclayton@google.com>
Reviewed-by: Chris Forbes <chrisforbes@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 60ba39b..9f5246d 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -862,6 +862,7 @@
 			case spv::OpImageQuerySize:
 			case spv::OpImageRead:
 			case spv::OpImageTexelPointer:
+			case spv::OpGroupNonUniformElect:
 				// Instructions that yield an intermediate value or divergent pointer
 				DefineResult(insn);
 				break;
@@ -2447,6 +2448,9 @@
 		case spv::OpCopyMemory:
 			return EmitCopyMemory(insn, state);
 
+		case spv::OpGroupNonUniformElect:
+			return EmitGroupNonUniform(insn, state);
+
 		default:
 			UNREACHABLE("%s", OpcodeName(opcode).c_str());
 			break;
@@ -5321,6 +5325,43 @@
 		return EmitResult::Continue;
 	}
 
+	SpirvShader::EmitResult SpirvShader::EmitGroupNonUniform(InsnIterator insn, EmitState *state) const
+	{
+		auto &type = getType(Type::ID(insn.word(1)));
+		Object::ID resultId = insn.word(2);
+		auto scope = GetScope(insn.word(3));
+		ASSERT_MSG(scope == spv::ScopeSubgroup, "Scope for Non Uniform Group Operations must be Subgroup for Vulkan 1.1");
+
+		auto &dst = state->routine->createIntermediate(resultId, type.sizeInComponents);
+
+		switch (insn.opcode())
+		{
+		case spv::OpGroupNonUniformElect:
+		{
+			// Result is true only in the active invocation with the lowest id
+			// in the group, otherwise result is false.
+			SIMD::Int active = state->activeLaneMask();
+			// 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);
+			auto elect = active & ~(v0111 & (active.xxyz | active.xxxy | active.xxxx));
+			dst.move(0, elect);
+			break;
+		}
+		default:
+			UNIMPLEMENTED("EmitGroupNonUniform op: %s", OpcodeName(type.opcode()).c_str());
+		}
+		return EmitResult::Continue;
+	}
+
+	spv::Scope SpirvShader::GetScope(Object::ID id) const
+	{
+		auto &scopeObj = getObject(id);
+		ASSERT(scopeObj.kind == Object::Kind::Constant);
+		ASSERT(getType(scopeObj.type).sizeInComponents == 1);
+		return spv::Scope(scopeObj.constantValue[0]);
+	}
+
 	void SpirvShader::emitEpilog(SpirvRoutine *routine) const
 	{
 		for (auto insn : *this)
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index cf38289..77da4d5 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -906,8 +906,10 @@
 		EmitResult EmitAtomicCompareExchange(InsnIterator insn, EmitState *state) const;
 		EmitResult EmitSampledImageCombineOrSplit(InsnIterator insn, EmitState *state) const;
 		EmitResult EmitCopyMemory(InsnIterator insn, EmitState *state) const;
+		EmitResult EmitGroupNonUniform(InsnIterator insn, EmitState *state) const;
 
 		SIMD::Pointer GetTexelAddress(SpirvRoutine const * routine, SIMD::Pointer base, GenericValue const & coordinate, Type const & imageType, Pointer<Byte> descriptor, int texelSize) const;
+		spv::Scope GetScope(Object::ID id) const;
 
 		// OpcodeName() returns the name of the opcode op.
 		// If NDEBUG is defined, then OpcodeName() will only return the numerical code.