fuzzer_pass_add_equation_instructions.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. // Copyright (c) 2020 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. #include "source/fuzz/fuzzer_pass_add_equation_instructions.h"
  15. #include <vector>
  16. #include "source/fuzz/fuzzer_util.h"
  17. #include "source/fuzz/transformation_equation_instruction.h"
  18. namespace spvtools {
  19. namespace fuzz {
  20. namespace {
  21. bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
  22. switch (bit_width) {
  23. case 32:
  24. return true;
  25. case 64:
  26. return ir_context->get_feature_mgr()->HasCapability(
  27. spv::Capability::Float64) &&
  28. ir_context->get_feature_mgr()->HasCapability(
  29. spv::Capability::Int64);
  30. case 16:
  31. return ir_context->get_feature_mgr()->HasCapability(
  32. spv::Capability::Float16) &&
  33. ir_context->get_feature_mgr()->HasCapability(
  34. spv::Capability::Int16);
  35. default:
  36. return false;
  37. }
  38. }
  39. } // namespace
  40. FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
  41. opt::IRContext* ir_context, TransformationContext* transformation_context,
  42. FuzzerContext* fuzzer_context,
  43. protobufs::TransformationSequence* transformations,
  44. bool ignore_inapplicable_transformations)
  45. : FuzzerPass(ir_context, transformation_context, fuzzer_context,
  46. transformations, ignore_inapplicable_transformations) {}
  47. void FuzzerPassAddEquationInstructions::Apply() {
  48. ForEachInstructionWithInstructionDescriptor(
  49. [this](opt::Function* function, opt::BasicBlock* block,
  50. opt::BasicBlock::iterator inst_it,
  51. const protobufs::InstructionDescriptor& instruction_descriptor) {
  52. if (!GetFuzzerContext()->ChoosePercentage(
  53. GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
  54. return;
  55. }
  56. // Check that it is OK to add an equation instruction before the given
  57. // instruction in principle - e.g. check that this does not lead to
  58. // inserting before an OpVariable or OpPhi instruction. We use OpIAdd
  59. // as an example opcode for this check, to be representative of *some*
  60. // opcode that defines an equation, even though we may choose a
  61. // different opcode below.
  62. if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(spv::Op::OpIAdd,
  63. inst_it)) {
  64. return;
  65. }
  66. // Get all available instructions with result ids and types that are not
  67. // OpUndef.
  68. std::vector<opt::Instruction*> available_instructions =
  69. FindAvailableInstructions(
  70. function, block, inst_it,
  71. [this](opt::IRContext* /*unused*/,
  72. opt::Instruction* instruction) -> bool {
  73. return instruction->result_id() && instruction->type_id() &&
  74. instruction->opcode() != spv::Op::OpUndef &&
  75. !GetTransformationContext()
  76. ->GetFactManager()
  77. ->IdIsIrrelevant(instruction->result_id());
  78. });
  79. // Try the opcodes for which we know how to make ids at random until
  80. // something works.
  81. std::vector<spv::Op> candidate_opcodes = {
  82. spv::Op::OpIAdd, spv::Op::OpISub, spv::Op::OpLogicalNot,
  83. spv::Op::OpSNegate, spv::Op::OpConvertUToF, spv::Op::OpConvertSToF,
  84. spv::Op::OpBitcast};
  85. do {
  86. auto opcode =
  87. GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
  88. switch (opcode) {
  89. case spv::Op::OpConvertSToF:
  90. case spv::Op::OpConvertUToF: {
  91. std::vector<const opt::Instruction*> candidate_instructions;
  92. for (const auto* inst :
  93. GetIntegerInstructions(available_instructions)) {
  94. const auto* type =
  95. GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  96. assert(type && "|inst| has invalid type");
  97. if (const auto* vector_type = type->AsVector()) {
  98. type = vector_type->element_type();
  99. }
  100. if (IsBitWidthSupported(GetIRContext(),
  101. type->AsInteger()->width())) {
  102. candidate_instructions.push_back(inst);
  103. }
  104. }
  105. if (candidate_instructions.empty()) {
  106. break;
  107. }
  108. const auto* operand =
  109. candidate_instructions[GetFuzzerContext()->RandomIndex(
  110. candidate_instructions)];
  111. const auto* type =
  112. GetIRContext()->get_type_mgr()->GetType(operand->type_id());
  113. assert(type && "Operand has invalid type");
  114. // Make sure a result type exists in the module.
  115. if (const auto* vector = type->AsVector()) {
  116. // We store element count in a separate variable since the
  117. // call FindOrCreate* functions below might invalidate
  118. // |vector| pointer.
  119. const auto element_count = vector->element_count();
  120. FindOrCreateVectorType(
  121. FindOrCreateFloatType(
  122. vector->element_type()->AsInteger()->width()),
  123. element_count);
  124. } else {
  125. FindOrCreateFloatType(type->AsInteger()->width());
  126. }
  127. ApplyTransformation(TransformationEquationInstruction(
  128. GetFuzzerContext()->GetFreshId(), opcode,
  129. {operand->result_id()}, instruction_descriptor));
  130. return;
  131. }
  132. case spv::Op::OpBitcast: {
  133. const auto candidate_instructions =
  134. GetNumericalInstructions(available_instructions);
  135. if (!candidate_instructions.empty()) {
  136. const auto* operand_inst =
  137. candidate_instructions[GetFuzzerContext()->RandomIndex(
  138. candidate_instructions)];
  139. const auto* operand_type =
  140. GetIRContext()->get_type_mgr()->GetType(
  141. operand_inst->type_id());
  142. assert(operand_type && "Operand instruction has invalid type");
  143. // Make sure a result type exists in the module.
  144. //
  145. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3539):
  146. // The only constraint on the types of OpBitcast's parameters
  147. // is that they must have the same number of bits. Consider
  148. // improving the code below to support this in full.
  149. if (const auto* vector = operand_type->AsVector()) {
  150. // We store element count in a separate variable since the
  151. // call FindOrCreate* functions below might invalidate
  152. // |vector| pointer.
  153. const auto element_count = vector->element_count();
  154. uint32_t element_type_id;
  155. if (const auto* int_type =
  156. vector->element_type()->AsInteger()) {
  157. element_type_id = FindOrCreateFloatType(int_type->width());
  158. } else {
  159. assert(vector->element_type()->AsFloat() &&
  160. "Vector must have numerical elements");
  161. element_type_id = FindOrCreateIntegerType(
  162. vector->element_type()->AsFloat()->width(),
  163. GetFuzzerContext()->ChooseEven());
  164. }
  165. FindOrCreateVectorType(element_type_id, element_count);
  166. } else if (const auto* int_type = operand_type->AsInteger()) {
  167. FindOrCreateFloatType(int_type->width());
  168. } else {
  169. assert(operand_type->AsFloat() &&
  170. "Operand is not a scalar of numerical type");
  171. FindOrCreateIntegerType(operand_type->AsFloat()->width(),
  172. GetFuzzerContext()->ChooseEven());
  173. }
  174. ApplyTransformation(TransformationEquationInstruction(
  175. GetFuzzerContext()->GetFreshId(), opcode,
  176. {operand_inst->result_id()}, instruction_descriptor));
  177. return;
  178. }
  179. } break;
  180. case spv::Op::OpIAdd:
  181. case spv::Op::OpISub: {
  182. // Instructions of integer (scalar or vector) result type are
  183. // suitable for these opcodes.
  184. auto integer_instructions =
  185. GetIntegerInstructions(available_instructions);
  186. if (!integer_instructions.empty()) {
  187. // There is at least one such instruction, so pick one at random
  188. // for the LHS of an equation.
  189. auto lhs = integer_instructions.at(
  190. GetFuzzerContext()->RandomIndex(integer_instructions));
  191. // For the RHS, we can use any instruction with an integer
  192. // scalar/vector result type of the same number of components
  193. // and the same bit-width for the underlying integer type.
  194. // Work out the element count and bit-width.
  195. auto lhs_type =
  196. GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
  197. uint32_t lhs_element_count;
  198. uint32_t lhs_bit_width;
  199. if (lhs_type->AsVector()) {
  200. lhs_element_count = lhs_type->AsVector()->element_count();
  201. lhs_bit_width = lhs_type->AsVector()
  202. ->element_type()
  203. ->AsInteger()
  204. ->width();
  205. } else {
  206. lhs_element_count = 1;
  207. lhs_bit_width = lhs_type->AsInteger()->width();
  208. }
  209. // Get all the instructions that match on element count and
  210. // bit-width.
  211. auto candidate_rhs_instructions = RestrictToElementBitWidth(
  212. RestrictToVectorWidth(integer_instructions,
  213. lhs_element_count),
  214. lhs_bit_width);
  215. // Choose a RHS instruction at random; there is guaranteed to
  216. // be at least one choice as the LHS will be available.
  217. auto rhs = candidate_rhs_instructions.at(
  218. GetFuzzerContext()->RandomIndex(
  219. candidate_rhs_instructions));
  220. // Add the equation instruction.
  221. ApplyTransformation(TransformationEquationInstruction(
  222. GetFuzzerContext()->GetFreshId(), opcode,
  223. {lhs->result_id(), rhs->result_id()},
  224. instruction_descriptor));
  225. return;
  226. }
  227. break;
  228. }
  229. case spv::Op::OpLogicalNot: {
  230. // Choose any available instruction of boolean scalar/vector
  231. // result type and equate its negation with a fresh id.
  232. auto boolean_instructions =
  233. GetBooleanInstructions(available_instructions);
  234. if (!boolean_instructions.empty()) {
  235. ApplyTransformation(TransformationEquationInstruction(
  236. GetFuzzerContext()->GetFreshId(), opcode,
  237. {boolean_instructions
  238. .at(GetFuzzerContext()->RandomIndex(
  239. boolean_instructions))
  240. ->result_id()},
  241. instruction_descriptor));
  242. return;
  243. }
  244. break;
  245. }
  246. case spv::Op::OpSNegate: {
  247. // Similar to OpLogicalNot, but for signed integer negation.
  248. auto integer_instructions =
  249. GetIntegerInstructions(available_instructions);
  250. if (!integer_instructions.empty()) {
  251. ApplyTransformation(TransformationEquationInstruction(
  252. GetFuzzerContext()->GetFreshId(), opcode,
  253. {integer_instructions
  254. .at(GetFuzzerContext()->RandomIndex(
  255. integer_instructions))
  256. ->result_id()},
  257. instruction_descriptor));
  258. return;
  259. }
  260. break;
  261. }
  262. default:
  263. assert(false && "Unexpected opcode.");
  264. break;
  265. }
  266. } while (!candidate_opcodes.empty());
  267. // Reaching here means that we did not manage to apply any
  268. // transformation at this point of the module.
  269. });
  270. }
  271. std::vector<opt::Instruction*>
  272. FuzzerPassAddEquationInstructions::GetIntegerInstructions(
  273. const std::vector<opt::Instruction*>& instructions) const {
  274. std::vector<opt::Instruction*> result;
  275. for (auto& inst : instructions) {
  276. auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  277. if (type->AsInteger() ||
  278. (type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
  279. result.push_back(inst);
  280. }
  281. }
  282. return result;
  283. }
  284. std::vector<opt::Instruction*>
  285. FuzzerPassAddEquationInstructions::GetFloatInstructions(
  286. const std::vector<opt::Instruction*>& instructions) const {
  287. std::vector<opt::Instruction*> result;
  288. for (auto& inst : instructions) {
  289. auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  290. if (type->AsFloat() ||
  291. (type->AsVector() && type->AsVector()->element_type()->AsFloat())) {
  292. result.push_back(inst);
  293. }
  294. }
  295. return result;
  296. }
  297. std::vector<opt::Instruction*>
  298. FuzzerPassAddEquationInstructions::GetBooleanInstructions(
  299. const std::vector<opt::Instruction*>& instructions) const {
  300. std::vector<opt::Instruction*> result;
  301. for (auto& inst : instructions) {
  302. auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  303. if (type->AsBool() ||
  304. (type->AsVector() && type->AsVector()->element_type()->AsBool())) {
  305. result.push_back(inst);
  306. }
  307. }
  308. return result;
  309. }
  310. std::vector<opt::Instruction*>
  311. FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
  312. const std::vector<opt::Instruction*>& instructions,
  313. uint32_t vector_width) const {
  314. std::vector<opt::Instruction*> result;
  315. for (auto& inst : instructions) {
  316. auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  317. // Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
  318. // otherwise derived from its vector type.
  319. uint32_t other_vector_width =
  320. type->AsVector() ? type->AsVector()->element_count() : 1;
  321. // Keep |inst| if the vector widths match.
  322. if (vector_width == other_vector_width) {
  323. result.push_back(inst);
  324. }
  325. }
  326. return result;
  327. }
  328. std::vector<opt::Instruction*>
  329. FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
  330. const std::vector<opt::Instruction*>& instructions,
  331. uint32_t bit_width) const {
  332. std::vector<opt::Instruction*> result;
  333. for (auto& inst : instructions) {
  334. const opt::analysis::Type* type =
  335. GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  336. if (type->AsVector()) {
  337. type = type->AsVector()->element_type();
  338. }
  339. assert((type->AsInteger() || type->AsFloat()) &&
  340. "Precondition: all input instructions must "
  341. "have integer or float scalar or vector type.");
  342. if ((type->AsInteger() && type->AsInteger()->width() == bit_width) ||
  343. (type->AsFloat() && type->AsFloat()->width() == bit_width)) {
  344. result.push_back(inst);
  345. }
  346. }
  347. return result;
  348. }
  349. std::vector<opt::Instruction*>
  350. FuzzerPassAddEquationInstructions::GetNumericalInstructions(
  351. const std::vector<opt::Instruction*>& instructions) const {
  352. std::vector<opt::Instruction*> result;
  353. for (auto* inst : instructions) {
  354. const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
  355. assert(type && "Instruction has invalid type");
  356. if (const auto* vector_type = type->AsVector()) {
  357. type = vector_type->element_type();
  358. }
  359. if (!type->AsInteger() && !type->AsFloat()) {
  360. // Only numerical scalars or vectors of numerical components are
  361. // supported.
  362. continue;
  363. }
  364. if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
  365. ? type->AsInteger()->width()
  366. : type->AsFloat()->width())) {
  367. continue;
  368. }
  369. result.push_back(inst);
  370. }
  371. return result;
  372. }
  373. } // namespace fuzz
  374. } // namespace spvtools