added_function_reducer.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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/added_function_reducer.h"
  15. #include "source/fuzz/instruction_message.h"
  16. #include "source/fuzz/replayer.h"
  17. #include "source/fuzz/transformation_add_function.h"
  18. #include "source/opt/build_module.h"
  19. #include "source/opt/ir_context.h"
  20. #include "source/reduce/reducer.h"
  21. namespace spvtools {
  22. namespace fuzz {
  23. AddedFunctionReducer::AddedFunctionReducer(
  24. spv_target_env target_env, MessageConsumer consumer,
  25. const std::vector<uint32_t>& binary_in,
  26. const protobufs::FactSequence& initial_facts,
  27. const protobufs::TransformationSequence& transformation_sequence_in,
  28. uint32_t index_of_add_function_transformation,
  29. const Shrinker::InterestingnessFunction& shrinker_interestingness_function,
  30. bool validate_during_replay, spv_validator_options validator_options,
  31. uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts)
  32. : target_env_(target_env),
  33. consumer_(std::move(consumer)),
  34. binary_in_(binary_in),
  35. initial_facts_(initial_facts),
  36. transformation_sequence_in_(transformation_sequence_in),
  37. index_of_add_function_transformation_(
  38. index_of_add_function_transformation),
  39. shrinker_interestingness_function_(shrinker_interestingness_function),
  40. validate_during_replay_(validate_during_replay),
  41. validator_options_(validator_options),
  42. shrinker_step_limit_(shrinker_step_limit),
  43. num_existing_shrink_attempts_(num_existing_shrink_attempts),
  44. num_reducer_interestingness_function_invocations_(0) {}
  45. AddedFunctionReducer::~AddedFunctionReducer() = default;
  46. AddedFunctionReducer::AddedFunctionReducerResult AddedFunctionReducer::Run() {
  47. // Replay all transformations before the AddFunction transformation, then
  48. // add the raw function associated with the AddFunction transformation.
  49. std::vector<uint32_t> binary_to_reduce;
  50. std::unordered_set<uint32_t> irrelevant_pointee_global_variables;
  51. ReplayPrefixAndAddFunction(&binary_to_reduce,
  52. &irrelevant_pointee_global_variables);
  53. // Set up spirv-reduce to use our very specific interestingness function.
  54. reduce::Reducer reducer(target_env_);
  55. reducer.SetMessageConsumer(consumer_);
  56. reducer.AddDefaultReductionPasses();
  57. reducer.SetInterestingnessFunction(
  58. [this, &irrelevant_pointee_global_variables](
  59. const std::vector<uint32_t>& binary_under_reduction,
  60. uint32_t /*unused*/) {
  61. return InterestingnessFunctionForReducingAddedFunction(
  62. binary_under_reduction, irrelevant_pointee_global_variables);
  63. });
  64. // Instruct spirv-reduce to only target the function with the id associated
  65. // with the AddFunction transformation that we care about.
  66. spvtools::ReducerOptions reducer_options;
  67. reducer_options.set_target_function(GetAddedFunctionId());
  68. // Bound the number of reduction steps that spirv-reduce can make according
  69. // to the overall shrinker step limit and the number of shrink attempts that
  70. // have already been tried.
  71. assert(shrinker_step_limit_ > num_existing_shrink_attempts_ &&
  72. "The added function reducer should not have been invoked.");
  73. reducer_options.set_step_limit(shrinker_step_limit_ -
  74. num_existing_shrink_attempts_);
  75. // Run spirv-reduce.
  76. std::vector<uint32_t> reduced_binary;
  77. auto reducer_result =
  78. reducer.Run(std::move(binary_to_reduce), &reduced_binary, reducer_options,
  79. validator_options_);
  80. if (reducer_result != reduce::Reducer::kComplete &&
  81. reducer_result != reduce::Reducer::kReachedStepLimit) {
  82. return {AddedFunctionReducerResultStatus::kReductionFailed,
  83. std::vector<uint32_t>(), protobufs::TransformationSequence(), 0};
  84. }
  85. // Provide the outer shrinker with an adapted sequence of transformations in
  86. // which the AddFunction transformation of interest has been simplified to use
  87. // the version of the added function that appears in |reduced_binary|.
  88. std::vector<uint32_t> binary_out;
  89. protobufs::TransformationSequence transformation_sequence_out;
  90. ReplayAdaptedTransformations(reduced_binary, &binary_out,
  91. &transformation_sequence_out);
  92. // We subtract 1 from |num_reducer_interestingness_function_invocations_| to
  93. // account for the fact that spirv-reduce invokes its interestingness test
  94. // once before reduction commences in order to check that the initial module
  95. // is interesting.
  96. assert(num_reducer_interestingness_function_invocations_ > 0 &&
  97. "At a minimum spirv-reduce should have invoked its interestingness "
  98. "test once.");
  99. return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
  100. std::move(transformation_sequence_out),
  101. num_reducer_interestingness_function_invocations_ - 1};
  102. }
  103. bool AddedFunctionReducer::InterestingnessFunctionForReducingAddedFunction(
  104. const std::vector<uint32_t>& binary_under_reduction,
  105. const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables) {
  106. uint32_t counter_for_shrinker_interestingness_function =
  107. num_existing_shrink_attempts_ +
  108. num_reducer_interestingness_function_invocations_;
  109. num_reducer_interestingness_function_invocations_++;
  110. // The reduced version of the added function must be limited to accessing
  111. // global variables appearing in |irrelevant_pointee_global_variables|. This
  112. // is to guard against the possibility of spirv-reduce changing a reference
  113. // to an irrelevant global to a reference to a regular global variable, which
  114. // could cause the added function to change the semantics of the original
  115. // module.
  116. auto ir_context =
  117. BuildModule(target_env_, consumer_, binary_under_reduction.data(),
  118. binary_under_reduction.size());
  119. assert(ir_context != nullptr && "The binary should be parsable.");
  120. for (auto& type_or_value : ir_context->module()->types_values()) {
  121. if (type_or_value.opcode() != spv::Op::OpVariable) {
  122. continue;
  123. }
  124. if (irrelevant_pointee_global_variables.count(type_or_value.result_id())) {
  125. continue;
  126. }
  127. if (!ir_context->get_def_use_mgr()->WhileEachUse(
  128. &type_or_value,
  129. [this, &ir_context](opt::Instruction* user,
  130. uint32_t /*unused*/) -> bool {
  131. auto block = ir_context->get_instr_block(user);
  132. if (block != nullptr &&
  133. block->GetParent()->result_id() == GetAddedFunctionId()) {
  134. return false;
  135. }
  136. return true;
  137. })) {
  138. return false;
  139. }
  140. }
  141. // For the binary to be deemed interesting, it must be possible to
  142. // successfully apply all the transformations, with the transformation at
  143. // index |index_of_add_function_transformation_| simplified to use the version
  144. // of the added function from |binary_under_reduction|.
  145. //
  146. // This might not be the case: spirv-reduce might have removed a chunk of the
  147. // added function on which future transformations depend.
  148. //
  149. // This is an optimization: the assumption is that having already shrunk the
  150. // transformation sequence down to minimal form, all transformations have a
  151. // role to play, and it's almost certainly a waste of time to invoke the
  152. // shrinker's interestingness function if we have eliminated transformations
  153. // that the shrinker previously tried to -- but could not -- eliminate.
  154. std::vector<uint32_t> binary_out;
  155. protobufs::TransformationSequence modified_transformations;
  156. ReplayAdaptedTransformations(binary_under_reduction, &binary_out,
  157. &modified_transformations);
  158. if (transformation_sequence_in_.transformation_size() !=
  159. modified_transformations.transformation_size()) {
  160. return false;
  161. }
  162. // The resulting binary must be deemed interesting according to the shrinker's
  163. // interestingness function.
  164. return shrinker_interestingness_function_(
  165. binary_out, counter_for_shrinker_interestingness_function);
  166. }
  167. void AddedFunctionReducer::ReplayPrefixAndAddFunction(
  168. std::vector<uint32_t>* binary_out,
  169. std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const {
  170. assert(transformation_sequence_in_
  171. .transformation(index_of_add_function_transformation_)
  172. .has_add_function() &&
  173. "A TransformationAddFunction is required at the given index.");
  174. auto replay_result = Replayer(target_env_, consumer_, binary_in_,
  175. initial_facts_, transformation_sequence_in_,
  176. index_of_add_function_transformation_,
  177. validate_during_replay_, validator_options_)
  178. .Run();
  179. assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
  180. "Replay should succeed");
  181. assert(static_cast<uint32_t>(
  182. replay_result.applied_transformations.transformation_size()) ==
  183. index_of_add_function_transformation_ &&
  184. "All requested transformations should have applied.");
  185. auto* ir_context = replay_result.transformed_module.get();
  186. for (auto& type_or_value : ir_context->module()->types_values()) {
  187. if (type_or_value.opcode() != spv::Op::OpVariable) {
  188. continue;
  189. }
  190. if (replay_result.transformation_context->GetFactManager()
  191. ->PointeeValueIsIrrelevant(type_or_value.result_id())) {
  192. irrelevant_pointee_global_variables->insert(type_or_value.result_id());
  193. }
  194. }
  195. // Add the function associated with the transformation at
  196. // |index_of_add_function_transformation| to the module. By construction this
  197. // should succeed.
  198. const protobufs::TransformationAddFunction&
  199. transformation_add_function_message =
  200. transformation_sequence_in_
  201. .transformation(index_of_add_function_transformation_)
  202. .add_function();
  203. bool success = TransformationAddFunction(transformation_add_function_message)
  204. .TryToAddFunction(ir_context);
  205. (void)success; // Keep release mode compilers happy.
  206. assert(success && "Addition of the function should have succeeded.");
  207. // Get the binary representation of the module with this function added.
  208. ir_context->module()->ToBinary(binary_out, false);
  209. }
  210. void AddedFunctionReducer::ReplayAdaptedTransformations(
  211. const std::vector<uint32_t>& binary_under_reduction,
  212. std::vector<uint32_t>* binary_out,
  213. protobufs::TransformationSequence* transformation_sequence_out) const {
  214. assert(index_of_add_function_transformation_ <
  215. static_cast<uint32_t>(
  216. transformation_sequence_in_.transformation_size()) &&
  217. "The relevant add function transformation must be present.");
  218. std::unique_ptr<opt::IRContext> ir_context_under_reduction =
  219. BuildModule(target_env_, consumer_, binary_under_reduction.data(),
  220. binary_under_reduction.size());
  221. assert(ir_context_under_reduction && "Error building module.");
  222. protobufs::TransformationSequence modified_transformations;
  223. for (uint32_t i = 0;
  224. i <
  225. static_cast<uint32_t>(transformation_sequence_in_.transformation_size());
  226. i++) {
  227. if (i == index_of_add_function_transformation_) {
  228. protobufs::TransformationAddFunction modified_add_function =
  229. transformation_sequence_in_
  230. .transformation(index_of_add_function_transformation_)
  231. .add_function();
  232. assert(GetAddedFunctionId() ==
  233. modified_add_function.instruction(0).result_id() &&
  234. "Unexpected result id for added function.");
  235. modified_add_function.clear_instruction();
  236. for (auto& function : *ir_context_under_reduction->module()) {
  237. if (function.result_id() != GetAddedFunctionId()) {
  238. continue;
  239. }
  240. function.ForEachInst(
  241. [&modified_add_function](const opt::Instruction* instruction) {
  242. *modified_add_function.add_instruction() =
  243. MakeInstructionMessage(instruction);
  244. });
  245. }
  246. assert(modified_add_function.instruction_size() > 0 &&
  247. "Some instructions for the added function should remain.");
  248. *modified_transformations.add_transformation()->mutable_add_function() =
  249. modified_add_function;
  250. } else {
  251. *modified_transformations.add_transformation() =
  252. transformation_sequence_in_.transformation(i);
  253. }
  254. }
  255. assert(
  256. transformation_sequence_in_.transformation_size() ==
  257. modified_transformations.transformation_size() &&
  258. "The original and modified transformations should have the same size.");
  259. auto replay_result = Replayer(target_env_, consumer_, binary_in_,
  260. initial_facts_, modified_transformations,
  261. modified_transformations.transformation_size(),
  262. validate_during_replay_, validator_options_)
  263. .Run();
  264. assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
  265. "Replay should succeed.");
  266. replay_result.transformed_module->module()->ToBinary(binary_out, false);
  267. *transformation_sequence_out =
  268. std::move(replay_result.applied_transformations);
  269. }
  270. uint32_t AddedFunctionReducer::GetAddedFunctionId() const {
  271. return transformation_sequence_in_
  272. .transformation(index_of_add_function_transformation_)
  273. .add_function()
  274. .instruction(0)
  275. .result_id();
  276. }
  277. } // namespace fuzz
  278. } // namespace spvtools