Coverage Report

Created: 2026-11-12 06:47

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