aboutsummaryrefslogtreecommitdiff
path: root/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
blob: e1977a64d33d16b3ee5eea3d3bcbc427fed266a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"

#include "source/fuzz/fuzzer_util.h"

namespace spvtools {
namespace fuzz {

namespace {
const uint32_t kOpCompositeExtractIndexLowOrderBits = 0;
const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
const uint32_t kArithmeticInstructionIndexRightInOperand = 1;
}  // namespace

TransformationReplaceAddSubMulWithCarryingExtended::
    TransformationReplaceAddSubMulWithCarryingExtended(
        protobufs::TransformationReplaceAddSubMulWithCarryingExtended message)
    : message_(std::move(message)) {}

TransformationReplaceAddSubMulWithCarryingExtended::
    TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id,
                                                       uint32_t result_id) {
  message_.set_struct_fresh_id(struct_fresh_id);
  message_.set_result_id(result_id);
}

bool TransformationReplaceAddSubMulWithCarryingExtended::IsApplicable(
    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
  // |message_.struct_fresh_id| must be fresh.
  if (!fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id())) {
    return false;
  }

  // |message_.result_id| must refer to a suitable OpIAdd, OpISub or OpIMul
  // instruction. The instruction must be defined.
  auto instruction =
      ir_context->get_def_use_mgr()->GetDef(message_.result_id());
  if (instruction == nullptr) {
    return false;
  }
  if (!TransformationReplaceAddSubMulWithCarryingExtended::
          IsInstructionSuitable(ir_context, *instruction)) {
    return false;
  }

  // The struct type for holding the intermediate result must exist in the
  // module. The struct type is based on the operand type.
  uint32_t operand_type_id = ir_context->get_def_use_mgr()
                                 ->GetDef(instruction->GetSingleWordInOperand(
                                     kArithmeticInstructionIndexLeftInOperand))
                                 ->type_id();

  uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
      ir_context, {operand_type_id, operand_type_id});
  if (struct_type_id == 0) {
    return false;
  }
  return true;
}

void TransformationReplaceAddSubMulWithCarryingExtended::Apply(
    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
  // |message_.struct_fresh_id| must be fresh.
  assert(fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id()) &&
         "|message_.struct_fresh_id| must be fresh");

  // Get the signedness of an operand if it is an int or the signedness of a
  // component if it is a vector.
  auto type_id =
      ir_context->get_def_use_mgr()->GetDef(message_.result_id())->type_id();
  auto type = ir_context->get_type_mgr()->GetType(type_id);
  bool operand_is_signed;
  if (type->kind() == opt::analysis::Type::kVector) {
    auto operand_type = type->AsVector()->element_type();
    operand_is_signed = operand_type->AsInteger()->IsSigned();
  } else {
    operand_is_signed = type->AsInteger()->IsSigned();
  }

  auto original_instruction =
      ir_context->get_def_use_mgr()->GetDef(message_.result_id());

  fuzzerutil::UpdateModuleIdBound(ir_context, message_.struct_fresh_id());

  // Determine the opcode of the new instruction that computes the result into a
  // struct.
  SpvOp new_instruction_opcode;

  switch (original_instruction->opcode()) {
    case SpvOpIAdd:
      new_instruction_opcode = SpvOpIAddCarry;
      break;
    case SpvOpISub:
      new_instruction_opcode = SpvOpISubBorrow;
      break;
    case SpvOpIMul:
      if (!operand_is_signed) {
        new_instruction_opcode = SpvOpUMulExtended;
      } else {
        new_instruction_opcode = SpvOpSMulExtended;
      }
      break;
    default:
      assert(false && "The instruction has an unsupported opcode.");
      return;
  }
  // Get the type of struct type id holding the intermediate result based on the
  // operand type.
  uint32_t operand_type_id =
      ir_context->get_def_use_mgr()
          ->GetDef(original_instruction->GetSingleWordInOperand(
              kArithmeticInstructionIndexLeftInOperand))
          ->type_id();

  uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
      ir_context, {operand_type_id, operand_type_id});
  // Avoid unused variables in release mode.
  (void)struct_type_id;
  assert(struct_type_id && "The struct type must exist in the module.");

  // Insert the new instruction that computes the result into a struct before
  // the |original_instruction|.
  original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
      ir_context, new_instruction_opcode, struct_type_id,
      message_.struct_fresh_id(),
      opt::Instruction::OperandList(
          {{SPV_OPERAND_TYPE_ID,
            {original_instruction->GetSingleWordInOperand(
                kArithmeticInstructionIndexLeftInOperand)}},
           {SPV_OPERAND_TYPE_ID,
            {original_instruction->GetSingleWordInOperand(
                kArithmeticInstructionIndexRightInOperand)}}})));

  // Insert the OpCompositeExtract after the added instruction. This instruction
  // takes the first component of the struct which represents low-order bits of
  // the operation. This is the original result.
  original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
      ir_context, SpvOpCompositeExtract, original_instruction->type_id(),
      message_.result_id(),
      opt::Instruction::OperandList(
          {{SPV_OPERAND_TYPE_ID, {message_.struct_fresh_id()}},
           {SPV_OPERAND_TYPE_LITERAL_INTEGER,
            {kOpCompositeExtractIndexLowOrderBits}}})));

  // Remove the original instruction.
  ir_context->KillInst(original_instruction);

  // We have modified the module so most analyzes are now invalid.
  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}

bool TransformationReplaceAddSubMulWithCarryingExtended::IsInstructionSuitable(
    opt::IRContext* ir_context, const opt::Instruction& instruction) {
  auto instruction_opcode = instruction.opcode();

  // Only instructions OpIAdd, OpISub, OpIMul are supported.
  switch (instruction_opcode) {
    case SpvOpIAdd:
    case SpvOpISub:
    case SpvOpIMul:
      break;
    default:
      return false;
  }
  uint32_t operand_1_type_id =
      ir_context->get_def_use_mgr()
          ->GetDef(instruction.GetSingleWordInOperand(
              kArithmeticInstructionIndexLeftInOperand))
          ->type_id();

  uint32_t operand_2_type_id =
      ir_context->get_def_use_mgr()
          ->GetDef(instruction.GetSingleWordInOperand(
              kArithmeticInstructionIndexRightInOperand))
          ->type_id();

  uint32_t result_type_id = instruction.type_id();

  // Both type ids of the operands and the result type ids must be equal.
  if (operand_1_type_id != operand_2_type_id) {
    return false;
  }
  if (operand_2_type_id != result_type_id) {
    return false;
  }

  // In case of OpIAdd and OpISub, the type must be unsigned.
  auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());

  switch (instruction_opcode) {
    case SpvOpIAdd:
    case SpvOpISub: {
      // In case of OpIAdd and OpISub if the operand is a vector, the component
      // type must be unsigned. Otherwise (if the operand is an int), the
      // operand must be unsigned.
      bool operand_is_signed =
          type->AsVector()
              ? type->AsVector()->element_type()->AsInteger()->IsSigned()
              : type->AsInteger()->IsSigned();
      if (operand_is_signed) {
        return false;
      }
    } break;
    default:
      break;
  }
  return true;
}

protobufs::Transformation
TransformationReplaceAddSubMulWithCarryingExtended::ToMessage() const {
  protobufs::Transformation result;
  *result.mutable_replace_add_sub_mul_with_carrying_extended() = message_;
  return result;
}

std::unordered_set<uint32_t>
TransformationReplaceAddSubMulWithCarryingExtended::GetFreshIds() const {
  return {message_.struct_fresh_id()};
}

}  // namespace fuzz
}  // namespace spvtools