Repository: KhronosGroup/SPIRV-Tools (language: python)

Issue:
Add folding rule to generate Fma instructions
Adding Fma instruction can speed up the code.  This was requested by
swiftshader, so they do not have to do this analysis themselves.  It can
also help reduce the code size, and the work the ICD compilers have to
do.

Fold multiply and subtraction into FMA with negation
This change adds a folding rule which transforms x * y - a and a - x * y
into FMA(x, y, -a) and FMA(-x, y, a), respectively.

While the SPIR-V instruction count remains the same, target instruction
sets typically feature FMA instruction variants that can negate an
operand. Also this transformation may unlock further optimizations which
eliminate the negation.

(Google bug: b/226145988)
spirv-opt fma folding causes large performance degradation on Adreno
I recently discovered the reason why spirv-opt was causing a major performance degradation on some of my shaders. I narrowed it down to the `simplify-instructions` stage and digging deeper I found the culprit were the folding rules that transform sequence of multiply adds and subs into FMAs.

The issue was introduced in these two commits:

- Add folding rule to generate Fma instructions ([https://github.com/KhronosGroup/SPIRV-Tools/pull/4783](https://github.com/KhronosGroup/SPIRV-Tools/pull/4783))
- Fold multiply and subtraction into FMA with negation ([https://github.com/KhronosGroup/SPIRV-Tools/pull/4808](https://github.com/KhronosGroup/SPIRV-Tools/pull/4808))

And the simplest solution is to comment out these two lines of code:

```c++
//rules_[spv::Op::OpFAdd].push_back(MergeMulAddArithmetic);
//rules_[spv::Op::OpFSub].push_back(MergeMulSubArithmetic);
```

The performance drop appears to be caused by poor handling of explicit FMA operations in the Adreno compiler. In particular, it appears that it's only implemented for fp32, so when the arguments are in fp16 they are converted to fp32 and the result is converted back to fp16. As a result, what was intended to be an optimization, actually results in a significant performance drop.

For the [Spark codecs](https://ludicon.com/spark/) that I've been developing, the proposed fix usually results in a 20% to 30% increased throughput.

Here's the example output of the Adreno Offline compiler for one of my codecs:

```
With FMA fix:
Total instruction count                                     :   868
ALU instruction count - 32 bit                              :   52
ALU instruction count - 16 bit                              :   466

Without FMA fix:
Total instruction count                                     :   1077
ALU instruction count - 32 bit                              :   200
ALU instruction count - 16 bit                              :   335
```

Note not only the increase in instruction count, but also the ratio between 32 bit and 16 bit ALU instructions.

Note, this affects all the Adreno devices that I'm aware of. A driver fix is on the way, but many devices will never be upgraded to the latest driver, so I would highly encourage everyone to adopt this workaround.

I've also tested the proposed change on many other devices: This has no effect on any Arm device, the results are the same in both cases. It produces a small performance improvement on PowerVR devices, and a very slight regression on AMD/Samsung.

---

In general, I would argue that spirv-opt should not be issuing FMAs. Originally the spirv spec did not include an FMA operation, this was added later through the extended instructions for GLSL:

https://registry.khronos.org/SPIR-V/specs/unified1/GLSL.std.450.html

My expectation is that FMA should be used in the same spirit as it was defined in GLSL. The purpose of FMA in GLSL was never to be an optimization, but to give the programmer more control over how certain expressions were evaluated, which in some cases would result in slower code. Outside of a precise statement the FMA does not impose any constrain, it's merely a hint, but inside precise statements the spec provides certain guarantees to ensure consistent evaluation:

https://registry.khronos.org/OpenGL-Refpages/gl4/html/fma.xhtml

For more background, long time ago I wrote about the motivations behind the precise statements and the fma intrinsic: 

[Watertight Tessellation: precise and fma](https://www.ludicon.com/castano/blog/2010/09/precise/)

SPIR-V doesn't have the concept of precise operations. It does expose the NoContraction decorator and we could overload it to mean that an FMA needs to be evaluated as if it was part of a precise statement, however this is not specified anywhere. In the absence of a formal specification I think it's natural for IHVs to respect FMAs in all scenarios, which could actually reduce optimization opportunities.


Discussion / hints:


I'll just correct a few things:

> Originally the spirv spec did not include an FMA operation, this was added later

The extended instructions for GLSL were released together with the initial Vulkan and SPIR-V release. FMA has always been there, it was never added later.

> SPIR-V doesn't have the concept of precise operations. It does expose the NoContraction decorator and we could overload it to mean that an FMA needs to be evaluated as if it was part of a precise statement, however this is not specified anywhere.

No, `NoContract` is already the same thing as precise in GLSL. This is specified in the Vulkan specification, appendix A ("Vulkan environment for SPIR-V") in the "Precision and Operation of SPIR-V Instructions" section. The reason it's not in the SPIR-V spec is that unlike GLSL, SPIR-V is used in different environments, like OpenCL, which have different precision requirements.

That being said, fusing mul+add is something that pretty much all vendor drivers do, so this optimization is unlikely to have much effect in practice. If one vendor has a bad (perf-wise) implementation of FMA then it shouldn't hurt to drop it.
Thanks for the correction. I had wrongly assumed the "Extended Instructions for GLSL" was an extension that came after the initial SPIR-V release.

However, I don't think the text in Appendix A that you point to is sufficiently clear. I'm also a bit confused, because reading the text of an earlier release of the "SPIR-V Extended Instructions for GLSL" document (revision 4):

https://registry.khronos.org/SPIR-V/specs/1.0/GLSL.std.450.pdf

The behavior of the Fma extended instruction and its interaction with the NoContract decorator is described very clearly.

> Computes a * b + c. In uses where this operation is decorated with NoContraction:
> - fma is considered a single operation, whereas the expression a * b + c is considered two operations.
> - The precision of fma can differ from the precision of the expression a * b + c.
> - fma will be computed with the same precision as any other fma decorated with NoContraction, giving invariant
results for the same input values of a, b, and c.
> Otherwise, in the absence of a NoContraction decoration, there are no special constraints on the number of
operations or difference in precision between fma and the expression a * b +c.

However, in revision 12 that text has disappeared. The description is not as precise and the interactions with NoContract are not there anymore:

> Computes a * b + c.

https://registry.khronos.org/SPIR-V/specs/unified1/GLSL.std.450.pdf

Looking at the change history I don't see any mention of this change so it appears that this is most likely an error.
I think it was because it was replaced with this text in the Vulkan spec (https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#spirvenv-evaluation-expressions):

> Implementations may rearrange floating-point operations using any of the mathematical properties governing the expressions in precise arithmetic, even where the floating- point operations do not share these properties. This includes, but is not limited to, associativity and distributivity, and may involve a different number of rounding steps than would occur if the operations were not rearranged. In shaders that use the SignedZeroInfNanPreserve Execution Mode the values must be preserved if they are generated after any rearrangement but the Execution Mode does not change which rearrangements are valid. This rearrangement can be prevented for particular operations by using the NoContraction decoration.

How is this not sufficiently clear? IMO it captures the same thing, while also being clear that `NoContract` also disallows algebraic rearrangement and this also affects other opcodes like `FMix`. And as I said before, different environments have different precision requirements so putting this Vulkan-specific text in the SPIR-V spec isn't appropriate.
I don't see how the revision 12 is more clear than revision 4, despite of the text you quote.

Also, my goal is to a) Have this regression fixed. and b) try to understand how this regression occurred in the first place in order to correct that.

I think one of the reasons why it may have occurred is due to a misunderstanding of what the fma instruction does and how it should be interpreted by the implementations. This misunderstanding may not have occurred if the documentation was more clear.

There are two elements that contributed to this regression:

- spirv-opt is treating SPIR-V Fma instructions as an optimization. However, the glsl language does not offer any guarantees about how fmas are executed outside precise statements.
- The Adreno compiler is interpreting SPIR-V Fma instructions literally and translating them to native multiply-add instructions.

In both cases there are different parties that have made mistakes interpreting the specification, which reinforces my belief that the specification could be more clear.
This was done for Swiftshader, but that is not as important. We will revert those changes to avoid the poor code generated by Adreno compilers.

As for why this change was done.

> spirv-opt is treating SPIR-V Fma instructions as an optimization. However, the glsl language does not offer any guarantees about how fmas are executed outside precise statements.

spirv-opt viewed it a neutral if you used the Fma or not because, without the nocontract, ICD compilers are free to use a hardware fma or split it up into two instructions in either case. Our view is that a reasonable ICD compiler would at worst be neutral. I do not view this as wrong. Since spirv-opt was used in a special way in Swiftshader, this optimization is what allowed it to consistently generate FMAs on the CPUs. We had a use case where it was a good idea to generate the FMAs, so we went ahead with it.

We cannot test every change on all of the different platforms that exists, and we rely on the community to report any oddities. Thanks for bringing it to our attention. We probably would have noticed it sooner, but the version of spirv-opt in the android NDK has not been updated since this change went in.

Fix patch:
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 5c68e291cd..5f83669940 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -1459,132 +1459,6 @@ FoldingRule FactorAddMuls() {
   };
 }
 
-// Replaces |inst| inplace with an FMA instruction |(x*y)+a|.
-void ReplaceWithFma(Instruction* inst, uint32_t x, uint32_t y, uint32_t a) {
-  uint32_t ext =
-      inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
-
-  if (ext == 0) {
-    inst->context()->AddExtInstImport("GLSL.std.450");
-    ext = inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
-    assert(ext != 0 &&
-           "Could not add the GLSL.std.450 extended instruction set");
-  }
-
-  std::vector<Operand> operands;
-  operands.push_back({SPV_OPERAND_TYPE_ID, {ext}});
-  operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {GLSLstd450Fma}});
-  operands.push_back({SPV_OPERAND_TYPE_ID, {x}});
-  operands.push_back({SPV_OPERAND_TYPE_ID, {y}});
-  operands.push_back({SPV_OPERAND_TYPE_ID, {a}});
-
-  inst->SetOpcode(spv::Op::OpExtInst);
-  inst->SetInOperands(std::move(operands));
-}
-
-// Folds a multiple and add into an Fma.
-//
-// Cases:
-// (x * y) + a = Fma x y a
-// a + (x * y) = Fma x y a
-bool MergeMulAddArithmetic(IRContext* context, Instruction* inst,
-                           const std::vector<const analysis::Constant*>&) {
-  assert(inst->opcode() == spv::Op::OpFAdd);
-
-  if (!inst->IsFloatingPointFoldingAllowed()) {
-    return false;
-  }
-
-  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
-  for (int i = 0; i < 2; i++) {
-    uint32_t op_id = inst->GetSingleWordInOperand(i);
-    Instruction* op_inst = def_use_mgr->GetDef(op_id);
-
-    if (op_inst->opcode() != spv::Op::OpFMul) {
-      continue;
-    }
-
-    if (!op_inst->IsFloatingPointFoldingAllowed()) {
-      continue;
-    }
-
-    uint32_t x = op_inst->GetSingleWordInOperand(0);
-    uint32_t y = op_inst->GetSingleWordInOperand(1);
-    uint32_t a = inst->GetSingleWordInOperand((i + 1) % 2);
-    ReplaceWithFma(inst, x, y, a);
-    return true;
-  }
-  return false;
-}
-
-// Replaces |sub| inplace with an FMA instruction |(x*y)+a| where |a| first gets
-// negated if |negate_addition| is true, otherwise |x| gets negated.
-void ReplaceWithFmaAndNegate(Instruction* sub, uint32_t x, uint32_t y,
-                             uint32_t a, bool negate_addition) {
-  uint32_t ext =
-      sub->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
-
-  if (ext == 0) {
-    sub->context()->AddExtInstImport("GLSL.std.450");
-    ext = sub->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
-    assert(ext != 0 &&
-           "Could not add the GLSL.std.450 extended instruction set");
-  }
-
-  InstructionBuilder ir_builder(
-      sub->context(), sub,
-      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
-
-  Instruction* neg = ir_builder.AddUnaryOp(sub->type_id(), spv::Op::OpFNegate,
-                                           negate_addition ? a : x);
-  uint32_t neg_op = neg->result_id();  // -a : -x
-
-  std::vector<Operand> operands;
-  operands.push_back({SPV_OPERAND_TYPE_ID, {ext}});
-  operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {GLSLstd450Fma}});
-  operands.push_back({SPV_OPERAND_TYPE_ID, {negate_addition ? x : neg_op}});
-  operands.push_back({SPV_OPERAND_TYPE_ID, {y}});
-  operands.push_back({SPV_OPERAND_TYPE_ID, {negate_addition ? neg_op : a}});
-
-  sub->SetOpcode(spv::Op::OpExtInst);
-  sub->SetInOperands(std::move(operands));
-}
-
-// Folds a multiply and subtract into an Fma and negation.
-//
-// Cases:
-// (x * y) - a = Fma x y -a
-// a - (x * y) = Fma -x y a
-bool MergeMulSubArithmetic(IRContext* context, Instruction* sub,
-                           const std::vector<const analysis::Constant*>&) {
-  assert(sub->opcode() == spv::Op::OpFSub);
-
-  if (!sub->IsFloatingPointFoldingAllowed()) {
-    return false;
-  }
-
-  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
-  for (int i = 0; i < 2; i++) {
-    uint32_t op_id = sub->GetSingleWordInOperand(i);
-    Instruction* mul = def_use_mgr->GetDef(op_id);
-
-    if (mul->opcode() != spv::Op::OpFMul) {
-      continue;
-    }
-
-    if (!mul->IsFloatingPointFoldingAllowed()) {
-      continue;
-    }
-
-    uint32_t x = mul->GetSingleWordInOperand(0);
-    uint32_t y = mul->GetSingleWordInOperand(1);
-    uint32_t a = sub->GetSingleWordInOperand((i + 1) % 2);
-    ReplaceWithFmaAndNegate(sub, x, y, a, i == 0);
-    return true;
-  }
-  return false;
-}
-
 FoldingRule IntMultipleBy1() {
   return [](IRContext*, Instruction* inst,
             const std::vector<const analysis::Constant*>& constants) {
@@ -2941,7 +2815,6 @@ void FoldingRules::AddFoldingRules() {
   rules_[spv::Op::OpFAdd].push_back(MergeAddSubArithmetic());
   rules_[spv::Op::OpFAdd].push_back(MergeGenericAddSubArithmetic());
   rules_[spv::Op::OpFAdd].push_back(FactorAddMuls());
-  rules_[spv::Op::OpFAdd].push_back(MergeMulAddArithmetic);
 
   rules_[spv::Op::OpFDiv].push_back(RedundantFDiv());
   rules_[spv::Op::OpFDiv].push_back(ReciprocalFDiv());
@@ -2962,7 +2835,6 @@ void FoldingRules::AddFoldingRules() {
   rules_[spv::Op::OpFSub].push_back(MergeSubNegateArithmetic());
   rules_[spv::Op::OpFSub].push_back(MergeSubAddArithmetic());
   rules_[spv::Op::OpFSub].push_back(MergeSubSubArithmetic());
-  rules_[spv::Op::OpFSub].push_back(MergeMulSubArithmetic);
 
   rules_[spv::Op::OpIAdd].push_back(RedundantIAdd());
   rules_[spv::Op::OpIAdd].push_back(MergeAddNegateArithmetic());


Test patch:
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index a4e0447c10..255449dbbf 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -7933,21 +7933,15 @@ ::testing::Values(
         3, true)
  ));
 
+// Issue #5658: The Adreno compiler does not handle 16-bit FMA instructions well.
+// We want to avoid this by not generating FMA. We decided to never generate
+// FMAs because, from a SPIR-V perspective, it is neutral. The ICD can generate
+// the FMA if it wants. The simplest code is no code.
 INSTANTIATE_TEST_SUITE_P(FmaGenerationMatchingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
-   // Test case 0: (x * y) + a = Fma(x, y, a)
+   // Test case 0: Don't fold (x * y) + a
    InstructionFoldingCase<bool>(
        Header() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
            "%main = OpFunction %void None %void_func\n" +
            "%main_lab = OpLabel\n" +
            "%x = OpVariable %_ptr_float Function\n" +
@@ -7961,20 +7955,10 @@ ::testing::Values(
            "OpStore %a %3\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       3, true),
-    // Test case 1:  a + (x * y) = Fma(x, y, a)
+       3, false),
+    // Test case 1: Don't fold a + (x * y)
    InstructionFoldingCase<bool>(
        Header() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
            "%main = OpFunction %void None %void_func\n" +
            "%main_lab = OpLabel\n" +
            "%x = OpVariable %_ptr_float Function\n" +
@@ -7988,20 +7972,10 @@ ::testing::Values(
            "OpStore %a %3\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       3, true),
-   // Test case 2: (x * y) + a = Fma(x, y, a) with vectors
+       3, false),
+   // Test case 2: Don't fold (x * y) + a with vectors
    InstructionFoldingCase<bool>(
        Header() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
            "%main = OpFunction %void None %void_func\n" +
            "%main_lab = OpLabel\n" +
            "%x = OpVariable %_ptr_v4float Function\n" +
@@ -8015,20 +7989,10 @@ ::testing::Values(
            "OpStore %a %3\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       3, true),
-    // Test case 3:  a + (x * y) = Fma(x, y, a) with vectors
+       3,false),
+    // Test case 3: Don't fold a + (x * y) with vectors
    InstructionFoldingCase<bool>(
        Header() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
            "%main = OpFunction %void None %void_func\n" +
            "%main_lab = OpLabel\n" +
            "%x = OpVariable %_ptr_float Function\n" +
@@ -8042,46 +8006,8 @@ ::testing::Values(
            "OpStore %a %3\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       3, true),
-    // Test 4: that the OpExtInstImport instruction is generated if it is missing.
-   InstructionFoldingCase<bool>(
-           std::string() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
-           "OpCapability Shader\n" +
-           "OpMemoryModel Logical GLSL450\n" +
-           "OpEntryPoint Fragment %main \"main\"\n" +
-           "OpExecutionMode %main OriginUpperLeft\n" +
-           "OpSource GLSL 140\n" +
-           "OpName %main \"main\"\n" +
-           "%void = OpTypeVoid\n" +
-           "%void_func = OpTypeFunction %void\n" +
-           "%bool = OpTypeBool\n" +
-           "%float = OpTypeFloat 32\n" +
-           "%_ptr_float = OpTypePointer Function %float\n" +
-           "%main = OpFunction %void None %void_func\n" +
-           "%main_lab = OpLabel\n" +
-           "%x = OpVariable %_ptr_float Function\n" +
-           "%y = OpVariable %_ptr_float Function\n" +
-           "%a = OpVariable %_ptr_float Function\n" +
-           "%lx = OpLoad %float %x\n" +
-           "%ly = OpLoad %float %y\n" +
-           "%mul = OpFMul %float %lx %ly\n" +
-           "%la = OpLoad %float %a\n" +
-           "%3 = OpFAdd %float %mul %la\n" +
-           "OpStore %a %3\n" +
-           "OpReturn\n" +
-           "OpFunctionEnd",
-       3, true),
-   // Test 5: Don't fold if the multiple is marked no contract.
+       3, false),
+   // Test 4: Don't fold if the multiple is marked no contract.
    InstructionFoldingCase<bool>(
        std::string() +
            "OpCapability Shader\n" +
@@ -8110,7 +8036,7 @@ ::testing::Values(
            "OpReturn\n" +
            "OpFunctionEnd",
        3, false),
-       // Test 6: Don't fold if the add is marked no contract.
+       // Test 5: Don't fold if the add is marked no contract.
        InstructionFoldingCase<bool>(
            std::string() +
                "OpCapability Shader\n" +
@@ -8139,20 +8065,9 @@ ::testing::Values(
                "OpReturn\n" +
                "OpFunctionEnd",
            3, false),
-    // Test case 7: (x * y) - a = Fma(x, y, -a)
+    // Test case 6: Don't fold (x * y) - a
     InstructionFoldingCase<bool>(
        Header() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[na:%\\w+]] = OpFNegate {{%\\w+}} [[la]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[na]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
            "%main = OpFunction %void None %void_func\n" +
            "%main_lab = OpLabel\n" +
            "%x = OpVariable %_ptr_float Function\n" +
@@ -8166,21 +8081,10 @@ ::testing::Values(
            "OpStore %a %3\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       3, true),
-   // Test case 8: a - (x * y) = Fma(-x, y, a)
+       3, false),
+   // Test case 7: Don't fold a - (x * y)
    InstructionFoldingCase<bool>(
        Header() +
-           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
-           "; CHECK: OpFunction\n" +
-           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
-           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
-           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
-           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
-           "; CHECK: [[nx:%\\w+]] = OpFNegate {{%\\w+}} [[lx]]\n" +
-           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[nx]] [[ly]] [[la]]\n" +
-           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
            "%main = OpFunction %void None %void_func\n" +
            "%main_lab = OpLabel\n" +
            "%x = OpVariable %_ptr_float Function\n" +
@@ -8194,7 +8098,7 @@ ::testing::Values(
            "OpStore %a %3\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       3, true)
+       3, false)
 ));
 
 using MatchingInstructionWithNoResultFoldingTest =