Fix https://github.com/KhronosGroup/SPIRV-Tools/issues/4462 (#4651)
This prevents CCP from making constant -> constant transitions when
evaluating instruction values. In this case, FClamp is evaluated twice.
On the first evaluation, if computes FClamp(0.5, 0.5, -1) which returns
-1. On the second evaluation, it computes FClamp(0.5, 0.5, VARYING)
which returns 0.5.
Both fold() computations are correct given the semantics of FClamp() but
this causes a lateral transition in the constant lattice which was not
being considered VARYING by CCP.
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 8b896d5..5099b47 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -102,6 +102,34 @@
return SSAPropagator::kInteresting;
}
+uint32_t CCPPass::ComputeLatticeMeet(Instruction* instr, uint32_t val2) {
+ // Given two values val1 and val2, the meet operation in the constant
+ // lattice uses the following rules:
+ //
+ // meet(val1, UNDEFINED) = val1
+ // meet(val1, VARYING) = VARYING
+ // meet(val1, val2) = val1 if val1 == val2
+ // meet(val1, val2) = VARYING if val1 != val2
+ //
+ // When two different values meet, the result is always varying because CCP
+ // does not allow lateral transitions in the lattice. This prevents
+ // infinite cycles during propagation.
+ auto val1_it = values_.find(instr->result_id());
+ if (val1_it == values_.end()) {
+ return val2;
+ }
+
+ uint32_t val1 = val1_it->second;
+ if (IsVaryingValue(val1)) {
+ return val1;
+ } else if (IsVaryingValue(val2)) {
+ return val2;
+ } else if (val1 != val2) {
+ return kVaryingSSAId;
+ }
+ return val2;
+}
+
SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
assert(instr->result_id() != 0 &&
"Expecting an instruction that produces a result");
@@ -115,8 +143,10 @@
if (IsVaryingValue(it->second)) {
return MarkInstructionVarying(instr);
} else {
- values_[instr->result_id()] = it->second;
- return SSAPropagator::kInteresting;
+ uint32_t new_val = ComputeLatticeMeet(instr, it->second);
+ values_[instr->result_id()] = new_val;
+ return IsVaryingValue(new_val) ? SSAPropagator::kVarying
+ : SSAPropagator::kInteresting;
}
}
return SSAPropagator::kNotInteresting;
@@ -142,9 +172,12 @@
if (folded_inst != nullptr) {
// We do not want to change the body of the function by adding new
// instructions. When folding we can only generate new constants.
- assert(folded_inst->IsConstant() && "CCP is only interested in constant.");
- values_[instr->result_id()] = folded_inst->result_id();
- return SSAPropagator::kInteresting;
+ assert(folded_inst->IsConstant() &&
+ "CCP is only interested in constant values.");
+ uint32_t new_val = ComputeLatticeMeet(instr, folded_inst->result_id());
+ values_[instr->result_id()] = new_val;
+ return IsVaryingValue(new_val) ? SSAPropagator::kVarying
+ : SSAPropagator::kInteresting;
}
// Conservatively mark this instruction as varying if any input id is varying.
diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h
index fb20c78..77ea9f8 100644
--- a/source/opt/ccp_pass.h
+++ b/source/opt/ccp_pass.h
@@ -92,6 +92,22 @@
// generated during propagation.
analysis::ConstantManager* const_mgr_;
+ // Returns a new value for |instr| by computing the meet operation between
+ // its existing value and |val2|.
+ //
+ // Given two values val1 and val2, the meet operation in the constant
+ // lattice uses the following rules:
+ //
+ // meet(val1, UNDEFINED) = val1
+ // meet(val1, VARYING) = VARYING
+ // meet(val1, val2) = val1 if val1 == val2
+ // meet(val1, val2) = VARYING if val1 != val2
+ //
+ // When two different values meet, the result is always varying because CCP
+ // does not allow lateral transitions in the lattice. This prevents
+ // infinite cycles during propagation.
+ uint32_t ComputeLatticeMeet(Instruction* instr, uint32_t val2);
+
// Constant value table. Each entry <id, const_decl_id> in this map
// represents the compile-time constant value for |id| as declared by
// |const_decl_id|. Each |const_decl_id| in this table is an OpConstant
diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp
index 212e051..ae7043b 100644
--- a/test/opt/ccp_test.cpp
+++ b/test/opt/ccp_test.cpp
@@ -1234,6 +1234,86 @@
SinglePassRunAndCheck<CCPPass>(text, text, false);
}
+
+// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/4462.
+// The test was causing a lateral movement in the constant lattice, which was
+// not being detected as varying by CCP. In this test, FClamp is evaluated
+// twice. On the first evaluation, if computes FClamp(0.5, 0.5, -1) which
+// returns -1. On the second evaluation, it computes FClamp(0.5, 0.5, VARYING)
+// which returns 0.5.
+//
+// Both fold() computations are correct given the semantics of FClamp() but
+// this causes a lateral transition in the constant lattice which was not being
+// considered VARYING by CCP.
+TEST_F(CCPTest, LateralLatticeTransition) {
+ const std::string text = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %gl_FragCoord %outColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource ESSL 310
+ OpName %main "main"
+ OpName %gl_FragCoord "gl_FragCoord"
+ OpName %outColor "outColor"
+ OpDecorate %gl_FragCoord BuiltIn FragCoord
+ OpDecorate %outColor Location 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %float_0_5 = OpConstant %float 0.5
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %float_0 = OpConstant %float 0
+ %bool = OpTypeBool
+ %float_n1 = OpConstant %float -1
+ %float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %outColor = OpVariable %_ptr_Output_v4float Output
+
+; This constant is created during the first evaluation of the CompositeConstruct
+; CHECK: [[new_constant:%\d+]] = OpConstantComposite %v4float %float_n1 %float_0_5 %float_0 %float_1
+
+ %main = OpFunction %void None %6
+ %19 = OpLabel
+ %20 = OpAccessChain %_ptr_Input_float %gl_FragCoord %uint_0
+ %21 = OpLoad %float %20
+ %22 = OpFOrdLessThan %bool %21 %float_0
+ OpSelectionMerge %23 None
+ OpBranchConditional %22 %24 %25
+ %24 = OpLabel
+ OpBranch %23
+ %25 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %27 = OpPhi %float %float_n1 %24 %float_0_5 %26
+ %28 = OpExtInst %float %1 FClamp %float_0_5 %float_0_5 %27
+
+ ; On first evaluation, the result from FClamp will return 0.5.
+ ; But on second evaluation, FClamp should return VARYING. Check
+ ; that CCP is not keeping the first result.
+ ; CHECK-NOT: %29 = OpCompositeConstruct %v4float %float_0_5 %float_0_5 %float_0 %float_1
+ %29 = OpCompositeConstruct %v4float %28 %float_0_5 %float_0 %float_1
+
+ ; CHECK-NOT: OpCopyObject %v4float [[new_constant]]
+ %42 = OpCopyObject %v4float %29
+
+ ; CHECK-NOT: OpStore %outColor [[new_constant]]
+ OpStore %outColor %42
+
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndMatch<CCPPass>(text, true);
+ EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
} // namespace
} // namespace opt
} // namespace spvtools