Coverage Report

Created: 2026-08-14 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/folding_rules.cpp
Line
Count
Source
1
// Copyright (c) 2018 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "source/opt/folding_rules.h"
16
17
#include <optional>
18
#include <utility>
19
20
#include "ir_builder.h"
21
#include "source/latest_version_glsl_std_450_header.h"
22
#include "source/opt/ir_context.h"
23
#include "source/spirv_constant.h"
24
#include "source/spirv_target_env.h"
25
26
namespace spvtools {
27
namespace opt {
28
namespace {
29
30
constexpr uint32_t kExtractCompositeIdInIdx = 0;
31
constexpr uint32_t kInsertObjectIdInIdx = 0;
32
constexpr uint32_t kInsertCompositeIdInIdx = 1;
33
constexpr uint32_t kExtInstSetIdInIdx = 0;
34
constexpr uint32_t kExtInstInstructionInIdx = 1;
35
constexpr uint32_t kFMixXIdInIdx = 2;
36
constexpr uint32_t kFMixYIdInIdx = 3;
37
constexpr uint32_t kFMixAIdInIdx = 4;
38
constexpr uint32_t kStoreObjectInIdx = 1;
39
40
// Some image instructions may contain an "image operands" argument.
41
// Returns the operand index for the "image operands".
42
// Returns -1 if the instruction does not have image operands.
43
237k
int32_t ImageOperandsMaskInOperandIndex(Instruction* inst) {
44
237k
  const auto opcode = inst->opcode();
45
237k
  switch (opcode) {
46
237k
    case spv::Op::OpImageSampleImplicitLod:
47
237k
    case spv::Op::OpImageSampleExplicitLod:
48
237k
    case spv::Op::OpImageSampleProjImplicitLod:
49
237k
    case spv::Op::OpImageSampleProjExplicitLod:
50
237k
    case spv::Op::OpImageFetch:
51
237k
    case spv::Op::OpImageRead:
52
237k
    case spv::Op::OpImageSparseSampleImplicitLod:
53
237k
    case spv::Op::OpImageSparseSampleExplicitLod:
54
237k
    case spv::Op::OpImageSparseSampleProjImplicitLod:
55
237k
    case spv::Op::OpImageSparseSampleProjExplicitLod:
56
237k
    case spv::Op::OpImageSparseFetch:
57
237k
    case spv::Op::OpImageSparseRead:
58
237k
      return inst->NumOperands() > 4 ? 2 : -1;
59
8
    case spv::Op::OpImageSampleDrefImplicitLod:
60
8
    case spv::Op::OpImageSampleDrefExplicitLod:
61
8
    case spv::Op::OpImageSampleProjDrefImplicitLod:
62
8
    case spv::Op::OpImageSampleProjDrefExplicitLod:
63
8
    case spv::Op::OpImageGather:
64
8
    case spv::Op::OpImageDrefGather:
65
8
    case spv::Op::OpImageSparseSampleDrefImplicitLod:
66
8
    case spv::Op::OpImageSparseSampleDrefExplicitLod:
67
8
    case spv::Op::OpImageSparseSampleProjDrefImplicitLod:
68
8
    case spv::Op::OpImageSparseSampleProjDrefExplicitLod:
69
8
    case spv::Op::OpImageSparseGather:
70
8
    case spv::Op::OpImageSparseDrefGather:
71
8
      return inst->NumOperands() > 5 ? 3 : -1;
72
0
    case spv::Op::OpImageWrite:
73
0
      return inst->NumOperands() > 3 ? 3 : -1;
74
0
    default:
75
0
      return -1;
76
237k
  }
77
237k
}
78
79
// Returns the element width of |type|.
80
5.18M
uint32_t ElementWidth(const analysis::Type* type) {
81
5.18M
  if (const analysis::CooperativeVectorNV* coopvec_type =
82
5.18M
          type->AsCooperativeVectorNV()) {
83
0
    return ElementWidth(coopvec_type->component_type());
84
5.18M
  } else if (const analysis::Vector* vec_type = type->AsVector()) {
85
1.85M
    return ElementWidth(vec_type->element_type());
86
3.32M
  } else if (const analysis::Float* float_type = type->AsFloat()) {
87
2.75M
    return float_type->width();
88
2.75M
  } else {
89
571k
    assert(type->AsInteger());
90
571k
    return type->AsInteger()->width();
91
571k
  }
92
5.18M
}
93
94
// Returns true if |type| is Float or a vector of Float.
95
5.43M
bool HasFloatingPoint(const analysis::Type* type) {
96
5.43M
  if (type->AsFloat()) {
97
1.75M
    return true;
98
3.67M
  } else if (const analysis::Vector* vec_type = type->AsVector()) {
99
2.98M
    return vec_type->element_type()->AsFloat() != nullptr;
100
2.98M
  }
101
102
693k
  return false;
103
5.43M
}
104
105
// Returns false if |val| is NaN, infinite or subnormal.
106
template <typename T>
107
115k
bool IsValidResult(T val) {
108
115k
  int classified = std::fpclassify(val);
109
115k
  switch (classified) {
110
6.37k
    case FP_NAN:
111
25.2k
    case FP_INFINITE:
112
27.8k
    case FP_SUBNORMAL:
113
27.8k
      return false;
114
88.0k
    default:
115
88.0k
      return true;
116
115k
  }
117
115k
}
Unexecuted instantiation: folding_rules.cpp:bool spvtools::opt::(anonymous namespace)::IsValidResult<double>(double)
folding_rules.cpp:bool spvtools::opt::(anonymous namespace)::IsValidResult<float>(float)
Line
Count
Source
107
115k
bool IsValidResult(T val) {
108
115k
  int classified = std::fpclassify(val);
109
115k
  switch (classified) {
110
6.37k
    case FP_NAN:
111
25.2k
    case FP_INFINITE:
112
27.8k
    case FP_SUBNORMAL:
113
27.8k
      return false;
114
88.0k
    default:
115
88.0k
      return true;
116
115k
  }
117
115k
}
118
119
const analysis::Constant* ConstInput(
120
3.80M
    const std::vector<const analysis::Constant*>& constants) {
121
3.80M
  return constants[0] ? constants[0] : constants[1];
122
3.80M
}
123
124
Instruction* NonConstInput(IRContext* context, const analysis::Constant* c,
125
1.79M
                           Instruction* inst) {
126
1.79M
  uint32_t in_op = c ? 1u : 0u;
127
1.79M
  return context->get_def_use_mgr()->GetDef(
128
1.79M
      inst->GetSingleWordInOperand(in_op));
129
1.79M
}
130
131
0
std::vector<uint32_t> ExtractInts(uint64_t val) {
132
0
  std::vector<uint32_t> words;
133
0
  words.push_back(static_cast<uint32_t>(val));
134
0
  words.push_back(static_cast<uint32_t>(val >> 32));
135
0
  return words;
136
0
}
137
138
std::vector<uint32_t> GetWordsFromScalarIntConstant(
139
1.58k
    const analysis::IntConstant* c) {
140
1.58k
  assert(c != nullptr);
141
1.58k
  uint32_t width = c->type()->AsInteger()->width();
142
1.58k
  assert(width == 8 || width == 16 || width == 32 || width == 64);
143
1.58k
  if (width == 64) {
144
0
    uint64_t uval = static_cast<uint64_t>(c->GetU64());
145
0
    return ExtractInts(uval);
146
0
  }
147
  // Section 2.2.1 of the SPIR-V spec guarantees that all integer types
148
  // smaller than 32-bits are automatically zero or sign extended to 32-bits.
149
1.58k
  return {c->GetU32BitValue()};
150
1.58k
}
151
152
std::vector<uint32_t> GetWordsFromScalarFloatConstant(
153
377
    const analysis::FloatConstant* c) {
154
377
  assert(c != nullptr);
155
377
  uint32_t width = c->type()->AsFloat()->width();
156
377
  assert(width == 16 || width == 32 || width == 64);
157
377
  if (width == 64) {
158
0
    utils::FloatProxy<double> result(c->GetDouble());
159
0
    return result.GetWords();
160
0
  }
161
  // Section 2.2.1 of the SPIR-V spec guarantees that all floating-point types
162
  // smaller than 32-bits are automatically zero extended to 32-bits.
163
377
  return {c->GetU32BitValue()};
164
377
}
165
166
std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant(
167
2.68k
    analysis::ConstantManager* const_mgr, const analysis::Constant* c) {
168
2.68k
  if (const auto* float_constant = c->AsFloatConstant()) {
169
377
    return GetWordsFromScalarFloatConstant(float_constant);
170
2.31k
  } else if (const auto* int_constant = c->AsIntConstant()) {
171
1.58k
    return GetWordsFromScalarIntConstant(int_constant);
172
1.58k
  } else if (const auto* vec_constant = c->AsVectorConstant()) {
173
117
    std::vector<uint32_t> words;
174
    // Retrieve all the components as 32bit words.
175
425
    for (const auto* comp : vec_constant->GetComponents()) {
176
425
      auto comp_in_words =
177
425
          GetWordsFromNumericScalarOrVectorConstant(const_mgr, comp);
178
425
      words.insert(words.end(), comp_in_words.begin(), comp_in_words.end());
179
425
    }
180
181
117
    if (ElementWidth(c->type()) >= 32) {
182
117
      return words;
183
117
    }
184
    // Check the element width and concactenate if the width is less than 32.
185
0
    if (ElementWidth(c->type()) == 8) {
186
0
      assert(words.size() <= 4);
187
      // Each 32-bit word will comprise 4 8-bit integers.
188
      // reverse the order when compacting.
189
0
      uint32_t compacted_word = 0;
190
0
      for (int32_t i = static_cast<int32_t>(words.size()) - 1; i >= 0; --i) {
191
0
        compacted_word <<= 8;
192
0
        compacted_word |= (words[i] & 0xFF);
193
0
      }
194
0
      return {compacted_word};
195
0
    } else if (ElementWidth(c->type()) == 16) {
196
0
      assert(words.size() <= 4);
197
0
      std::vector<uint32_t> compacted_words;
198
      // Each 32-bit word will comprise 2 16-bit integers.
199
      // reverse the order pair-wise when compacting.
200
0
      for (uint32_t i = 0; i < words.size(); i += 2) {
201
0
        uint32_t word1 = words[i];
202
0
        uint32_t word2 = (i + 1 < words.size()) ? words[i + 1] : 0;
203
0
        uint32_t compacted_word = (word2 << 16) | (word1 & 0xFFFF);
204
0
        compacted_words.push_back(compacted_word);
205
0
      }
206
0
      return compacted_words;
207
0
    }
208
0
    assert(false && "Unhandled element width");
209
609
  } else if (c->AsNullConstant()) {
210
609
    uint32_t num_elements = 1;
211
212
609
    if (const auto* vec_type = c->type()->AsVector()) {
213
0
      num_elements = vec_type->element_count();
214
0
    }
215
216
    // We need to check the element width to determine how many 32-bit words are
217
    // needed.
218
609
    uint32_t element_width = ElementWidth(c->type());
219
609
    if (element_width < 32) {
220
0
      num_elements = (num_elements + 1) / 2;
221
609
    } else if (element_width == 64) {
222
0
      num_elements = num_elements * 2;
223
0
    }
224
609
    return std::vector<uint32_t>(num_elements, 0);
225
609
  }
226
0
  return {};
227
2.68k
}
228
229
const analysis::Constant* ConvertWordsToNumericScalarOrVectorConstant(
230
    analysis::ConstantManager* const_mgr, const std::vector<uint32_t>& words,
231
2.26k
    const analysis::Type* type) {
232
2.26k
  const spvtools::opt::analysis::Integer* int_type = type->AsInteger();
233
234
2.26k
  if (int_type && int_type->width() <= 32) {
235
1.14k
    assert(words.size() == 1);
236
1.14k
    return const_mgr->GenerateIntegerConstant(int_type, words[0]);
237
1.14k
  }
238
239
1.11k
  if (int_type || type->AsFloat()) return const_mgr->GetConstant(type, words);
240
117
  if (const auto* vec_type = type->AsVector())
241
117
    return const_mgr->GetNumericVectorConstantWithWords(vec_type, words);
242
0
  return nullptr;
243
117
}
244
245
// Returns the negation of |c|. |c| must be a 32 or 64 bit floating point
246
// constant.
247
uint32_t NegateFloatingPointConstant(analysis::ConstantManager* const_mgr,
248
2.52k
                                     const analysis::Constant* c) {
249
2.52k
  assert(c);
250
2.52k
  assert(c->type()->AsFloat());
251
2.52k
  uint32_t width = c->type()->AsFloat()->width();
252
2.52k
  assert(width == 32 || width == 64);
253
2.52k
  std::vector<uint32_t> words;
254
2.52k
  if (width == 64) {
255
0
    utils::FloatProxy<double> result(c->GetDouble() * -1.0);
256
0
    words = result.GetWords();
257
2.52k
  } else {
258
2.52k
    utils::FloatProxy<float> result(c->GetFloat() * -1.0f);
259
2.52k
    words = result.GetWords();
260
2.52k
  }
261
262
2.52k
  const analysis::Constant* negated_const =
263
2.52k
      const_mgr->GetConstant(c->type(), std::move(words));
264
2.52k
  return const_mgr->GetDefiningInstruction(negated_const)->result_id();
265
2.52k
}
266
267
// Negates the integer constant |c|. Returns the id of the defining instruction.
268
uint32_t NegateIntegerConstant(analysis::ConstantManager* const_mgr,
269
630
                               const analysis::Constant* c) {
270
630
  assert(c);
271
630
  assert(c->type()->AsInteger());
272
630
  uint32_t width = c->type()->AsInteger()->width();
273
630
  assert(width == 32 || width == 64);
274
630
  std::vector<uint32_t> words;
275
630
  if (width == 64) {
276
0
    uint64_t uval = static_cast<uint64_t>(0 - c->GetU64());
277
0
    words = ExtractInts(uval);
278
630
  } else {
279
630
    words.push_back(static_cast<uint32_t>(0 - c->GetU32()));
280
630
  }
281
282
630
  const analysis::Constant* negated_const =
283
630
      const_mgr->GetConstant(c->type(), std::move(words));
284
630
  return const_mgr->GetDefiningInstruction(negated_const)->result_id();
285
630
}
286
287
// Negates the vector constant |c|. Returns the id of the defining instruction.
288
uint32_t NegateVectorConstant(analysis::ConstantManager* const_mgr,
289
684
                              const analysis::Constant* c) {
290
684
  assert(const_mgr && c);
291
684
  assert(c->type()->AsVector());
292
684
  if (c->AsNullConstant()) {
293
    // 0.0 vs -0.0 shouldn't matter.
294
0
    return const_mgr->GetDefiningInstruction(c)->result_id();
295
684
  } else {
296
684
    const analysis::Type* component_type =
297
684
        c->AsVectorConstant()->component_type();
298
684
    std::vector<uint32_t> words;
299
1.36k
    for (auto& comp : c->AsVectorConstant()->GetComponents()) {
300
1.36k
      if (component_type->AsFloat()) {
301
1.36k
        words.push_back(NegateFloatingPointConstant(const_mgr, comp));
302
1.36k
      } else {
303
0
        assert(component_type->AsInteger());
304
0
        words.push_back(NegateIntegerConstant(const_mgr, comp));
305
0
      }
306
1.36k
    }
307
308
684
    const analysis::Constant* negated_const =
309
684
        const_mgr->GetConstant(c->type(), std::move(words));
310
684
    return const_mgr->GetDefiningInstruction(negated_const)->result_id();
311
684
  }
312
684
}
313
314
// Negates |c|. Returns the id of the defining instruction.
315
uint32_t NegateConstant(analysis::ConstantManager* const_mgr,
316
2.46k
                        const analysis::Constant* c) {
317
2.46k
  if (c->type()->AsVector()) {
318
684
    return NegateVectorConstant(const_mgr, c);
319
1.78k
  } else if (c->type()->AsFloat()) {
320
1.15k
    return NegateFloatingPointConstant(const_mgr, c);
321
1.15k
  } else {
322
630
    assert(c->type()->AsInteger());
323
630
    return NegateIntegerConstant(const_mgr, c);
324
630
  }
325
2.46k
}
326
327
// Takes the reciprocal of |c|. |c|'s type must be Float or a vector of Float.
328
// Returns 0 if the reciprocal is NaN, infinite or subnormal.
329
uint32_t Reciprocal(analysis::ConstantManager* const_mgr,
330
91.6k
                    const analysis::Constant* c) {
331
91.6k
  assert(const_mgr && c);
332
91.6k
  assert(c->type()->AsFloat());
333
334
91.6k
  uint32_t width = c->type()->AsFloat()->width();
335
91.6k
  assert(width == 32 || width == 64);
336
91.6k
  std::vector<uint32_t> words;
337
338
91.6k
  if (c->IsZero()) {
339
17.7k
    return 0;
340
17.7k
  }
341
342
73.8k
  if (width == 64) {
343
0
    spvtools::utils::FloatProxy<double> result(1.0 / c->GetDouble());
344
0
    if (!IsValidResult(result.getAsFloat())) return 0;
345
0
    words = result.GetWords();
346
73.8k
  } else {
347
73.8k
    spvtools::utils::FloatProxy<float> result(1.0f / c->GetFloat());
348
73.8k
    if (!IsValidResult(result.getAsFloat())) return 0;
349
57.7k
    words = result.GetWords();
350
57.7k
  }
351
352
57.7k
  const analysis::Constant* negated_const =
353
57.7k
      const_mgr->GetConstant(c->type(), std::move(words));
354
57.7k
  return const_mgr->GetDefiningInstruction(negated_const)->result_id();
355
73.8k
}
356
357
// Replaces fdiv where second operand is constant with fmul.
358
16.0k
FoldingRule ReciprocalFDiv() {
359
16.0k
  return [](IRContext* context, Instruction* inst,
360
103k
            const std::vector<const analysis::Constant*>& constants) {
361
103k
    assert(inst->opcode() == spv::Op::OpFDiv);
362
103k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
363
103k
    const analysis::Type* type =
364
103k
        context->get_type_mgr()->GetType(inst->type_id());
365
366
103k
    if (type->IsCooperativeMatrix()) {
367
0
      return false;
368
0
    }
369
370
103k
    if (!inst->IsFloatingPointFoldingAllowed()) return false;
371
372
102k
    uint32_t width = ElementWidth(type);
373
102k
    if (width != 32 && width != 64) return false;
374
375
102k
    if (constants[1] != nullptr) {
376
63.4k
      uint32_t id = 0;
377
63.4k
      if (const analysis::VectorConstant* vector_const =
378
63.4k
              constants[1]->AsVectorConstant()) {
379
52.0k
        std::vector<uint32_t> neg_ids;
380
80.3k
        for (auto& comp : vector_const->GetComponents()) {
381
80.3k
          id = Reciprocal(const_mgr, comp);
382
80.3k
          if (id == 0) return false;
383
48.8k
          neg_ids.push_back(id);
384
48.8k
        }
385
20.5k
        const analysis::Constant* negated_const =
386
20.5k
            const_mgr->GetConstant(constants[1]->type(), std::move(neg_ids));
387
20.5k
        id = const_mgr->GetDefiningInstruction(negated_const)->result_id();
388
20.5k
      } else if (constants[1]->AsFloatConstant()) {
389
11.2k
        id = Reciprocal(const_mgr, constants[1]);
390
11.2k
        if (id == 0) return false;
391
11.2k
      } else {
392
        // Don't fold a null constant.
393
263
        return false;
394
263
      }
395
29.3k
      inst->SetOpcode(spv::Op::OpFMul);
396
29.3k
      inst->SetInOperands(
397
29.3k
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0u)}},
398
29.3k
           {SPV_OPERAND_TYPE_ID, {id}}});
399
29.3k
      return true;
400
63.4k
    }
401
402
39.5k
    return false;
403
102k
  };
404
16.0k
}
405
406
// Elides consecutive negate instructions.
407
32.0k
FoldingRule MergeNegateArithmetic() {
408
32.0k
  return [](IRContext* context, Instruction* inst,
409
32.0k
            const std::vector<const analysis::Constant*>& constants) {
410
7.30k
    assert(inst->opcode() == spv::Op::OpFNegate ||
411
7.30k
           inst->opcode() == spv::Op::OpSNegate);
412
7.30k
    (void)constants;
413
7.30k
    const analysis::Type* type =
414
7.30k
        context->get_type_mgr()->GetType(inst->type_id());
415
7.30k
    if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
416
0
      return false;
417
418
7.30k
    Instruction* op_inst =
419
7.30k
        context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
420
7.30k
    if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed())
421
0
      return false;
422
423
7.30k
    if (op_inst->opcode() == inst->opcode()) {
424
      // Elide negates.
425
157
      inst->SetOpcode(spv::Op::OpCopyObject);
426
157
      inst->SetInOperands(
427
157
          {{SPV_OPERAND_TYPE_ID, {op_inst->GetSingleWordInOperand(0u)}}});
428
157
      return true;
429
157
    }
430
431
7.14k
    return false;
432
7.30k
  };
433
32.0k
}
434
435
// Merges negate into a mul or div operation if that operation contains a
436
// constant operand.
437
// Cases:
438
// -(x * 2) = x * -2
439
// -(2 * x) = x * -2
440
// -(x / 2) = x / -2
441
// -(2 / x) = -2 / x
442
32.0k
FoldingRule MergeNegateMulDivArithmetic() {
443
32.0k
  return [](IRContext* context, Instruction* inst,
444
32.0k
            const std::vector<const analysis::Constant*>& constants) {
445
6.99k
    assert(inst->opcode() == spv::Op::OpFNegate ||
446
6.99k
           inst->opcode() == spv::Op::OpSNegate);
447
6.99k
    (void)constants;
448
6.99k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
449
6.99k
    const analysis::Type* type =
450
6.99k
        context->get_type_mgr()->GetType(inst->type_id());
451
452
6.99k
    if (type->IsCooperativeMatrix()) {
453
0
      return false;
454
0
    }
455
456
6.99k
    if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
457
0
      return false;
458
459
6.99k
    Instruction* op_inst =
460
6.99k
        context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
461
6.99k
    if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed())
462
0
      return false;
463
464
6.99k
    uint32_t width = ElementWidth(type);
465
6.99k
    if (width != 32 && width != 64) return false;
466
467
6.99k
    spv::Op opcode = op_inst->opcode();
468
6.99k
    if (opcode != spv::Op::OpFMul && opcode != spv::Op::OpFDiv &&
469
6.31k
        opcode != spv::Op::OpIMul && opcode != spv::Op::OpSDiv) {
470
6.24k
      return false;
471
6.24k
    }
472
473
748
    std::vector<const analysis::Constant*> op_constants =
474
748
        const_mgr->GetOperandConstants(op_inst);
475
    // Merge negate into mul or div if one operand is constant.
476
748
    if (op_constants[0] == nullptr && op_constants[1] == nullptr) {
477
279
      return false;
478
279
    }
479
480
469
    bool zero_is_variable = op_constants[0] == nullptr;
481
469
    const analysis::Constant* c = ConstInput(op_constants);
482
469
    uint32_t neg_id = NegateConstant(const_mgr, c);
483
469
    uint32_t non_const_id = zero_is_variable
484
469
                                ? op_inst->GetSingleWordInOperand(0u)
485
469
                                : op_inst->GetSingleWordInOperand(1u);
486
    // Change this instruction to a mul/div.
487
469
    inst->SetOpcode(op_inst->opcode());
488
469
    if (opcode == spv::Op::OpFDiv || opcode == spv::Op::OpUDiv ||
489
441
        opcode == spv::Op::OpSDiv) {
490
34
      uint32_t op0 = zero_is_variable ? non_const_id : neg_id;
491
34
      uint32_t op1 = zero_is_variable ? neg_id : non_const_id;
492
34
      inst->SetInOperands(
493
34
          {{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}});
494
435
    } else {
495
435
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}},
496
435
                           {SPV_OPERAND_TYPE_ID, {neg_id}}});
497
435
    }
498
469
    return true;
499
748
  };
500
32.0k
}
501
502
// Merges negate into a add or sub operation if that operation contains a
503
// constant operand.
504
// Cases:
505
// -(x + 2) = -2 - x
506
// -(2 + x) = -2 - x
507
// -(x - 2) = 2 - x
508
// -(2 - x) = x - 2
509
32.0k
FoldingRule MergeNegateAddSubArithmetic() {
510
32.0k
  return [](IRContext* context, Instruction* inst,
511
32.0k
            const std::vector<const analysis::Constant*>& constants) {
512
7.12k
    assert(inst->opcode() == spv::Op::OpFNegate ||
513
7.12k
           inst->opcode() == spv::Op::OpSNegate);
514
7.12k
    (void)constants;
515
7.12k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
516
7.12k
    const analysis::Type* type =
517
7.12k
        context->get_type_mgr()->GetType(inst->type_id());
518
519
7.12k
    if (type->IsCooperativeMatrix()) {
520
0
      return false;
521
0
    }
522
523
7.12k
    if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
524
0
      return false;
525
526
7.12k
    Instruction* op_inst =
527
7.12k
        context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
528
7.12k
    if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed())
529
0
      return false;
530
531
7.12k
    uint32_t width = ElementWidth(type);
532
7.12k
    if (width != 32 && width != 64) return false;
533
534
7.12k
    if (op_inst->opcode() == spv::Op::OpFAdd ||
535
6.92k
        op_inst->opcode() == spv::Op::OpFSub ||
536
6.70k
        op_inst->opcode() == spv::Op::OpIAdd ||
537
6.44k
        op_inst->opcode() == spv::Op::OpISub) {
538
683
      std::vector<const analysis::Constant*> op_constants =
539
683
          const_mgr->GetOperandConstants(op_inst);
540
683
      if (op_constants[0] || op_constants[1]) {
541
248
        bool zero_is_variable = op_constants[0] == nullptr;
542
248
        bool is_add = (op_inst->opcode() == spv::Op::OpFAdd) ||
543
187
                      (op_inst->opcode() == spv::Op::OpIAdd);
544
248
        bool swap_operands = !is_add || zero_is_variable;
545
248
        bool negate_const = is_add;
546
248
        const analysis::Constant* c = ConstInput(op_constants);
547
248
        uint32_t const_id = 0;
548
248
        if (negate_const) {
549
156
          const_id = NegateConstant(const_mgr, c);
550
156
        } else {
551
92
          const_id = zero_is_variable ? op_inst->GetSingleWordInOperand(1u)
552
92
                                      : op_inst->GetSingleWordInOperand(0u);
553
92
        }
554
555
        // Swap operands if necessary and make the instruction a subtraction.
556
248
        uint32_t op0 =
557
248
            zero_is_variable ? op_inst->GetSingleWordInOperand(0u) : const_id;
558
248
        uint32_t op1 =
559
248
            zero_is_variable ? const_id : op_inst->GetSingleWordInOperand(1u);
560
248
        if (swap_operands) std::swap(op0, op1);
561
248
        inst->SetOpcode(HasFloatingPoint(type) ? spv::Op::OpFSub
562
248
                                               : spv::Op::OpISub);
563
248
        inst->SetInOperands(
564
248
            {{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}});
565
248
        return true;
566
248
      }
567
683
    }
568
569
6.87k
    return false;
570
7.12k
  };
571
32.0k
}
572
573
// Returns true if |c| has a zero element.
574
202k
bool HasZero(const analysis::Constant* c) {
575
202k
  if (c->AsNullConstant()) {
576
534
    return true;
577
534
  }
578
201k
  if (const analysis::VectorConstant* vec_const = c->AsVectorConstant()) {
579
70.0k
    for (auto& comp : vec_const->GetComponents())
580
105k
      if (HasZero(comp)) return true;
581
131k
  } else {
582
131k
    assert(c->AsScalarConstant());
583
131k
    return c->AsScalarConstant()->IsZero();
584
131k
  }
585
586
27.0k
  return false;
587
201k
}
588
589
// Performs |input1| |opcode| |input2| and returns the merged constant result
590
// id. Returns 0 if the result is not a valid value. The input types must be
591
// Float.
592
uint32_t PerformFloatingPointOperation(analysis::ConstantManager* const_mgr,
593
                                       spv::Op opcode,
594
                                       const analysis::Constant* input1,
595
42.0k
                                       const analysis::Constant* input2) {
596
42.0k
  const analysis::Type* type = input1->type();
597
42.0k
  assert(type->AsFloat());
598
42.0k
  uint32_t width = type->AsFloat()->width();
599
42.0k
  assert(width == 32 || width == 64);
600
42.0k
  std::vector<uint32_t> words;
601
42.0k
#define FOLD_OP(op)                                                          \
602
42.0k
  if (width == 64) {                                                         \
603
0
    utils::FloatProxy<double> val =                                          \
604
0
        input1->GetDouble() op input2->GetDouble();                          \
605
0
    double dval = val.getAsFloat();                                          \
606
0
    if (!IsValidResult(dval)) return 0;                                      \
607
0
    words = val.GetWords();                                                  \
608
42.0k
  } else {                                                                   \
609
42.0k
    utils::FloatProxy<float> val = input1->GetFloat() op input2->GetFloat(); \
610
42.0k
    float fval = val.getAsFloat();                                           \
611
42.0k
    if (!IsValidResult(fval)) return 0;                                      \
612
42.0k
    words = val.GetWords();                                                  \
613
30.3k
  }                                                                          \
614
42.0k
  static_assert(true, "require extra semicolon")
615
42.0k
  switch (opcode) {
616
6.84k
    case spv::Op::OpFMul:
617
6.84k
      FOLD_OP(*);
618
3.69k
      break;
619
2.45k
    case spv::Op::OpFDiv:
620
2.45k
      if (HasZero(input2)) return 0;
621
2.45k
      FOLD_OP(/);
622
1.62k
      break;
623
25.9k
    case spv::Op::OpFAdd:
624
25.9k
      FOLD_OP(+);
625
20.2k
      break;
626
6.80k
    case spv::Op::OpFSub:
627
6.80k
      FOLD_OP(-);
628
4.71k
      break;
629
0
    default:
630
0
      assert(false && "Unexpected operation");
631
0
      break;
632
42.0k
  }
633
30.3k
#undef FOLD_OP
634
30.3k
  const analysis::Constant* merged_const = const_mgr->GetConstant(type, words);
635
30.3k
  return const_mgr->GetDefiningInstruction(merged_const)->result_id();
636
42.0k
}
637
638
// Performs |input1| |opcode| |input2| and returns the merged constant result
639
// id. Returns 0 if the result is not a valid value. The input types must be
640
// Integers.
641
uint32_t PerformIntegerOperation(analysis::ConstantManager* const_mgr,
642
                                 spv::Op opcode,
643
                                 const analysis::Constant* input1,
644
7.24k
                                 const analysis::Constant* input2) {
645
7.24k
  assert(input1->type()->AsInteger());
646
7.24k
  const analysis::Integer* type = input1->type()->AsInteger();
647
7.24k
  uint32_t width = type->AsInteger()->width();
648
7.24k
  assert(width == 32 || width == 64);
649
7.24k
  std::vector<uint32_t> words;
650
  // Regardless of the sign of the constant, folding is performed on an unsigned
651
  // interpretation of the constant data. This avoids signed integer overflow
652
  // while folding, and works because sign is irrelevant for the IAdd, ISub and
653
  // IMul instructions.
654
7.24k
#define FOLD_OP(op)                                      \
655
7.24k
  if (width == 64) {                                     \
656
0
    uint64_t val = input1->GetU64() op input2->GetU64(); \
657
0
    words = ExtractInts(val);                            \
658
7.24k
  } else {                                               \
659
7.24k
    uint32_t val = input1->GetU32() op input2->GetU32(); \
660
7.24k
    words.push_back(val);                                \
661
7.24k
  }                                                      \
662
7.24k
  static_assert(true, "require extra semicolon")
663
7.24k
  switch (opcode) {
664
355
    case spv::Op::OpIMul:
665
355
      FOLD_OP(*);
666
355
      break;
667
0
    case spv::Op::OpSDiv:
668
0
    case spv::Op::OpUDiv:
669
0
      assert(false && "Should not merge integer division");
670
0
      break;
671
2.75k
    case spv::Op::OpIAdd:
672
2.75k
      FOLD_OP(+);
673
2.75k
      break;
674
1.92k
    case spv::Op::OpISub:
675
1.92k
      FOLD_OP(-);
676
1.92k
      break;
677
11
    case spv::Op::OpBitwiseXor:
678
11
      FOLD_OP(^);
679
11
      break;
680
1.15k
    case spv::Op::OpBitwiseOr:
681
1.15k
      FOLD_OP(|);
682
1.15k
      break;
683
1.04k
    case spv::Op::OpBitwiseAnd:
684
1.04k
      FOLD_OP(&);
685
1.04k
      break;
686
0
    default:
687
0
      assert(false && "Unexpected operation");
688
0
      break;
689
7.24k
  }
690
7.24k
#undef FOLD_OP
691
7.24k
  const analysis::Constant* merged_const = const_mgr->GetConstant(type, words);
692
7.24k
  return const_mgr->GetDefiningInstruction(merged_const)->result_id();
693
7.24k
}
694
695
// Performs |input1| |opcode| |input2| and returns the merged constant result
696
// id. Returns 0 if the result is not a valid value. The input types must be
697
// Integers, Floats or Vectors of such.
698
uint32_t PerformOperation(analysis::ConstantManager* const_mgr, spv::Op opcode,
699
                          const analysis::Constant* input1,
700
39.5k
                          const analysis::Constant* input2) {
701
39.5k
  assert(input1 && input2);
702
39.5k
  const analysis::Type* type = input1->type();
703
39.5k
  std::vector<uint32_t> words;
704
39.5k
  if (const analysis::Vector* vector_type = type->AsVector()) {
705
12.4k
    const analysis::Type* ele_type = vector_type->element_type();
706
31.3k
    for (uint32_t i = 0; i != vector_type->element_count(); ++i) {
707
22.2k
      uint32_t id = 0;
708
709
22.2k
      const analysis::Constant* input1_comp = nullptr;
710
22.2k
      if (const analysis::VectorConstant* input1_vector =
711
22.2k
              input1->AsVectorConstant()) {
712
22.2k
        input1_comp = input1_vector->GetComponents()[i];
713
22.2k
      } else {
714
0
        assert(input1->AsNullConstant());
715
0
        input1_comp = const_mgr->GetConstant(ele_type, {});
716
0
      }
717
718
22.2k
      const analysis::Constant* input2_comp = nullptr;
719
22.2k
      if (const analysis::VectorConstant* input2_vector =
720
22.2k
              input2->AsVectorConstant()) {
721
22.2k
        input2_comp = input2_vector->GetComponents()[i];
722
22.2k
      } else {
723
0
        assert(input2->AsNullConstant());
724
0
        input2_comp = const_mgr->GetConstant(ele_type, {});
725
0
      }
726
727
22.2k
      if (ele_type->AsFloat()) {
728
22.2k
        id = PerformFloatingPointOperation(const_mgr, opcode, input1_comp,
729
22.2k
                                           input2_comp);
730
22.2k
      } else {
731
4
        assert(ele_type->AsInteger());
732
4
        id = PerformIntegerOperation(const_mgr, opcode, input1_comp,
733
4
                                     input2_comp);
734
4
      }
735
22.2k
      if (id == 0) return 0;
736
18.8k
      words.push_back(id);
737
18.8k
    }
738
9.12k
    const analysis::Constant* merged_const =
739
9.12k
        const_mgr->GetConstant(type, words);
740
9.12k
    return const_mgr->GetDefiningInstruction(merged_const)->result_id();
741
27.0k
  } else if (type->AsFloat()) {
742
19.7k
    return PerformFloatingPointOperation(const_mgr, opcode, input1, input2);
743
19.7k
  } else {
744
7.24k
    assert(type->AsInteger());
745
7.24k
    return PerformIntegerOperation(const_mgr, opcode, input1, input2);
746
7.24k
  }
747
39.5k
}
748
749
// Merges consecutive multiplies where each contains one constant operand.
750
// Cases:
751
// 2 * (x * 2) = x * 4
752
// 2 * (2 * x) = x * 4
753
// (x * 2) * 2 = x * 4
754
// (2 * x) * 2 = x * 4
755
32.0k
FoldingRule MergeMulMulArithmetic() {
756
32.0k
  return [](IRContext* context, Instruction* inst,
757
205k
            const std::vector<const analysis::Constant*>& constants) {
758
205k
    assert(inst->opcode() == spv::Op::OpFMul ||
759
205k
           inst->opcode() == spv::Op::OpIMul);
760
205k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
761
205k
    const analysis::Type* type =
762
205k
        context->get_type_mgr()->GetType(inst->type_id());
763
764
205k
    if (type->IsCooperativeMatrix()) {
765
0
      return false;
766
0
    }
767
768
205k
    if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
769
101
      return false;
770
771
205k
    uint32_t width = ElementWidth(type);
772
205k
    if (width != 32 && width != 64) return false;
773
774
    // Determine the constant input and the variable input in |inst|.
775
205k
    const analysis::Constant* const_input1 = ConstInput(constants);
776
205k
    if (!const_input1) return false;
777
135k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
778
135k
    if (HasFloatingPoint(type) && !other_inst->IsFloatingPointFoldingAllowed())
779
69
      return false;
780
781
135k
    if (other_inst->opcode() == inst->opcode()) {
782
5.90k
      std::vector<const analysis::Constant*> other_constants =
783
5.90k
          const_mgr->GetOperandConstants(other_inst);
784
5.90k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
785
5.90k
      if (!const_input2) return false;
786
787
4.06k
      bool other_first_is_variable = other_constants[0] == nullptr;
788
4.06k
      uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(),
789
4.06k
                                            const_input1, const_input2);
790
4.06k
      if (merged_id == 0) return false;
791
792
1.98k
      uint32_t non_const_id = other_first_is_variable
793
1.98k
                                  ? other_inst->GetSingleWordInOperand(0u)
794
1.98k
                                  : other_inst->GetSingleWordInOperand(1u);
795
1.98k
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}},
796
1.98k
                           {SPV_OPERAND_TYPE_ID, {merged_id}}});
797
1.98k
      return true;
798
4.06k
    }
799
800
129k
    return false;
801
135k
  };
802
32.0k
}
803
804
// Merges divides into subsequent multiplies if each instruction contains one
805
// constant operand. Does not support integer operations.
806
// Cases:
807
// 2 * (x / 2) = x * 1
808
// 2 * (2 / x) = 4 / x
809
// (x / 2) * 2 = x * 1
810
// (2 / x) * 2 = 4 / x
811
// (y / x) * x = y
812
// x * (y / x) = y
813
16.0k
FoldingRule MergeMulDivArithmetic() {
814
16.0k
  return [](IRContext* context, Instruction* inst,
815
188k
            const std::vector<const analysis::Constant*>& constants) {
816
188k
    assert(inst->opcode() == spv::Op::OpFMul);
817
188k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
818
188k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
819
820
188k
    const analysis::Type* type =
821
188k
        context->get_type_mgr()->GetType(inst->type_id());
822
823
188k
    if (type->IsCooperativeMatrix()) {
824
0
      return false;
825
0
    }
826
827
188k
    if (!inst->IsFloatingPointFoldingAllowed()) return false;
828
829
188k
    uint32_t width = ElementWidth(type);
830
188k
    if (width != 32 && width != 64) return false;
831
832
565k
    for (uint32_t i = 0; i < 2; i++) {
833
377k
      uint32_t op_id = inst->GetSingleWordInOperand(i);
834
377k
      Instruction* op_inst = def_use_mgr->GetDef(op_id);
835
377k
      if (op_inst->opcode() == spv::Op::OpFDiv) {
836
15.5k
        if (op_inst->GetSingleWordInOperand(1) ==
837
15.5k
            inst->GetSingleWordInOperand(1 - i)) {
838
327
          inst->SetOpcode(spv::Op::OpCopyObject);
839
327
          inst->SetInOperands(
840
327
              {{SPV_OPERAND_TYPE_ID, {op_inst->GetSingleWordInOperand(0)}}});
841
327
          return true;
842
327
        }
843
15.5k
      }
844
377k
    }
845
846
188k
    const analysis::Constant* const_input1 = ConstInput(constants);
847
188k
    if (!const_input1) return false;
848
121k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
849
121k
    if (!other_inst->IsFloatingPointFoldingAllowed()) return false;
850
851
121k
    if (other_inst->opcode() == spv::Op::OpFDiv) {
852
2.83k
      std::vector<const analysis::Constant*> other_constants =
853
2.83k
          const_mgr->GetOperandConstants(other_inst);
854
2.83k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
855
2.83k
      if (!const_input2 || HasZero(const_input2)) return false;
856
857
758
      bool other_first_is_variable = other_constants[0] == nullptr;
858
      // If the variable value is the second operand of the divide, multiply
859
      // the constants together. Otherwise divide the constants.
860
758
      uint32_t merged_id = PerformOperation(
861
758
          const_mgr,
862
758
          other_first_is_variable ? other_inst->opcode() : inst->opcode(),
863
758
          const_input1, const_input2);
864
758
      if (merged_id == 0) return false;
865
866
320
      uint32_t non_const_id = other_first_is_variable
867
320
                                  ? other_inst->GetSingleWordInOperand(0u)
868
320
                                  : other_inst->GetSingleWordInOperand(1u);
869
870
      // If the variable value is on the second operand of the div, then this
871
      // operation is a div. Otherwise it should be a multiply.
872
320
      inst->SetOpcode(other_first_is_variable ? inst->opcode()
873
320
                                              : other_inst->opcode());
874
320
      if (other_first_is_variable) {
875
28
        inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}},
876
28
                             {SPV_OPERAND_TYPE_ID, {merged_id}}});
877
292
      } else {
878
292
        inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {merged_id}},
879
292
                             {SPV_OPERAND_TYPE_ID, {non_const_id}}});
880
292
      }
881
320
      return true;
882
758
    }
883
884
118k
    return false;
885
121k
  };
886
16.0k
}
887
888
// Merges multiply of constant and negation.
889
// Cases:
890
// (-x) * 2 = x * -2
891
// 2 * (-x) = x * -2
892
32.0k
FoldingRule MergeMulNegateArithmetic() {
893
32.0k
  return [](IRContext* context, Instruction* inst,
894
202k
            const std::vector<const analysis::Constant*>& constants) {
895
202k
    assert(inst->opcode() == spv::Op::OpFMul ||
896
202k
           inst->opcode() == spv::Op::OpIMul);
897
202k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
898
202k
    const analysis::Type* type =
899
202k
        context->get_type_mgr()->GetType(inst->type_id());
900
901
202k
    if (type->IsCooperativeMatrix()) {
902
0
      return false;
903
0
    }
904
905
202k
    bool uses_float = HasFloatingPoint(type);
906
202k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
907
908
202k
    uint32_t width = ElementWidth(type);
909
202k
    if (width != 32 && width != 64) return false;
910
911
202k
    const analysis::Constant* const_input1 = ConstInput(constants);
912
202k
    if (!const_input1) return false;
913
133k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
914
133k
    if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
915
69
      return false;
916
917
133k
    if (other_inst->opcode() == spv::Op::OpFNegate ||
918
133k
        other_inst->opcode() == spv::Op::OpSNegate) {
919
44
      uint32_t neg_id = NegateConstant(const_mgr, const_input1);
920
921
44
      inst->SetInOperands(
922
44
          {{SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}},
923
44
           {SPV_OPERAND_TYPE_ID, {neg_id}}});
924
44
      return true;
925
44
    }
926
927
133k
    return false;
928
133k
  };
929
32.0k
}
930
931
// Returns true if |inst| is negation op and is safe to fold.
932
1.76M
static bool IsFoldableNegation(const Instruction* inst) {
933
1.76M
  return (inst->opcode() == spv::Op::OpSNegate ||
934
1.76M
          (inst->opcode() == spv::Op::OpFNegate &&
935
888
           inst->IsFloatingPointFoldingAllowed()));
936
1.76M
}
937
938
// Merges multiplies / divisions of two negations.
939
// Cases:
940
// (-x) * (-y) = x * y
941
// (-x) / (-y) = x / y
942
80.0k
FoldingRule MergeDivMulDoubleNegative() {
943
80.0k
  return [](IRContext* context, Instruction* inst,
944
585k
            const std::vector<const analysis::Constant*>&) {
945
585k
    assert(inst->opcode() == spv::Op::OpFMul ||
946
585k
           inst->opcode() == spv::Op::OpVectorTimesScalar ||
947
585k
           inst->opcode() == spv::Op::OpFDiv ||
948
585k
           inst->opcode() == spv::Op::OpIMul ||
949
585k
           inst->opcode() == spv::Op::OpSDiv);
950
951
585k
    const analysis::Type* type =
952
585k
        context->get_type_mgr()->GetType(inst->type_id());
953
954
585k
    bool uses_float = HasFloatingPoint(type);
955
585k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
956
957
584k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
958
584k
    Instruction* lhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
959
584k
    Instruction* rhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
960
961
584k
    if (IsFoldableNegation(lhs) && IsFoldableNegation(rhs)) {
962
12
      inst->SetInOperands(
963
12
          {{SPV_OPERAND_TYPE_ID, {lhs->GetSingleWordInOperand(0u)}},
964
12
           {SPV_OPERAND_TYPE_ID, {rhs->GetSingleWordInOperand(0u)}}});
965
12
      return true;
966
12
    }
967
584k
    return false;
968
584k
  };
969
80.0k
}
970
971
// Merges consecutive divides if each instruction contains one constant operand.
972
// Does not support integer division.
973
// Cases:
974
// 2 / (x / 2) = 4 / x
975
// 4 / (2 / x) = 2 * x
976
// (4 / x) / 2 = 2 / x
977
// (x / 2) / 2 = x / 4
978
16.0k
FoldingRule MergeDivDivArithmetic() {
979
16.0k
  return [](IRContext* context, Instruction* inst,
980
73.6k
            const std::vector<const analysis::Constant*>& constants) {
981
73.6k
    assert(inst->opcode() == spv::Op::OpFDiv);
982
73.6k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
983
73.6k
    const analysis::Type* type =
984
73.6k
        context->get_type_mgr()->GetType(inst->type_id());
985
986
73.6k
    if (type->IsCooperativeMatrix()) {
987
0
      return false;
988
0
    }
989
990
73.6k
    if (!inst->IsFloatingPointFoldingAllowed()) return false;
991
992
73.6k
    uint32_t width = ElementWidth(type);
993
73.6k
    if (width != 32 && width != 64) return false;
994
995
73.6k
    const analysis::Constant* const_input1 = ConstInput(constants);
996
73.6k
    if (!const_input1 || HasZero(const_input1)) return false;
997
24.0k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
998
24.0k
    if (!other_inst->IsFloatingPointFoldingAllowed()) return false;
999
1000
24.0k
    bool first_is_variable = constants[0] == nullptr;
1001
24.0k
    if (other_inst->opcode() == inst->opcode()) {
1002
2.05k
      std::vector<const analysis::Constant*> other_constants =
1003
2.05k
          const_mgr->GetOperandConstants(other_inst);
1004
2.05k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
1005
2.05k
      if (!const_input2 || HasZero(const_input2)) return false;
1006
1007
1.60k
      bool other_first_is_variable = other_constants[0] == nullptr;
1008
1009
1.60k
      spv::Op merge_op = inst->opcode();
1010
1.60k
      if (other_first_is_variable) {
1011
        // Constants magnify.
1012
845
        merge_op = spv::Op::OpFMul;
1013
845
      }
1014
1015
      // This is an x / (*) case. Swap the inputs. Doesn't harm multiply
1016
      // because it is commutative.
1017
1.60k
      if (first_is_variable) std::swap(const_input1, const_input2);
1018
1.60k
      uint32_t merged_id =
1019
1.60k
          PerformOperation(const_mgr, merge_op, const_input1, const_input2);
1020
1.60k
      if (merged_id == 0) return false;
1021
1022
741
      uint32_t non_const_id = other_first_is_variable
1023
741
                                  ? other_inst->GetSingleWordInOperand(0u)
1024
741
                                  : other_inst->GetSingleWordInOperand(1u);
1025
1026
741
      spv::Op op = inst->opcode();
1027
741
      if (!first_is_variable && !other_first_is_variable) {
1028
        // Effectively div of 1/x, so change to multiply.
1029
499
        op = spv::Op::OpFMul;
1030
499
      }
1031
1032
741
      uint32_t op1 = merged_id;
1033
741
      uint32_t op2 = non_const_id;
1034
741
      if (first_is_variable && other_first_is_variable) std::swap(op1, op2);
1035
741
      inst->SetOpcode(op);
1036
741
      inst->SetInOperands(
1037
741
          {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}});
1038
741
      return true;
1039
1.60k
    }
1040
1041
22.0k
    return false;
1042
24.0k
  };
1043
16.0k
}
1044
1045
// Fold multiplies succeeded by divides where each instruction contains a
1046
// constant operand. Does not support integer divide.
1047
// Cases:
1048
// 4 / (x * 2) = 2 / x
1049
// 4 / (2 * x) = 2 / x
1050
// (x * 4) / 2 = x * 2
1051
// (4 * x) / 2 = x * 2
1052
// (x * y) / x = y
1053
// (y * x) / x = y
1054
16.0k
FoldingRule MergeDivMulArithmetic() {
1055
16.0k
  return [](IRContext* context, Instruction* inst,
1056
72.9k
            const std::vector<const analysis::Constant*>& constants) {
1057
72.9k
    assert(inst->opcode() == spv::Op::OpFDiv);
1058
72.9k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1059
72.9k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1060
1061
72.9k
    const analysis::Type* type =
1062
72.9k
        context->get_type_mgr()->GetType(inst->type_id());
1063
1064
72.9k
    if (type->IsCooperativeMatrix()) {
1065
0
      return false;
1066
0
    }
1067
1068
72.9k
    if (!inst->IsFloatingPointFoldingAllowed()) return false;
1069
1070
72.8k
    uint32_t width = ElementWidth(type);
1071
72.8k
    if (width != 32 && width != 64) return false;
1072
1073
72.8k
    uint32_t op_id = inst->GetSingleWordInOperand(0);
1074
72.8k
    Instruction* op_inst = def_use_mgr->GetDef(op_id);
1075
1076
72.8k
    if (op_inst->opcode() == spv::Op::OpFMul) {
1077
3.74k
      for (uint32_t i = 0; i < 2; i++) {
1078
2.64k
        if (op_inst->GetSingleWordInOperand(i) ==
1079
2.64k
            inst->GetSingleWordInOperand(1)) {
1080
239
          inst->SetOpcode(spv::Op::OpCopyObject);
1081
239
          inst->SetInOperands({{SPV_OPERAND_TYPE_ID,
1082
239
                                {op_inst->GetSingleWordInOperand(1 - i)}}});
1083
239
          return true;
1084
239
        }
1085
2.64k
      }
1086
1.33k
    }
1087
1088
72.6k
    const analysis::Constant* const_input1 = ConstInput(constants);
1089
72.6k
    if (!const_input1 || HasZero(const_input1)) return false;
1090
23.1k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
1091
23.1k
    if (!other_inst->IsFloatingPointFoldingAllowed()) return false;
1092
1093
23.0k
    bool first_is_variable = constants[0] == nullptr;
1094
23.0k
    if (other_inst->opcode() == spv::Op::OpFMul) {
1095
1.35k
      std::vector<const analysis::Constant*> other_constants =
1096
1.35k
          const_mgr->GetOperandConstants(other_inst);
1097
1.35k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
1098
1.35k
      if (!const_input2) return false;
1099
1100
991
      bool other_first_is_variable = other_constants[0] == nullptr;
1101
1102
      // This is an x / (*) case. Swap the inputs.
1103
991
      if (first_is_variable) std::swap(const_input1, const_input2);
1104
991
      uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(),
1105
991
                                            const_input1, const_input2);
1106
991
      if (merged_id == 0) return false;
1107
1108
658
      uint32_t non_const_id = other_first_is_variable
1109
658
                                  ? other_inst->GetSingleWordInOperand(0u)
1110
658
                                  : other_inst->GetSingleWordInOperand(1u);
1111
1112
658
      uint32_t op1 = merged_id;
1113
658
      uint32_t op2 = non_const_id;
1114
658
      if (first_is_variable) std::swap(op1, op2);
1115
1116
      // Convert to multiply
1117
658
      if (first_is_variable) inst->SetOpcode(other_inst->opcode());
1118
658
      inst->SetInOperands(
1119
658
          {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}});
1120
658
      return true;
1121
991
    }
1122
1123
21.7k
    return false;
1124
23.0k
  };
1125
16.0k
}
1126
1127
// Fold divides of a constant and a negation.
1128
// Cases:
1129
// (-x) / 2 = x / -2
1130
// 2 / (-x) = -2 / x
1131
16.0k
FoldingRule MergeDivNegateArithmetic() {
1132
16.0k
  return [](IRContext* context, Instruction* inst,
1133
72.0k
            const std::vector<const analysis::Constant*>& constants) {
1134
72.0k
    assert(inst->opcode() == spv::Op::OpFDiv);
1135
72.0k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1136
72.0k
    if (!inst->IsFloatingPointFoldingAllowed()) return false;
1137
1138
71.9k
    const analysis::Constant* const_input1 = ConstInput(constants);
1139
71.9k
    if (!const_input1) return false;
1140
44.4k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
1141
44.4k
    if (!other_inst->IsFloatingPointFoldingAllowed()) return false;
1142
1143
44.3k
    bool first_is_variable = constants[0] == nullptr;
1144
44.3k
    if (other_inst->opcode() == spv::Op::OpFNegate) {
1145
39
      uint32_t neg_id = NegateConstant(const_mgr, const_input1);
1146
1147
39
      if (first_is_variable) {
1148
24
        inst->SetInOperands(
1149
24
            {{SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}},
1150
24
             {SPV_OPERAND_TYPE_ID, {neg_id}}});
1151
24
      } else {
1152
15
        inst->SetInOperands(
1153
15
            {{SPV_OPERAND_TYPE_ID, {neg_id}},
1154
15
             {SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}});
1155
15
      }
1156
39
      return true;
1157
39
    }
1158
1159
44.3k
    return false;
1160
44.3k
  };
1161
16.0k
}
1162
1163
// Folds addition, where one side is a negation.
1164
// (-x) + y = y - x
1165
// y + (-x) = y - x
1166
32.0k
FoldingRule MergeAddNegateArithmetic() {
1167
32.0k
  return [](IRContext* context, Instruction* inst,
1168
492k
            const std::vector<const analysis::Constant*>&) {
1169
492k
    assert(inst->opcode() == spv::Op::OpFAdd ||
1170
492k
           inst->opcode() == spv::Op::OpIAdd);
1171
492k
    const analysis::Type* type =
1172
492k
        context->get_type_mgr()->GetType(inst->type_id());
1173
492k
    bool uses_float = HasFloatingPoint(type);
1174
492k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1175
1176
486k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1177
486k
    Instruction* lhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
1178
486k
    Instruction* rhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
1179
1180
486k
    auto TrySubstitute = [inst, uses_float](Instruction* first,
1181
972k
                                            Instruction* second) {
1182
972k
      if (IsFoldableNegation(first)) {
1183
62
        inst->SetOpcode(uses_float ? spv::Op::OpFSub : spv::Op::OpISub);
1184
62
        inst->SetInOperands(
1185
62
            {{SPV_OPERAND_TYPE_ID, {second->result_id()}},
1186
62
             {SPV_OPERAND_TYPE_ID, {first->GetSingleWordInOperand(0u)}}});
1187
62
        return true;
1188
62
      }
1189
971k
      return false;
1190
972k
    };
1191
1192
486k
    return TrySubstitute(lhs, rhs) || TrySubstitute(rhs, lhs);
1193
492k
  };
1194
32.0k
}
1195
1196
// Folds subtraction, where one side is a negation.
1197
// Cases:
1198
// (-x) - 2 = -2 - x
1199
// y - (-x) = x + y
1200
32.0k
FoldingRule MergeSubNegateArithmetic() {
1201
32.0k
  return [](IRContext* context, Instruction* inst,
1202
140k
            const std::vector<const analysis::Constant*>& constants) {
1203
140k
    assert(inst->opcode() == spv::Op::OpFSub ||
1204
140k
           inst->opcode() == spv::Op::OpISub);
1205
140k
    const analysis::Type* type =
1206
140k
        context->get_type_mgr()->GetType(inst->type_id());
1207
1208
140k
    bool uses_float = HasFloatingPoint(type);
1209
140k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1210
1211
135k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1212
135k
    Instruction* lhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
1213
135k
    Instruction* rhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
1214
1215
135k
    if (IsFoldableNegation(rhs)) {
1216
650
      inst->SetOpcode(uses_float ? spv::Op::OpFAdd : spv::Op::OpIAdd);
1217
650
      inst->SetInOperands(
1218
650
          {{SPV_OPERAND_TYPE_ID, {lhs->result_id()}},
1219
650
           {SPV_OPERAND_TYPE_ID, {rhs->GetSingleWordInOperand(0)}}});
1220
650
      return true;
1221
650
    }
1222
1223
135k
    if (type->IsCooperativeMatrix()) {
1224
0
      return false;
1225
0
    }
1226
1227
135k
    uint32_t width = ElementWidth(type);
1228
135k
    if (width != 32 && width != 64) return false;
1229
1230
135k
    if (constants[1] && IsFoldableNegation(lhs)) {
1231
39
      inst->SetInOperands(
1232
39
          {{SPV_OPERAND_TYPE_ID,
1233
39
            {NegateConstant(context->get_constant_mgr(), constants[1])}},
1234
39
           {SPV_OPERAND_TYPE_ID, {lhs->GetSingleWordInOperand(0)}}});
1235
39
      return true;
1236
39
    }
1237
135k
    return false;
1238
135k
  };
1239
32.0k
}
1240
1241
// Folds addition of an addition where each operation has a constant operand.
1242
// Cases:
1243
// (x + 2) + 2 = x + 4
1244
// (2 + x) + 2 = x + 4
1245
// 2 + (x + 2) = x + 4
1246
// 2 + (2 + x) = x + 4
1247
32.0k
FoldingRule MergeAddAddArithmetic() {
1248
32.0k
  return [](IRContext* context, Instruction* inst,
1249
492k
            const std::vector<const analysis::Constant*>& constants) {
1250
492k
    assert(inst->opcode() == spv::Op::OpFAdd ||
1251
492k
           inst->opcode() == spv::Op::OpIAdd);
1252
492k
    const analysis::Type* type =
1253
492k
        context->get_type_mgr()->GetType(inst->type_id());
1254
1255
492k
    if (type->IsCooperativeMatrix()) {
1256
0
      return false;
1257
0
    }
1258
1259
492k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1260
492k
    bool uses_float = HasFloatingPoint(type);
1261
492k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1262
1263
485k
    uint32_t width = ElementWidth(type);
1264
485k
    if (width != 32 && width != 64) return false;
1265
1266
485k
    const analysis::Constant* const_input1 = ConstInput(constants);
1267
485k
    if (!const_input1) return false;
1268
145k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
1269
145k
    if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
1270
373
      return false;
1271
1272
145k
    if (other_inst->opcode() == spv::Op::OpFAdd ||
1273
133k
        other_inst->opcode() == spv::Op::OpIAdd) {
1274
15.3k
      std::vector<const analysis::Constant*> other_constants =
1275
15.3k
          const_mgr->GetOperandConstants(other_inst);
1276
15.3k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
1277
15.3k
      if (!const_input2) return false;
1278
1279
12.9k
      Instruction* non_const_input =
1280
12.9k
          NonConstInput(context, other_constants[0], other_inst);
1281
12.9k
      uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(),
1282
12.9k
                                            const_input1, const_input2);
1283
12.9k
      if (merged_id == 0) return false;
1284
1285
9.74k
      inst->SetInOperands(
1286
9.74k
          {{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}},
1287
9.74k
           {SPV_OPERAND_TYPE_ID, {merged_id}}});
1288
9.74k
      return true;
1289
12.9k
    }
1290
130k
    return false;
1291
145k
  };
1292
32.0k
}
1293
1294
// Folds addition of a subtraction where each operation has a constant operand.
1295
// Cases:
1296
// (x - 2) + 2 = x + 0
1297
// (2 - x) + 2 = 4 - x
1298
// 2 + (x - 2) = x + 0
1299
// 2 + (2 - x) = 4 - x
1300
32.0k
FoldingRule MergeAddSubArithmetic() {
1301
32.0k
  return [](IRContext* context, Instruction* inst,
1302
482k
            const std::vector<const analysis::Constant*>& constants) {
1303
482k
    assert(inst->opcode() == spv::Op::OpFAdd ||
1304
482k
           inst->opcode() == spv::Op::OpIAdd);
1305
482k
    const analysis::Type* type =
1306
482k
        context->get_type_mgr()->GetType(inst->type_id());
1307
1308
482k
    if (type->IsCooperativeMatrix()) {
1309
0
      return false;
1310
0
    }
1311
1312
482k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1313
482k
    bool uses_float = HasFloatingPoint(type);
1314
482k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1315
1316
476k
    uint32_t width = ElementWidth(type);
1317
476k
    if (width != 32 && width != 64) return false;
1318
1319
476k
    const analysis::Constant* const_input1 = ConstInput(constants);
1320
476k
    if (!const_input1) return false;
1321
136k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
1322
136k
    if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
1323
373
      return false;
1324
1325
135k
    if (other_inst->opcode() == spv::Op::OpFSub ||
1326
133k
        other_inst->opcode() == spv::Op::OpISub) {
1327
3.17k
      std::vector<const analysis::Constant*> other_constants =
1328
3.17k
          const_mgr->GetOperandConstants(other_inst);
1329
3.17k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
1330
3.17k
      if (!const_input2) return false;
1331
1332
2.99k
      bool first_is_variable = other_constants[0] == nullptr;
1333
2.99k
      spv::Op op = inst->opcode();
1334
2.99k
      uint32_t op1 = 0;
1335
2.99k
      uint32_t op2 = 0;
1336
2.99k
      if (first_is_variable) {
1337
        // Subtract constants. Non-constant operand is first.
1338
2.70k
        op1 = other_inst->GetSingleWordInOperand(0u);
1339
2.70k
        op2 = PerformOperation(const_mgr, other_inst->opcode(), const_input1,
1340
2.70k
                               const_input2);
1341
2.70k
      } else {
1342
        // Add constants. Constant operand is first. Change the opcode.
1343
291
        op1 = PerformOperation(const_mgr, inst->opcode(), const_input1,
1344
291
                               const_input2);
1345
291
        op2 = other_inst->GetSingleWordInOperand(1u);
1346
291
        op = other_inst->opcode();
1347
291
      }
1348
2.99k
      if (op1 == 0 || op2 == 0) return false;
1349
1350
1.86k
      inst->SetOpcode(op);
1351
1.86k
      inst->SetInOperands(
1352
1.86k
          {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}});
1353
1.86k
      return true;
1354
2.99k
    }
1355
132k
    return false;
1356
135k
  };
1357
32.0k
}
1358
1359
// Folds subtraction of an addition where each operand has a constant operand.
1360
// Cases:
1361
// (x + 2) - 2 = x + 0
1362
// (2 + x) - 2 = x + 0
1363
// 2 - (x + 2) = 0 - x
1364
// 2 - (2 + x) = 0 - x
1365
32.0k
FoldingRule MergeSubAddArithmetic() {
1366
32.0k
  return [](IRContext* context, Instruction* inst,
1367
140k
            const std::vector<const analysis::Constant*>& constants) {
1368
140k
    assert(inst->opcode() == spv::Op::OpFSub ||
1369
140k
           inst->opcode() == spv::Op::OpISub);
1370
140k
    const analysis::Type* type =
1371
140k
        context->get_type_mgr()->GetType(inst->type_id());
1372
1373
140k
    if (type->IsCooperativeMatrix()) {
1374
0
      return false;
1375
0
    }
1376
1377
140k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1378
140k
    bool uses_float = HasFloatingPoint(type);
1379
140k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1380
1381
135k
    uint32_t width = ElementWidth(type);
1382
135k
    if (width != 32 && width != 64) return false;
1383
1384
135k
    const analysis::Constant* const_input1 = ConstInput(constants);
1385
135k
    if (!const_input1) return false;
1386
82.6k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
1387
82.6k
    if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
1388
11
      return false;
1389
1390
82.6k
    if (other_inst->opcode() == spv::Op::OpFAdd ||
1391
79.0k
        other_inst->opcode() == spv::Op::OpIAdd) {
1392
11.1k
      std::vector<const analysis::Constant*> other_constants =
1393
11.1k
          const_mgr->GetOperandConstants(other_inst);
1394
11.1k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
1395
11.1k
      if (!const_input2) return false;
1396
1397
3.19k
      Instruction* non_const_input =
1398
3.19k
          NonConstInput(context, other_constants[0], other_inst);
1399
1400
      // If the first operand of the sub is not a constant, swap the constants
1401
      // so the subtraction has the correct operands.
1402
3.19k
      if (constants[0] == nullptr) std::swap(const_input1, const_input2);
1403
      // Subtract the constants.
1404
3.19k
      uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(),
1405
3.19k
                                            const_input1, const_input2);
1406
3.19k
      spv::Op op = inst->opcode();
1407
3.19k
      uint32_t op1 = 0;
1408
3.19k
      uint32_t op2 = 0;
1409
3.19k
      if (constants[0] == nullptr) {
1410
        // Non-constant operand is first. Change the opcode.
1411
1.88k
        op1 = non_const_input->result_id();
1412
1.88k
        op2 = merged_id;
1413
1.88k
        op = other_inst->opcode();
1414
1.88k
      } else {
1415
        // Constant operand is first.
1416
1.31k
        op1 = merged_id;
1417
1.31k
        op2 = non_const_input->result_id();
1418
1.31k
      }
1419
3.19k
      if (op1 == 0 || op2 == 0) return false;
1420
1421
2.61k
      inst->SetOpcode(op);
1422
2.61k
      inst->SetInOperands(
1423
2.61k
          {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}});
1424
2.61k
      return true;
1425
3.19k
    }
1426
71.4k
    return false;
1427
82.6k
  };
1428
32.0k
}
1429
1430
// Folds subtraction of a subtraction where each operand has a constant operand.
1431
// Cases:
1432
// (x - 2) - 2 = x - 4
1433
// (2 - x) - 2 = 0 - x
1434
// 2 - (x - 2) = 4 - x
1435
// 2 - (2 - x) = x + 0
1436
32.0k
FoldingRule MergeSubSubArithmetic() {
1437
32.0k
  return [](IRContext* context, Instruction* inst,
1438
137k
            const std::vector<const analysis::Constant*>& constants) {
1439
137k
    assert(inst->opcode() == spv::Op::OpFSub ||
1440
137k
           inst->opcode() == spv::Op::OpISub);
1441
137k
    const analysis::Type* type =
1442
137k
        context->get_type_mgr()->GetType(inst->type_id());
1443
1444
137k
    if (type->IsCooperativeMatrix()) {
1445
0
      return false;
1446
0
    }
1447
1448
137k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1449
137k
    bool uses_float = HasFloatingPoint(type);
1450
137k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1451
1452
132k
    uint32_t width = ElementWidth(type);
1453
132k
    if (width != 32 && width != 64) return false;
1454
1455
132k
    const analysis::Constant* const_input1 = ConstInput(constants);
1456
132k
    if (!const_input1) return false;
1457
80.0k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
1458
80.0k
    if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
1459
11
      return false;
1460
1461
80.0k
    if (other_inst->opcode() == spv::Op::OpFSub ||
1462
72.2k
        other_inst->opcode() == spv::Op::OpISub) {
1463
8.72k
      std::vector<const analysis::Constant*> other_constants =
1464
8.72k
          const_mgr->GetOperandConstants(other_inst);
1465
8.72k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
1466
8.72k
      if (!const_input2) return false;
1467
1468
8.23k
      Instruction* non_const_input =
1469
8.23k
          NonConstInput(context, other_constants[0], other_inst);
1470
1471
      // Merge the constants.
1472
8.23k
      uint32_t merged_id = 0;
1473
8.23k
      spv::Op merge_op = inst->opcode();
1474
8.23k
      if (other_constants[0] == nullptr) {
1475
6.64k
        merge_op = uses_float ? spv::Op::OpFAdd : spv::Op::OpIAdd;
1476
6.64k
      } else if (constants[0] == nullptr) {
1477
470
        std::swap(const_input1, const_input2);
1478
470
      }
1479
8.23k
      merged_id =
1480
8.23k
          PerformOperation(const_mgr, merge_op, const_input1, const_input2);
1481
8.23k
      if (merged_id == 0) return false;
1482
1483
5.72k
      spv::Op op = inst->opcode();
1484
5.72k
      if (constants[0] != nullptr && other_constants[0] != nullptr) {
1485
        // Change the operation.
1486
818
        op = uses_float ? spv::Op::OpFAdd : spv::Op::OpIAdd;
1487
818
      }
1488
1489
5.72k
      uint32_t op1 = 0;
1490
5.72k
      uint32_t op2 = 0;
1491
5.72k
      if ((constants[0] == nullptr) ^ (other_constants[0] == nullptr)) {
1492
612
        op1 = merged_id;
1493
612
        op2 = non_const_input->result_id();
1494
5.11k
      } else {
1495
5.11k
        op1 = non_const_input->result_id();
1496
5.11k
        op2 = merged_id;
1497
5.11k
      }
1498
1499
5.72k
      inst->SetOpcode(op);
1500
5.72k
      inst->SetInOperands(
1501
5.72k
          {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}});
1502
5.72k
      return true;
1503
8.23k
    }
1504
71.2k
    return false;
1505
80.0k
  };
1506
32.0k
}
1507
1508
// Helper function for MergeGenericAddSubArithmetic. If |addend| and
1509
// subtrahend of |sub| is the same, merge to copy of minuend of |sub|.
1510
948k
bool MergeGenericAddendSub(uint32_t addend, uint32_t sub, Instruction* inst) {
1511
948k
  IRContext* context = inst->context();
1512
948k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1513
948k
  Instruction* sub_inst = def_use_mgr->GetDef(sub);
1514
948k
  if (sub_inst->opcode() != spv::Op::OpFSub &&
1515
940k
      sub_inst->opcode() != spv::Op::OpISub)
1516
938k
    return false;
1517
9.59k
  if (sub_inst->opcode() == spv::Op::OpFSub &&
1518
7.79k
      !sub_inst->IsFloatingPointFoldingAllowed())
1519
0
    return false;
1520
9.59k
  if (addend != sub_inst->GetSingleWordInOperand(1)) return false;
1521
1.29k
  inst->SetOpcode(spv::Op::OpCopyObject);
1522
1.29k
  inst->SetInOperands(
1523
1.29k
      {{SPV_OPERAND_TYPE_ID, {sub_inst->GetSingleWordInOperand(0)}}});
1524
1.29k
  context->UpdateDefUse(inst);
1525
1.29k
  return true;
1526
9.59k
}
1527
1528
// Folds addition of a subtraction where the subtrahend is equal to the
1529
// other addend. Return a copy of the minuend. Accepts generic (const and
1530
// non-const) operands.
1531
// Cases:
1532
// (a - b) + b = a
1533
// b + (a - b) = a
1534
32.0k
FoldingRule MergeGenericAddSubArithmetic() {
1535
32.0k
  return [](IRContext* context, Instruction* inst,
1536
480k
            const std::vector<const analysis::Constant*>&) {
1537
480k
    assert(inst->opcode() == spv::Op::OpFAdd ||
1538
480k
           inst->opcode() == spv::Op::OpIAdd);
1539
480k
    const analysis::Type* type =
1540
480k
        context->get_type_mgr()->GetType(inst->type_id());
1541
1542
480k
    if (type->IsCooperativeMatrix()) {
1543
0
      return false;
1544
0
    }
1545
1546
480k
    bool uses_float = HasFloatingPoint(type);
1547
480k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1548
1549
474k
    uint32_t width = ElementWidth(type);
1550
474k
    if (width != 32 && width != 64) return false;
1551
1552
474k
    uint32_t add_op0 = inst->GetSingleWordInOperand(0);
1553
474k
    uint32_t add_op1 = inst->GetSingleWordInOperand(1);
1554
474k
    if (MergeGenericAddendSub(add_op0, add_op1, inst)) return true;
1555
474k
    return MergeGenericAddendSub(add_op1, add_op0, inst);
1556
474k
  };
1557
32.0k
}
1558
1559
// Helper function for FactorAddSubMuls.
1560
// If |factor0_0| is the same as |factor1_0|, generate:
1561
//   |factor0_0| * (|factor0_1| + |factor1_1|)
1562
//   |factor0_0| * (|factor0_1| - |factor1_1|)
1563
bool FactorAddSubMulsOpnds(uint32_t factor0_0, uint32_t factor0_1,
1564
                           uint32_t factor1_0, uint32_t factor1_1,
1565
5.11k
                           Instruction* inst) {
1566
5.11k
  IRContext* context = inst->context();
1567
5.11k
  if (factor0_0 != factor1_0) return false;
1568
238
  InstructionBuilder ir_builder(
1569
238
      context, inst,
1570
238
      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
1571
238
  Instruction* new_add_inst = ir_builder.AddBinaryOp(
1572
238
      inst->type_id(), inst->opcode(), factor0_1, factor1_1);
1573
238
  if (!new_add_inst) {
1574
0
    return false;
1575
0
  }
1576
1577
238
  bool is_float =
1578
238
      inst->opcode() == spv::Op::OpFAdd || inst->opcode() == spv::Op::OpFSub;
1579
238
  inst->SetOpcode(is_float ? spv::Op::OpFMul : spv::Op::OpIMul);
1580
238
  inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {factor0_0}},
1581
238
                       {SPV_OPERAND_TYPE_ID, {new_add_inst->result_id()}}});
1582
238
  context->UpdateDefUse(inst);
1583
238
  return true;
1584
238
}
1585
1586
// Perform the following factoring identity, handling all operand order
1587
// combinations:
1588
//   (a * b) + (a * c) = a * (b + c)
1589
//   (a * b) - (a * c) = a * (b - c)
1590
64.0k
FoldingRule FactorAddSubMuls() {
1591
64.0k
  return [](IRContext* context, Instruction* inst,
1592
609k
            const std::vector<const analysis::Constant*>&) {
1593
609k
    assert(inst->opcode() == spv::Op::OpFAdd ||
1594
609k
           inst->opcode() == spv::Op::OpFSub ||
1595
609k
           inst->opcode() == spv::Op::OpIAdd ||
1596
609k
           inst->opcode() == spv::Op::OpISub);
1597
609k
    const analysis::Type* type =
1598
609k
        context->get_type_mgr()->GetType(inst->type_id());
1599
609k
    bool uses_float = HasFloatingPoint(type);
1600
609k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1601
1602
598k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1603
598k
    uint32_t add_op0 = inst->GetSingleWordInOperand(0);
1604
598k
    Instruction* add_op0_inst = def_use_mgr->GetDef(add_op0);
1605
598k
    if (add_op0_inst->opcode() != spv::Op::OpFMul &&
1606
580k
        add_op0_inst->opcode() != spv::Op::OpIMul)
1607
576k
      return false;
1608
22.0k
    uint32_t add_op1 = inst->GetSingleWordInOperand(1);
1609
22.0k
    Instruction* add_op1_inst = def_use_mgr->GetDef(add_op1);
1610
22.0k
    if (add_op1_inst->opcode() != spv::Op::OpFMul &&
1611
19.9k
        add_op1_inst->opcode() != spv::Op::OpIMul)
1612
19.2k
      return false;
1613
1614
    // Only perform this optimization if both of the muls only have one use.
1615
    // Otherwise this is a deoptimization in size and performance.
1616
2.73k
    if (def_use_mgr->NumUses(add_op0_inst) > 1) return false;
1617
1.52k
    if (def_use_mgr->NumUses(add_op1_inst) > 1) return false;
1618
1619
1.40k
    if (add_op0_inst->opcode() == spv::Op::OpFMul &&
1620
1.32k
        (!add_op0_inst->IsFloatingPointFoldingAllowed() ||
1621
1.32k
         !add_op1_inst->IsFloatingPointFoldingAllowed()))
1622
0
      return false;
1623
1624
3.76k
    for (int i = 0; i < 2; i++) {
1625
7.47k
      for (int j = 0; j < 2; j++) {
1626
        // Check if operand i in add_op0_inst matches operand j in add_op1_inst.
1627
5.11k
        if (FactorAddSubMulsOpnds(add_op0_inst->GetSingleWordInOperand(i),
1628
5.11k
                                  add_op0_inst->GetSingleWordInOperand(1 - i),
1629
5.11k
                                  add_op1_inst->GetSingleWordInOperand(j),
1630
5.11k
                                  add_op1_inst->GetSingleWordInOperand(1 - j),
1631
5.11k
                                  inst))
1632
238
          return true;
1633
5.11k
      }
1634
2.60k
    }
1635
1.16k
    return false;
1636
1.40k
  };
1637
64.0k
}
1638
1639
// Reassociate integer instructions where both operands share the same opcode
1640
// and both source instructions contain a constant.
1641
// e.g:
1642
//   (a * C0) * (C1 * b) = (C0 * C1) * (a * b)
1643
//   (a ^ C0) ^ (b ^ C1) = (C0 ^ C1) ^ (a ^ b)
1644
//   (C0 | a) | (b | C1) = (C0 | C1) | (a | b)
1645
//   (a & C0) & (b & C1) = (C0 & C1) & (a & b)
1646
static const constexpr spv::Op ReassociateNestedGenericIntOps[] = {
1647
    spv::Op::OpIMul, spv::Op::OpBitwiseOr, spv::Op::OpBitwiseXor,
1648
    spv::Op::OpBitwiseAnd};
1649
1650
64.0k
FoldingRule ReassociateNestedGenericInt(spv::Op opcode) {
1651
64.0k
  assert(std::find(std::begin(ReassociateNestedGenericIntOps),
1652
64.0k
                   std::end(ReassociateNestedGenericIntOps),
1653
64.0k
                   opcode) != std::end(ReassociateNestedGenericIntOps) &&
1654
64.0k
         "Wrong opcode.");
1655
1656
64.0k
  return [opcode](IRContext* context, Instruction* inst,
1657
64.0k
                  const std::vector<const analysis::Constant*>& constants) {
1658
    // Handled by other folding rules.
1659
61.7k
    if (constants[0] || constants[1]) {
1660
32.4k
      return false;
1661
32.4k
    }
1662
1663
29.3k
    if (inst->opcode() != opcode) {
1664
0
      return false;
1665
0
    }
1666
1667
29.3k
    const analysis::Type* type =
1668
29.3k
        context->get_type_mgr()->GetType(inst->type_id());
1669
1670
29.3k
    if (type->IsCooperativeMatrix()) {
1671
0
      return false;
1672
0
    }
1673
1674
29.3k
    uint32_t width = ElementWidth(type);
1675
29.3k
    if (width != 32 && width != 64) return false;
1676
1677
29.3k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1678
29.3k
    Instruction* lhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
1679
29.3k
    Instruction* rhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
1680
1681
29.3k
    if (lhs->opcode() != opcode || rhs->opcode() != opcode) {
1682
28.1k
      return false;
1683
28.1k
    }
1684
1685
1.13k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1686
1.13k
    std::vector<const analysis::Constant*> lhs_constants =
1687
1.13k
        const_mgr->GetOperandConstants(lhs);
1688
1.13k
    const analysis::Constant* lhs_const = ConstInput(lhs_constants);
1689
1.13k
    if (!lhs_const) {
1690
806
      return false;
1691
806
    }
1692
1693
330
    std::vector<const analysis::Constant*> rhs_constants =
1694
330
        const_mgr->GetOperandConstants(rhs);
1695
330
    const analysis::Constant* rhs_const = ConstInput(rhs_constants);
1696
330
    if (!rhs_const) {
1697
129
      return false;
1698
129
    }
1699
1700
201
    uint32_t merged_constant =
1701
201
        PerformOperation(const_mgr, opcode, lhs_const, rhs_const);
1702
201
    if (!merged_constant) {
1703
0
      return false;
1704
0
    }
1705
1706
201
    InstructionBuilder ir_builder(
1707
201
        context, inst,
1708
201
        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
1709
1710
201
    Instruction* new_rhs = ir_builder.AddBinaryOp(
1711
201
        inst->type_id(), opcode,
1712
201
        NonConstInput(context, lhs_constants[0], lhs)->result_id(),
1713
201
        NonConstInput(context, rhs_constants[0], rhs)->result_id());
1714
1715
201
    if (!new_rhs) {
1716
0
      return false;
1717
0
    }
1718
1719
201
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {merged_constant}},
1720
201
                         {SPV_OPERAND_TYPE_ID, {new_rhs->result_id()}}});
1721
201
    return true;
1722
201
  };
1723
64.0k
}
1724
1725
// Reassociate floating point mul/div instructions, which have mul/div inputs,
1726
// both of which contain a constant.
1727
// e.g:
1728
//   (a * C0) / (C1 / b) =  (C0 / C1) * (a * b)
1729
//   (C0 / a) * (b / C1) =  (C0 / C1) * (b / a)
1730
//   (a / C0) / (b * C1) =  (1 / (C0 * C1)) * (a / b)
1731
32.0k
FoldingRule ReassociateNestedMulDivFloat() {
1732
32.0k
  return [](IRContext* context, Instruction* inst,
1733
260k
            const std::vector<const analysis::Constant*>& constants) {
1734
260k
    assert(inst->opcode() == spv::Op::OpFMul ||
1735
260k
           inst->opcode() == spv::Op::OpFDiv);
1736
1737
    // Handled by other folding rules.
1738
260k
    if (constants[0] || constants[1]) {
1739
165k
      return false;
1740
165k
    }
1741
1742
94.6k
    const analysis::Type* type =
1743
94.6k
        context->get_type_mgr()->GetType(inst->type_id());
1744
1745
94.6k
    if (type->IsCooperativeMatrix()) {
1746
0
      return false;
1747
0
    }
1748
1749
94.6k
    uint32_t width = ElementWidth(type);
1750
94.6k
    if (width != 32 && width != 64) return false;
1751
1752
94.6k
    if (!inst->IsFloatingPointFoldingAllowed()) return false;
1753
1754
94.6k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1755
94.6k
    Instruction* lhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
1756
94.6k
    Instruction* rhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
1757
1758
94.6k
    bool lhs_is_mul = lhs->opcode() == spv::Op::OpFMul;
1759
94.6k
    bool lhs_is_div = lhs->opcode() == spv::Op::OpFDiv;
1760
94.6k
    bool rhs_is_mul = rhs->opcode() == spv::Op::OpFMul;
1761
94.6k
    bool rhs_is_div = rhs->opcode() == spv::Op::OpFDiv;
1762
94.6k
    if (!(lhs_is_mul || lhs_is_div) || !(rhs_is_mul || rhs_is_div)) {
1763
91.2k
      return false;
1764
91.2k
    }
1765
1766
3.46k
    if (!lhs->IsFloatingPointFoldingAllowed() ||
1767
3.46k
        !rhs->IsFloatingPointFoldingAllowed()) {
1768
0
      return false;
1769
0
    }
1770
1771
3.46k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1772
3.46k
    std::vector<const analysis::Constant*> lhs_constants =
1773
3.46k
        const_mgr->GetOperandConstants(lhs);
1774
3.46k
    if (!lhs_constants[0] && !lhs_constants[1]) {
1775
2.36k
      return false;
1776
2.36k
    }
1777
1778
1.09k
    std::vector<const analysis::Constant*> rhs_constants =
1779
1.09k
        const_mgr->GetOperandConstants(rhs);
1780
1.09k
    if (!rhs_constants[0] && !rhs_constants[1]) {
1781
214
      return false;
1782
214
    }
1783
1784
885
    const analysis::Constant* lhs_const =
1785
885
        lhs_constants[0] ? lhs_constants[0] : lhs_constants[1];
1786
885
    const analysis::Constant* rhs_const =
1787
885
        rhs_constants[0] ? rhs_constants[0] : rhs_constants[1];
1788
885
    if (!lhs_const || !rhs_const) return false;
1789
1790
885
    bool const_lhs_rcp = lhs_constants[0] ? false : lhs_is_div;
1791
885
    bool const_rhs_rcp = rhs_constants[0] ? false : rhs_is_div;
1792
1793
885
    uint32_t non_const_lhs = lhs_constants[0] ? lhs->GetSingleWordInOperand(1)
1794
885
                                              : lhs->GetSingleWordInOperand(0);
1795
885
    bool non_const_lhs_rcp = lhs_constants[0] ? lhs_is_div : false;
1796
1797
885
    uint32_t non_const_rhs = rhs_constants[0] ? rhs->GetSingleWordInOperand(1)
1798
885
                                              : rhs->GetSingleWordInOperand(0);
1799
885
    bool non_const_rhs_rcp = rhs_constants[0] ? rhs_is_div : false;
1800
1801
    // Rcp the rhs if we're actually dividing it.
1802
885
    if (inst->opcode() == spv::Op::OpFDiv) {
1803
137
      const_rhs_rcp = !const_rhs_rcp;
1804
137
      non_const_rhs_rcp = !non_const_rhs_rcp;
1805
137
    }
1806
1807
885
    if (const_lhs_rcp) {
1808
20
      lhs_const =
1809
20
          const_mgr->FindDeclaredConstant(Reciprocal(const_mgr, lhs_const));
1810
20
      if (!lhs_const) {
1811
20
        return false;
1812
20
      }
1813
20
    }
1814
865
    if (const_rhs_rcp) {
1815
133
      rhs_const =
1816
133
          const_mgr->FindDeclaredConstant(Reciprocal(const_mgr, rhs_const));
1817
133
      if (!rhs_const) {
1818
39
        return false;
1819
39
      }
1820
133
    }
1821
1822
826
    uint32_t merged_constant =
1823
826
        PerformOperation(const_mgr, spv::Op::OpFMul, lhs_const, rhs_const);
1824
1825
826
    if (!merged_constant) {
1826
264
      return false;
1827
264
    }
1828
1829
562
    spv::Op op = spv::Op::OpNop;
1830
562
    Instruction* new_rhs = nullptr;
1831
1832
562
    InstructionBuilder ir_builder(
1833
562
        context, inst,
1834
562
        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
1835
1836
    //  a * b => C * (b * a)
1837
562
    if (!non_const_lhs_rcp && !non_const_rhs_rcp) {
1838
363
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), spv::Op::OpFMul,
1839
363
                                       non_const_lhs, non_const_rhs);
1840
363
      op = spv::Op::OpFMul;
1841
363
    }
1842
    // 1/a * b => C * (b / a)
1843
199
    else if (non_const_lhs_rcp && !non_const_rhs_rcp) {
1844
7
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), spv::Op::OpFDiv,
1845
7
                                       non_const_rhs, non_const_lhs);
1846
7
      op = spv::Op::OpFMul;
1847
7
    }
1848
    //  a * 1/b => C * (a / b)
1849
192
    else if (!non_const_lhs_rcp && non_const_rhs_rcp) {
1850
67
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), spv::Op::OpFDiv,
1851
67
                                       non_const_lhs, non_const_rhs);
1852
67
      op = spv::Op::OpFMul;
1853
67
    }
1854
    // 1/a * 1/b => C / (a * b)
1855
125
    else {
1856
125
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), spv::Op::OpFMul,
1857
125
                                       non_const_lhs, non_const_rhs);
1858
125
      op = spv::Op::OpFDiv;
1859
125
    }
1860
1861
562
    if (!new_rhs) {
1862
0
      return false;
1863
0
    }
1864
1865
562
    inst->SetOpcode(op);
1866
562
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {merged_constant}},
1867
562
                         {SPV_OPERAND_TYPE_ID, {new_rhs->result_id()}}});
1868
562
    return true;
1869
562
  };
1870
32.0k
}
1871
1872
// Reassociate add/sub instructions, which have add/sub inputs,
1873
// both of which contain a constant.
1874
// e.g:
1875
//   (a + C0) - (C1 - b) =  (C0 - C1) + (a + b)
1876
//   (C0 - a) + (b - C1) =  (C0 - C1) + (b - a)
1877
//   (a - C0) - (b + C1) = (-C0 - C1) + (a - b)
1878
64.0k
FoldingRule ReassociateNestedAddSub() {
1879
64.0k
  return [](IRContext* context, Instruction* inst,
1880
610k
            const std::vector<const analysis::Constant*>& constants) {
1881
610k
    assert(inst->opcode() == spv::Op::OpFAdd ||
1882
610k
           inst->opcode() == spv::Op::OpIAdd ||
1883
610k
           inst->opcode() == spv::Op::OpFSub ||
1884
610k
           inst->opcode() == spv::Op::OpISub);
1885
1886
    // Handled by other folding rules.
1887
610k
    if (constants[0] || constants[1]) {
1888
209k
      return false;
1889
209k
    }
1890
1891
401k
    const analysis::Type* type =
1892
401k
        context->get_type_mgr()->GetType(inst->type_id());
1893
1894
401k
    if (type->IsCooperativeMatrix()) {
1895
0
      return false;
1896
0
    }
1897
1898
401k
    uint32_t width = ElementWidth(type);
1899
401k
    if (width != 32 && width != 64) return false;
1900
1901
401k
    bool uses_float = HasFloatingPoint(type);
1902
401k
    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
1903
1904
392k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
1905
392k
    Instruction* lhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
1906
392k
    Instruction* rhs = def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
1907
1908
392k
    spv::Op add_op = uses_float ? spv::Op::OpFAdd : spv::Op::OpIAdd;
1909
392k
    spv::Op sub_op = uses_float ? spv::Op::OpFSub : spv::Op::OpISub;
1910
1911
392k
    bool lhs_is_add = lhs->opcode() == add_op;
1912
392k
    bool lhs_is_sub = lhs->opcode() == sub_op;
1913
392k
    bool rhs_is_add = rhs->opcode() == add_op;
1914
392k
    bool rhs_is_sub = rhs->opcode() == sub_op;
1915
392k
    if (!(lhs_is_add || lhs_is_sub) || !(rhs_is_add || rhs_is_sub)) {
1916
389k
      return false;
1917
389k
    }
1918
1919
2.77k
    if (uses_float && (!lhs->IsFloatingPointFoldingAllowed() ||
1920
2.34k
                       !rhs->IsFloatingPointFoldingAllowed())) {
1921
0
      return false;
1922
0
    }
1923
1924
2.77k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
1925
2.77k
    std::vector<const analysis::Constant*> lhs_constants =
1926
2.77k
        const_mgr->GetOperandConstants(lhs);
1927
2.77k
    if (!lhs_constants[0] && !lhs_constants[1]) {
1928
1.05k
      return false;
1929
1.05k
    }
1930
1931
1.71k
    std::vector<const analysis::Constant*> rhs_constants =
1932
1.71k
        const_mgr->GetOperandConstants(rhs);
1933
1.71k
    if (!rhs_constants[0] && !rhs_constants[1]) {
1934
51
      return false;
1935
51
    }
1936
1937
1.66k
    const analysis::Constant* lhs_const =
1938
1.66k
        lhs_constants[0] ? lhs_constants[0] : lhs_constants[1];
1939
1.66k
    const analysis::Constant* rhs_const =
1940
1.66k
        rhs_constants[0] ? rhs_constants[0] : rhs_constants[1];
1941
1.66k
    if (!lhs_const || !rhs_const) return false;
1942
1943
1.66k
    bool const_lhs_neg = lhs_constants[0] ? false : lhs_is_sub;
1944
1.66k
    bool const_rhs_neg = rhs_constants[0] ? false : rhs_is_sub;
1945
1946
1.66k
    uint32_t non_const_lhs = lhs_constants[0] ? lhs->GetSingleWordInOperand(1)
1947
1.66k
                                              : lhs->GetSingleWordInOperand(0);
1948
1.66k
    bool non_const_lhs_neg = lhs_constants[0] ? lhs_is_sub : false;
1949
1950
1.66k
    uint32_t non_const_rhs = rhs_constants[0] ? rhs->GetSingleWordInOperand(1)
1951
1.66k
                                              : rhs->GetSingleWordInOperand(0);
1952
1.66k
    bool non_const_rhs_neg = rhs_constants[0] ? rhs_is_sub : false;
1953
1954
    // Negate the rhs if we're actually subtracting it.
1955
1.66k
    if (inst->opcode() == spv::Op::OpFSub ||
1956
951
        inst->opcode() == spv::Op::OpISub) {
1957
714
      const_rhs_neg = !const_rhs_neg;
1958
714
      non_const_rhs_neg = !non_const_rhs_neg;
1959
714
    }
1960
1961
1.66k
    if (const_lhs_neg) {
1962
495
      lhs_const =
1963
495
          const_mgr->FindDeclaredConstant(NegateConstant(const_mgr, lhs_const));
1964
495
      if (!lhs_const) {
1965
0
        return false;
1966
0
      }
1967
495
    }
1968
1.66k
    if (const_rhs_neg) {
1969
1.22k
      rhs_const =
1970
1.22k
          const_mgr->FindDeclaredConstant(NegateConstant(const_mgr, rhs_const));
1971
1.22k
      if (!rhs_const) {
1972
0
        return false;
1973
0
      }
1974
1.22k
    }
1975
1976
1.66k
    uint32_t merged_constant =
1977
1.66k
        PerformOperation(const_mgr, add_op, lhs_const, rhs_const);
1978
1979
1.66k
    if (!merged_constant) {
1980
262
      return false;
1981
262
    }
1982
1983
1.40k
    spv::Op op = spv::Op::OpNop;
1984
1.40k
    Instruction* new_rhs = nullptr;
1985
1986
1.40k
    InstructionBuilder ir_builder(
1987
1.40k
        context, inst,
1988
1.40k
        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
1989
1990
    //  a +  b => C + (b + a)
1991
1.40k
    if (!non_const_lhs_neg && !non_const_rhs_neg) {
1992
734
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), add_op, non_const_lhs,
1993
734
                                       non_const_rhs);
1994
734
      op = add_op;
1995
734
    }
1996
    // -a +  b => C + (b - a)
1997
668
    else if (non_const_lhs_neg && !non_const_rhs_neg) {
1998
33
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), sub_op, non_const_rhs,
1999
33
                                       non_const_lhs);
2000
33
      op = add_op;
2001
33
    }
2002
    //  a + -b => C + (a - b)
2003
635
    else if (!non_const_lhs_neg && non_const_rhs_neg) {
2004
20
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), sub_op, non_const_lhs,
2005
20
                                       non_const_rhs);
2006
20
      op = add_op;
2007
20
    }
2008
    // -a + -b => C - (a + b)
2009
615
    else {
2010
615
      new_rhs = ir_builder.AddBinaryOp(inst->type_id(), add_op, non_const_lhs,
2011
615
                                       non_const_rhs);
2012
615
      op = sub_op;
2013
615
    }
2014
2015
1.40k
    if (!new_rhs) {
2016
0
      return false;
2017
0
    }
2018
2019
1.40k
    inst->SetOpcode(op);
2020
1.40k
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {merged_constant}},
2021
1.40k
                         {SPV_OPERAND_TYPE_ID, {new_rhs->result_id()}}});
2022
1.40k
    return true;
2023
1.40k
  };
2024
64.0k
}
2025
2026
16.0k
FoldingRule IntMultipleBy1() {
2027
16.0k
  return [](IRContext*, Instruction* inst,
2028
16.0k
            const std::vector<const analysis::Constant*>& constants) {
2029
15.1k
    assert(inst->opcode() == spv::Op::OpIMul &&
2030
15.1k
           "Wrong opcode.  Should be OpIMul.");
2031
44.9k
    for (uint32_t i = 0; i < 2; i++) {
2032
30.3k
      if (constants[i] == nullptr) {
2033
17.2k
        continue;
2034
17.2k
      }
2035
13.0k
      const analysis::IntConstant* int_constant = constants[i]->AsIntConstant();
2036
13.0k
      if (int_constant) {
2037
12.8k
        uint32_t width = ElementWidth(int_constant->type());
2038
12.8k
        if (width != 32 && width != 64) return false;
2039
12.8k
        bool is_one = (width == 32) ? int_constant->GetU32BitValue() == 1u
2040
12.8k
                                    : int_constant->GetU64BitValue() == 1ull;
2041
12.8k
        if (is_one) {
2042
487
          inst->SetOpcode(spv::Op::OpCopyObject);
2043
487
          inst->SetInOperands(
2044
487
              {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1 - i)}}});
2045
487
          return true;
2046
487
        }
2047
12.8k
      }
2048
13.0k
    }
2049
14.6k
    return false;
2050
15.1k
  };
2051
16.0k
}
2052
2053
// Returns the number of elements that the |index|th in operand in |inst|
2054
// contributes to the result of |inst|.  |inst| must be an
2055
// OpCompositeConstructInstruction.
2056
uint32_t GetNumOfElementsContributedByOperand(IRContext* context,
2057
                                              const Instruction* inst,
2058
17.7k
                                              uint32_t index) {
2059
17.7k
  assert(inst->opcode() == spv::Op::OpCompositeConstruct);
2060
17.7k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2061
17.7k
  analysis::TypeManager* type_mgr = context->get_type_mgr();
2062
2063
17.7k
  analysis::Vector* result_type =
2064
17.7k
      type_mgr->GetType(inst->type_id())->AsVector();
2065
17.7k
  if (result_type == nullptr) {
2066
    // If the result of the OpCompositeConstruct is not a vector then every
2067
    // operands corresponds to a single element in the result.
2068
0
    return 1;
2069
0
  }
2070
2071
  // If the result type is a vector then the operands are either scalars or
2072
  // vectors. If it is a scalar, then it corresponds to a single element.  If it
2073
  // is a vector, then each element in the vector will be an element in the
2074
  // result.
2075
17.7k
  uint32_t id = inst->GetSingleWordInOperand(index);
2076
17.7k
  Instruction* def = def_use_mgr->GetDef(id);
2077
17.7k
  analysis::Vector* type = type_mgr->GetType(def->type_id())->AsVector();
2078
17.7k
  if (type == nullptr) {
2079
17.7k
    return 1;
2080
17.7k
  }
2081
0
  return type->element_count();
2082
17.7k
}
2083
2084
// Returns the in-operands for an OpCompositeExtract instruction that are needed
2085
// to extract the |result_index|th element in the result of |inst| without using
2086
// the result of |inst|. Returns the empty vector if |result_index| is
2087
// out-of-bounds. |inst| must be an |OpCompositeConstruct| instruction.
2088
std::vector<Operand> GetExtractOperandsForElementOfCompositeConstruct(
2089
35.6k
    IRContext* context, const Instruction* inst, uint32_t result_index) {
2090
35.6k
  assert(inst->opcode() == spv::Op::OpCompositeConstruct);
2091
35.6k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2092
35.6k
  analysis::TypeManager* type_mgr = context->get_type_mgr();
2093
2094
35.6k
  analysis::Type* result_type = type_mgr->GetType(inst->type_id());
2095
35.6k
  if (result_type->AsVector() == nullptr) {
2096
24.8k
    if (result_index < inst->NumInOperands()) {
2097
24.8k
      uint32_t id = inst->GetSingleWordInOperand(result_index);
2098
24.8k
      return {Operand(SPV_OPERAND_TYPE_ID, {id})};
2099
24.8k
    }
2100
0
    return {};
2101
24.8k
  }
2102
2103
  // If the result type is a vector, then vector operands are concatenated.
2104
10.7k
  uint32_t total_element_count = 0;
2105
17.7k
  for (uint32_t idx = 0; idx < inst->NumInOperands(); ++idx) {
2106
17.7k
    uint32_t element_count =
2107
17.7k
        GetNumOfElementsContributedByOperand(context, inst, idx);
2108
17.7k
    total_element_count += element_count;
2109
17.7k
    if (result_index < total_element_count) {
2110
10.7k
      std::vector<Operand> operands;
2111
10.7k
      uint32_t id = inst->GetSingleWordInOperand(idx);
2112
10.7k
      Instruction* operand_def = def_use_mgr->GetDef(id);
2113
10.7k
      analysis::Type* operand_type = type_mgr->GetType(operand_def->type_id());
2114
2115
10.7k
      operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
2116
10.7k
      if (operand_type->AsVector()) {
2117
0
        uint32_t start_index_of_id = total_element_count - element_count;
2118
0
        uint32_t index_into_id = result_index - start_index_of_id;
2119
0
        operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index_into_id}});
2120
0
      }
2121
10.7k
      return operands;
2122
10.7k
    }
2123
17.7k
  }
2124
0
  return {};
2125
10.7k
}
2126
2127
// If the OpCompositeConstruct that feeds an OpCopyLogical can be retyped to
2128
// the OpCopyLogical's result type, the layout conversion can be expressed at
2129
// constituent granularity instead of at aggregate granularity. This rewrites
2130
// the OpCopyLogical as an OpCompositeConstruct of the result type, using the
2131
// same constituents where their types already match the corresponding
2132
// field/element of the result type, and inserting per-field OpCopyLogical
2133
// instructions only for the fields that genuinely require a layout
2134
// conversion.
2135
bool CompositeConstructFeedingCopyLogical(
2136
    IRContext* context, Instruction* inst,
2137
0
    const std::vector<const analysis::Constant*>&) {
2138
0
  assert(inst->opcode() == spv::Op::OpCopyLogical &&
2139
0
         "Wrong opcode.  Should be OpCopyLogical.");
2140
0
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2141
2142
0
  uint32_t src_id = inst->GetSingleWordInOperand(0);
2143
0
  Instruction* src_inst = def_use_mgr->GetDef(src_id);
2144
0
  if (src_inst->opcode() != spv::Op::OpCompositeConstruct) {
2145
0
    return false;
2146
0
  }
2147
2148
0
  Instruction* dst_type_inst = def_use_mgr->GetDef(inst->type_id());
2149
0
  const uint32_t num_constituents = src_inst->NumInOperands();
2150
2151
  // Determine the expected type id for each constituent of the destination
2152
  // type.
2153
0
  std::vector<uint32_t> expected_type_ids;
2154
0
  expected_type_ids.reserve(num_constituents);
2155
0
  if (dst_type_inst->opcode() == spv::Op::OpTypeStruct) {
2156
0
    if (dst_type_inst->NumInOperands() != num_constituents) {
2157
0
      return false;
2158
0
    }
2159
0
    for (uint32_t i = 0; i < num_constituents; ++i) {
2160
0
      expected_type_ids.push_back(dst_type_inst->GetSingleWordInOperand(i));
2161
0
    }
2162
0
  } else if (dst_type_inst->opcode() == spv::Op::OpTypeArray) {
2163
0
    const uint32_t elem_type_id = dst_type_inst->GetSingleWordInOperand(0);
2164
0
    for (uint32_t i = 0; i < num_constituents; ++i) {
2165
0
      expected_type_ids.push_back(elem_type_id);
2166
0
    }
2167
0
  } else {
2168
0
    return false;
2169
0
  }
2170
2171
  // Build the new constituent list, inserting OpCopyLogical instructions for
2172
  // the fields whose types differ from the result type.
2173
0
  InstructionBuilder ir_builder(
2174
0
      context, inst,
2175
0
      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
2176
0
  std::vector<Operand> operands;
2177
0
  operands.reserve(num_constituents);
2178
0
  for (uint32_t i = 0; i < num_constituents; ++i) {
2179
0
    const uint32_t cid = src_inst->GetSingleWordInOperand(i);
2180
0
    Instruction* cdef = def_use_mgr->GetDef(cid);
2181
0
    if (cdef->type_id() == expected_type_ids[i]) {
2182
0
      operands.push_back({SPV_OPERAND_TYPE_ID, {cid}});
2183
0
      continue;
2184
0
    }
2185
0
    if (def_use_mgr->GetDef(expected_type_ids[i])->opcode() ==
2186
0
        spv::Op::OpTypePointer) {
2187
0
      assert(def_use_mgr->GetDef(expected_type_ids[i])->opcode() !=
2188
0
                 spv::Op::OpTypePointer &&
2189
0
             "Unreachable for valid input");
2190
0
    }
2191
0
    Instruction* per_field_copy = ir_builder.AddUnaryOp(
2192
0
        expected_type_ids[i], spv::Op::OpCopyLogical, cid);
2193
0
    if (per_field_copy == nullptr) {
2194
0
      return false;
2195
0
    }
2196
0
    operands.push_back({SPV_OPERAND_TYPE_ID, {per_field_copy->result_id()}});
2197
0
  }
2198
2199
0
  inst->SetOpcode(spv::Op::OpCompositeConstruct);
2200
0
  inst->SetInOperands(std::move(operands));
2201
0
  context->UpdateDefUse(inst);
2202
0
  return true;
2203
0
}
2204
2205
bool CompositeConstructFeedingExtract(
2206
    IRContext* context, Instruction* inst,
2207
278k
    const std::vector<const analysis::Constant*>&) {
2208
  // If the input to an OpCompositeExtract is an OpCompositeConstruct,
2209
  // then we can simply use the appropriate element in the construction.
2210
278k
  assert(inst->opcode() == spv::Op::OpCompositeExtract &&
2211
278k
         "Wrong opcode.  Should be OpCompositeExtract.");
2212
278k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2213
2214
  // If there are no index operands, then this rule cannot do anything.
2215
278k
  if (inst->NumInOperands() <= 1) {
2216
0
    return false;
2217
0
  }
2218
2219
278k
  uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2220
278k
  Instruction* cinst = def_use_mgr->GetDef(cid);
2221
2222
278k
  if (cinst->opcode() != spv::Op::OpCompositeConstruct) {
2223
242k
    return false;
2224
242k
  }
2225
2226
35.6k
  uint32_t index_into_result = inst->GetSingleWordInOperand(1);
2227
35.6k
  std::vector<Operand> operands =
2228
35.6k
      GetExtractOperandsForElementOfCompositeConstruct(context, cinst,
2229
35.6k
                                                       index_into_result);
2230
2231
35.6k
  if (operands.empty()) {
2232
0
    return false;
2233
0
  }
2234
2235
  // Add the remaining indices for extraction.
2236
35.6k
  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
2237
31
    operands.push_back(
2238
31
        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {inst->GetSingleWordInOperand(i)}});
2239
31
  }
2240
2241
35.6k
  if (operands.size() == 1) {
2242
    // If there were no extra indices, then we have the final object.  No need
2243
    // to extract any more.
2244
35.5k
    inst->SetOpcode(spv::Op::OpCopyObject);
2245
35.5k
  }
2246
2247
35.6k
  inst->SetInOperands(std::move(operands));
2248
35.6k
  return true;
2249
35.6k
}
2250
2251
// Walks the indexes chain from |start| to |end| of an OpCompositeInsert or
2252
// OpCompositeExtract instruction, and returns the type id of the final element
2253
// being accessed. Returns 0 if a valid type could not be found.
2254
uint32_t GetElementType(uint32_t type_id, Instruction::iterator start,
2255
                        Instruction::iterator end,
2256
96.4k
                        const analysis::DefUseManager* def_use_manager) {
2257
96.4k
  for (auto index : make_range(std::move(start), std::move(end))) {
2258
1.08k
    const Instruction* type_inst = def_use_manager->GetDef(type_id);
2259
1.08k
    assert(index.type == SPV_OPERAND_TYPE_LITERAL_INTEGER &&
2260
1.08k
           index.words.size() == 1);
2261
1.08k
    switch (type_inst->opcode()) {
2262
518
      case spv::Op::OpTypeArray:
2263
518
      case spv::Op::OpTypeMatrix:
2264
518
      case spv::Op::OpTypeVector:
2265
518
      case spv::Op::OpTypeVectorIdEXT:
2266
518
        type_id = type_inst->GetSingleWordInOperand(0);
2267
518
        break;
2268
566
      case spv::Op::OpTypeStruct:
2269
566
        type_id = type_inst->GetSingleWordInOperand(index.words[0]);
2270
566
        break;
2271
0
      default:
2272
0
        return 0;
2273
1.08k
    }
2274
1.08k
  }
2275
96.4k
  return type_id;
2276
96.4k
}
2277
2278
// If the input to an OpCompositeExtract is an OpCopyLogical, then we can
2279
// hoist the extraction before the copy.
2280
bool CopyLogicalFeedingExtract(IRContext* context, Instruction* inst,
2281
237k
                               const std::vector<const analysis::Constant*>&) {
2282
237k
  assert(inst->opcode() == spv::Op::OpCompositeExtract &&
2283
237k
         "Wrong opcode.  Should be OpCompositeExtract.");
2284
2285
237k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2286
237k
  uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2287
237k
  Instruction* cinst = def_use_mgr->GetDef(cid);
2288
2289
237k
  if (cinst->opcode() != spv::Op::OpCopyLogical) {
2290
237k
    return false;
2291
237k
  }
2292
2293
0
  uint32_t original_composite_id = cinst->GetSingleWordInOperand(0);
2294
0
  Instruction* original_composite_inst =
2295
0
      def_use_mgr->GetDef(original_composite_id);
2296
2297
0
  std::vector<uint32_t> indices;
2298
0
  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
2299
0
    indices.push_back(inst->GetSingleWordInOperand(i));
2300
0
  }
2301
2302
0
  uint32_t original_element_type_id =
2303
0
      GetElementType(original_composite_inst->type_id(), inst->begin() + 3,
2304
0
                     inst->end(), def_use_mgr);
2305
0
  assert(original_element_type_id != 0 &&
2306
0
         "Could not find the element type.  Invalid SPIR-V.");
2307
2308
0
  InstructionBuilder ir_builder(
2309
0
      context, inst,
2310
0
      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
2311
2312
0
  Instruction* new_extract = ir_builder.AddCompositeExtract(
2313
0
      original_element_type_id, original_composite_id, indices);
2314
2315
0
  if (original_element_type_id == inst->type_id())
2316
0
    inst->SetOpcode(spv::Op::OpCopyObject);
2317
0
  else
2318
0
    inst->SetOpcode(spv::Op::OpCopyLogical);
2319
0
  inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {new_extract->result_id()}}});
2320
0
  return true;
2321
0
}
2322
2323
// If the input to an OpCompositeExtract is an OpLoad, we can change the
2324
// load into a load of an OpAccessChain.
2325
bool LoadFeedingExtract(IRContext* context, Instruction* inst,
2326
237k
                        const std::vector<const analysis::Constant*>&) {
2327
237k
  assert(inst->opcode() == spv::Op::OpCompositeExtract &&
2328
237k
         "Wrong opcode.  Should be OpCompositeExtract.");
2329
2330
237k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2331
237k
  uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2332
237k
  Instruction* cinst = def_use_mgr->GetDef(cid);
2333
2334
237k
  if (cinst->opcode() != spv::Op::OpLoad) {
2335
178k
    return false;
2336
178k
  }
2337
2338
59.1k
  Instruction* composite_type_inst = def_use_mgr->GetDef(cinst->type_id());
2339
59.1k
  if (composite_type_inst->opcode() != spv::Op::OpTypeStruct &&
2340
30.9k
      composite_type_inst->opcode() != spv::Op::OpTypeArray) {
2341
12.9k
    return false;
2342
12.9k
  }
2343
2344
  // Check the memory operands.
2345
46.2k
  if (cinst->NumInOperands() > 1) {
2346
597
    uint32_t memory_access_mask = cinst->GetSingleWordInOperand(1);
2347
597
    if (memory_access_mask & uint32_t(spv::MemoryAccessMask::Volatile)) {
2348
22
      return false;
2349
22
    }
2350
597
  }
2351
2352
46.2k
  uint32_t ptr_id = cinst->GetSingleWordInOperand(0);
2353
46.2k
  Instruction* ptr_inst = def_use_mgr->GetDef(ptr_id);
2354
46.2k
  Instruction* ptr_type_inst = def_use_mgr->GetDef(ptr_inst->type_id());
2355
46.2k
  assert(ptr_type_inst->opcode() == spv::Op::OpTypePointer);
2356
46.2k
  spv::StorageClass storage_class =
2357
46.2k
      static_cast<spv::StorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
2358
2359
  // If the storage class is Function or Private, we do not want to fold.
2360
  // These are the storage classes that the local-access-chain-convert pass
2361
  // works on.
2362
46.2k
  if (storage_class == spv::StorageClass::Function ||
2363
46.2k
      storage_class == spv::StorageClass::Private) {
2364
46.2k
    return false;
2365
46.2k
  }
2366
2367
0
  analysis::ConstantManager* const_mgr = context->get_constant_mgr();
2368
0
  analysis::TypeManager* type_mgr = context->get_type_mgr();
2369
0
  std::vector<uint32_t> index_ids;
2370
0
  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
2371
0
    uint32_t index = inst->GetSingleWordInOperand(i);
2372
0
    const analysis::Constant* index_const =
2373
0
        const_mgr->GetConstant(type_mgr->GetUIntType(), {index});
2374
0
    index_ids.push_back(
2375
0
        const_mgr->GetDefiningInstruction(index_const)->result_id());
2376
0
  }
2377
2378
0
  InstructionBuilder ir_builder(
2379
0
      context, cinst,
2380
0
      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
2381
2382
0
  uint32_t element_ptr_type_id =
2383
0
      type_mgr->FindPointerToType(inst->type_id(), storage_class);
2384
0
  if (element_ptr_type_id == 0) {
2385
0
    return false;
2386
0
  }
2387
2388
0
  Instruction* access_chain =
2389
0
      ir_builder.AddAccessChain(element_ptr_type_id, ptr_id, index_ids);
2390
0
  std::vector<Operand> load_operands;
2391
0
  load_operands.push_back({SPV_OPERAND_TYPE_ID, {access_chain->result_id()}});
2392
2393
0
  if (cinst->NumInOperands() > 1) {
2394
0
    uint32_t memory_access_mask = cinst->GetSingleWordInOperand(1);
2395
0
    load_operands.push_back(
2396
0
        {SPV_OPERAND_TYPE_MEMORY_ACCESS, {memory_access_mask}});
2397
2398
0
    uint32_t current_operand_index = 2;
2399
0
    if (memory_access_mask & uint32_t(spv::MemoryAccessMask::Aligned)) {
2400
0
      uint32_t original_alignment =
2401
0
          cinst->GetSingleWordInOperand(current_operand_index);
2402
2403
0
      std::vector<uint32_t> extract_indices;
2404
0
      for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
2405
0
        extract_indices.push_back(inst->GetSingleWordInOperand(i));
2406
0
      }
2407
2408
0
      std::optional<uint32_t> offset =
2409
0
          type_mgr->GetType(cinst->type_id())->GetByteOffset(extract_indices);
2410
0
      if (!offset) {
2411
0
        return false;
2412
0
      }
2413
2414
0
      uint32_t new_alignment = original_alignment;
2415
0
      if (*offset != 0) {
2416
0
        uint32_t offset_alignment = *offset & ~(*offset - 1);
2417
0
        new_alignment = std::min(original_alignment, offset_alignment);
2418
0
      }
2419
2420
0
      load_operands.push_back(
2421
0
          {SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, {new_alignment}});
2422
0
      current_operand_index++;
2423
0
    }
2424
2425
    // Copy the remaining operands
2426
0
    for (; current_operand_index < cinst->NumInOperands();
2427
0
         ++current_operand_index) {
2428
0
      load_operands.push_back(cinst->GetInOperand(current_operand_index));
2429
0
    }
2430
0
  }
2431
2432
0
  uint32_t load_result_id = context->TakeNextId();
2433
0
  if (load_result_id == 0) return false;
2434
2435
0
  std::unique_ptr<Instruction> new_load_inst(
2436
0
      new Instruction(context, spv::Op::OpLoad, inst->type_id(), load_result_id,
2437
0
                      load_operands));
2438
0
  Instruction* new_load = ir_builder.AddInstruction(std::move(new_load_inst));
2439
2440
0
  inst->SetOpcode(spv::Op::OpCopyObject);
2441
0
  inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {new_load->result_id()}}});
2442
2443
0
  return true;
2444
0
}
2445
2446
// Returns true of |inst_1| and |inst_2| have the same indexes that will be used
2447
// to index into a composite object, excluding the last index.  The two
2448
// instructions must have the same opcode, and be either OpCompositeExtract or
2449
// OpCompositeInsert instructions.
2450
1.21M
bool HaveSameIndexesExceptForLast(Instruction* inst_1, Instruction* inst_2) {
2451
1.21M
  assert(inst_1->opcode() == inst_2->opcode() &&
2452
1.21M
         "Expecting the opcodes to be the same.");
2453
1.21M
  assert((inst_1->opcode() == spv::Op::OpCompositeInsert ||
2454
1.21M
          inst_1->opcode() == spv::Op::OpCompositeExtract) &&
2455
1.21M
         "Instructions must be OpCompositeInsert or OpCompositeExtract.");
2456
2457
1.21M
  if (inst_1->NumInOperands() != inst_2->NumInOperands()) {
2458
1.96k
    return false;
2459
1.96k
  }
2460
2461
1.21M
  uint32_t first_index_position =
2462
1.21M
      (inst_1->opcode() == spv::Op::OpCompositeInsert ? 2 : 1);
2463
1.21M
  for (uint32_t i = first_index_position; i < inst_1->NumInOperands() - 1;
2464
1.21M
       i++) {
2465
8.02k
    if (inst_1->GetSingleWordInOperand(i) !=
2466
8.02k
        inst_2->GetSingleWordInOperand(i)) {
2467
464
      return false;
2468
464
    }
2469
8.02k
  }
2470
1.21M
  return true;
2471
1.21M
}
2472
2473
// If the OpCompositeConstruct is simply putting back together elements that
2474
// where extracted from the same source, we can simply reuse the source.
2475
//
2476
// This is a common code pattern because of the way that scalar replacement
2477
// works.
2478
bool CompositeExtractFeedingConstruct(
2479
    IRContext* context, Instruction* inst,
2480
94.4k
    const std::vector<const analysis::Constant*>&) {
2481
94.4k
  assert(inst->opcode() == spv::Op::OpCompositeConstruct &&
2482
94.4k
         "Wrong opcode.  Should be OpCompositeConstruct.");
2483
94.4k
  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2484
94.4k
  uint32_t original_id = 0;
2485
2486
94.4k
  if (inst->NumInOperands() == 0) {
2487
    // The struct being constructed has no members.
2488
0
    return false;
2489
0
  }
2490
2491
  // Check each element to make sure they are:
2492
  // - extractions
2493
  // - extracting the same position they are inserting
2494
  // - all extract from the same id.
2495
94.4k
  Instruction* first_element_inst = nullptr;
2496
121k
  for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
2497
119k
    const uint32_t element_id = inst->GetSingleWordInOperand(i);
2498
119k
    Instruction* element_inst = def_use_mgr->GetDef(element_id);
2499
119k
    if (first_element_inst == nullptr) {
2500
94.4k
      first_element_inst = element_inst;
2501
94.4k
    }
2502
2503
119k
    if (element_inst->opcode() != spv::Op::OpCompositeExtract) {
2504
88.9k
      return false;
2505
88.9k
    }
2506
2507
30.8k
    if (!HaveSameIndexesExceptForLast(element_inst, first_element_inst)) {
2508
0
      return false;
2509
0
    }
2510
2511
30.8k
    if (element_inst->GetSingleWordInOperand(element_inst->NumInOperands() -
2512
30.8k
                                             1) != i) {
2513
3.67k
      return false;
2514
3.67k
    }
2515
2516
27.1k
    if (i == 0) {
2517
9.45k
      original_id =
2518
9.45k
          element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2519
17.7k
    } else if (original_id !=
2520
17.7k
               element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx)) {
2521
424
      return false;
2522
424
    }
2523
27.1k
  }
2524
94.4k
  assert(first_element_inst != nullptr);
2525
2526
  // The last check it to see that the object being extracted from is the
2527
  // correct type.
2528
1.35k
  Instruction* original_inst = def_use_mgr->GetDef(original_id);
2529
1.35k
  uint32_t original_type_id =
2530
1.35k
      GetElementType(original_inst->type_id(), first_element_inst->begin() + 3,
2531
1.35k
                     first_element_inst->end() - 1, def_use_mgr);
2532
2533
1.35k
  if (inst->type_id() != original_type_id) {
2534
210
    return false;
2535
210
  }
2536
2537
1.14k
  if (first_element_inst->NumInOperands() == 2) {
2538
    // Simplify by using the original object.
2539
1.14k
    inst->SetOpcode(spv::Op::OpCopyObject);
2540
1.14k
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}});
2541
1.14k
    return true;
2542
1.14k
  }
2543
2544
  // Copies the original id and all indexes except for the last to the new
2545
  // extract instruction.
2546
0
  inst->SetOpcode(spv::Op::OpCompositeExtract);
2547
0
  inst->SetInOperands(std::vector<Operand>(first_element_inst->begin() + 2,
2548
0
                                           first_element_inst->end() - 1));
2549
0
  return true;
2550
1.14k
}
2551
2552
16.0k
FoldingRule InsertFeedingExtract() {
2553
16.0k
  return [](IRContext* context, Instruction* inst,
2554
361k
            const std::vector<const analysis::Constant*>&) {
2555
361k
    assert(inst->opcode() == spv::Op::OpCompositeExtract &&
2556
361k
           "Wrong opcode.  Should be OpCompositeExtract.");
2557
361k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2558
361k
    uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2559
361k
    Instruction* cinst = def_use_mgr->GetDef(cid);
2560
2561
361k
    if (cinst->opcode() != spv::Op::OpCompositeInsert) {
2562
277k
      return false;
2563
277k
    }
2564
2565
    // Find the first position where the list of insert and extract indicies
2566
    // differ, if at all.
2567
83.6k
    uint32_t i;
2568
104k
    for (i = 1; i < inst->NumInOperands(); ++i) {
2569
84.2k
      if (i + 1 >= cinst->NumInOperands()) {
2570
0
        break;
2571
0
      }
2572
2573
84.2k
      if (inst->GetSingleWordInOperand(i) !=
2574
84.2k
          cinst->GetSingleWordInOperand(i + 1)) {
2575
63.0k
        break;
2576
63.0k
      }
2577
84.2k
    }
2578
2579
    // We are extracting the element that was inserted.
2580
83.6k
    if (i == inst->NumInOperands() && i + 1 == cinst->NumInOperands()) {
2581
20.3k
      inst->SetOpcode(spv::Op::OpCopyObject);
2582
20.3k
      inst->SetInOperands(
2583
20.3k
          {{SPV_OPERAND_TYPE_ID,
2584
20.3k
            {cinst->GetSingleWordInOperand(kInsertObjectIdInIdx)}}});
2585
20.3k
      return true;
2586
20.3k
    }
2587
2588
    // Extracting the value that was inserted along with values for the base
2589
    // composite.  Cannot do anything.
2590
63.3k
    if (i == inst->NumInOperands()) {
2591
337
      return false;
2592
337
    }
2593
2594
    // Extracting an element of the value that was inserted.  Extract from
2595
    // that value directly.
2596
63.0k
    if (i + 1 == cinst->NumInOperands()) {
2597
0
      std::vector<Operand> operands;
2598
0
      operands.push_back(
2599
0
          {SPV_OPERAND_TYPE_ID,
2600
0
           {cinst->GetSingleWordInOperand(kInsertObjectIdInIdx)}});
2601
0
      for (; i < inst->NumInOperands(); ++i) {
2602
0
        operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER,
2603
0
                            {inst->GetSingleWordInOperand(i)}});
2604
0
      }
2605
0
      inst->SetInOperands(std::move(operands));
2606
0
      return true;
2607
0
    }
2608
2609
    // Extracting a value that is disjoint from the element being inserted.
2610
    // Rewrite the extract to use the composite input to the insert.
2611
63.0k
    std::vector<Operand> operands;
2612
63.0k
    operands.push_back(
2613
63.0k
        {SPV_OPERAND_TYPE_ID,
2614
63.0k
         {cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx)}});
2615
126k
    for (i = 1; i < inst->NumInOperands(); ++i) {
2616
63.5k
      operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER,
2617
63.5k
                          {inst->GetSingleWordInOperand(i)}});
2618
63.5k
    }
2619
63.0k
    inst->SetInOperands(std::move(operands));
2620
63.0k
    return true;
2621
63.0k
  };
2622
16.0k
}
2623
2624
// When a VectorShuffle is feeding an Extract, we can extract from one of the
2625
// operands of the VectorShuffle.  We just need to adjust the index in the
2626
// extract instruction.
2627
16.0k
FoldingRule VectorShuffleFeedingExtract() {
2628
16.0k
  return [](IRContext* context, Instruction* inst,
2629
242k
            const std::vector<const analysis::Constant*>&) {
2630
242k
    assert(inst->opcode() == spv::Op::OpCompositeExtract &&
2631
242k
           "Wrong opcode.  Should be OpCompositeExtract.");
2632
242k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2633
242k
    analysis::TypeManager* type_mgr = context->get_type_mgr();
2634
242k
    uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2635
242k
    Instruction* cinst = def_use_mgr->GetDef(cid);
2636
2637
242k
    if (cinst->opcode() != spv::Op::OpVectorShuffle) {
2638
237k
      return false;
2639
237k
    }
2640
2641
    // Find the size of the first vector operand of the VectorShuffle
2642
4.65k
    Instruction* first_input =
2643
4.65k
        def_use_mgr->GetDef(cinst->GetSingleWordInOperand(0));
2644
4.65k
    analysis::Type* first_input_type =
2645
4.65k
        type_mgr->GetType(first_input->type_id());
2646
4.65k
    assert(first_input_type->AsVector() &&
2647
4.65k
           "Input to vector shuffle should be vectors.");
2648
4.65k
    uint32_t first_input_size = first_input_type->AsVector()->element_count();
2649
2650
    // Get index of the element the vector shuffle is placing in the position
2651
    // being extracted.
2652
4.65k
    uint32_t new_index =
2653
4.65k
        cinst->GetSingleWordInOperand(2 + inst->GetSingleWordInOperand(1));
2654
2655
    // Extracting an undefined value so fold this extract into an undef.
2656
4.65k
    const uint32_t undef_literal_value = 0xffffffff;
2657
4.65k
    if (new_index == undef_literal_value) {
2658
217
      inst->SetOpcode(spv::Op::OpUndef);
2659
217
      inst->SetInOperands({});
2660
217
      return true;
2661
217
    }
2662
2663
    // Get the id of the of the vector the elemtent comes from, and update the
2664
    // index if needed.
2665
4.43k
    uint32_t new_vector = 0;
2666
4.43k
    if (new_index < first_input_size) {
2667
2.83k
      new_vector = cinst->GetSingleWordInOperand(0);
2668
2.83k
    } else {
2669
1.59k
      new_vector = cinst->GetSingleWordInOperand(1);
2670
1.59k
      new_index -= first_input_size;
2671
1.59k
    }
2672
2673
    // Update the extract instruction.
2674
4.43k
    inst->SetInOperand(kExtractCompositeIdInIdx, {new_vector});
2675
4.43k
    inst->SetInOperand(1, {new_index});
2676
4.43k
    return true;
2677
4.65k
  };
2678
16.0k
}
2679
2680
// When an FMix with is feeding an Extract that extracts an element whose
2681
// corresponding |a| in the FMix is 0 or 1, we can extract from one of the
2682
// operands of the FMix.
2683
16.0k
FoldingRule FMixFeedingExtract() {
2684
16.0k
  return [](IRContext* context, Instruction* inst,
2685
237k
            const std::vector<const analysis::Constant*>&) {
2686
237k
    assert(inst->opcode() == spv::Op::OpCompositeExtract &&
2687
237k
           "Wrong opcode.  Should be OpCompositeExtract.");
2688
237k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
2689
237k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
2690
2691
237k
    uint32_t composite_id =
2692
237k
        inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
2693
237k
    Instruction* composite_inst = def_use_mgr->GetDef(composite_id);
2694
2695
237k
    if (composite_inst->opcode() != spv::Op::OpExtInst) {
2696
218k
      return false;
2697
218k
    }
2698
2699
19.6k
    uint32_t inst_set_id =
2700
19.6k
        context->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
2701
2702
19.6k
    if (composite_inst->GetSingleWordInOperand(kExtInstSetIdInIdx) !=
2703
19.6k
            inst_set_id ||
2704
19.6k
        composite_inst->GetSingleWordInOperand(kExtInstInstructionInIdx) !=
2705
19.6k
            GLSLstd450FMix) {
2706
14.5k
      return false;
2707
14.5k
    }
2708
2709
    // Get the |a| for the FMix instruction.
2710
5.12k
    uint32_t a_id = composite_inst->GetSingleWordInOperand(kFMixAIdInIdx);
2711
5.12k
    std::unique_ptr<Instruction> a(inst->Clone(context));
2712
5.12k
    a->SetInOperand(kExtractCompositeIdInIdx, {a_id});
2713
5.12k
    context->get_instruction_folder().FoldInstruction(a.get());
2714
2715
5.12k
    if (a->opcode() != spv::Op::OpCopyObject) {
2716
1.22k
      return false;
2717
1.22k
    }
2718
2719
3.90k
    const analysis::Constant* a_const =
2720
3.90k
        const_mgr->FindDeclaredConstant(a->GetSingleWordInOperand(0));
2721
2722
3.90k
    if (!a_const) {
2723
1.96k
      return false;
2724
1.96k
    }
2725
2726
1.93k
    bool use_x = false;
2727
2728
1.93k
    assert(a_const->type()->AsFloat());
2729
2730
1.93k
    const analysis::Type* type =
2731
1.93k
        context->get_type_mgr()->GetType(inst->type_id());
2732
1.93k
    uint32_t width = ElementWidth(type);
2733
1.93k
    if (width != 32 && width != 64) {
2734
      // We won't support folding half float values.
2735
0
      return false;
2736
0
    }
2737
2738
1.93k
    double element_value = a_const->GetValueAsDouble();
2739
1.93k
    if (element_value == 0.0) {
2740
72
      use_x = true;
2741
1.86k
    } else if (element_value == 1.0) {
2742
34
      use_x = false;
2743
1.82k
    } else {
2744
1.82k
      return false;
2745
1.82k
    }
2746
2747
    // Get the id of the of the vector the element comes from.
2748
106
    uint32_t new_vector = 0;
2749
106
    if (use_x) {
2750
72
      new_vector = composite_inst->GetSingleWordInOperand(kFMixXIdInIdx);
2751
72
    } else {
2752
34
      new_vector = composite_inst->GetSingleWordInOperand(kFMixYIdInIdx);
2753
34
    }
2754
2755
    // Update the extract instruction.
2756
106
    inst->SetInOperand(kExtractCompositeIdInIdx, {new_vector});
2757
106
    return true;
2758
1.93k
  };
2759
16.0k
}
2760
2761
// Returns the number of elements in the composite type |type|.  Returns 0 if
2762
// |type| is a scalar value. Return UINT32_MAX when the size is unknown at
2763
// compile time.
2764
95.1k
uint32_t GetNumberOfElements(const analysis::Type* type) {
2765
95.1k
  if (auto* vector_type = type->AsVector()) {
2766
85.9k
    return vector_type->element_count();
2767
85.9k
  }
2768
9.20k
  if (auto* matrix_type = type->AsMatrix()) {
2769
0
    return matrix_type->element_count();
2770
0
  }
2771
9.20k
  if (auto* struct_type = type->AsStruct()) {
2772
3.56k
    return static_cast<uint32_t>(struct_type->element_types().size());
2773
3.56k
  }
2774
5.64k
  if (auto* array_type = type->AsArray()) {
2775
5.64k
    if (array_type->length_info().words[0] ==
2776
5.64k
            analysis::Array::LengthInfo::kConstant &&
2777
5.64k
        array_type->length_info().words.size() == 2) {
2778
5.64k
      return array_type->length_info().words[1];
2779
5.64k
    }
2780
0
    return UINT32_MAX;
2781
5.64k
  }
2782
0
  return 0;
2783
5.64k
}
2784
2785
// Returns a map with the set of values that were inserted into an object by
2786
// the chain of OpCompositeInsertInstruction starting with |inst|.
2787
// The map will map the index to the value inserted at that index. An empty map
2788
// will be returned if the map could not be properly generated.
2789
95.1k
std::map<uint32_t, uint32_t> GetInsertedValues(Instruction* inst) {
2790
95.1k
  analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr();
2791
95.1k
  std::map<uint32_t, uint32_t> values_inserted;
2792
95.1k
  Instruction* current_inst = inst;
2793
1.27M
  while (current_inst->opcode() == spv::Op::OpCompositeInsert) {
2794
1.18M
    if (current_inst->NumInOperands() > inst->NumInOperands()) {
2795
      // This is to catch the case
2796
      //   %2 = OpCompositeInsert %m2x2int %v2int_1_0 %m2x2int_undef 0
2797
      //   %3 = OpCompositeInsert %m2x2int %int_4 %2 0 0
2798
      //   %4 = OpCompositeInsert %m2x2int %v2int_2_3 %3 1
2799
      // In this case we cannot do a single construct to get the matrix.
2800
936
      uint32_t partially_inserted_element_index =
2801
936
          current_inst->GetSingleWordInOperand(inst->NumInOperands() - 1);
2802
936
      if (values_inserted.count(partially_inserted_element_index) == 0)
2803
214
        return {};
2804
936
    }
2805
1.18M
    if (HaveSameIndexesExceptForLast(inst, current_inst)) {
2806
1.18M
      values_inserted.insert(
2807
1.18M
          {current_inst->GetSingleWordInOperand(current_inst->NumInOperands() -
2808
1.18M
                                                1),
2809
1.18M
           current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx)});
2810
1.18M
    }
2811
1.18M
    current_inst = def_use_mgr->GetDef(
2812
1.18M
        current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx));
2813
1.18M
  }
2814
94.9k
  return values_inserted;
2815
95.1k
}
2816
2817
// Returns true of there is an entry in |values_inserted| for every element of
2818
// |Type|.
2819
bool DoInsertedValuesCoverEntireObject(
2820
95.1k
    const analysis::Type* type, std::map<uint32_t, uint32_t>& values_inserted) {
2821
95.1k
  uint32_t container_size = GetNumberOfElements(type);
2822
95.1k
  if (container_size != values_inserted.size()) {
2823
84.2k
    return false;
2824
84.2k
  }
2825
2826
10.8k
  if (values_inserted.rbegin()->first >= container_size) {
2827
0
    return false;
2828
0
  }
2829
10.8k
  return true;
2830
10.8k
}
2831
2832
// Returns id of the type of the element that immediately contains the element
2833
// being inserted by the OpCompositeInsert instruction |inst|. Returns 0 if it
2834
// could not be found.
2835
95.1k
uint32_t GetContainerTypeId(Instruction* inst) {
2836
95.1k
  assert(inst->opcode() == spv::Op::OpCompositeInsert);
2837
95.1k
  analysis::DefUseManager* def_use_manager = inst->context()->get_def_use_mgr();
2838
95.1k
  uint32_t container_type_id = GetElementType(
2839
95.1k
      inst->type_id(), inst->begin() + 4, inst->end() - 1, def_use_manager);
2840
95.1k
  return container_type_id;
2841
95.1k
}
2842
2843
// Returns an OpCompositeConstruct instruction that build an object with
2844
// |type_id| out of the values in |values_inserted|.  Each value will be
2845
// placed at the index corresponding to the value.  The new instruction will
2846
// be placed before |insert_before|.
2847
Instruction* BuildCompositeConstruct(
2848
    uint32_t type_id, const std::map<uint32_t, uint32_t>& values_inserted,
2849
10.8k
    Instruction* insert_before) {
2850
10.8k
  InstructionBuilder ir_builder(
2851
10.8k
      insert_before->context(), insert_before,
2852
10.8k
      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
2853
2854
10.8k
  std::vector<uint32_t> ids_in_order;
2855
22.0k
  for (auto it : values_inserted) {
2856
22.0k
    ids_in_order.push_back(it.second);
2857
22.0k
  }
2858
10.8k
  Instruction* construct =
2859
10.8k
      ir_builder.AddCompositeConstruct(type_id, ids_in_order);
2860
10.8k
  return construct;
2861
10.8k
}
2862
2863
// Replaces the OpCompositeInsert |inst| that inserts |construct| into the same
2864
// object as |inst| with final index removed.  If the resulting
2865
// OpCompositeInsert instruction would have no remaining indexes, the
2866
// instruction is replaced with an OpCopyObject instead.
2867
10.8k
void InsertConstructedObject(Instruction* inst, const Instruction* construct) {
2868
10.8k
  if (inst->NumInOperands() == 3) {
2869
10.7k
    inst->SetOpcode(spv::Op::OpCopyObject);
2870
10.7k
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {construct->result_id()}}});
2871
10.7k
  } else {
2872
53
    inst->SetInOperand(kInsertObjectIdInIdx, {construct->result_id()});
2873
53
    inst->RemoveOperand(inst->NumOperands() - 1);
2874
53
  }
2875
10.8k
}
2876
2877
// Replaces a series of |OpCompositeInsert| instruction that cover the entire
2878
// object with an |OpCompositeConstruct|.
2879
bool CompositeInsertToCompositeConstruct(
2880
    IRContext* context, Instruction* inst,
2881
95.1k
    const std::vector<const analysis::Constant*>&) {
2882
95.1k
  assert(inst->opcode() == spv::Op::OpCompositeInsert &&
2883
95.1k
         "Wrong opcode.  Should be OpCompositeInsert.");
2884
95.1k
  if (inst->NumInOperands() < 3) return false;
2885
2886
95.1k
  std::map<uint32_t, uint32_t> values_inserted = GetInsertedValues(inst);
2887
95.1k
  uint32_t container_type_id = GetContainerTypeId(inst);
2888
95.1k
  if (container_type_id == 0) {
2889
0
    return false;
2890
0
  }
2891
2892
95.1k
  analysis::TypeManager* type_mgr = context->get_type_mgr();
2893
95.1k
  const analysis::Type* container_type = type_mgr->GetType(container_type_id);
2894
95.1k
  assert(container_type && "GetContainerTypeId returned a bad id.");
2895
95.1k
  if (!DoInsertedValuesCoverEntireObject(container_type, values_inserted)) {
2896
84.2k
    return false;
2897
84.2k
  }
2898
2899
10.8k
  Instruction* construct =
2900
10.8k
      BuildCompositeConstruct(container_type_id, values_inserted, inst);
2901
10.8k
  InsertConstructedObject(inst, construct);
2902
10.8k
  return true;
2903
95.1k
}
2904
2905
16.0k
FoldingRule RedundantPhi() {
2906
  // An OpPhi instruction where all values are the same or the result of the phi
2907
  // itself, can be replaced by the value itself.
2908
16.0k
  return [](IRContext*, Instruction* inst,
2909
322k
            const std::vector<const analysis::Constant*>&) {
2910
322k
    assert(inst->opcode() == spv::Op::OpPhi &&
2911
322k
           "Wrong opcode.  Should be OpPhi.");
2912
2913
322k
    uint32_t incoming_value = 0;
2914
2915
742k
    for (uint32_t i = 0; i < inst->NumInOperands(); i += 2) {
2916
665k
      uint32_t op_id = inst->GetSingleWordInOperand(i);
2917
665k
      if (op_id == inst->result_id()) {
2918
60.1k
        continue;
2919
60.1k
      }
2920
2921
604k
      if (incoming_value == 0) {
2922
322k
        incoming_value = op_id;
2923
322k
      } else if (op_id != incoming_value) {
2924
        // Found two possible value.  Can't simplify.
2925
244k
        return false;
2926
244k
      }
2927
604k
    }
2928
2929
77.6k
    if (incoming_value == 0) {
2930
      // Code looks invalid.  Don't do anything.
2931
0
      return false;
2932
0
    }
2933
2934
    // We have a single incoming value.  Simplify using that value.
2935
77.6k
    inst->SetOpcode(spv::Op::OpCopyObject);
2936
77.6k
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {incoming_value}}});
2937
77.6k
    return true;
2938
77.6k
  };
2939
16.0k
}
2940
2941
16.0k
FoldingRule BitCastScalarOrVector() {
2942
16.0k
  return [](IRContext* context, Instruction* inst,
2943
16.0k
            const std::vector<const analysis::Constant*>& constants) {
2944
2.58k
    assert(inst->opcode() == spv::Op::OpBitcast && constants.size() == 1);
2945
2.58k
    if (constants[0] == nullptr) return false;
2946
2947
1.78k
    const analysis::Type* type =
2948
1.78k
        context->get_type_mgr()->GetType(inst->type_id());
2949
1.78k
    if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
2950
9
      return false;
2951
2952
1.78k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
2953
1.78k
    std::vector<uint32_t> words =
2954
1.78k
        GetWordsFromNumericScalarOrVectorConstant(const_mgr, constants[0]);
2955
1.78k
    if (words.size() == 0) return false;
2956
2957
1.78k
    const analysis::Constant* bitcasted_constant =
2958
1.78k
        ConvertWordsToNumericScalarOrVectorConstant(const_mgr, words, type);
2959
1.78k
    if (!bitcasted_constant) return false;
2960
2961
1.78k
    auto new_feeder_id =
2962
1.78k
        const_mgr->GetDefiningInstruction(bitcasted_constant, inst->type_id())
2963
1.78k
            ->result_id();
2964
1.78k
    inst->SetOpcode(spv::Op::OpCopyObject);
2965
1.78k
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {new_feeder_id}}});
2966
1.78k
    return true;
2967
1.78k
  };
2968
16.0k
}
2969
2970
// Remove indirect bitcasts which have no effect.
2971
//   uint32 x;  asuint32(x)            => x
2972
//   uint32 x;  asuint32(asint32(x))   => x
2973
//   float32 x; asuint32(asint32(x))   => asuint32(x)
2974
16.0k
FoldingRule RedundantBitcast() {
2975
16.0k
  return [](IRContext* context, Instruction* inst,
2976
16.0k
            const std::vector<const analysis::Constant*>&) {
2977
802
    assert(inst->opcode() == spv::Op::OpBitcast);
2978
2979
802
    analysis::DefUseManager* def_mgr = context->get_def_use_mgr();
2980
802
    Instruction* child = def_mgr->GetDef(inst->GetSingleWordInOperand(0));
2981
2982
802
    if (inst->type_id() == child->type_id()) {
2983
146
      inst->SetOpcode(spv::Op::OpCopyObject);
2984
146
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {child->result_id()}}});
2985
146
      return true;
2986
146
    }
2987
2988
656
    if (child->opcode() != spv::Op::OpBitcast) {
2989
656
      return false;
2990
656
    }
2991
2992
0
    if (def_mgr->GetDef(child->GetSingleWordInOperand(0))->type_id() ==
2993
0
        inst->type_id()) {
2994
0
      inst->SetOpcode(spv::Op::OpCopyObject);
2995
0
    }
2996
0
    inst->SetInOperands(
2997
0
        {{SPV_OPERAND_TYPE_ID, {child->GetSingleWordInOperand(0)}}});
2998
2999
0
    return true;
3000
656
  };
3001
16.0k
}
3002
3003
16.0k
FoldingRule BitReverseScalarOrVector() {
3004
16.0k
  return [](IRContext* context, Instruction* inst,
3005
16.0k
            const std::vector<const analysis::Constant*>& constants) {
3006
727
    assert(inst->opcode() == spv::Op::OpBitReverse && constants.size() == 1);
3007
727
    if (constants[0] == nullptr) return false;
3008
3009
482
    const analysis::Type* type =
3010
482
        context->get_type_mgr()->GetType(inst->type_id());
3011
482
    assert(!HasFloatingPoint(type) &&
3012
482
           "BitReverse cannot be applied to floating point types.");
3013
482
    assert((type->AsInteger() || type->AsVector()) &&
3014
482
           "BitReverse can only be applied to integer scalars or vectors.");
3015
482
    assert((ElementWidth(type) == 32) &&
3016
482
           "BitReverse can only be applied to integer types of width 32");
3017
3018
482
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
3019
482
    std::vector<uint32_t> words =
3020
482
        GetWordsFromNumericScalarOrVectorConstant(const_mgr, constants[0]);
3021
482
    if (words.size() == 0) return false;
3022
3023
486
    for (uint32_t& word : words) {
3024
      // Reverse the bits in each word.
3025
486
      word = ((word & 0x55555555) << 1) | ((word >> 1) & 0x55555555);
3026
486
      word = ((word & 0x33333333) << 2) | ((word >> 2) & 0x33333333);
3027
486
      word = ((word & 0x0F0F0F0F) << 4) | ((word >> 4) & 0x0F0F0F0F);
3028
486
      word = ((word & 0x00FF00FF) << 8) | ((word >> 8) & 0x00FF00FF);
3029
486
      word = (word << 16) | (word >> 16);
3030
486
    }
3031
3032
482
    const analysis::Constant* bitreversed_constant =
3033
482
        ConvertWordsToNumericScalarOrVectorConstant(const_mgr, words, type);
3034
482
    if (!bitreversed_constant) return false;
3035
3036
482
    auto new_feeder_id =
3037
482
        const_mgr->GetDefiningInstruction(bitreversed_constant, inst->type_id())
3038
482
            ->result_id();
3039
482
    inst->SetOpcode(spv::Op::OpCopyObject);
3040
482
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {new_feeder_id}}});
3041
482
    return true;
3042
482
  };
3043
16.0k
}
3044
3045
16.0k
FoldingRule RedundantSelect() {
3046
  // An OpSelect instruction where both values are the same or the condition is
3047
  // constant can be replaced by one of the values
3048
16.0k
  return [](IRContext*, Instruction* inst,
3049
16.6k
            const std::vector<const analysis::Constant*>& constants) {
3050
16.6k
    assert(inst->opcode() == spv::Op::OpSelect &&
3051
16.6k
           "Wrong opcode.  Should be OpSelect.");
3052
16.6k
    assert(inst->NumInOperands() == 3);
3053
16.6k
    assert(constants.size() == 3);
3054
3055
16.6k
    uint32_t true_id = inst->GetSingleWordInOperand(1);
3056
16.6k
    uint32_t false_id = inst->GetSingleWordInOperand(2);
3057
3058
16.6k
    if (true_id == false_id) {
3059
      // Both results are the same, condition doesn't matter
3060
118
      inst->SetOpcode(spv::Op::OpCopyObject);
3061
118
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {true_id}}});
3062
118
      return true;
3063
16.5k
    } else if (constants[0]) {
3064
1.39k
      const analysis::Type* type = constants[0]->type();
3065
1.39k
      if (type->AsBool()) {
3066
        // Scalar constant value, select the corresponding value.
3067
988
        inst->SetOpcode(spv::Op::OpCopyObject);
3068
988
        if (constants[0]->AsNullConstant() ||
3069
988
            !constants[0]->AsBoolConstant()->value()) {
3070
682
          inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {false_id}}});
3071
682
        } else {
3072
306
          inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {true_id}}});
3073
306
        }
3074
988
        return true;
3075
988
      } else {
3076
403
        assert(type->AsVector());
3077
403
        if (constants[0]->AsNullConstant()) {
3078
          // All values come from false id.
3079
0
          inst->SetOpcode(spv::Op::OpCopyObject);
3080
0
          inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {false_id}}});
3081
0
          return true;
3082
403
        } else {
3083
          // Convert to a vector shuffle.
3084
403
          std::vector<Operand> ops;
3085
403
          ops.push_back({SPV_OPERAND_TYPE_ID, {true_id}});
3086
403
          ops.push_back({SPV_OPERAND_TYPE_ID, {false_id}});
3087
403
          const analysis::VectorConstant* vector_const =
3088
403
              constants[0]->AsVectorConstant();
3089
403
          uint32_t size =
3090
403
              static_cast<uint32_t>(vector_const->GetComponents().size());
3091
1.21k
          for (uint32_t i = 0; i != size; ++i) {
3092
809
            const analysis::Constant* component =
3093
809
                vector_const->GetComponents()[i];
3094
809
            if (component->AsNullConstant() ||
3095
809
                !component->AsBoolConstant()->value()) {
3096
              // Selecting from the false vector which is the second input
3097
              // vector to the shuffle. Offset the index by |size|.
3098
31
              ops.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i + size}});
3099
778
            } else {
3100
              // Selecting from true vector which is the first input vector to
3101
              // the shuffle.
3102
778
              ops.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}});
3103
778
            }
3104
809
          }
3105
3106
403
          inst->SetOpcode(spv::Op::OpVectorShuffle);
3107
403
          inst->SetInOperands(std::move(ops));
3108
403
          return true;
3109
403
        }
3110
403
      }
3111
1.39k
    }
3112
3113
15.1k
    return false;
3114
16.6k
  };
3115
16.0k
}
3116
3117
11.1k
std::optional<bool> GetBoolConstantKind(const analysis::Constant* c) {
3118
11.1k
  if (!c) {
3119
3.85k
    return {};
3120
3.85k
  }
3121
7.25k
  if (auto composite = c->AsCompositeConstant()) {
3122
0
    auto& components = composite->GetComponents();
3123
0
    if (components.empty()) {
3124
0
      return {};
3125
0
    }
3126
0
    auto first = GetBoolConstantKind(components[0]);
3127
0
    if (!first) {
3128
0
      return {};
3129
0
    }
3130
0
    if (std::all_of(std::begin(components) + 1, std::end(components),
3131
0
                    [first](const analysis::Constant* c2) {
3132
0
                      return GetBoolConstantKind(c2) == first;
3133
0
                    })) {
3134
0
      return first;
3135
0
    }
3136
0
    return {};
3137
7.25k
  } else if (c->AsNullConstant()) {
3138
7
    return false;
3139
7.25k
  } else if (c->AsBoolConstant()) {
3140
7.25k
    return c->AsBoolConstant()->value();
3141
7.25k
  }
3142
0
  return {};
3143
7.25k
}
3144
3145
// Fold OpSelect instructions which have constant booleans as their result.
3146
//   x ? true  : false =  x
3147
//   x ? false : true  = !x
3148
16.0k
FoldingRule FoldConstantBooleanSelect() {
3149
16.0k
  return [](IRContext* context, Instruction* inst,
3150
16.0k
            const std::vector<const analysis::Constant*>& constants) {
3151
15.1k
    assert(inst->opcode() == spv::Op::OpSelect);
3152
15.1k
    assert(inst->NumInOperands() == 3);
3153
15.1k
    assert(constants.size() == 3);
3154
3155
15.1k
    if (!constants[1] || !constants[2]) {
3156
10.3k
      return false;
3157
10.3k
    }
3158
3159
4.82k
    analysis::DefUseManager* def_mgr = context->get_def_use_mgr();
3160
4.82k
    if (inst->type_id() !=
3161
4.82k
        def_mgr->GetDef(inst->GetSingleWordInOperand(0))->type_id()) {
3162
4.74k
      return false;
3163
4.74k
    }
3164
3165
85
    std::optional<bool> uniform_true = GetBoolConstantKind(constants[1]);
3166
85
    std::optional<bool> uniform_false = GetBoolConstantKind(constants[2]);
3167
3168
85
    if (!uniform_true || !uniform_false) {
3169
0
      return false;
3170
0
    }
3171
3172
85
    if (uniform_true.value() && !uniform_false.value()) {
3173
29
      inst->SetOpcode(spv::Op::OpCopyObject);
3174
29
      inst->SetInOperands(
3175
29
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
3176
29
      return true;
3177
56
    } else if (!uniform_true.value() && uniform_false.value()) {
3178
56
      inst->SetOpcode(spv::Op::OpLogicalNot);
3179
56
      inst->SetInOperands(
3180
56
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
3181
56
      return true;
3182
56
    }
3183
0
    return false;
3184
85
  };
3185
16.0k
}
3186
3187
// Fold OpLogicalAnd instructions which have a constant true on one side.
3188
//   x && true = x
3189
//   true && x = x
3190
16.0k
FoldingRule RedundantLogicalAnd() {
3191
16.0k
  return [](IRContext* context, Instruction* inst,
3192
16.0k
            const std::vector<const analysis::Constant*>& constants) {
3193
8.51k
    assert(inst->opcode() == spv::Op::OpLogicalAnd);
3194
3195
8.51k
    if (GetBoolConstantKind(ConstInput(constants)) ==
3196
8.51k
        std::optional<bool>(true)) {
3197
5.17k
      Instruction* other_inst = NonConstInput(context, constants[0], inst);
3198
5.17k
      inst->SetOpcode(spv::Op::OpCopyObject);
3199
5.17k
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {other_inst->result_id()}}});
3200
5.17k
      return true;
3201
5.17k
    }
3202
3.33k
    return false;
3203
8.51k
  };
3204
16.0k
}
3205
3206
// Fold OpLogicalOr instructions which have a constant false on one side.
3207
//   x || false = x
3208
//   false || x = x
3209
16.0k
FoldingRule RedundantLogicalOr() {
3210
16.0k
  return [](IRContext* context, Instruction* inst,
3211
16.0k
            const std::vector<const analysis::Constant*>& constants) {
3212
635
    assert(inst->opcode() == spv::Op::OpLogicalOr);
3213
3214
635
    if (GetBoolConstantKind(ConstInput(constants)) ==
3215
635
        std::optional<bool>(false)) {
3216
108
      Instruction* other_inst = NonConstInput(context, constants[0], inst);
3217
108
      inst->SetOpcode(spv::Op::OpCopyObject);
3218
108
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {other_inst->result_id()}}});
3219
108
      return true;
3220
108
    }
3221
527
    return false;
3222
635
  };
3223
16.0k
}
3224
3225
// Fold concurrent OpLogicalNot instructions:
3226
//   !!x = x
3227
16.0k
FoldingRule RedundantLogicalNot() {
3228
16.0k
  return [](IRContext* context, Instruction* inst,
3229
16.0k
            const std::vector<const analysis::Constant*>&) {
3230
15.6k
    assert(inst->opcode() == spv::Op::OpLogicalNot);
3231
15.6k
    Instruction* child =
3232
15.6k
        context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
3233
15.6k
    if (child->opcode() == spv::Op::OpLogicalNot) {
3234
53
      inst->SetOpcode(spv::Op::OpCopyObject);
3235
53
      inst->SetInOperands(
3236
53
          {{SPV_OPERAND_TYPE_ID, {child->GetSingleWordInOperand(0)}}});
3237
53
      return true;
3238
53
    }
3239
15.6k
    return false;
3240
15.6k
  };
3241
16.0k
}
3242
3243
// Cases handled:
3244
//  ((a ? C0 : C1) == C2)  =  ((a ? (C0 == C2) : (C1 == C2))
3245
//  ((a ? C0 : C1) != C2)  =  ((a ? (C0 != C2) : (C1 != C2))
3246
//  ((a ? C0 : C1) <  C2)  =  ((a ? (C0 <  C2) : (C1 <  C2))
3247
//  ((a ? C0 : C1) <= C2)  =  ((a ? (C0 <= C2) : (C1 <= C2))
3248
//  ((a ? C0 : C1) >  C2)  =  ((a ? (C0 >  C2) : (C1 >  C2))
3249
//  ((a ? C0 : C1) >= C2)  =  ((a ? (C0 >= C2) : (C1 >= C2))
3250
//  ((a ? C0 : C1) || C2)  =  ((a ? (C0 || C2) : (C1 || C2))
3251
//  ((a ? C0 : C1) && C2)  =  ((a ? (C0 && C2) : (C1 && C2))
3252
//  ((a ? C0 : C1) +  C2)  =  ((a ? (C0 +  C2) : (C1 +  C2))
3253
//  ((a ? C0 : C1) -  C2)  =  ((a ? (C0 -  C2) : (C1 -  C2))
3254
//  ((a ? C0 : C1) *  C2)  =  ((a ? (C0 *  C2) : (C1 *  C2))
3255
//  ((a ? C0 : C1) /  C2)  =  ((a ? (C0 /  C2) : (C1 /  C2))
3256
//  ((a ? C0 : C1) >> C2)  =  ((a ? (C0 >> C2) : (C1 >> C2))
3257
//  ((a ? C0 : C1) << C2)  =  ((a ? (C0 << C2) : (C1 << C2))
3258
//  ((a ? C0 : C1) ^  C2)  =  ((a ? (C0 ^  C2) : (C1 ^  C2))
3259
//  ((a ? C0 : C1) |  C2)  =  ((a ? (C0 |  C2) : (C1 |  C2))
3260
//  ((a ? C0 : C1) &  C2)  =  ((a ? (C0 &  C2) : (C1 &  C2))
3261
static const constexpr spv::Op MergeBinaryOpSelectOps[] = {
3262
    spv::Op::OpLogicalEqual,
3263
    spv::Op::OpLogicalNotEqual,
3264
    spv::Op::OpLogicalAnd,
3265
    spv::Op::OpLogicalOr,
3266
    spv::Op::OpIEqual,
3267
    spv::Op::OpINotEqual,
3268
    spv::Op::OpUGreaterThan,
3269
    spv::Op::OpSGreaterThan,
3270
    spv::Op::OpUGreaterThanEqual,
3271
    spv::Op::OpSGreaterThanEqual,
3272
    spv::Op::OpULessThan,
3273
    spv::Op::OpSLessThan,
3274
    spv::Op::OpULessThanEqual,
3275
    spv::Op::OpSLessThanEqual,
3276
    spv::Op::OpFOrdEqual,
3277
    spv::Op::OpFUnordEqual,
3278
    spv::Op::OpFOrdNotEqual,
3279
    spv::Op::OpFUnordNotEqual,
3280
    spv::Op::OpFOrdLessThan,
3281
    spv::Op::OpFUnordLessThan,
3282
    spv::Op::OpFOrdGreaterThan,
3283
    spv::Op::OpFUnordGreaterThan,
3284
    spv::Op::OpFOrdLessThanEqual,
3285
    spv::Op::OpFUnordLessThanEqual,
3286
    spv::Op::OpFOrdGreaterThanEqual,
3287
    spv::Op::OpFUnordGreaterThanEqual,
3288
    spv::Op::OpIAdd,
3289
    spv::Op::OpFAdd,
3290
    spv::Op::OpISub,
3291
    spv::Op::OpFSub,
3292
    spv::Op::OpIMul,
3293
    spv::Op::OpFMul,
3294
    spv::Op::OpUDiv,
3295
    spv::Op::OpSDiv,
3296
    spv::Op::OpFDiv,
3297
    spv::Op::OpVectorTimesScalar,
3298
    spv::Op::OpShiftRightLogical,
3299
    spv::Op::OpShiftRightArithmetic,
3300
    spv::Op::OpShiftLeftLogical,
3301
    spv::Op::OpBitwiseXor,
3302
    spv::Op::OpBitwiseOr,
3303
    spv::Op::OpBitwiseAnd};
3304
3305
672k
FoldingRule MergeBinaryOpSelect(spv::Op opcode) {
3306
672k
  assert(std::find(std::begin(MergeBinaryOpSelectOps),
3307
672k
                   std::end(MergeBinaryOpSelectOps),
3308
672k
                   opcode) != std::end(MergeBinaryOpSelectOps) &&
3309
672k
         "Wrong opcode.");
3310
3311
672k
  return [opcode](IRContext* context, Instruction* inst,
3312
1.59M
                  const std::vector<const analysis::Constant*>& constants) {
3313
1.59M
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
3314
1.59M
    analysis::TypeManager* type_mgr = context->get_type_mgr();
3315
1.59M
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
3316
3317
1.59M
    const analysis::Constant* const_input = ConstInput(constants);
3318
1.59M
    if (!const_input) {
3319
816k
      return false;
3320
816k
    }
3321
780k
    Instruction* non_const = NonConstInput(context, constants[0], inst);
3322
780k
    if (non_const->opcode() != spv::Op::OpSelect) {
3323
779k
      return false;
3324
779k
    }
3325
966
    std::vector<const analysis::Constant*> select_constants =
3326
966
        const_mgr->GetOperandConstants(non_const);
3327
966
    if (!select_constants[1] || !select_constants[2]) {
3328
329
      return false;
3329
329
    }
3330
3331
    // The OpSelect that will be created below will use the condition from
3332
    // `non_const` and a result type matching `inst`. Before SPIR-V 1.4,
3333
    // OpSelect could not have a scalar condition with a vector result.
3334
    // We must avoid generating the OpSelect if that would happen.
3335
637
    const analysis::Type* result_type = type_mgr->GetType(inst->type_id());
3336
637
    if (result_type && result_type->AsVector()) {
3337
0
      Instruction* cond_inst =
3338
0
          def_use_mgr->GetDef(non_const->GetSingleWordInOperand(0));
3339
0
      const analysis::Type* cond_type = type_mgr->GetType(cond_inst->type_id());
3340
0
      if (cond_type && !cond_type->AsVector()) {
3341
0
        if (spvVersionForTargetEnv(context->grammar().target_env()) <
3342
0
            SPV_SPIRV_VERSION_WORD(1, 4)) {
3343
0
          return false;
3344
0
        }
3345
0
      }
3346
0
    }
3347
3348
637
    InstructionBuilder ir_builder(
3349
637
        context, inst,
3350
637
        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
3351
3352
637
    Instruction *lhs, *rhs;
3353
637
    if (constants[0]) {
3354
319
      lhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
3355
319
                                   inst->GetSingleWordInOperand(0),
3356
319
                                   non_const->GetSingleWordInOperand(1));
3357
319
      rhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
3358
319
                                   inst->GetSingleWordInOperand(0),
3359
319
                                   non_const->GetSingleWordInOperand(2));
3360
319
    } else {
3361
318
      lhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
3362
318
                                   non_const->GetSingleWordInOperand(1),
3363
318
                                   inst->GetSingleWordInOperand(1));
3364
318
      rhs = ir_builder.AddBinaryOp(inst->type_id(), opcode,
3365
318
                                   non_const->GetSingleWordInOperand(2),
3366
318
                                   inst->GetSingleWordInOperand(1));
3367
318
    }
3368
3369
637
    if (!lhs || !rhs) {
3370
0
      return false;
3371
0
    }
3372
3373
637
    if (context->get_instruction_folder().FoldInstruction(lhs)) {
3374
637
      context->AnalyzeDefUse(lhs);
3375
1.27k
      while (lhs->opcode() == spv::Op::OpCopyObject) {
3376
637
        lhs = def_use_mgr->GetDef(lhs->GetSingleWordInOperand(0));
3377
637
      }
3378
637
    }
3379
637
    if (context->get_instruction_folder().FoldInstruction(rhs)) {
3380
637
      context->AnalyzeDefUse(rhs);
3381
1.27k
      while (rhs->opcode() == spv::Op::OpCopyObject) {
3382
637
        rhs = def_use_mgr->GetDef(rhs->GetSingleWordInOperand(0));
3383
637
      }
3384
637
    }
3385
637
    inst->SetOpcode(spv::Op::OpSelect);
3386
637
    inst->SetInOperands(
3387
637
        {{SPV_OPERAND_TYPE_ID, {non_const->GetSingleWordInOperand(0)}},
3388
637
         {SPV_OPERAND_TYPE_ID, {lhs->result_id()}},
3389
637
         {SPV_OPERAND_TYPE_ID, {rhs->result_id()}}});
3390
637
    return true;
3391
637
  };
3392
672k
}
3393
3394
// Fold OpLogicalNot instructions that follow a comparison,
3395
// if the comparison is only used by that instruction.
3396
//
3397
// !(a == b) = (a != b)
3398
// !(a != b) = (a == b)
3399
// !(a < b)  = (a >= b)
3400
// !(a >= b) = (a < b)
3401
// !(a > b)  = (a <= b)
3402
// !(a <= b) = (a > b)
3403
16.0k
FoldingRule FoldLogicalNotComparison() {
3404
16.0k
  return [](IRContext* context, Instruction* inst,
3405
16.0k
            const std::vector<const analysis::Constant*>&) {
3406
15.6k
    assert(inst->opcode() == spv::Op::OpLogicalNot);
3407
15.6k
    analysis::DefUseManager* def_mgr = context->get_def_use_mgr();
3408
15.6k
    Instruction* child =
3409
15.6k
        context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
3410
3411
15.6k
    if (def_mgr->NumUses(child) > 1) {
3412
8.23k
      return false;
3413
8.23k
    }
3414
3415
7.38k
    spv::Op new_opcode = spv::Op::OpNop;
3416
7.38k
    switch (child->opcode()) {
3417
      // (a == b) <=> (a != b)
3418
2
      case spv::Op::OpIEqual:
3419
2
        new_opcode = spv::Op::OpINotEqual;
3420
2
        break;
3421
29
      case spv::Op::OpINotEqual:
3422
29
        new_opcode = spv::Op::OpIEqual;
3423
29
        break;
3424
134
      case spv::Op::OpFOrdEqual:
3425
134
        new_opcode = spv::Op::OpFUnordNotEqual;
3426
134
        break;
3427
127
      case spv::Op::OpFOrdNotEqual:
3428
127
        new_opcode = spv::Op::OpFUnordEqual;
3429
127
        break;
3430
111
      case spv::Op::OpFUnordEqual:
3431
111
        new_opcode = spv::Op::OpFOrdNotEqual;
3432
111
        break;
3433
55
      case spv::Op::OpFUnordNotEqual:
3434
55
        new_opcode = spv::Op::OpFOrdEqual;
3435
55
        break;
3436
9
      case spv::Op::OpLogicalEqual:
3437
9
        new_opcode = spv::Op::OpLogicalNotEqual;
3438
9
        break;
3439
7
      case spv::Op::OpLogicalNotEqual:
3440
7
        new_opcode = spv::Op::OpLogicalEqual;
3441
7
        break;
3442
3443
      // (a > b) <=> (a <= b)
3444
4
      case spv::Op::OpUGreaterThan:
3445
4
        new_opcode = spv::Op::OpULessThanEqual;
3446
4
        break;
3447
4
      case spv::Op::OpULessThanEqual:
3448
4
        new_opcode = spv::Op::OpUGreaterThan;
3449
4
        break;
3450
38
      case spv::Op::OpSGreaterThan:
3451
38
        new_opcode = spv::Op::OpSLessThanEqual;
3452
38
        break;
3453
2
      case spv::Op::OpSLessThanEqual:
3454
2
        new_opcode = spv::Op::OpSGreaterThan;
3455
2
        break;
3456
2.45k
      case spv::Op::OpFOrdGreaterThan:
3457
2.45k
        new_opcode = spv::Op::OpFUnordLessThanEqual;
3458
2.45k
        break;
3459
139
      case spv::Op::OpFOrdLessThanEqual:
3460
139
        new_opcode = spv::Op::OpFUnordGreaterThan;
3461
139
        break;
3462
117
      case spv::Op::OpFUnordGreaterThan:
3463
117
        new_opcode = spv::Op::OpFOrdLessThanEqual;
3464
117
        break;
3465
138
      case spv::Op::OpFUnordLessThanEqual:
3466
138
        new_opcode = spv::Op::OpFOrdGreaterThan;
3467
138
        break;
3468
3469
      // (a < b) <=> (a >= b)
3470
4
      case spv::Op::OpULessThan:
3471
4
        new_opcode = spv::Op::OpUGreaterThanEqual;
3472
4
        break;
3473
5
      case spv::Op::OpUGreaterThanEqual:
3474
5
        new_opcode = spv::Op::OpULessThan;
3475
5
        break;
3476
2
      case spv::Op::OpSLessThan:
3477
2
        new_opcode = spv::Op::OpSGreaterThanEqual;
3478
2
        break;
3479
4
      case spv::Op::OpSGreaterThanEqual:
3480
4
        new_opcode = spv::Op::OpSLessThan;
3481
4
        break;
3482
2.84k
      case spv::Op::OpFOrdLessThan:
3483
2.84k
        new_opcode = spv::Op::OpFUnordGreaterThanEqual;
3484
2.84k
        break;
3485
70
      case spv::Op::OpFOrdGreaterThanEqual:
3486
70
        new_opcode = spv::Op::OpFUnordLessThan;
3487
70
        break;
3488
30
      case spv::Op::OpFUnordLessThan:
3489
30
        new_opcode = spv::Op::OpFOrdGreaterThanEqual;
3490
30
        break;
3491
52
      case spv::Op::OpFUnordGreaterThanEqual:
3492
52
        new_opcode = spv::Op::OpFOrdLessThan;
3493
52
        break;
3494
3495
998
      default:
3496
998
        break;
3497
7.38k
    }
3498
3499
7.38k
    if (new_opcode == spv::Op::OpNop) {
3500
998
      return false;
3501
998
    }
3502
3503
6.38k
    inst->SetOpcode(new_opcode);
3504
6.38k
    inst->SetInOperands(
3505
6.38k
        {{SPV_OPERAND_TYPE_ID, {child->GetSingleWordInOperand(0)}},
3506
6.38k
         {SPV_OPERAND_TYPE_ID, {child->GetSingleWordInOperand(1)}}});
3507
3508
6.38k
    return true;
3509
7.38k
  };
3510
16.0k
}
3511
3512
// (a == true)  =  a
3513
// (a == false) = !a
3514
// (a != true)  = !a
3515
// (a != false) =  a
3516
32.0k
FoldingRule RedundantLogicalEqual() {
3517
32.0k
  return [](IRContext* context, Instruction* inst,
3518
32.0k
            const std::vector<const analysis::Constant*>& constants) {
3519
2.11k
    assert(inst->opcode() == spv::Op::OpLogicalEqual ||
3520
2.11k
           inst->opcode() == spv::Op::OpLogicalNotEqual);
3521
3522
2.11k
    const analysis::Constant* const_input = ConstInput(constants);
3523
2.11k
    if (!const_input) {
3524
316
      return false;
3525
316
    }
3526
3527
1.79k
    analysis::DefUseManager* def_mgr = context->get_def_use_mgr();
3528
1.79k
    if (inst->type_id() !=
3529
1.79k
        def_mgr->GetDef(inst->GetSingleWordInOperand(0))->type_id()) {
3530
0
      return false;
3531
0
    }
3532
3533
1.79k
    std::optional<bool> uniform_const = GetBoolConstantKind(const_input);
3534
1.79k
    if (!uniform_const) {
3535
0
      return false;
3536
0
    }
3537
3538
1.79k
    bool direct_copy = inst->opcode() == spv::Op::OpLogicalEqual
3539
1.79k
                           ? uniform_const.value()
3540
1.79k
                           : !uniform_const.value();
3541
3542
1.79k
    inst->SetOpcode(direct_copy ? spv::Op::OpCopyObject
3543
1.79k
                                : spv::Op::OpLogicalNot);
3544
1.79k
    inst->SetInOperands(
3545
1.79k
        {{SPV_OPERAND_TYPE_ID,
3546
1.79k
          {NonConstInput(context, constants[0], inst)->result_id()}}});
3547
1.79k
    return true;
3548
1.79k
  };
3549
32.0k
}
3550
3551
enum class FloatConstantKind { Unknown, Zero, One };
3552
3553
2.17M
FloatConstantKind getFloatConstantKind(const analysis::Constant* constant) {
3554
2.17M
  if (constant == nullptr) {
3555
1.29M
    return FloatConstantKind::Unknown;
3556
1.29M
  }
3557
3558
2.17M
  assert(HasFloatingPoint(constant->type()) && "Unexpected constant type");
3559
3560
886k
  if (constant->AsNullConstant()) {
3561
2.71k
    return FloatConstantKind::Zero;
3562
883k
  } else if (const analysis::VectorConstant* vc =
3563
883k
                 constant->AsVectorConstant()) {
3564
221k
    const std::vector<const analysis::Constant*>& components =
3565
221k
        vc->GetComponents();
3566
221k
    assert(!components.empty());
3567
3568
221k
    FloatConstantKind kind = getFloatConstantKind(components[0]);
3569
3570
435k
    for (size_t i = 1; i < components.size(); ++i) {
3571
259k
      if (getFloatConstantKind(components[i]) != kind) {
3572
45.9k
        return FloatConstantKind::Unknown;
3573
45.9k
      }
3574
259k
    }
3575
3576
175k
    return kind;
3577
662k
  } else if (const analysis::FloatConstant* fc = constant->AsFloatConstant()) {
3578
662k
    if (fc->IsZero()) return FloatConstantKind::Zero;
3579
3580
585k
    uint32_t width = fc->type()->AsFloat()->width();
3581
585k
    if (width != 32 && width != 64) return FloatConstantKind::Unknown;
3582
3583
585k
    double value = (width == 64) ? fc->GetDoubleValue() : fc->GetFloatValue();
3584
3585
585k
    if (value == 0.0) {
3586
10.4k
      return FloatConstantKind::Zero;
3587
574k
    } else if (value == 1.0) {
3588
30.3k
      return FloatConstantKind::One;
3589
544k
    } else {
3590
544k
      return FloatConstantKind::Unknown;
3591
544k
    }
3592
585k
  } else {
3593
0
    return FloatConstantKind::Unknown;
3594
0
  }
3595
886k
}
3596
3597
16.0k
FoldingRule RedundantFAdd() {
3598
16.0k
  return [](IRContext*, Instruction* inst,
3599
424k
            const std::vector<const analysis::Constant*>& constants) {
3600
424k
    assert(inst->opcode() == spv::Op::OpFAdd &&
3601
424k
           "Wrong opcode.  Should be OpFAdd.");
3602
424k
    assert(constants.size() == 2);
3603
3604
424k
    if (!inst->IsFloatingPointFoldingAllowed()) {
3605
6.06k
      return false;
3606
6.06k
    }
3607
3608
418k
    FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
3609
418k
    FloatConstantKind kind1 = getFloatConstantKind(constants[1]);
3610
3611
418k
    if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::Zero) {
3612
11.1k
      inst->SetOpcode(spv::Op::OpCopyObject);
3613
11.1k
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID,
3614
11.1k
                            {inst->GetSingleWordInOperand(
3615
11.1k
                                kind0 == FloatConstantKind::Zero ? 1 : 0)}}});
3616
11.1k
      return true;
3617
11.1k
    }
3618
3619
407k
    return false;
3620
418k
  };
3621
16.0k
}
3622
3623
16.0k
FoldingRule RedundantFSub() {
3624
16.0k
  return [](IRContext*, Instruction* inst,
3625
96.8k
            const std::vector<const analysis::Constant*>& constants) {
3626
96.8k
    assert(inst->opcode() == spv::Op::OpFSub &&
3627
96.8k
           "Wrong opcode.  Should be OpFSub.");
3628
96.8k
    assert(constants.size() == 2);
3629
3630
96.8k
    if (!inst->IsFloatingPointFoldingAllowed()) {
3631
4.86k
      return false;
3632
4.86k
    }
3633
3634
92.0k
    FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
3635
92.0k
    FloatConstantKind kind1 = getFloatConstantKind(constants[1]);
3636
3637
92.0k
    if (kind0 == FloatConstantKind::Zero) {
3638
1.90k
      inst->SetOpcode(spv::Op::OpFNegate);
3639
1.90k
      inst->SetInOperands(
3640
1.90k
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1)}}});
3641
1.90k
      return true;
3642
1.90k
    }
3643
3644
90.0k
    if (kind1 == FloatConstantKind::Zero) {
3645
2.29k
      inst->SetOpcode(spv::Op::OpCopyObject);
3646
2.29k
      inst->SetInOperands(
3647
2.29k
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
3648
2.29k
      return true;
3649
2.29k
    }
3650
3651
87.8k
    return false;
3652
90.0k
  };
3653
16.0k
}
3654
3655
16.0k
FoldingRule RedundantFMul() {
3656
16.0k
  return [](IRContext*, Instruction* inst,
3657
197k
            const std::vector<const analysis::Constant*>& constants) {
3658
197k
    assert(inst->opcode() == spv::Op::OpFMul &&
3659
197k
           "Wrong opcode.  Should be OpFMul.");
3660
197k
    assert(constants.size() == 2);
3661
3662
197k
    if (!inst->IsFloatingPointFoldingAllowed()) {
3663
101
      return false;
3664
101
    }
3665
3666
197k
    FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
3667
197k
    FloatConstantKind kind1 = getFloatConstantKind(constants[1]);
3668
3669
197k
    if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::Zero) {
3670
5.42k
      inst->SetOpcode(spv::Op::OpCopyObject);
3671
5.42k
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID,
3672
5.42k
                            {inst->GetSingleWordInOperand(
3673
5.42k
                                kind0 == FloatConstantKind::Zero ? 0 : 1)}}});
3674
5.42k
      return true;
3675
5.42k
    }
3676
3677
192k
    if (kind0 == FloatConstantKind::One || kind1 == FloatConstantKind::One) {
3678
1.63k
      inst->SetOpcode(spv::Op::OpCopyObject);
3679
1.63k
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID,
3680
1.63k
                            {inst->GetSingleWordInOperand(
3681
1.63k
                                kind0 == FloatConstantKind::One ? 1 : 0)}}});
3682
1.63k
      return true;
3683
1.63k
    }
3684
3685
190k
    return false;
3686
192k
  };
3687
16.0k
}
3688
3689
16.0k
FoldingRule RedundantFDiv() {
3690
16.0k
  return [](IRContext*, Instruction* inst,
3691
104k
            const std::vector<const analysis::Constant*>& constants) {
3692
104k
    assert(inst->opcode() == spv::Op::OpFDiv &&
3693
104k
           "Wrong opcode.  Should be OpFDiv.");
3694
104k
    assert(constants.size() == 2);
3695
3696
104k
    if (!inst->IsFloatingPointFoldingAllowed()) {
3697
27
      return false;
3698
27
    }
3699
3700
104k
    FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
3701
104k
    FloatConstantKind kind1 = getFloatConstantKind(constants[1]);
3702
3703
104k
    if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::One) {
3704
1.24k
      inst->SetOpcode(spv::Op::OpCopyObject);
3705
1.24k
      inst->SetInOperands(
3706
1.24k
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
3707
1.24k
      return true;
3708
1.24k
    }
3709
3710
102k
    return false;
3711
104k
  };
3712
16.0k
}
3713
3714
16.0k
FoldingRule RedundantFMod() {
3715
16.0k
  return [](IRContext*, Instruction* inst,
3716
64.1k
            const std::vector<const analysis::Constant*>& constants) {
3717
64.1k
    assert(inst->opcode() == spv::Op::OpFMod &&
3718
64.1k
           "Wrong opcode.  Should be OpFMod.");
3719
64.1k
    assert(constants.size() == 2);
3720
3721
64.1k
    if (!inst->IsFloatingPointFoldingAllowed()) {
3722
248
      return false;
3723
248
    }
3724
3725
63.9k
    FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
3726
3727
63.9k
    if (kind0 == FloatConstantKind::Zero) {
3728
6.07k
      inst->SetOpcode(spv::Op::OpCopyObject);
3729
6.07k
      inst->SetInOperands(
3730
6.07k
          {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
3731
6.07k
      return true;
3732
6.07k
    }
3733
3734
57.8k
    return false;
3735
63.9k
  };
3736
16.0k
}
3737
3738
9.83k
FoldingRule RedundantFMix() {
3739
9.83k
  return [](IRContext* context, Instruction* inst,
3740
9.83k
            const std::vector<const analysis::Constant*>& constants) {
3741
8.96k
    assert(inst->opcode() == spv::Op::OpExtInst &&
3742
8.96k
           "Wrong opcode.  Should be OpExtInst.");
3743
3744
8.96k
    if (!inst->IsFloatingPointFoldingAllowed()) {
3745
0
      return false;
3746
0
    }
3747
3748
8.96k
    uint32_t instSetId =
3749
8.96k
        context->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
3750
3751
8.96k
    if (inst->GetSingleWordInOperand(kExtInstSetIdInIdx) == instSetId &&
3752
8.96k
        inst->GetSingleWordInOperand(kExtInstInstructionInIdx) ==
3753
8.96k
            GLSLstd450FMix) {
3754
8.96k
      assert(constants.size() == 5);
3755
3756
8.96k
      FloatConstantKind kind4 = getFloatConstantKind(constants[4]);
3757
3758
8.96k
      if (kind4 == FloatConstantKind::Zero || kind4 == FloatConstantKind::One) {
3759
116
        inst->SetOpcode(spv::Op::OpCopyObject);
3760
116
        inst->SetInOperands(
3761
116
            {{SPV_OPERAND_TYPE_ID,
3762
116
              {inst->GetSingleWordInOperand(kind4 == FloatConstantKind::Zero
3763
116
                                                ? kFMixXIdInIdx
3764
116
                                                : kFMixYIdInIdx)}}});
3765
116
        return true;
3766
116
      }
3767
8.96k
    }
3768
3769
8.84k
    return false;
3770
8.96k
  };
3771
9.83k
}
3772
3773
// Returns a folding rule that folds the instruction to operand |foldToArg|
3774
// (0 or 1) if operand |arg| (0 or 1) is a zero constant.
3775
272k
FoldingRule RedundantBinaryOpWithZeroOperand(uint32_t arg, uint32_t foldToArg) {
3776
272k
  return [arg, foldToArg](
3777
272k
             IRContext* context, Instruction* inst,
3778
316k
             const std::vector<const analysis::Constant*>& constants) {
3779
316k
    assert(constants.size() == 2);
3780
3781
316k
    if (constants[arg] && constants[arg]->IsZero()) {
3782
6.89k
      auto operand = inst->GetSingleWordInOperand(foldToArg);
3783
6.89k
      auto operand_type = constants[arg]->type();
3784
3785
6.89k
      const analysis::Type* inst_type =
3786
6.89k
          context->get_type_mgr()->GetType(inst->type_id());
3787
6.89k
      if (inst_type->IsSame(operand_type)) {
3788
6.80k
        inst->SetOpcode(spv::Op::OpCopyObject);
3789
6.80k
      } else {
3790
88
        inst->SetOpcode(spv::Op::OpBitcast);
3791
88
      }
3792
6.89k
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {operand}}});
3793
6.89k
      return true;
3794
6.89k
    }
3795
309k
    return false;
3796
316k
  };
3797
272k
}
3798
3799
// This rule handles any of RedundantBinaryRhs0Ops with a 0 or vector 0 on the
3800
// right-hand side (a | 0 => a).
3801
static const constexpr spv::Op RedundantBinaryRhs0Ops[] = {
3802
    spv::Op::OpBitwiseOr,
3803
    spv::Op::OpBitwiseXor,
3804
    spv::Op::OpShiftRightLogical,
3805
    spv::Op::OpShiftRightArithmetic,
3806
    spv::Op::OpShiftLeftLogical,
3807
    spv::Op::OpIAdd,
3808
    spv::Op::OpISub};
3809
112k
FoldingRule RedundantBinaryRhs0(spv::Op op) {
3810
112k
  assert(std::find(std::begin(RedundantBinaryRhs0Ops),
3811
112k
                   std::end(RedundantBinaryRhs0Ops),
3812
112k
                   op) != std::end(RedundantBinaryRhs0Ops) &&
3813
112k
         "Wrong opcode.");
3814
112k
  (void)op;
3815
112k
  return RedundantBinaryOpWithZeroOperand(1, 0);
3816
112k
}
3817
3818
// This rule handles any of RedundantBinaryLhs0Ops with a 0 or vector 0 on the
3819
// left-hand side (0 | a => a).
3820
static const constexpr spv::Op RedundantBinaryLhs0Ops[] = {
3821
    spv::Op::OpBitwiseOr, spv::Op::OpBitwiseXor, spv::Op::OpIAdd};
3822
48.0k
FoldingRule RedundantBinaryLhs0(spv::Op op) {
3823
48.0k
  assert(std::find(std::begin(RedundantBinaryLhs0Ops),
3824
48.0k
                   std::end(RedundantBinaryLhs0Ops),
3825
48.0k
                   op) != std::end(RedundantBinaryLhs0Ops) &&
3826
48.0k
         "Wrong opcode.");
3827
48.0k
  (void)op;
3828
48.0k
  return RedundantBinaryOpWithZeroOperand(0, 1);
3829
48.0k
}
3830
3831
// This rule handles shifts and divisions of 0 or vector 0 by any amount
3832
// (0 >> a => 0).
3833
static const constexpr spv::Op RedundantBinaryLhs0To0Ops[] = {
3834
    spv::Op::OpShiftRightLogical,
3835
    spv::Op::OpShiftRightArithmetic,
3836
    spv::Op::OpShiftLeftLogical,
3837
    spv::Op::OpSDiv,
3838
    spv::Op::OpUDiv,
3839
    spv::Op::OpSMod,
3840
    spv::Op::OpUMod};
3841
112k
FoldingRule RedundantBinaryLhs0To0(spv::Op op) {
3842
112k
  assert(std::find(std::begin(RedundantBinaryLhs0To0Ops),
3843
112k
                   std::end(RedundantBinaryLhs0To0Ops),
3844
112k
                   op) != std::end(RedundantBinaryLhs0To0Ops) &&
3845
112k
         "Wrong opcode.");
3846
112k
  (void)op;
3847
112k
  return RedundantBinaryOpWithZeroOperand(0, 0);
3848
112k
}
3849
3850
48.0k
FoldingRule ReassociateCommutiveOp() {
3851
48.0k
  return [](IRContext* context, Instruction* inst,
3852
48.5k
            const std::vector<const analysis::Constant*>& constants) {
3853
48.5k
    const analysis::Type* type =
3854
48.5k
        context->get_type_mgr()->GetType(inst->type_id());
3855
48.5k
    uint32_t width = ElementWidth(type);
3856
48.5k
    if (width != 32) return false;
3857
3858
48.5k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
3859
48.5k
    const analysis::Constant* const_input1 = ConstInput(constants);
3860
48.5k
    if (!const_input1) return false;
3861
21.3k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
3862
3863
21.3k
    if (other_inst->opcode() == inst->opcode()) {
3864
6.90k
      std::vector<const analysis::Constant*> other_constants =
3865
6.90k
          const_mgr->GetOperandConstants(other_inst);
3866
6.90k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
3867
6.90k
      if (!const_input2) return false;
3868
3869
2.00k
      Instruction* non_const_input =
3870
2.00k
          NonConstInput(context, other_constants[0], other_inst);
3871
2.00k
      uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(),
3872
2.00k
                                            const_input1, const_input2);
3873
3874
2.00k
      if (merged_id == 0) return false;
3875
2.00k
      inst->SetInOperands(
3876
2.00k
          {{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}},
3877
2.00k
           {SPV_OPERAND_TYPE_ID, {merged_id}}});
3878
2.00k
      return true;
3879
2.00k
    }
3880
3881
14.4k
    return false;
3882
21.3k
  };
3883
48.0k
}
3884
3885
// A | (b | C) = b | (A | C)
3886
// A ^ (b ^ C) = b ^ (A ^ C)
3887
// A & (b & C) = b & (A & C)
3888
// Where A and C are constants
3889
static const constexpr spv::Op ReassociateCommutiveBitwiseOps[] = {
3890
    spv::Op::OpBitwiseOr, spv::Op::OpBitwiseXor, spv::Op::OpBitwiseAnd};
3891
48.0k
FoldingRule ReassociateCommutiveBitwise(spv::Op op) {
3892
48.0k
  assert(std::find(std::begin(ReassociateCommutiveBitwiseOps),
3893
48.0k
                   std::end(ReassociateCommutiveBitwiseOps),
3894
48.0k
                   op) != std::end(ReassociateCommutiveBitwiseOps) &&
3895
48.0k
         "Wrong opcode.");
3896
48.0k
  (void)op;
3897
48.0k
  return ReassociateCommutiveOp();
3898
48.0k
}
3899
3900
// Returns true if all elements in |c| are 1.
3901
17.7k
bool IsAllInt1(const analysis::Constant* c) {
3902
17.7k
  if (auto composite = c->AsCompositeConstant()) {
3903
0
    auto& components = composite->GetComponents();
3904
0
    return std::all_of(std::begin(components), std::end(components), IsAllInt1);
3905
17.7k
  } else if (c->AsIntConstant()) {
3906
17.7k
    return c->GetSignExtendedValue() == 1;
3907
17.7k
  }
3908
3909
25
  return false;
3910
17.7k
}
3911
3912
// This rule handles divisions by 1 or vector 1 (a / 1 => a).
3913
32.0k
FoldingRule RedundantSUDiv() {
3914
32.0k
  return [](IRContext* context, Instruction* inst,
3915
32.0k
            const std::vector<const analysis::Constant*>& constants) {
3916
12.4k
    assert(constants.size() == 2);
3917
12.4k
    assert((inst->opcode() == spv::Op::OpUDiv ||
3918
12.4k
            inst->opcode() == spv::Op::OpSDiv) &&
3919
12.4k
           "Wrong opcode.");
3920
3921
12.4k
    if (constants[1] && IsAllInt1(constants[1])) {
3922
952
      auto operand = inst->GetSingleWordInOperand(0);
3923
952
      auto operand_type = constants[1]->type();
3924
3925
952
      const analysis::Type* inst_type =
3926
952
          context->get_type_mgr()->GetType(inst->type_id());
3927
952
      if (inst_type->IsSame(operand_type)) {
3928
770
        inst->SetOpcode(spv::Op::OpCopyObject);
3929
770
      } else {
3930
182
        inst->SetOpcode(spv::Op::OpBitcast);
3931
182
      }
3932
952
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {operand}}});
3933
952
      return true;
3934
952
    }
3935
11.5k
    return false;
3936
12.4k
  };
3937
32.0k
}
3938
3939
// This rule handles modulo from division by 1 or vector 1 (a % 1 => 0).
3940
32.0k
FoldingRule RedundantSUMod() {
3941
32.0k
  return [](IRContext* context, Instruction* inst,
3942
32.0k
            const std::vector<const analysis::Constant*>& constants) {
3943
8.04k
    assert(constants.size() == 2);
3944
8.04k
    assert((inst->opcode() == spv::Op::OpUMod ||
3945
8.04k
            inst->opcode() == spv::Op::OpSMod) &&
3946
8.04k
           "Wrong opcode.");
3947
3948
8.04k
    if (constants[1] && IsAllInt1(constants[1])) {
3949
865
      auto type = context->get_type_mgr()->GetType(inst->type_id());
3950
865
      auto zero_id = context->get_constant_mgr()->GetNullConstId(type);
3951
3952
865
      inst->SetOpcode(spv::Op::OpCopyObject);
3953
865
      inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
3954
865
      return true;
3955
865
    }
3956
7.17k
    return false;
3957
8.04k
  };
3958
32.0k
}
3959
3960
// Utility function for applying |callback| to |input1| and |input2|.
3961
// If they are vectors it applies element wise.
3962
// The constants |input1| and |input2| must be integers or a vector of integers.
3963
template <typename Callback>
3964
void ForEachIntegerConstantPair(analysis::ConstantManager* const_mgr,
3965
                                const analysis::Constant* input1,
3966
                                const analysis::Constant* input2,
3967
1.57k
                                Callback&& callback) {
3968
1.57k
  assert(input1 && input2);
3969
3970
1.57k
  auto Dispatch = [&callback](const analysis::Constant* lhs,
3971
1.57k
                              const analysis::Constant* rhs) {
3972
1.57k
    assert(lhs->type()->AsInteger());
3973
1.57k
    const analysis::Integer* type = lhs->type()->AsInteger();
3974
1.57k
    uint32_t width = type->AsInteger()->width();
3975
1.57k
    assert(width == 32 || width == 64);
3976
1.57k
    if (width == 32) {
3977
1.57k
      callback(lhs->GetU32(), rhs->GetU32());
3978
1.57k
    } else {
3979
0
      callback(lhs->GetU64(), rhs->GetU64());
3980
0
    }
3981
1.57k
  };
folding_rules.cpp:spvtools::opt::(anonymous namespace)::ForEachIntegerConstantPair<spvtools::opt::(anonymous namespace)::RedundantAndOrXor()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}>(spvtools::opt::analysis::ConstantManager*, spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*, spvtools::opt::(anonymous namespace)::RedundantAndOrXor()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}&&)::{lambda(spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*)#1}::operator()(spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*) const
Line
Count
Source
3971
769
                              const analysis::Constant* rhs) {
3972
769
    assert(lhs->type()->AsInteger());
3973
769
    const analysis::Integer* type = lhs->type()->AsInteger();
3974
769
    uint32_t width = type->AsInteger()->width();
3975
769
    assert(width == 32 || width == 64);
3976
769
    if (width == 32) {
3977
769
      callback(lhs->GetU32(), rhs->GetU32());
3978
769
    } else {
3979
0
      callback(lhs->GetU64(), rhs->GetU64());
3980
0
    }
3981
769
  };
folding_rules.cpp:spvtools::opt::(anonymous namespace)::ForEachIntegerConstantPair<spvtools::opt::(anonymous namespace)::RedundantAndAddSub()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}>(spvtools::opt::analysis::ConstantManager*, spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*, spvtools::opt::(anonymous namespace)::RedundantAndAddSub()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}&&)::{lambda(spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*)#1}::operator()(spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*) const
Line
Count
Source
3971
801
                              const analysis::Constant* rhs) {
3972
801
    assert(lhs->type()->AsInteger());
3973
801
    const analysis::Integer* type = lhs->type()->AsInteger();
3974
801
    uint32_t width = type->AsInteger()->width();
3975
801
    assert(width == 32 || width == 64);
3976
801
    if (width == 32) {
3977
801
      callback(lhs->GetU32(), rhs->GetU32());
3978
801
    } else {
3979
0
      callback(lhs->GetU64(), rhs->GetU64());
3980
0
    }
3981
801
  };
3982
3983
1.57k
  const analysis::Type* type = input1->type();
3984
1.57k
  if (const analysis::Vector* vector_type = type->AsVector()) {
3985
0
    const analysis::Type* ele_type = vector_type->element_type();
3986
0
    assert(ele_type->AsInteger());
3987
0
    for (uint32_t i = 0; i != vector_type->element_count(); ++i) {
3988
0
      const analysis::Constant* input1_comp = nullptr;
3989
0
      if (const analysis::VectorConstant* input1_vector =
3990
0
              input1->AsVectorConstant()) {
3991
0
        input1_comp = input1_vector->GetComponents()[i];
3992
0
      } else {
3993
0
        assert(input1->AsNullConstant());
3994
0
        input1_comp = const_mgr->GetConstant(ele_type, {});
3995
0
      }
3996
3997
0
      const analysis::Constant* input2_comp = nullptr;
3998
0
      if (const analysis::VectorConstant* input2_vector =
3999
0
              input2->AsVectorConstant()) {
4000
0
        input2_comp = input2_vector->GetComponents()[i];
4001
0
      } else {
4002
0
        assert(input2->AsNullConstant());
4003
0
        input2_comp = const_mgr->GetConstant(ele_type, {});
4004
0
      }
4005
4006
0
      assert(ele_type->AsInteger());
4007
0
      Dispatch(input1_comp, input2_comp);
4008
0
    }
4009
4010
1.57k
  } else {
4011
1.57k
    assert(type->AsInteger());
4012
1.57k
    Dispatch(input1, input2);
4013
1.57k
  }
4014
1.57k
}
folding_rules.cpp:void spvtools::opt::(anonymous namespace)::ForEachIntegerConstantPair<spvtools::opt::(anonymous namespace)::RedundantAndOrXor()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}>(spvtools::opt::analysis::ConstantManager*, spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*, spvtools::opt::(anonymous namespace)::RedundantAndOrXor()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}&&)
Line
Count
Source
3967
769
                                Callback&& callback) {
3968
769
  assert(input1 && input2);
3969
3970
769
  auto Dispatch = [&callback](const analysis::Constant* lhs,
3971
769
                              const analysis::Constant* rhs) {
3972
769
    assert(lhs->type()->AsInteger());
3973
769
    const analysis::Integer* type = lhs->type()->AsInteger();
3974
769
    uint32_t width = type->AsInteger()->width();
3975
769
    assert(width == 32 || width == 64);
3976
769
    if (width == 32) {
3977
769
      callback(lhs->GetU32(), rhs->GetU32());
3978
769
    } else {
3979
769
      callback(lhs->GetU64(), rhs->GetU64());
3980
769
    }
3981
769
  };
3982
3983
769
  const analysis::Type* type = input1->type();
3984
769
  if (const analysis::Vector* vector_type = type->AsVector()) {
3985
0
    const analysis::Type* ele_type = vector_type->element_type();
3986
0
    assert(ele_type->AsInteger());
3987
0
    for (uint32_t i = 0; i != vector_type->element_count(); ++i) {
3988
0
      const analysis::Constant* input1_comp = nullptr;
3989
0
      if (const analysis::VectorConstant* input1_vector =
3990
0
              input1->AsVectorConstant()) {
3991
0
        input1_comp = input1_vector->GetComponents()[i];
3992
0
      } else {
3993
0
        assert(input1->AsNullConstant());
3994
0
        input1_comp = const_mgr->GetConstant(ele_type, {});
3995
0
      }
3996
3997
0
      const analysis::Constant* input2_comp = nullptr;
3998
0
      if (const analysis::VectorConstant* input2_vector =
3999
0
              input2->AsVectorConstant()) {
4000
0
        input2_comp = input2_vector->GetComponents()[i];
4001
0
      } else {
4002
0
        assert(input2->AsNullConstant());
4003
0
        input2_comp = const_mgr->GetConstant(ele_type, {});
4004
0
      }
4005
4006
0
      assert(ele_type->AsInteger());
4007
0
      Dispatch(input1_comp, input2_comp);
4008
0
    }
4009
4010
769
  } else {
4011
769
    assert(type->AsInteger());
4012
769
    Dispatch(input1, input2);
4013
769
  }
4014
769
}
folding_rules.cpp:void spvtools::opt::(anonymous namespace)::ForEachIntegerConstantPair<spvtools::opt::(anonymous namespace)::RedundantAndAddSub()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}>(spvtools::opt::analysis::ConstantManager*, spvtools::opt::analysis::Constant const*, spvtools::opt::analysis::Constant const*, spvtools::opt::(anonymous namespace)::RedundantAndAddSub()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}&&)
Line
Count
Source
3967
801
                                Callback&& callback) {
3968
801
  assert(input1 && input2);
3969
3970
801
  auto Dispatch = [&callback](const analysis::Constant* lhs,
3971
801
                              const analysis::Constant* rhs) {
3972
801
    assert(lhs->type()->AsInteger());
3973
801
    const analysis::Integer* type = lhs->type()->AsInteger();
3974
801
    uint32_t width = type->AsInteger()->width();
3975
801
    assert(width == 32 || width == 64);
3976
801
    if (width == 32) {
3977
801
      callback(lhs->GetU32(), rhs->GetU32());
3978
801
    } else {
3979
801
      callback(lhs->GetU64(), rhs->GetU64());
3980
801
    }
3981
801
  };
3982
3983
801
  const analysis::Type* type = input1->type();
3984
801
  if (const analysis::Vector* vector_type = type->AsVector()) {
3985
0
    const analysis::Type* ele_type = vector_type->element_type();
3986
0
    assert(ele_type->AsInteger());
3987
0
    for (uint32_t i = 0; i != vector_type->element_count(); ++i) {
3988
0
      const analysis::Constant* input1_comp = nullptr;
3989
0
      if (const analysis::VectorConstant* input1_vector =
3990
0
              input1->AsVectorConstant()) {
3991
0
        input1_comp = input1_vector->GetComponents()[i];
3992
0
      } else {
3993
0
        assert(input1->AsNullConstant());
3994
0
        input1_comp = const_mgr->GetConstant(ele_type, {});
3995
0
      }
3996
3997
0
      const analysis::Constant* input2_comp = nullptr;
3998
0
      if (const analysis::VectorConstant* input2_vector =
3999
0
              input2->AsVectorConstant()) {
4000
0
        input2_comp = input2_vector->GetComponents()[i];
4001
0
      } else {
4002
0
        assert(input2->AsNullConstant());
4003
0
        input2_comp = const_mgr->GetConstant(ele_type, {});
4004
0
      }
4005
4006
0
      assert(ele_type->AsInteger());
4007
0
      Dispatch(input1_comp, input2_comp);
4008
0
    }
4009
4010
801
  } else {
4011
801
    assert(type->AsInteger());
4012
801
    Dispatch(input1, input2);
4013
801
  }
4014
801
}
4015
4016
// Folds redundant xor and or ops that are part of an and.
4017
// Cases handled:
4018
// 0b1110 & (a | 0b0001) = a & 0b1110
4019
// 0b1110 & (a ^ 0b0001) = a & 0b1110
4020
// 0b0110 & (a | 0b1110) = 0b0110
4021
16.0k
FoldingRule RedundantAndOrXor() {
4022
16.0k
  return [](IRContext* context, Instruction* inst,
4023
16.0k
            const std::vector<const analysis::Constant*>& constants) {
4024
13.2k
    assert(inst->opcode() == spv::Op::OpBitwiseAnd && "Wrong opcode.");
4025
13.2k
    const analysis::Type* type =
4026
13.2k
        context->get_type_mgr()->GetType(inst->type_id());
4027
13.2k
    uint32_t width = ElementWidth(type);
4028
13.2k
    if ((width != 32) && (width != 64)) return false;
4029
4030
13.2k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
4031
13.2k
    const analysis::Constant* const_input1 = ConstInput(constants);
4032
13.2k
    if (!const_input1) return false;
4033
9.97k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
4034
4035
9.97k
    if (other_inst->opcode() == spv::Op::OpBitwiseOr ||
4036
8.48k
        other_inst->opcode() == spv::Op::OpBitwiseXor) {
4037
1.60k
      std::vector<const analysis::Constant*> other_constants =
4038
1.60k
          const_mgr->GetOperandConstants(other_inst);
4039
1.60k
      const analysis::Constant* const_input2 = ConstInput(other_constants);
4040
1.60k
      if (!const_input2) return false;
4041
4042
769
      bool can_convert_to_const = other_inst->opcode() == spv::Op::OpBitwiseOr;
4043
769
      bool can_remove_inner = true;
4044
4045
769
      ForEachIntegerConstantPair(
4046
769
          const_mgr, const_input1, const_input2,
4047
769
          [&can_remove_inner, &can_convert_to_const](auto lhs, auto rhs) {
4048
            // Only convert to const if 'and' is a subset of 'or'
4049
769
            can_convert_to_const = can_convert_to_const && ((lhs & rhs) == lhs);
4050
            // Only remove 'xor'/'or' if no bits intersect with 'and'
4051
769
            can_remove_inner = can_remove_inner && ((lhs & rhs) == 0);
4052
769
          });
folding_rules.cpp:auto spvtools::opt::(anonymous namespace)::RedundantAndOrXor()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}::operator()<unsigned int, unsigned int>(unsigned int, unsigned int) const
Line
Count
Source
4047
769
          [&can_remove_inner, &can_convert_to_const](auto lhs, auto rhs) {
4048
            // Only convert to const if 'and' is a subset of 'or'
4049
769
            can_convert_to_const = can_convert_to_const && ((lhs & rhs) == lhs);
4050
            // Only remove 'xor'/'or' if no bits intersect with 'and'
4051
769
            can_remove_inner = can_remove_inner && ((lhs & rhs) == 0);
4052
769
          });
Unexecuted instantiation: folding_rules.cpp:auto spvtools::opt::(anonymous namespace)::RedundantAndOrXor()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}::operator()<unsigned long, unsigned long>(unsigned long, unsigned long) const
4053
4054
769
      if (can_convert_to_const) {
4055
63
        Instruction* const_inst =
4056
63
            const_mgr->GetDefiningInstruction(const_input1);
4057
63
        inst->SetOpcode(spv::Op::OpCopyObject);
4058
63
        inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {const_inst->result_id()}}});
4059
63
        return true;
4060
706
      } else if (can_remove_inner) {
4061
59
        Instruction* non_const_input =
4062
59
            NonConstInput(context, other_constants[0], other_inst);
4063
59
        Instruction* const_inst =
4064
59
            const_mgr->GetDefiningInstruction(const_input1);
4065
59
        inst->SetInOperands(
4066
59
            {{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}},
4067
59
             {SPV_OPERAND_TYPE_ID, {const_inst->result_id()}}});
4068
59
        return true;
4069
59
      }
4070
769
    }
4071
9.02k
    return false;
4072
9.97k
  };
4073
16.0k
}
4074
4075
// Folds redundant add and sub ops that are part of an and.
4076
// Cases handled:
4077
// 1 & (b + 2) = b & 1
4078
// 1 & (b - 2) = b & 1
4079
16.0k
FoldingRule RedundantAndAddSub() {
4080
16.0k
  return [](IRContext* context, Instruction* inst,
4081
16.0k
            const std::vector<const analysis::Constant*>& constants) {
4082
13.1k
    assert(inst->opcode() == spv::Op::OpBitwiseAnd && "Wrong opcode.");
4083
13.1k
    const analysis::Type* type =
4084
13.1k
        context->get_type_mgr()->GetType(inst->type_id());
4085
13.1k
    uint32_t width = ElementWidth(type);
4086
13.1k
    if ((width != 32) && (width != 64)) return false;
4087
4088
13.1k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
4089
13.1k
    const analysis::Constant* const_input1 = ConstInput(constants);
4090
13.1k
    if (!const_input1) return false;
4091
9.85k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
4092
4093
9.85k
    if (other_inst->opcode() != spv::Op::OpIAdd &&
4094
9.05k
        other_inst->opcode() != spv::Op::OpISub) {
4095
8.74k
      return false;
4096
8.74k
    }
4097
1.10k
    std::vector<const analysis::Constant*> other_constants =
4098
1.10k
        const_mgr->GetOperandConstants(other_inst);
4099
1.10k
    const analysis::Constant* const_input2 = ConstInput(other_constants);
4100
1.10k
    if (!const_input2) return false;
4101
4102
    // Only valid for subtraction if const is on the right
4103
870
    if ((other_inst->opcode() == spv::Op::OpISub) && other_constants[0]) {
4104
69
      return false;
4105
69
    }
4106
4107
801
    bool can_remove_inner = true;
4108
801
    ForEachIntegerConstantPair(const_mgr, const_input1, const_input2,
4109
801
                               [&can_remove_inner](auto and_op, auto add_op) {
4110
801
                                 if (can_remove_inner) {
4111
                                   // Only valid if no bits from the +/- could
4112
                                   // affect bits from the & operation.
4113
801
                                   can_remove_inner =
4114
801
                                       utils::LSB(add_op) > and_op;
4115
801
                                 }
4116
801
                               });
folding_rules.cpp:auto spvtools::opt::(anonymous namespace)::RedundantAndAddSub()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}::operator()<unsigned int, unsigned int>(unsigned int, unsigned int) const
Line
Count
Source
4109
801
                               [&can_remove_inner](auto and_op, auto add_op) {
4110
801
                                 if (can_remove_inner) {
4111
                                   // Only valid if no bits from the +/- could
4112
                                   // affect bits from the & operation.
4113
801
                                   can_remove_inner =
4114
801
                                       utils::LSB(add_op) > and_op;
4115
801
                                 }
4116
801
                               });
Unexecuted instantiation: folding_rules.cpp:auto spvtools::opt::(anonymous namespace)::RedundantAndAddSub()::$_0::operator()(spvtools::opt::IRContext*, spvtools::opt::Instruction*, std::__1::vector<spvtools::opt::analysis::Constant const*, std::__1::allocator<spvtools::opt::analysis::Constant const*> > const&) const::{lambda(auto:1, auto:2)#1}::operator()<unsigned long, unsigned long>(unsigned long, unsigned long) const
4117
4118
801
    if (can_remove_inner) {
4119
71
      Instruction* non_const_input =
4120
71
          NonConstInput(context, other_constants[0], other_inst);
4121
71
      Instruction* const_inst = const_mgr->GetDefiningInstruction(const_input1);
4122
71
      inst->SetInOperands(
4123
71
          {{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}},
4124
71
           {SPV_OPERAND_TYPE_ID, {const_inst->result_id()}}});
4125
71
      return true;
4126
71
    }
4127
730
    return false;
4128
801
  };
4129
16.0k
}
4130
4131
// Folds redundant shift ops that are part of an and.
4132
// Cases handled:
4133
// 1 & (b << 1) = 0
4134
// 0x80000000 & (b >> 1) = 0
4135
16.0k
FoldingRule RedundantAndShift() {
4136
16.0k
  return [](IRContext* context, Instruction* inst,
4137
16.0k
            const std::vector<const analysis::Constant*>& constants) {
4138
13.0k
    assert(inst->opcode() == spv::Op::OpBitwiseAnd && "Wrong opcode.");
4139
13.0k
    const analysis::Type* type =
4140
13.0k
        context->get_type_mgr()->GetType(inst->type_id());
4141
13.0k
    uint32_t width = ElementWidth(type);
4142
13.0k
    if (width != 8 && width != 16 && width != 32 && width != 64) return false;
4143
13.0k
    const uint64_t width_mask =
4144
13.0k
        (width == 64) ? ~0ull : ((1ull << width) - 1ull);
4145
4146
13.0k
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
4147
13.0k
    const analysis::Constant* const_input1 = ConstInput(constants);
4148
13.0k
    if (!const_input1) return false;
4149
9.78k
    Instruction* other_inst = NonConstInput(context, constants[0], inst);
4150
4151
9.78k
    spv::Op other_op = other_inst->opcode();
4152
9.78k
    if (other_op != spv::Op::OpShiftLeftLogical &&
4153
9.59k
        other_op != spv::Op::OpShiftRightLogical) {
4154
9.30k
      return false;
4155
9.30k
    }
4156
4157
477
    std::vector<const analysis::Constant*> other_constants =
4158
477
        const_mgr->GetOperandConstants(other_inst);
4159
4160
    // Only valid if const is on the right.
4161
477
    if (other_constants[0]) return false;
4162
375
    const analysis::Constant* const_input2 = other_constants[1];
4163
375
    if (!const_input2) return false;
4164
4165
146
    auto get_value_u64 =
4166
292
        [](const analysis::Constant* c) -> std::optional<uint64_t> {
4167
292
      if (!c) return std::nullopt;
4168
292
      const analysis::Integer* int_t = c->type()->AsInteger();
4169
292
      if (!int_t) return std::nullopt;
4170
292
      return c->GetZeroExtendedValue();
4171
292
    };
4172
4173
146
    auto can_fold_component =
4174
146
        [&](const analysis::Constant* mask_const,
4175
146
            const analysis::Constant* shift_const) -> std::optional<bool> {
4176
146
      auto lhs = get_value_u64(mask_const);
4177
146
      auto rhs = get_value_u64(shift_const);
4178
146
      if (!lhs || !rhs) return std::nullopt;
4179
146
      if (*rhs >= width) return false;
4180
146
      uint64_t lhs_masked = *lhs & width_mask;
4181
146
      if (other_op == spv::Op::OpShiftRightLogical) {
4182
80
        return ((lhs_masked << *rhs) & width_mask) == 0;
4183
80
      }
4184
66
      return ((lhs_masked >> *rhs) & width_mask) == 0;
4185
146
    };
4186
4187
146
    if (const analysis::Vector* mask_vec = type->AsVector()) {
4188
0
      const analysis::Vector* shift_vec = const_input2->type()->AsVector();
4189
0
      if (!shift_vec ||
4190
0
          shift_vec->element_count() != mask_vec->element_count()) {
4191
0
        return false;
4192
0
      }
4193
0
      const auto mask_components = const_input1->GetVectorComponents(const_mgr);
4194
0
      const auto shift_components =
4195
0
          const_input2->GetVectorComponents(const_mgr);
4196
0
      for (uint32_t i = 0; i != mask_vec->element_count(); ++i) {
4197
0
        auto result =
4198
0
            can_fold_component(mask_components[i], shift_components[i]);
4199
0
        if (!result || !*result) return false;
4200
0
      }
4201
146
    } else {
4202
146
      if (const_input2->type()->AsVector()) return false;
4203
146
      auto result = can_fold_component(const_input1, const_input2);
4204
146
      if (!result || !*result) return false;
4205
146
    }
4206
4207
31
    auto zero_id = context->get_constant_mgr()->GetNullConstId(type);
4208
31
    inst->SetOpcode(spv::Op::OpCopyObject);
4209
31
    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
4210
31
    return true;
4211
146
  };
4212
16.0k
}
4213
4214
// This rule look for a dot with a constant vector containing a single 1 and
4215
// the rest 0s.  This is the same as doing an extract.
4216
16.0k
FoldingRule DotProductDoingExtract() {
4217
16.0k
  return [](IRContext* context, Instruction* inst,
4218
16.0k
            const std::vector<const analysis::Constant*>& constants) {
4219
52
    assert(inst->opcode() == spv::Op::OpDot &&
4220
52
           "Wrong opcode.  Should be OpDot.");
4221
4222
52
    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
4223
4224
52
    if (!inst->IsFloatingPointFoldingAllowed()) {
4225
0
      return false;
4226
0
    }
4227
4228
154
    for (int i = 0; i < 2; ++i) {
4229
103
      if (!constants[i]) {
4230
58
        continue;
4231
58
      }
4232
4233
45
      const analysis::Vector* vector_type = constants[i]->type()->AsVector();
4234
45
      assert(vector_type && "Inputs to OpDot must be vectors.");
4235
45
      const analysis::Float* element_type =
4236
45
          vector_type->element_type()->AsFloat();
4237
45
      assert(element_type && "Inputs to OpDot must be vectors of floats.");
4238
45
      uint32_t element_width = element_type->width();
4239
45
      if (element_width != 32 && element_width != 64) {
4240
0
        return false;
4241
0
      }
4242
4243
45
      std::vector<const analysis::Constant*> components;
4244
45
      components = constants[i]->GetVectorComponents(const_mgr);
4245
4246
45
      constexpr uint32_t kNotFound = std::numeric_limits<uint32_t>::max();
4247
4248
45
      uint32_t component_with_one = kNotFound;
4249
45
      bool all_others_zero = true;
4250
49
      for (uint32_t j = 0; j < components.size(); ++j) {
4251
48
        const analysis::Constant* element = components[j];
4252
48
        double value =
4253
48
            (element_width == 32 ? element->GetFloat() : element->GetDouble());
4254
48
        if (value == 0.0) {
4255
2
          continue;
4256
46
        } else if (value == 1.0) {
4257
2
          if (component_with_one == kNotFound) {
4258
2
            component_with_one = j;
4259
2
          } else {
4260
0
            component_with_one = kNotFound;
4261
0
            break;
4262
0
          }
4263
44
        } else {
4264
44
          all_others_zero = false;
4265
44
          break;
4266
44
        }
4267
48
      }
4268
4269
45
      if (!all_others_zero || component_with_one == kNotFound) {
4270
44
        continue;
4271
44
      }
4272
4273
1
      std::vector<Operand> operands;
4274
1
      operands.push_back(
4275
1
          {SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1u - i)}});
4276
1
      operands.push_back(
4277
1
          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {component_with_one}});
4278
4279
1
      inst->SetOpcode(spv::Op::OpCompositeExtract);
4280
1
      inst->SetInOperands(std::move(operands));
4281
1
      return true;
4282
45
    }
4283
51
    return false;
4284
52
  };
4285
16.0k
}
4286
4287
// If we are storing an undef, then we can remove the store.
4288
//
4289
// TODO: We can do something similar for OpImageWrite, but checking for volatile
4290
// is complicated.  Waiting to see if it is needed.
4291
16.0k
FoldingRule StoringUndef() {
4292
16.0k
  return [](IRContext* context, Instruction* inst,
4293
1.05M
            const std::vector<const analysis::Constant*>&) {
4294
1.05M
    assert(inst->opcode() == spv::Op::OpStore &&
4295
1.05M
           "Wrong opcode.  Should be OpStore.");
4296
4297
1.05M
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
4298
4299
    // If this is a volatile store, the store cannot be removed.
4300
1.05M
    if (inst->NumInOperands() == 3) {
4301
8.42k
      if (inst->GetSingleWordInOperand(2) &
4302
8.42k
          uint32_t(spv::MemoryAccessMask::Volatile)) {
4303
5.47k
        return false;
4304
5.47k
      }
4305
8.42k
    }
4306
4307
1.04M
    uint32_t object_id = inst->GetSingleWordInOperand(kStoreObjectInIdx);
4308
1.04M
    Instruction* object_inst = def_use_mgr->GetDef(object_id);
4309
1.04M
    if (object_inst->opcode() == spv::Op::OpUndef) {
4310
19.1k
      inst->ToNop();
4311
19.1k
      return true;
4312
19.1k
    }
4313
1.02M
    return false;
4314
1.04M
  };
4315
16.0k
}
4316
4317
16.0k
FoldingRule VectorShuffleFeedingShuffle() {
4318
16.0k
  return [](IRContext* context, Instruction* inst,
4319
24.9k
            const std::vector<const analysis::Constant*>&) {
4320
24.9k
    assert(inst->opcode() == spv::Op::OpVectorShuffle &&
4321
24.9k
           "Wrong opcode.  Should be OpVectorShuffle.");
4322
4323
24.9k
    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
4324
24.9k
    analysis::TypeManager* type_mgr = context->get_type_mgr();
4325
4326
24.9k
    Instruction* feeding_shuffle_inst =
4327
24.9k
        def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
4328
24.9k
    analysis::Vector* op0_type =
4329
24.9k
        type_mgr->GetType(feeding_shuffle_inst->type_id())->AsVector();
4330
24.9k
    uint32_t op0_length = op0_type->element_count();
4331
4332
24.9k
    bool feeder_is_op0 = true;
4333
24.9k
    if (feeding_shuffle_inst->opcode() != spv::Op::OpVectorShuffle) {
4334
24.6k
      feeding_shuffle_inst =
4335
24.6k
          def_use_mgr->GetDef(inst->GetSingleWordInOperand(1));
4336
24.6k
      feeder_is_op0 = false;
4337
24.6k
    }
4338
4339
24.9k
    if (feeding_shuffle_inst->opcode() != spv::Op::OpVectorShuffle) {
4340
24.2k
      return false;
4341
24.2k
    }
4342
4343
651
    Instruction* feeder2 =
4344
651
        def_use_mgr->GetDef(feeding_shuffle_inst->GetSingleWordInOperand(0));
4345
651
    analysis::Vector* feeder_op0_type =
4346
651
        type_mgr->GetType(feeder2->type_id())->AsVector();
4347
651
    uint32_t feeder_op0_length = feeder_op0_type->element_count();
4348
4349
651
    uint32_t new_feeder_id = 0;
4350
651
    std::vector<Operand> new_operands;
4351
651
    new_operands.resize(
4352
651
        2, {SPV_OPERAND_TYPE_ID, {0}});  // Place holders for vector operands.
4353
651
    const uint32_t undef_literal = 0xffffffff;
4354
2.14k
    for (uint32_t op = 2; op < inst->NumInOperands(); ++op) {
4355
1.55k
      uint32_t component_index = inst->GetSingleWordInOperand(op);
4356
4357
      // Do not interpret the undefined value literal as coming from operand 1.
4358
1.55k
      if (component_index != undef_literal &&
4359
1.45k
          feeder_is_op0 == (component_index < op0_length)) {
4360
        // This component comes from the feeding_shuffle_inst.  Update
4361
        // |component_index| to be the index into the operand of the feeder.
4362
4363
        // Adjust component_index to get the index into the operands of the
4364
        // feeding_shuffle_inst.
4365
802
        if (component_index >= op0_length) {
4366
455
          component_index -= op0_length;
4367
455
        }
4368
802
        component_index =
4369
802
            feeding_shuffle_inst->GetSingleWordInOperand(component_index + 2);
4370
4371
        // Check if we are using a component from the first or second operand of
4372
        // the feeding instruction.
4373
802
        if (component_index < feeder_op0_length) {
4374
616
          if (new_feeder_id == 0) {
4375
            // First time through, save the id of the operand the element comes
4376
            // from.
4377
361
            new_feeder_id = feeding_shuffle_inst->GetSingleWordInOperand(0);
4378
361
          } else if (new_feeder_id !=
4379
255
                     feeding_shuffle_inst->GetSingleWordInOperand(0)) {
4380
            // We need both elements of the feeding_shuffle_inst, so we cannot
4381
            // fold.
4382
41
            return false;
4383
41
          }
4384
616
        } else if (component_index != undef_literal) {
4385
121
          if (new_feeder_id == 0) {
4386
            // First time through, save the id of the operand the element comes
4387
            // from.
4388
82
            new_feeder_id = feeding_shuffle_inst->GetSingleWordInOperand(1);
4389
82
          } else if (new_feeder_id !=
4390
39
                     feeding_shuffle_inst->GetSingleWordInOperand(1)) {
4391
            // We need both elements of the feeding_shuffle_inst, so we cannot
4392
            // fold.
4393
22
            return false;
4394
22
          }
4395
99
          component_index -= feeder_op0_length;
4396
99
        }
4397
4398
739
        if (!feeder_is_op0 && component_index != undef_literal) {
4399
427
          component_index += op0_length;
4400
427
        }
4401
739
      }
4402
1.49k
      new_operands.push_back(
4403
1.49k
          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {component_index}});
4404
1.49k
    }
4405
4406
588
    if (new_feeder_id == 0) {
4407
208
      analysis::ConstantManager* const_mgr = context->get_constant_mgr();
4408
208
      const analysis::Type* type =
4409
208
          type_mgr->GetType(feeding_shuffle_inst->type_id());
4410
208
      const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
4411
208
      new_feeder_id =
4412
208
          const_mgr->GetDefiningInstruction(null_const, 0)->result_id();
4413
208
    }
4414
4415
588
    if (feeder_is_op0) {
4416
      // If the size of the first vector operand changed then the indices
4417
      // referring to the second operand need to be adjusted.
4418
170
      Instruction* new_feeder_inst = def_use_mgr->GetDef(new_feeder_id);
4419
170
      analysis::Type* new_feeder_type =
4420
170
          type_mgr->GetType(new_feeder_inst->type_id());
4421
170
      uint32_t new_op0_size = new_feeder_type->AsVector()->element_count();
4422
170
      int32_t adjustment = op0_length - new_op0_size;
4423
4424
170
      if (adjustment != 0) {
4425
254
        for (uint32_t i = 2; i < new_operands.size(); i++) {
4426
171
          uint32_t operand = inst->GetSingleWordInOperand(i);
4427
171
          if (operand >= op0_length && operand != undef_literal) {
4428
49
            new_operands[i].words[0] -= adjustment;
4429
49
          }
4430
171
        }
4431
83
      }
4432
4433
170
      new_operands[0].words[0] = new_feeder_id;
4434
170
      new_operands[1] = inst->GetInOperand(1);
4435
418
    } else {
4436
418
      new_operands[1].words[0] = new_feeder_id;
4437
418
      new_operands[0] = inst->GetInOperand(0);
4438
418
    }
4439
4440
588
    inst->SetInOperands(std::move(new_operands));
4441
588
    return true;
4442
651
  };
4443
16.0k
}
4444
4445
// Removes duplicate ids from the interface list of an OpEntryPoint
4446
// instruction.
4447
16.0k
FoldingRule RemoveRedundantOperands() {
4448
16.0k
  return [](IRContext*, Instruction* inst,
4449
16.0k
            const std::vector<const analysis::Constant*>&) {
4450
0
    assert(inst->opcode() == spv::Op::OpEntryPoint &&
4451
0
           "Wrong opcode.  Should be OpEntryPoint.");
4452
0
    bool has_redundant_operand = false;
4453
0
    std::unordered_set<uint32_t> seen_operands;
4454
0
    std::vector<Operand> new_operands;
4455
4456
0
    new_operands.emplace_back(inst->GetOperand(0));
4457
0
    new_operands.emplace_back(inst->GetOperand(1));
4458
0
    new_operands.emplace_back(inst->GetOperand(2));
4459
0
    for (uint32_t i = 3; i < inst->NumOperands(); ++i) {
4460
0
      if (seen_operands.insert(inst->GetSingleWordOperand(i)).second) {
4461
0
        new_operands.emplace_back(inst->GetOperand(i));
4462
0
      } else {
4463
0
        has_redundant_operand = true;
4464
0
      }
4465
0
    }
4466
4467
0
    if (!has_redundant_operand) {
4468
0
      return false;
4469
0
    }
4470
4471
0
    inst->SetInOperands(std::move(new_operands));
4472
0
    return true;
4473
0
  };
4474
16.0k
}
4475
4476
// If an image instruction's operand is a constant, updates the image operand
4477
// flag from Offset to ConstOffset.
4478
400k
FoldingRule UpdateImageOperands() {
4479
400k
  return [](IRContext*, Instruction* inst,
4480
400k
            const std::vector<const analysis::Constant*>& constants) {
4481
237k
    const auto opcode = inst->opcode();
4482
237k
    (void)opcode;
4483
237k
    assert((opcode == spv::Op::OpImageSampleImplicitLod ||
4484
237k
            opcode == spv::Op::OpImageSampleExplicitLod ||
4485
237k
            opcode == spv::Op::OpImageSampleDrefImplicitLod ||
4486
237k
            opcode == spv::Op::OpImageSampleDrefExplicitLod ||
4487
237k
            opcode == spv::Op::OpImageSampleProjImplicitLod ||
4488
237k
            opcode == spv::Op::OpImageSampleProjExplicitLod ||
4489
237k
            opcode == spv::Op::OpImageSampleProjDrefImplicitLod ||
4490
237k
            opcode == spv::Op::OpImageSampleProjDrefExplicitLod ||
4491
237k
            opcode == spv::Op::OpImageFetch ||
4492
237k
            opcode == spv::Op::OpImageGather ||
4493
237k
            opcode == spv::Op::OpImageDrefGather ||
4494
237k
            opcode == spv::Op::OpImageRead || opcode == spv::Op::OpImageWrite ||
4495
237k
            opcode == spv::Op::OpImageSparseSampleImplicitLod ||
4496
237k
            opcode == spv::Op::OpImageSparseSampleExplicitLod ||
4497
237k
            opcode == spv::Op::OpImageSparseSampleDrefImplicitLod ||
4498
237k
            opcode == spv::Op::OpImageSparseSampleDrefExplicitLod ||
4499
237k
            opcode == spv::Op::OpImageSparseSampleProjImplicitLod ||
4500
237k
            opcode == spv::Op::OpImageSparseSampleProjExplicitLod ||
4501
237k
            opcode == spv::Op::OpImageSparseSampleProjDrefImplicitLod ||
4502
237k
            opcode == spv::Op::OpImageSparseSampleProjDrefExplicitLod ||
4503
237k
            opcode == spv::Op::OpImageSparseFetch ||
4504
237k
            opcode == spv::Op::OpImageSparseGather ||
4505
237k
            opcode == spv::Op::OpImageSparseDrefGather ||
4506
237k
            opcode == spv::Op::OpImageSparseRead) &&
4507
237k
           "Wrong opcode.  Should be an image instruction.");
4508
4509
237k
    int32_t operand_index = ImageOperandsMaskInOperandIndex(inst);
4510
237k
    if (operand_index >= 0) {
4511
14
      auto image_operands = inst->GetSingleWordInOperand(operand_index);
4512
14
      if (image_operands & uint32_t(spv::ImageOperandsMask::Offset)) {
4513
0
        uint32_t offset_operand_index = operand_index + 1;
4514
0
        if (image_operands & uint32_t(spv::ImageOperandsMask::Bias))
4515
0
          offset_operand_index++;
4516
0
        if (image_operands & uint32_t(spv::ImageOperandsMask::Lod))
4517
0
          offset_operand_index++;
4518
0
        if (image_operands & uint32_t(spv::ImageOperandsMask::Grad))
4519
0
          offset_operand_index += 2;
4520
0
        assert(((image_operands &
4521
0
                 uint32_t(spv::ImageOperandsMask::ConstOffset)) == 0) &&
4522
0
               "Offset and ConstOffset may not be used together");
4523
0
        if (offset_operand_index < inst->NumOperands()) {
4524
0
          if (constants[offset_operand_index]) {
4525
0
            if (constants[offset_operand_index]->IsZero()) {
4526
0
              inst->RemoveInOperand(offset_operand_index);
4527
0
            } else {
4528
0
              image_operands = image_operands |
4529
0
                               uint32_t(spv::ImageOperandsMask::ConstOffset);
4530
0
            }
4531
0
            image_operands =
4532
0
                image_operands & ~uint32_t(spv::ImageOperandsMask::Offset);
4533
0
            inst->SetInOperand(operand_index, {image_operands});
4534
0
            return true;
4535
0
          }
4536
0
        }
4537
0
      }
4538
14
    }
4539
4540
237k
    return false;
4541
237k
  };
4542
400k
}
4543
4544
}  // namespace
4545
4546
16.0k
void FoldingRules::AddFoldingRules() {
4547
  // Add all folding rules to the list for the opcodes to which they apply.
4548
  // Note that the order in which rules are added to the list matters. If a rule
4549
  // applies to the instruction, the rest of the rules will not be attempted.
4550
  // Take that into consideration.
4551
16.0k
  for (auto op : RedundantBinaryRhs0Ops)
4552
112k
    rules_[op].push_back(RedundantBinaryRhs0(op));
4553
16.0k
  for (auto op : RedundantBinaryLhs0Ops)
4554
48.0k
    rules_[op].push_back(RedundantBinaryLhs0(op));
4555
16.0k
  for (auto op : RedundantBinaryLhs0To0Ops)
4556
112k
    rules_[op].push_back(RedundantBinaryLhs0To0(op));
4557
16.0k
  for (auto op : ReassociateCommutiveBitwiseOps)
4558
48.0k
    rules_[op].push_back(ReassociateCommutiveBitwise(op));
4559
16.0k
  for (auto op : ReassociateNestedGenericIntOps)
4560
64.0k
    rules_[op].push_back(ReassociateNestedGenericInt(op));
4561
16.0k
  for (auto op : MergeBinaryOpSelectOps)
4562
672k
    rules_[op].push_back(MergeBinaryOpSelect(op));
4563
16.0k
  rules_[spv::Op::OpSDiv].push_back(RedundantSUDiv());
4564
16.0k
  rules_[spv::Op::OpUDiv].push_back(RedundantSUDiv());
4565
16.0k
  rules_[spv::Op::OpSMod].push_back(RedundantSUMod());
4566
16.0k
  rules_[spv::Op::OpUMod].push_back(RedundantSUMod());
4567
4568
16.0k
  rules_[spv::Op::OpBitcast].push_back(BitCastScalarOrVector());
4569
16.0k
  rules_[spv::Op::OpBitcast].push_back(RedundantBitcast());
4570
4571
16.0k
  rules_[spv::Op::OpBitReverse].push_back(BitReverseScalarOrVector());
4572
4573
16.0k
  rules_[spv::Op::OpCompositeConstruct].push_back(
4574
16.0k
      CompositeExtractFeedingConstruct);
4575
4576
16.0k
  rules_[spv::Op::OpCopyLogical].push_back(
4577
16.0k
      CompositeConstructFeedingCopyLogical);
4578
4579
16.0k
  rules_[spv::Op::OpCompositeExtract].push_back(InsertFeedingExtract());
4580
16.0k
  rules_[spv::Op::OpCompositeExtract].push_back(
4581
16.0k
      CompositeConstructFeedingExtract);
4582
16.0k
  rules_[spv::Op::OpCompositeExtract].push_back(VectorShuffleFeedingExtract());
4583
16.0k
  rules_[spv::Op::OpCompositeExtract].push_back(FMixFeedingExtract());
4584
16.0k
  rules_[spv::Op::OpCompositeExtract].push_back(CopyLogicalFeedingExtract);
4585
16.0k
  rules_[spv::Op::OpCompositeExtract].push_back(LoadFeedingExtract);
4586
4587
16.0k
  rules_[spv::Op::OpCompositeInsert].push_back(
4588
16.0k
      CompositeInsertToCompositeConstruct);
4589
4590
16.0k
  rules_[spv::Op::OpDot].push_back(DotProductDoingExtract());
4591
4592
16.0k
  rules_[spv::Op::OpEntryPoint].push_back(RemoveRedundantOperands());
4593
4594
16.0k
  rules_[spv::Op::OpFAdd].push_back(RedundantFAdd());
4595
16.0k
  rules_[spv::Op::OpFAdd].push_back(MergeAddNegateArithmetic());
4596
16.0k
  rules_[spv::Op::OpFAdd].push_back(MergeAddAddArithmetic());
4597
16.0k
  rules_[spv::Op::OpFAdd].push_back(MergeAddSubArithmetic());
4598
16.0k
  rules_[spv::Op::OpFAdd].push_back(MergeGenericAddSubArithmetic());
4599
16.0k
  rules_[spv::Op::OpFAdd].push_back(ReassociateNestedAddSub());
4600
16.0k
  rules_[spv::Op::OpFAdd].push_back(FactorAddSubMuls());
4601
4602
16.0k
  rules_[spv::Op::OpFDiv].push_back(RedundantFDiv());
4603
16.0k
  rules_[spv::Op::OpFDiv].push_back(ReciprocalFDiv());
4604
16.0k
  rules_[spv::Op::OpFDiv].push_back(MergeDivDivArithmetic());
4605
16.0k
  rules_[spv::Op::OpFDiv].push_back(MergeDivMulArithmetic());
4606
16.0k
  rules_[spv::Op::OpFDiv].push_back(MergeDivNegateArithmetic());
4607
16.0k
  rules_[spv::Op::OpFDiv].push_back(MergeDivMulDoubleNegative());
4608
16.0k
  rules_[spv::Op::OpFDiv].push_back(ReassociateNestedMulDivFloat());
4609
4610
16.0k
  rules_[spv::Op::OpFMod].push_back(RedundantFMod());
4611
4612
16.0k
  rules_[spv::Op::OpFMul].push_back(RedundantFMul());
4613
16.0k
  rules_[spv::Op::OpFMul].push_back(MergeMulMulArithmetic());
4614
16.0k
  rules_[spv::Op::OpFMul].push_back(MergeMulDivArithmetic());
4615
16.0k
  rules_[spv::Op::OpFMul].push_back(MergeMulNegateArithmetic());
4616
16.0k
  rules_[spv::Op::OpFMul].push_back(MergeDivMulDoubleNegative());
4617
16.0k
  rules_[spv::Op::OpFMul].push_back(ReassociateNestedMulDivFloat());
4618
4619
16.0k
  rules_[spv::Op::OpVectorTimesScalar].push_back(MergeDivMulDoubleNegative());
4620
4621
16.0k
  rules_[spv::Op::OpFNegate].push_back(MergeNegateArithmetic());
4622
16.0k
  rules_[spv::Op::OpFNegate].push_back(MergeNegateAddSubArithmetic());
4623
16.0k
  rules_[spv::Op::OpFNegate].push_back(MergeNegateMulDivArithmetic());
4624
4625
16.0k
  rules_[spv::Op::OpFSub].push_back(RedundantFSub());
4626
16.0k
  rules_[spv::Op::OpFSub].push_back(MergeSubNegateArithmetic());
4627
16.0k
  rules_[spv::Op::OpFSub].push_back(MergeSubAddArithmetic());
4628
16.0k
  rules_[spv::Op::OpFSub].push_back(MergeSubSubArithmetic());
4629
16.0k
  rules_[spv::Op::OpFSub].push_back(ReassociateNestedAddSub());
4630
16.0k
  rules_[spv::Op::OpFSub].push_back(FactorAddSubMuls());
4631
4632
16.0k
  rules_[spv::Op::OpIAdd].push_back(MergeAddNegateArithmetic());
4633
16.0k
  rules_[spv::Op::OpIAdd].push_back(MergeAddAddArithmetic());
4634
16.0k
  rules_[spv::Op::OpIAdd].push_back(MergeAddSubArithmetic());
4635
16.0k
  rules_[spv::Op::OpIAdd].push_back(MergeGenericAddSubArithmetic());
4636
16.0k
  rules_[spv::Op::OpIAdd].push_back(ReassociateNestedAddSub());
4637
16.0k
  rules_[spv::Op::OpIAdd].push_back(FactorAddSubMuls());
4638
4639
16.0k
  rules_[spv::Op::OpSDiv].push_back(MergeDivMulDoubleNegative());
4640
4641
16.0k
  rules_[spv::Op::OpIMul].push_back(IntMultipleBy1());
4642
16.0k
  rules_[spv::Op::OpIMul].push_back(MergeMulMulArithmetic());
4643
16.0k
  rules_[spv::Op::OpIMul].push_back(MergeMulNegateArithmetic());
4644
16.0k
  rules_[spv::Op::OpIMul].push_back(MergeDivMulDoubleNegative());
4645
4646
16.0k
  rules_[spv::Op::OpISub].push_back(MergeSubNegateArithmetic());
4647
16.0k
  rules_[spv::Op::OpISub].push_back(MergeSubAddArithmetic());
4648
16.0k
  rules_[spv::Op::OpISub].push_back(MergeSubSubArithmetic());
4649
16.0k
  rules_[spv::Op::OpISub].push_back(ReassociateNestedAddSub());
4650
16.0k
  rules_[spv::Op::OpISub].push_back(FactorAddSubMuls());
4651
4652
16.0k
  rules_[spv::Op::OpBitwiseAnd].push_back(RedundantAndOrXor());
4653
16.0k
  rules_[spv::Op::OpBitwiseAnd].push_back(RedundantAndAddSub());
4654
16.0k
  rules_[spv::Op::OpBitwiseAnd].push_back(RedundantAndShift());
4655
4656
16.0k
  rules_[spv::Op::OpPhi].push_back(RedundantPhi());
4657
4658
16.0k
  rules_[spv::Op::OpSNegate].push_back(MergeNegateArithmetic());
4659
16.0k
  rules_[spv::Op::OpSNegate].push_back(MergeNegateMulDivArithmetic());
4660
16.0k
  rules_[spv::Op::OpSNegate].push_back(MergeNegateAddSubArithmetic());
4661
4662
16.0k
  rules_[spv::Op::OpSelect].push_back(RedundantSelect());
4663
16.0k
  rules_[spv::Op::OpSelect].push_back(FoldConstantBooleanSelect());
4664
4665
16.0k
  rules_[spv::Op::OpLogicalAnd].push_back(RedundantLogicalAnd());
4666
4667
16.0k
  rules_[spv::Op::OpLogicalOr].push_back(RedundantLogicalOr());
4668
4669
16.0k
  rules_[spv::Op::OpLogicalNot].push_back(RedundantLogicalNot());
4670
16.0k
  rules_[spv::Op::OpLogicalNot].push_back(FoldLogicalNotComparison());
4671
4672
16.0k
  rules_[spv::Op::OpLogicalEqual].push_back(RedundantLogicalEqual());
4673
16.0k
  rules_[spv::Op::OpLogicalNotEqual].push_back(RedundantLogicalEqual());
4674
4675
16.0k
  rules_[spv::Op::OpStore].push_back(StoringUndef());
4676
4677
16.0k
  rules_[spv::Op::OpVectorShuffle].push_back(VectorShuffleFeedingShuffle());
4678
4679
16.0k
  rules_[spv::Op::OpImageSampleImplicitLod].push_back(UpdateImageOperands());
4680
16.0k
  rules_[spv::Op::OpImageSampleExplicitLod].push_back(UpdateImageOperands());
4681
16.0k
  rules_[spv::Op::OpImageSampleDrefImplicitLod].push_back(
4682
16.0k
      UpdateImageOperands());
4683
16.0k
  rules_[spv::Op::OpImageSampleDrefExplicitLod].push_back(
4684
16.0k
      UpdateImageOperands());
4685
16.0k
  rules_[spv::Op::OpImageSampleProjImplicitLod].push_back(
4686
16.0k
      UpdateImageOperands());
4687
16.0k
  rules_[spv::Op::OpImageSampleProjExplicitLod].push_back(
4688
16.0k
      UpdateImageOperands());
4689
16.0k
  rules_[spv::Op::OpImageSampleProjDrefImplicitLod].push_back(
4690
16.0k
      UpdateImageOperands());
4691
16.0k
  rules_[spv::Op::OpImageSampleProjDrefExplicitLod].push_back(
4692
16.0k
      UpdateImageOperands());
4693
16.0k
  rules_[spv::Op::OpImageFetch].push_back(UpdateImageOperands());
4694
16.0k
  rules_[spv::Op::OpImageGather].push_back(UpdateImageOperands());
4695
16.0k
  rules_[spv::Op::OpImageDrefGather].push_back(UpdateImageOperands());
4696
16.0k
  rules_[spv::Op::OpImageRead].push_back(UpdateImageOperands());
4697
16.0k
  rules_[spv::Op::OpImageWrite].push_back(UpdateImageOperands());
4698
16.0k
  rules_[spv::Op::OpImageSparseSampleImplicitLod].push_back(
4699
16.0k
      UpdateImageOperands());
4700
16.0k
  rules_[spv::Op::OpImageSparseSampleExplicitLod].push_back(
4701
16.0k
      UpdateImageOperands());
4702
16.0k
  rules_[spv::Op::OpImageSparseSampleDrefImplicitLod].push_back(
4703
16.0k
      UpdateImageOperands());
4704
16.0k
  rules_[spv::Op::OpImageSparseSampleDrefExplicitLod].push_back(
4705
16.0k
      UpdateImageOperands());
4706
16.0k
  rules_[spv::Op::OpImageSparseSampleProjImplicitLod].push_back(
4707
16.0k
      UpdateImageOperands());
4708
16.0k
  rules_[spv::Op::OpImageSparseSampleProjExplicitLod].push_back(
4709
16.0k
      UpdateImageOperands());
4710
16.0k
  rules_[spv::Op::OpImageSparseSampleProjDrefImplicitLod].push_back(
4711
16.0k
      UpdateImageOperands());
4712
16.0k
  rules_[spv::Op::OpImageSparseSampleProjDrefExplicitLod].push_back(
4713
16.0k
      UpdateImageOperands());
4714
16.0k
  rules_[spv::Op::OpImageSparseFetch].push_back(UpdateImageOperands());
4715
16.0k
  rules_[spv::Op::OpImageSparseGather].push_back(UpdateImageOperands());
4716
16.0k
  rules_[spv::Op::OpImageSparseDrefGather].push_back(UpdateImageOperands());
4717
16.0k
  rules_[spv::Op::OpImageSparseRead].push_back(UpdateImageOperands());
4718
4719
16.0k
  FeatureManager* feature_manager = context_->get_feature_mgr();
4720
  // Add rules for GLSLstd450
4721
16.0k
  uint32_t ext_inst_glslstd450_id =
4722
16.0k
      feature_manager->GetExtInstImportId_GLSLstd450();
4723
16.0k
  if (ext_inst_glslstd450_id != 0) {
4724
9.83k
    ext_rules_[{ext_inst_glslstd450_id, GLSLstd450FMix}].push_back(
4725
9.83k
        RedundantFMix());
4726
9.83k
  }
4727
16.0k
}
4728
}  // namespace opt
4729
}  // namespace spvtools