Add generic folding function and use in CCP
authorSteven Perron <stevenperron@google.com>
Tue, 9 Jan 2018 17:45:46 +0000 (12:45 -0500)
committerSteven Perron <stevenperron@google.com>
Mon, 22 Jan 2018 19:26:49 +0000 (14:26 -0500)
The current folding routines have a very cumbersome interface, make them
harder to use, and not a obvious how to extend.

This change is to create a new interface for the folding routines, and
show how it can be used by calling it from CCP.

This does not make a significant change to the behaviour of CCP.  In
general it should produce the same code as before; however it is
possible that an instruction that takes 32-bit integers as inputs and
the result is not a 32-bit integer or bool will not be folded as before.

It seems like andriod has a problem with INT32_MAX and the like.  I'll
explicitly define those if the are not already defined.

source/opt/constants.h
source/opt/fold.cpp
source/opt/fold.h
test/opt/CMakeLists.txt
test/opt/fold_test.cpp [new file with mode: 0644]

index 84906b5..47cfa8a 100644 (file)
@@ -120,6 +120,31 @@ class IntConstant : public ScalarConstant {
   IntConstant* AsIntConstant() override { return this; }
   const IntConstant* AsIntConstant() const override { return this; }
 
+  int32_t GetS32BitValue() const {
+    // Relies on signed values smaller than 32-bit being sign extended.  See
+    // section 2.2.1 of the SPIR-V spec.
+    assert(words().size() == 1);
+    return words()[0];
+  }
+
+  uint32_t GetU32BitValue() const {
+    // Relies on unsigned values smaller than 32-bit being zero extended.  See
+    // section 2.2.1 of the SPIR-V spec.
+    assert(words().size() == 1);
+    return words()[0];
+  }
+
+  bool IsZero() const {
+    bool is_zero = true;
+    for (uint32_t v : words()) {
+      if (v != 0) {
+        is_zero = false;
+        break;
+      }
+    }
+    return is_zero;
+  }
+
   // Make a copy of this IntConstant instance.
   std::unique_ptr<IntConstant> CopyIntConstant() const {
     return MakeUnique<IntConstant>(type_->AsInteger(), words_);
index 97314d4..7492f8e 100644 (file)
 // limitations under the License.
 
 #include "fold.h"
+
 #include "def_use_manager.h"
 #include "ir_context.h"
 
 #include <cassert>
+#include <cstdint>
 #include <vector>
 
 namespace spvtools {
@@ -24,6 +26,18 @@ namespace opt {
 
 namespace {
 
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483648)
+#endif
+
+#ifndef INT32_MAX
+#define INT32_MAX 2147483647
+#endif
+
+#ifndef UINT32_MAX
+#define UINT32_MAX 0xffffffff /* 4294967295U */
+#endif
+
 // Returns the single-word result from performing the given unary operation on
 // the operand value which is passed in as a 32-bit word.
 uint32_t UnaryOperate(SpvOp opcode, uint32_t operand) {
@@ -195,6 +209,258 @@ uint32_t FoldScalars(SpvOp opcode,
   return OperateWords(opcode, operand_values_in_raw_words);
 }
 
+// Returns true if |inst| is a binary operation that takes two integers as
+// parameters and folds to a constant that can be represented as an unsigned
+// 32-bit value.  If |inst| can be folded, the resulting value is returned
+// in |*result|.  Valid result types for the instruction are any integer (signed
+// or unsigned) with 32-bits or less, or a boolean value.
+bool FoldBinaryIntegerOpToConstant(ir::Instruction* inst, uint32_t* result) {
+  SpvOp opcode = inst->opcode();
+  ir::IRContext* context = inst->context();
+  analysis::ConstantManager* const_manger = context->get_constant_mgr();
+
+  uint32_t ids[2];
+  const analysis::IntConstant* constants[2];
+  for (uint32_t i = 0; i < 2; i++) {
+    const ir::Operand* operand = &inst->GetInOperand(i);
+    if (operand->type != SPV_OPERAND_TYPE_ID) {
+      return false;
+    }
+    ids[i] = operand->words[0];
+    const analysis::Constant* constant =
+        const_manger->FindDeclaredConstant(ids[i]);
+    constants[i] = (constant != nullptr ? constant->AsIntConstant() : nullptr);
+  }
+
+  switch (opcode) {
+    // Arthimetics
+    case SpvOp::SpvOpIMul:
+      for (uint32_t i = 0; i < 2; i++) {
+        if (constants[i] != nullptr && constants[i]->IsZero()) {
+          *result = 0;
+          return true;
+        }
+      }
+      break;
+    case SpvOp::SpvOpUDiv:
+    case SpvOp::SpvOpSDiv:
+    case SpvOp::SpvOpSRem:
+    case SpvOp::SpvOpSMod:
+    case SpvOp::SpvOpUMod:
+      // This changes undefined behaviour (ie divide by 0) into a 0.
+      for (uint32_t i = 0; i < 2; i++) {
+        if (constants[i] != nullptr && constants[i]->IsZero()) {
+          *result = 0;
+          return true;
+        }
+      }
+      break;
+
+    // Shifting
+    case SpvOp::SpvOpShiftRightLogical:
+    case SpvOp::SpvOpShiftLeftLogical:
+      if (constants[1] != nullptr) {
+        // When shifting by a value larger than the size of the result, the
+        // result is undefined.  We are setting the undefined behaviour to a
+        // result of 0.
+        uint32_t shift_amount = constants[1]->GetU32BitValue();
+        if (shift_amount >= 32) {
+          *result = 0;
+          return true;
+        }
+      }
+      break;
+
+    // Bitwise operations
+    case SpvOp::SpvOpBitwiseOr:
+      for (uint32_t i = 0; i < 2; i++) {
+        if (constants[i] != nullptr) {
+          // TODO: Change the mask against a value based on the bit width of the
+          // instruction result type.  This way we can handle say 16-bit values
+          // as well.
+          uint32_t mask = constants[i]->GetU32BitValue();
+          if (mask == 0xFFFFFFFF) {
+            *result = 0xFFFFFFFF;
+            return true;
+          }
+        }
+      }
+      break;
+    case SpvOp::SpvOpBitwiseAnd:
+      for (uint32_t i = 0; i < 2; i++) {
+        if (constants[i] != nullptr) {
+          if (constants[i]->IsZero()) {
+            *result = 0;
+            return true;
+          }
+        }
+      }
+      break;
+
+    // Comparison
+    case SpvOp::SpvOpULessThan:
+      if (constants[0] != nullptr &&
+          constants[0]->GetU32BitValue() == UINT32_MAX) {
+        *result = false;
+        return true;
+      }
+      if (constants[1] != nullptr && constants[1]->GetU32BitValue() == 0) {
+        *result = false;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpSLessThan:
+      if (constants[0] != nullptr &&
+          constants[0]->GetS32BitValue() == INT32_MAX) {
+        *result = false;
+        return true;
+      }
+      if (constants[1] != nullptr &&
+          constants[1]->GetS32BitValue() == INT32_MIN) {
+        *result = false;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpUGreaterThan:
+      if (constants[0] != nullptr && constants[0]->IsZero()) {
+        *result = false;
+        return true;
+      }
+      if (constants[1] != nullptr &&
+          constants[1]->GetU32BitValue() == UINT32_MAX) {
+        *result = false;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpSGreaterThan:
+      if (constants[0] != nullptr &&
+          constants[0]->GetS32BitValue() == INT32_MIN) {
+        *result = false;
+        return true;
+      }
+      if (constants[1] != nullptr &&
+          constants[1]->GetS32BitValue() == INT32_MAX) {
+        *result = false;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpULessThanEqual:
+      if (constants[0] != nullptr && constants[0]->IsZero()) {
+        *result = true;
+        return true;
+      }
+      if (constants[1] != nullptr &&
+          constants[1]->GetU32BitValue() == UINT32_MAX) {
+        *result = true;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpSLessThanEqual:
+      if (constants[0] != nullptr &&
+          constants[0]->GetS32BitValue() == INT32_MIN) {
+        *result = true;
+        return true;
+      }
+      if (constants[1] != nullptr &&
+          constants[1]->GetS32BitValue() == INT32_MAX) {
+        *result = true;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpUGreaterThanEqual:
+      if (constants[0] != nullptr &&
+          constants[0]->GetU32BitValue() == UINT32_MAX) {
+        *result = true;
+        return true;
+      }
+      if (constants[1] != nullptr && constants[1]->GetU32BitValue() == 0) {
+        *result = true;
+        return true;
+      }
+      break;
+    case SpvOp::SpvOpSGreaterThanEqual:
+      if (constants[0] != nullptr &&
+          constants[0]->GetS32BitValue() == INT32_MAX) {
+        *result = true;
+        return true;
+      }
+      if (constants[1] != nullptr &&
+          constants[1]->GetS32BitValue() == INT32_MIN) {
+        *result = true;
+        return true;
+      }
+      break;
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if |inst| is a binary operation on two boolean values, and folds
+// to a constant boolean value.  If |inst| can be folded, the result value is
+// returned in |*result|.
+bool FoldBinaryBooleanOpToConstant(ir::Instruction* inst, uint32_t* result) {
+  SpvOp opcode = inst->opcode();
+  ir::IRContext* context = inst->context();
+  analysis::ConstantManager* const_manger = context->get_constant_mgr();
+
+  uint32_t ids[2];
+  const analysis::BoolConstant* constants[2];
+  for (uint32_t i = 0; i < 2; i++) {
+    const ir::Operand* operand = &inst->GetInOperand(i);
+    if (operand->type != SPV_OPERAND_TYPE_ID) {
+      return false;
+    }
+    ids[i] = operand->words[0];
+    const analysis::Constant* constant =
+        const_manger->FindDeclaredConstant(ids[i]);
+    constants[i] = (constant != nullptr ? constant->AsBoolConstant() : nullptr);
+  }
+
+  switch (opcode) {
+      // Logical
+    case SpvOp::SpvOpLogicalOr:
+      for (uint32_t i = 0; i < 2; i++) {
+        if (constants[i] != nullptr) {
+          if (constants[i]->value()) {
+            *result = true;
+            return true;
+          }
+        }
+      }
+      break;
+    case SpvOp::SpvOpLogicalAnd:
+      for (uint32_t i = 0; i < 2; i++) {
+        if (constants[i] != nullptr) {
+          if (!constants[i]->value()) {
+            *result = false;
+            return true;
+          }
+        }
+      }
+      break;
+
+    default:
+      break;
+  }
+  return false;
+}
+
+// Returns true if |inst| can be folded to an constant.  If it can, the value
+// is returned in |result|.  If not, |result| is unchanged.  It is assumed that
+// not all operands are constant.  Those cases are handled by |FoldScalar|.
+bool FoldIntegerOpToConstant(ir::Instruction* inst, uint32_t* result) {
+  assert(IsFoldableOpcode(inst->opcode()) &&
+         "Unhandled instruction opcode in FoldScalars");
+  switch (inst->NumInOperands()) {
+    case 2:
+      return FoldBinaryIntegerOpToConstant(inst, result) ||
+             FoldBinaryBooleanOpToConstant(inst, result);
+    default:
+      return false;
+  }
+}
+
 std::vector<uint32_t> FoldVectors(
     SpvOp opcode, uint32_t num_dims,
     const std::vector<const analysis::Constant*>& operands) {
@@ -314,15 +580,24 @@ ir::Instruction* FoldInstructionToConstant(
     constants.push_back(const_op);
   });
 
+  uint32_t result_val = 0;
+  bool successful = false;
   // If all parameters are constant, fold the instruction to a constant.
   if (!missing_constants) {
-    uint32_t result_val = FoldScalars(inst->opcode(), constants);
+    result_val = FoldScalars(inst->opcode(), constants);
+    successful = true;
+  }
+
+  if (!successful) {
+    successful = FoldIntegerOpToConstant(inst, &result_val);
+  }
+
+  if (successful) {
     const analysis::Constant* result_const =
         const_mgr->GetConstant(const_mgr->GetType(inst), {result_val});
     return const_mgr->GetDefiningInstruction(result_const);
   }
 
-  // TODO: Add other folding opportunities that will generate a constant.
   return nullptr;
 }
 
@@ -338,6 +613,7 @@ bool IsFoldableType(ir::Instruction* type_inst) {
   // Nothing else yet.
   return false;
 }
+
 ir::Instruction* FoldInstruction(ir::Instruction* inst,
                                  std::function<uint32_t(uint32_t)> id_map) {
   ir::Instruction* folded_inst = FoldInstructionToConstant(inst, id_map);
index 32a083b..0438ccf 100644 (file)
@@ -93,7 +93,7 @@ ir::Instruction* FoldInstruction(ir::Instruction* inst,
 // The same as above when |id_map| is the identity function.
 inline ir::Instruction* FoldInstruction(ir::Instruction* inst) {
   auto identity_map = [](uint32_t id) { return id; };
-  return FoldInstructionToConstant(inst, identity_map);
+  return FoldInstruction(inst, identity_map);
 }
 
 }  // namespace opt
index 7f8f70f..b0af324 100644 (file)
@@ -271,3 +271,8 @@ add_spvtools_unittest(TARGET ir_builder
   SRCS ir_builder.cpp
   LIBS SPIRV-Tools-opt
 )
+
+add_spvtools_unittest(TARGET instruction_folding
+  SRCS fold_test.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt
+)
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
new file mode 100644 (file)
index 0000000..67de442
--- /dev/null
@@ -0,0 +1,991 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <memory>
+#include <unordered_set>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <opt/fold.h>
+
+#include "opt/build_module.h"
+#include "opt/def_use_manager.h"
+#include "opt/ir_context.h"
+#include "opt/module.h"
+#include "pass_utils.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace {
+
+using ::testing::Contains;
+
+using namespace spvtools;
+using spvtools::opt::analysis::DefUseManager;
+
+template <class ResultType>
+struct InstructionFoldingCase {
+  InstructionFoldingCase(const std::string& tb, uint32_t id, ResultType result)
+      : test_body(tb), id_to_fold(id), expected_result(result) {}
+
+  std::string test_body;
+  uint32_t id_to_fold;
+  ResultType expected_result;
+};
+
+using IntegerInstructionFoldingTest =
+    ::testing::TestWithParam<InstructionFoldingCase<uint32_t>>;
+
+TEST_P(IntegerInstructionFoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  inst = opt::FoldInstruction(inst);
+
+  // Make sure the instruction folded as expected.
+  EXPECT_NE(inst, nullptr);
+  if (inst != nullptr) {
+    EXPECT_EQ(inst->opcode(), SpvOpConstant);
+    opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+    const opt::analysis::IntConstant* result =
+        const_mrg->GetConstantFromInst(inst)->AsIntConstant();
+    EXPECT_NE(result, nullptr);
+    if (result != nullptr) {
+      EXPECT_EQ(result->GetU32BitValue(), tc.expected_result);
+    }
+  }
+}
+
+// Returns a common SPIR-V header for all of the test that follow.
+const std::string& Header() {
+  static const std::string header = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+%void = OpTypeVoid
+%void_func = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%short = OpTypeInt 16 1
+%int = OpTypeInt 32 1
+%long = OpTypeInt 64 1
+%uint = OpTypeInt 32 1
+%_ptr_int = OpTypePointer Function %int
+%_ptr_uint = OpTypePointer Function %uint
+%_ptr_bool = OpTypePointer Function %bool
+%short_0 = OpConstant %short 0
+%short_3 = OpConstant %short 3
+%int_0 = OpConstant %int 0
+%int_3 = OpConstant %int 3
+%int_min = OpConstant %int -2147483648
+%int_max = OpConstant %int 2147483647
+%long_0 = OpConstant %long 0
+%long_3 = OpConstant %long 3
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_32 = OpConstant %uint 32
+%uint_max = OpConstant %uint -1
+)";
+
+  return header;
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest,
+                        ::testing::Values(
+  // Test case 0: fold 0*n
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+    "%main_lab = OpLabel\n" +
+           "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+           "%2 = OpIMul %int %int_0 %load\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+    2, 0),
+  // Test case 1: fold n*0
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpIMul %int %load %int_0\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 2: fold 0/n (signed)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSDiv %int %int_0 %load\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+        2, 0),
+  // Test case 3: fold n/0 (signed)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSDiv %int %load %int_0\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 4: fold 0/n (unsigned)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_uint Function\n" +
+        "%load = OpLoad %uint %n\n" +
+        "%2 = OpUDiv %uint %uint_0 %load\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 5: fold n/0 (unsigned)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSDiv %int %load %int_0\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 6: fold 0 remainder n
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSRem %int %int_0 %load\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 7: fold n remainder 0
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSRem %int %load %int_0\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 8: fold 0%n (signed)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSMod %int %int_0 %load\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 9: fold n%0 (signed)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_int Function\n" +
+        "%load = OpLoad %int %n\n" +
+        "%2 = OpSMod %int %load %int_0\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 10: fold 0%n (unsigned)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_uint Function\n" +
+        "%load = OpLoad %uint %n\n" +
+        "%2 = OpUMod %uint %uint_0 %load\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 11: fold n%0 (unsigned)
+  InstructionFoldingCase<uint32_t>(
+    Header() + "%main = OpFunction %void None %void_func\n" +
+        "%main_lab = OpLabel\n" +
+        "%n = OpVariable %_ptr_uint Function\n" +
+        "%load = OpLoad %uint %n\n" +
+        "%2 = OpUMod %uint %load %uint_0\n" +
+        "OpReturn\n" +
+        "OpFunctionEnd",
+    2, 0),
+  // Test case 12: fold n << 32
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpShiftLeftLogical %uint %load %uint_32\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 13: fold n >> 32
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpShiftRightLogical %uint %load %uint_32\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 14: fold n | 0xFFFFFFFF
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+  "%main_lab = OpLabel\n" +
+  "%n = OpVariable %_ptr_uint Function\n" +
+  "%load = OpLoad %uint %n\n" +
+  "%2 = OpBitwiseOr %uint %load %uint_max\n" +
+  "OpReturn\n" +
+  "OpFunctionEnd",
+  2, 0xFFFFFFFF),
+  // Test case 15: fold 0xFFFFFFFF | n
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpBitwiseOr %uint %uint_max %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0xFFFFFFFF),
+  // Test case 16: fold n & 0
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpBitwiseAnd %uint %load %uint_0\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0)
+));
+// clang-format on
+
+using BooleanInstructionFoldingTest =
+    ::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(BooleanInstructionFoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  inst = opt::FoldInstruction(inst);
+
+  // Make sure the instruction folded as expected.
+  EXPECT_NE(inst, nullptr);
+  if (inst != nullptr) {
+    std::vector<SpvOp> bool_opcodes = {SpvOpConstantTrue, SpvOpConstantFalse};
+    EXPECT_THAT(bool_opcodes, Contains(inst->opcode()));
+    opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+    const opt::analysis::BoolConstant* result =
+        const_mrg->GetConstantFromInst(inst)->AsBoolConstant();
+    EXPECT_NE(result, nullptr);
+    if (result != nullptr) {
+      EXPECT_EQ(result->value(), tc.expected_result);
+    }
+  }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTest,
+                        ::testing::Values(
+  // Test case 0: fold true || n
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_bool Function\n" +
+          "%load = OpLoad %bool %n\n" +
+          "%2 = OpLogicalOr %bool %true %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 1: fold n || true
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_bool Function\n" +
+          "%load = OpLoad %bool %n\n" +
+          "%2 = OpLogicalOr %bool %load %true\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 2: fold false && n
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_bool Function\n" +
+          "%load = OpLoad %bool %n\n" +
+          "%2 = OpLogicalAnd %bool %false %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 3: fold n && false
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_bool Function\n" +
+          "%load = OpLoad %bool %n\n" +
+          "%2 = OpLogicalAnd %bool %load %false\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 4: fold n < 0 (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpULessThan %bool %load %uint_0\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 5: fold UINT_MAX < n (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpULessThan %bool %uint_max %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 6: fold INT_MAX < n (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSLessThan %bool %int_max %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 7: fold n < INT_MIN (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSLessThan %bool %load %int_min\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 8: fold 0 > n (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpUGreaterThan %bool %uint_0 %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 9: fold n > UINT_MAX (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpUGreaterThan %bool %load %uint_max\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 10: fold n > INT_MAX (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSGreaterThan %bool %load %int_max\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 11: fold INT_MIN > n (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpSGreaterThan %bool %int_min %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, false),
+  // Test case 12: fold 0 <= n (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpULessThanEqual %bool %uint_0 %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 13: fold n <= UINT_MAX (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpULessThanEqual %bool %load %uint_max\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 14: fold INT_MIN <= n (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSLessThanEqual %bool %int_min %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 15: fold n <= INT_MAX (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSLessThanEqual %bool %load %int_max\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 16: fold n >= 0 (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpUGreaterThanEqual %bool %load %uint_0\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 17: fold UINT_MAX >= n (unsigned)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load = OpLoad %uint %n\n" +
+          "%2 = OpUGreaterThanEqual %bool %uint_max %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 18: fold n >= INT_MIN (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSGreaterThanEqual %bool %load %int_min\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true),
+  // Test case 19: fold INT_MAX >= n (signed)
+  InstructionFoldingCase<bool>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpSGreaterThanEqual %bool %int_max %load\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, true)
+));
+// clang-format on
+
+using InstructionNotFoldedTest =
+    ::testing::TestWithParam<InstructionFoldingCase<void*>>;
+
+TEST_P(InstructionNotFoldedTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  inst = opt::FoldInstruction(inst);
+
+  // Make sure the instruction folded as expected.
+  EXPECT_EQ(inst, nullptr);
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, InstructionNotFoldedTest,
+  ::testing::Values(
+  // Test case 0: Don't fold n * m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpIMul %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 1: Don't fold n / m (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpUDiv %uint %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 2: Don't fold n / m (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpSDiv %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 3: Don't fold n remainder m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpSRem %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 4: Don't fold n % m (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpSMod %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 5: Don't fold n % m (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpUMod %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+    // Test case 6: Don't fold n << m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpShiftRightLogical %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 7: Don't fold n >> m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpShiftLeftLogical %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 8: Don't fold n | m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpBitwiseOr %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 9: Don't fold n & m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpBitwiseAnd %int %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 10: Don't fold n < m (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpULessThan %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 11: Don't fold n > m (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpUGreaterThan %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 12: Don't fold n <= m (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpULessThanEqual %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 13: Don't fold n >= m (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%m = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%load_m = OpLoad %uint %m\n" +
+          "%2 = OpUGreaterThanEqual %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 14: Don't fold n < m (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpULessThan %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 15: Don't fold n > m (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpUGreaterThan %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 16: Don't fold n <= m (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpULessThanEqual %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 17: Don't fold n >= m (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%m = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%load_m = OpLoad %int %m\n" +
+          "%2 = OpUGreaterThanEqual %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 18: Don't fold n || m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_bool Function\n" +
+          "%m = OpVariable %_ptr_bool Function\n" +
+          "%load_n = OpLoad %bool %n\n" +
+          "%load_m = OpLoad %bool %m\n" +
+          "%2 = OpLogicalOr %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 19: Don't fold n && m
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_bool Function\n" +
+          "%m = OpVariable %_ptr_bool Function\n" +
+          "%load_n = OpLoad %bool %n\n" +
+          "%load_m = OpLoad %bool %m\n" +
+          "%2 = OpLogicalAnd %bool %load_n %load_m\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 20: Don't fold n * 3
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpIMul %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 21: Don't fold n / 3 (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpUDiv %uint %load_n %uint_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 22: Don't fold n / 3 (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpSDiv %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 23: Don't fold n remainder 3
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpSRem %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 24: Don't fold n % 3 (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpSMod %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 25: Don't fold n % 3 (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpUMod %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 26: Don't fold n << 3
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpShiftRightLogical %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 27: Don't fold n >> 3
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpShiftLeftLogical %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 28: Don't fold n | 3
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpBitwiseOr %int %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 29: Don't fold n & 3
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpBitwiseAnd %uint %load_n %uint_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 30: Don't fold n < 3 (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpULessThan %bool %load_n %uint_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 31: Don't fold n > 3 (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpUGreaterThan %bool %load_n %uint_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 32: Don't fold n <= 3 (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpULessThanEqual %bool %load_n %uint_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 33: Don't fold n >= 3 (unsigned)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_uint Function\n" +
+          "%load_n = OpLoad %uint %n\n" +
+          "%2 = OpUGreaterThanEqual %bool %load_n %uint_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 34: Don't fold n < 3 (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpULessThan %bool %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 35: Don't fold n > 3 (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpUGreaterThan %bool %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 36: Don't fold n <= 3 (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpULessThanEqual %bool %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 37: Don't fold n >= 3 (signed)
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load_n = OpLoad %int %n\n" +
+          "%2 = OpUGreaterThanEqual %bool %load_n %int_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 38: Don't fold 0 + 3 (long), bad length
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpIAdd %long %long_0 %long_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr),
+  // Test case 39: Don't fold 0 + 3 (short), bad length
+  InstructionFoldingCase<void*>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpIAdd %short %short_0 %short_3\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, nullptr)
+  ));
+// clang-format on
+}  // anonymous namespace