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>
Wed, 10 Jan 2018 18:17:25 +0000 (13:17 -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.

source/opt/ccp_pass.cpp
source/opt/fold.cpp
source/opt/fold.h
source/opt/instruction.cpp
source/opt/instruction.h

index dc8aecd..ed633ca 100644 (file)
@@ -120,58 +120,41 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(ir::Instruction* instr) {
     return MarkInstructionVarying(instr);
   }
 
-  // Otherwise, see if the RHS of the assignment folds into a constant value.
-  std::vector<uint32_t> cst_val_ids;
-  bool missing_constants = false;
-  bool varying_values = false;
-  instr->ForEachInId([this, &cst_val_ids, &missing_constants,
-                      &varying_values](uint32_t* op_id) {
+  // See if the RHS of the assignment folds into a constant value.
+  auto map_func = [this](uint32_t id) {
+    auto it = values_.find(id);
+    if (it == values_.end() || IsVaryingValue(it->second)) {
+      return id;
+    }
+    return it->second;
+  };
+  ir::Instruction* folded_inst =
+      opt::FoldInstructionToConstant(instr, map_func);
+  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;
+  }
+
+  // If not, see if there is a least one unknown operand to the instruction.  If
+  // so, we might be able to fold it later.
+  bool could_be_improved = false;
+  instr->ForEachInId([this, &could_be_improved](uint32_t* op_id) {
     auto it = values_.find(*op_id);
     if (it == values_.end()) {
-      missing_constants = true;
-      return;
-    } else if (IsVaryingValue(it->second)) {
-      varying_values = true;
+      could_be_improved = true;
       return;
     }
-    cst_val_ids.push_back(it->second);
   });
-
-  // If we did not find a constant value for every operand in the instruction,
-  // do not bother folding it.  Indicate that this instruction does not produce
-  // an interesting value for now.
-  if (missing_constants) {
+  if (could_be_improved) {
     return SSAPropagator::kNotInteresting;
   }
 
-  // If we found at least one varying value, the instruction will never fold
-  // into anything interesting.  Mark it varying.
-  if (varying_values) {
-    return MarkInstructionVarying(instr);
-  }
-
-  auto constants = const_mgr_->GetConstantsFromIds(cst_val_ids);
-  assert(constants.size() != 0 && "Found undeclared constants");
-
-  // If any of the constants are not supported by the folder, we will not be
-  // able to produce a constant out of this instruction.  Consider it varying
-  // in that case.
-  if (!std::all_of(constants.begin(), constants.end(),
-                   [](const analysis::Constant* cst) {
-                     return IsFoldableConstant(cst);
-                   })) {
-    return MarkInstructionVarying(instr);
-  }
-
-  // Otherwise, fold the instruction with all the operands to produce a new
-  // constant.
-  uint32_t result_val = FoldScalars(instr->opcode(), constants);
-  const analysis::Constant* result_const =
-      const_mgr_->GetConstant(const_mgr_->GetType(instr), {result_val});
-  ir::Instruction* const_decl =
-      const_mgr_->GetDefiningInstruction(result_const);
-  values_[instr->result_id()] = const_decl->result_id();
-  return SSAPropagator::kInteresting;
+  // Otherwise, we will never be able to fold this instruction, so mark it
+  // varying.
+  return MarkInstructionVarying(instr);
 }
 
 SSAPropagator::PropStatus CCPPass::VisitBranch(ir::Instruction* instr,
index 96f6640..97314d4 100644 (file)
@@ -14,6 +14,7 @@
 
 #include "fold.h"
 #include "def_use_manager.h"
+#include "ir_context.h"
 
 #include <cassert>
 #include <vector>
@@ -289,5 +290,65 @@ bool IsFoldableConstant(const analysis::Constant* cst) {
     return cst->AsNullConstant() != nullptr;
 }
 
+ir::Instruction* FoldInstructionToConstant(
+    ir::Instruction* inst, std::function<uint32_t(uint32_t)> id_map) {
+  if (!inst->IsFoldable()) {
+    return nullptr;
+  }
+
+  ir::IRContext* context = inst->context();
+  analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+
+  // Collect the values of the constant parameters.
+  std::vector<const analysis::Constant*> constants;
+  bool missing_constants = false;
+  inst->ForEachInId([&constants, &missing_constants, const_mgr,
+                     &id_map](uint32_t* op_id) {
+    uint32_t id = id_map(*op_id);
+    const analysis::Constant* const_op = const_mgr->FindDeclaredConstant(id);
+    if (!const_op || !IsFoldableConstant(const_op)) {
+      constants.push_back(nullptr);
+      missing_constants = true;
+      return;
+    }
+    constants.push_back(const_op);
+  });
+
+  // If all parameters are constant, fold the instruction to a constant.
+  if (!missing_constants) {
+    uint32_t result_val = FoldScalars(inst->opcode(), constants);
+    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;
+}
+
+bool IsFoldableType(ir::Instruction* type_inst) {
+  // Support 32-bit integers.
+  if (type_inst->opcode() == SpvOpTypeInt) {
+    return type_inst->GetSingleWordInOperand(0) == 32;
+  }
+  // Support booleans.
+  if (type_inst->opcode() == SpvOpTypeBool) {
+    return true;
+  }
+  // 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);
+  if (folded_inst != nullptr) {
+    return folded_inst;
+  }
+
+  // TODO: Add other folding opportunities that do not necessarily fold to a
+  // constant.
+  return nullptr;
+}
+
 }  // namespace opt
 }  // namespace spvtools
index 52954a5..32a083b 100644 (file)
@@ -59,6 +59,43 @@ bool IsFoldableOpcode(SpvOp opcode);
 // Returns true if |cst| is supported by FoldScalars and FoldVectors.
 bool IsFoldableConstant(const analysis::Constant* cst);
 
+// Returns true if |FoldInstructionToConstant| could fold an instruction whose
+// result type is |type_inst|.
+bool IsFoldableType(ir::Instruction* type_inst);
+
+// Tries to fold |inst| to a single constant, when the input ids to |inst| have
+// been substituted using |id_map|.  Returns a pointer to the OpConstant*
+// instruction if successful.  If necessary, a new constant instruction is
+// created and placed in the global values section.
+//
+// |id_map| is a function that takes one result id and returns another.  It can
+// be used for things like CCP where it is known that some ids contain a
+// constant, but the instruction itself has not been updated yet.  This can map
+// those ids to the appropriate constants.
+ir::Instruction* FoldInstructionToConstant(
+    ir::Instruction* inst, std::function<uint32_t(uint32_t)> id_map);
+
+// Tries to fold |inst| to a simpler instruction that computes the same value,
+// when the input ids to |inst| have been substituted using |id_map|.  Returns a
+// pointer to the simplified instruction if successful.  If necessary, a new
+// instruction is created and placed in the global values section, for
+// constants, or after |inst| for other instructions.
+//
+// |inst| must be an instruction that exists in the body of a function.
+//
+// |id_map| is a function that takes one result id and returns another.  It can
+// be used for things like CCP where it is known that some ids contain a
+// constant, but the instruction itself has not been updated yet.  This can map
+// those ids to the appropriate constants.
+ir::Instruction* FoldInstruction(ir::Instruction* inst,
+                                 std::function<uint32_t(uint32_t)> id_map);
+
+// 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);
+}
+
 }  // namespace opt
 }  // namespace spvtools
 
index 45b7d2f..2b83ca1 100644 (file)
@@ -468,7 +468,13 @@ bool Instruction::IsOpaqueType() const {
   }
 }
 
-bool Instruction::IsFoldable() const { return opt::IsFoldableOpcode(opcode()); }
+bool Instruction::IsFoldable() const {
+  if (!opt::IsFoldableOpcode(opcode())) {
+    return false;
+  }
+  Instruction* type = context()->get_def_use_mgr()->GetDef(type_id());
+  return opt::IsFoldableType(type);
+}
 
 }  // namespace ir
 }  // namespace spvtools
index 49836cd..61de1f9 100644 (file)
@@ -347,6 +347,10 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
   Instruction* InsertBefore(std::unique_ptr<Instruction>&& i);
   using utils::IntrusiveNodeBase<Instruction>::InsertBefore;
 
+  // Returns true if |this| is an instruction defining a constant, but not a
+  // Spec constant.
+  inline bool IsConstant() const;
+
  private:
   // Returns the total count of result type id and result id.
   uint32_t TypeResultIdCount() const {
@@ -545,6 +549,11 @@ bool Instruction::IsDecoration() const {
 bool Instruction::IsLoad() const { return spvOpcodeIsLoad(opcode()); }
 
 bool Instruction::IsAtomicOp() const { return spvOpcodeIsAtomicOp(opcode()); }
+
+bool Instruction::IsConstant() const {
+  return spvOpcodeIsConstant(opcode()) &&
+         !spvOpcodeIsScalarSpecConstant(opcode());
+}
 }  // namespace ir
 }  // namespace spvtools