fuzzer_pass_donate_modules.cpp 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  1. // Copyright (c) 2019 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_donate_modules.h"
  15. #include <map>
  16. #include <queue>
  17. #include <set>
  18. #include "source/fuzz/call_graph.h"
  19. #include "source/fuzz/instruction_message.h"
  20. #include "source/fuzz/transformation_add_constant_boolean.h"
  21. #include "source/fuzz/transformation_add_constant_composite.h"
  22. #include "source/fuzz/transformation_add_constant_null.h"
  23. #include "source/fuzz/transformation_add_constant_scalar.h"
  24. #include "source/fuzz/transformation_add_function.h"
  25. #include "source/fuzz/transformation_add_global_undef.h"
  26. #include "source/fuzz/transformation_add_global_variable.h"
  27. #include "source/fuzz/transformation_add_spec_constant_op.h"
  28. #include "source/fuzz/transformation_add_type_array.h"
  29. #include "source/fuzz/transformation_add_type_boolean.h"
  30. #include "source/fuzz/transformation_add_type_float.h"
  31. #include "source/fuzz/transformation_add_type_function.h"
  32. #include "source/fuzz/transformation_add_type_int.h"
  33. #include "source/fuzz/transformation_add_type_matrix.h"
  34. #include "source/fuzz/transformation_add_type_pointer.h"
  35. #include "source/fuzz/transformation_add_type_struct.h"
  36. #include "source/fuzz/transformation_add_type_vector.h"
  37. namespace spvtools {
  38. namespace fuzz {
  39. FuzzerPassDonateModules::FuzzerPassDonateModules(
  40. opt::IRContext* ir_context, TransformationContext* transformation_context,
  41. FuzzerContext* fuzzer_context,
  42. protobufs::TransformationSequence* transformations,
  43. const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers)
  44. : FuzzerPass(ir_context, transformation_context, fuzzer_context,
  45. transformations),
  46. donor_suppliers_(donor_suppliers) {}
  47. FuzzerPassDonateModules::~FuzzerPassDonateModules() = default;
  48. void FuzzerPassDonateModules::Apply() {
  49. // If there are no donor suppliers, this fuzzer pass is a no-op.
  50. if (donor_suppliers_.empty()) {
  51. return;
  52. }
  53. // Donate at least one module, and probabilistically decide when to stop
  54. // donating modules.
  55. do {
  56. // Choose a donor supplier at random, and get the module that it provides.
  57. std::unique_ptr<opt::IRContext> donor_ir_context = donor_suppliers_.at(
  58. GetFuzzerContext()->RandomIndex(donor_suppliers_))();
  59. assert(donor_ir_context != nullptr && "Supplying of donor failed");
  60. assert(fuzzerutil::IsValid(
  61. donor_ir_context.get(),
  62. GetTransformationContext()->GetValidatorOptions()) &&
  63. "The donor module must be valid");
  64. // Donate the supplied module.
  65. //
  66. // Randomly decide whether to make the module livesafe (see
  67. // FactFunctionIsLivesafe); doing so allows it to be used for live code
  68. // injection but restricts its behaviour to allow this, and means that its
  69. // functions cannot be transformed as if they were arbitrary dead code.
  70. bool make_livesafe = GetFuzzerContext()->ChoosePercentage(
  71. GetFuzzerContext()->ChanceOfMakingDonorLivesafe());
  72. DonateSingleModule(donor_ir_context.get(), make_livesafe);
  73. } while (GetFuzzerContext()->ChoosePercentage(
  74. GetFuzzerContext()->GetChanceOfDonatingAdditionalModule()));
  75. }
  76. void FuzzerPassDonateModules::DonateSingleModule(
  77. opt::IRContext* donor_ir_context, bool make_livesafe) {
  78. // The ids used by the donor module may very well clash with ids defined in
  79. // the recipient module. Furthermore, some instructions defined in the donor
  80. // module will be equivalent to instructions defined in the recipient module,
  81. // and it is not always legal to re-declare equivalent instructions. For
  82. // example, OpTypeVoid cannot be declared twice.
  83. //
  84. // To handle this, we maintain a mapping from an id used in the donor module
  85. // to the corresponding id that will be used by the donated code when it
  86. // appears in the recipient module.
  87. //
  88. // This mapping is populated in two ways:
  89. // (1) by mapping a donor instruction's result id to the id of some equivalent
  90. // existing instruction in the recipient (e.g. this has to be done for
  91. // OpTypeVoid)
  92. // (2) by mapping a donor instruction's result id to a freshly chosen id that
  93. // is guaranteed to be different from any id already used by the recipient
  94. // (or from any id already chosen to handle a previous donor id)
  95. std::map<uint32_t, uint32_t> original_id_to_donated_id;
  96. HandleExternalInstructionImports(donor_ir_context,
  97. &original_id_to_donated_id);
  98. HandleTypesAndValues(donor_ir_context, &original_id_to_donated_id);
  99. HandleFunctions(donor_ir_context, &original_id_to_donated_id, make_livesafe);
  100. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3115) Handle some
  101. // kinds of decoration.
  102. }
  103. SpvStorageClass FuzzerPassDonateModules::AdaptStorageClass(
  104. SpvStorageClass donor_storage_class) {
  105. switch (donor_storage_class) {
  106. case SpvStorageClassFunction:
  107. case SpvStorageClassPrivate:
  108. case SpvStorageClassWorkgroup:
  109. // We leave these alone
  110. return donor_storage_class;
  111. case SpvStorageClassInput:
  112. case SpvStorageClassOutput:
  113. case SpvStorageClassUniform:
  114. case SpvStorageClassUniformConstant:
  115. case SpvStorageClassPushConstant:
  116. case SpvStorageClassImage:
  117. case SpvStorageClassStorageBuffer:
  118. // We change these to Private
  119. return SpvStorageClassPrivate;
  120. default:
  121. // Handle other cases on demand.
  122. assert(false && "Currently unsupported storage class.");
  123. return SpvStorageClassMax;
  124. }
  125. }
  126. void FuzzerPassDonateModules::HandleExternalInstructionImports(
  127. opt::IRContext* donor_ir_context,
  128. std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
  129. // Consider every external instruction set import in the donor module.
  130. for (auto& donor_import : donor_ir_context->module()->ext_inst_imports()) {
  131. const auto& donor_import_name_words = donor_import.GetInOperand(0).words;
  132. // Look for an identical import in the recipient module.
  133. for (auto& existing_import : GetIRContext()->module()->ext_inst_imports()) {
  134. const auto& existing_import_name_words =
  135. existing_import.GetInOperand(0).words;
  136. if (donor_import_name_words == existing_import_name_words) {
  137. // A matching import has found. Map the result id for the donor import
  138. // to the id of the existing import, so that when donor instructions
  139. // rely on the import they will be rewritten to use the existing import.
  140. original_id_to_donated_id->insert(
  141. {donor_import.result_id(), existing_import.result_id()});
  142. break;
  143. }
  144. }
  145. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3116): At present
  146. // we do not handle donation of instruction imports, i.e. we do not allow
  147. // the donor to import instruction sets that the recipient did not already
  148. // import. It might be a good idea to allow this, but it requires some
  149. // thought.
  150. assert(original_id_to_donated_id->count(donor_import.result_id()) &&
  151. "Donation of imports is not yet supported.");
  152. }
  153. }
  154. void FuzzerPassDonateModules::HandleTypesAndValues(
  155. opt::IRContext* donor_ir_context,
  156. std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
  157. // Consider every type/global/constant/undef in the module.
  158. for (auto& type_or_value : donor_ir_context->module()->types_values()) {
  159. HandleTypeOrValue(type_or_value, original_id_to_donated_id);
  160. }
  161. }
  162. void FuzzerPassDonateModules::HandleTypeOrValue(
  163. const opt::Instruction& type_or_value,
  164. std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
  165. // The type/value instruction generates a result id, and we need to associate
  166. // the donor's result id with a new result id. That new result id will either
  167. // be the id of some existing instruction, or a fresh id. This variable
  168. // captures it.
  169. uint32_t new_result_id;
  170. // Decide how to handle each kind of instruction on a case-by-case basis.
  171. //
  172. // Because the donor module is required to be valid, when we encounter a
  173. // type comprised of component types (e.g. an aggregate or pointer), we know
  174. // that its component types will have been considered previously, and that
  175. // |original_id_to_donated_id| will already contain an entry for them.
  176. switch (type_or_value.opcode()) {
  177. case SpvOpTypeImage:
  178. case SpvOpTypeSampledImage:
  179. case SpvOpTypeSampler:
  180. // We do not donate types and variables that relate to images and
  181. // samplers, so we skip these types and subsequently skip anything that
  182. // depends on them.
  183. return;
  184. case SpvOpTypeVoid: {
  185. // Void has to exist already in order for us to have an entry point.
  186. // Get the existing id of void.
  187. opt::analysis::Void void_type;
  188. new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type);
  189. assert(new_result_id &&
  190. "The module being transformed will always have 'void' type "
  191. "declared.");
  192. } break;
  193. case SpvOpTypeBool: {
  194. // Bool cannot be declared multiple times, so use its existing id if
  195. // present, or add a declaration of Bool with a fresh id if not.
  196. opt::analysis::Bool bool_type;
  197. auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
  198. if (bool_type_id) {
  199. new_result_id = bool_type_id;
  200. } else {
  201. new_result_id = GetFuzzerContext()->GetFreshId();
  202. ApplyTransformation(TransformationAddTypeBoolean(new_result_id));
  203. }
  204. } break;
  205. case SpvOpTypeInt: {
  206. // Int cannot be declared multiple times with the same width and
  207. // signedness, so check whether an existing identical Int type is
  208. // present and use its id if so. Otherwise add a declaration of the
  209. // Int type used by the donor, with a fresh id.
  210. const uint32_t width = type_or_value.GetSingleWordInOperand(0);
  211. const bool is_signed =
  212. static_cast<bool>(type_or_value.GetSingleWordInOperand(1));
  213. opt::analysis::Integer int_type(width, is_signed);
  214. auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
  215. if (int_type_id) {
  216. new_result_id = int_type_id;
  217. } else {
  218. new_result_id = GetFuzzerContext()->GetFreshId();
  219. ApplyTransformation(
  220. TransformationAddTypeInt(new_result_id, width, is_signed));
  221. }
  222. } break;
  223. case SpvOpTypeFloat: {
  224. // Similar to SpvOpTypeInt.
  225. const uint32_t width = type_or_value.GetSingleWordInOperand(0);
  226. opt::analysis::Float float_type(width);
  227. auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
  228. if (float_type_id) {
  229. new_result_id = float_type_id;
  230. } else {
  231. new_result_id = GetFuzzerContext()->GetFreshId();
  232. ApplyTransformation(TransformationAddTypeFloat(new_result_id, width));
  233. }
  234. } break;
  235. case SpvOpTypeVector: {
  236. // It is not legal to have two Vector type declarations with identical
  237. // element types and element counts, so check whether an existing
  238. // identical Vector type is present and use its id if so. Otherwise add
  239. // a declaration of the Vector type used by the donor, with a fresh id.
  240. // When considering the vector's component type id, we look up the id
  241. // use in the donor to find the id to which this has been remapped.
  242. uint32_t component_type_id = original_id_to_donated_id->at(
  243. type_or_value.GetSingleWordInOperand(0));
  244. auto component_type =
  245. GetIRContext()->get_type_mgr()->GetType(component_type_id);
  246. assert(component_type && "The base type should be registered.");
  247. auto component_count = type_or_value.GetSingleWordInOperand(1);
  248. opt::analysis::Vector vector_type(component_type, component_count);
  249. auto vector_type_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
  250. if (vector_type_id) {
  251. new_result_id = vector_type_id;
  252. } else {
  253. new_result_id = GetFuzzerContext()->GetFreshId();
  254. ApplyTransformation(TransformationAddTypeVector(
  255. new_result_id, component_type_id, component_count));
  256. }
  257. } break;
  258. case SpvOpTypeMatrix: {
  259. // Similar to SpvOpTypeVector.
  260. uint32_t column_type_id = original_id_to_donated_id->at(
  261. type_or_value.GetSingleWordInOperand(0));
  262. auto column_type =
  263. GetIRContext()->get_type_mgr()->GetType(column_type_id);
  264. assert(column_type && column_type->AsVector() &&
  265. "The column type should be a registered vector type.");
  266. auto column_count = type_or_value.GetSingleWordInOperand(1);
  267. opt::analysis::Matrix matrix_type(column_type, column_count);
  268. auto matrix_type_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
  269. if (matrix_type_id) {
  270. new_result_id = matrix_type_id;
  271. } else {
  272. new_result_id = GetFuzzerContext()->GetFreshId();
  273. ApplyTransformation(TransformationAddTypeMatrix(
  274. new_result_id, column_type_id, column_count));
  275. }
  276. } break;
  277. case SpvOpTypeArray: {
  278. // It is OK to have multiple structurally identical array types, so
  279. // we go ahead and add a remapped version of the type declared by the
  280. // donor.
  281. uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
  282. if (!original_id_to_donated_id->count(component_type_id)) {
  283. // We did not donate the component type of this array type, so we
  284. // cannot donate the array type.
  285. return;
  286. }
  287. new_result_id = GetFuzzerContext()->GetFreshId();
  288. ApplyTransformation(TransformationAddTypeArray(
  289. new_result_id, original_id_to_donated_id->at(component_type_id),
  290. original_id_to_donated_id->at(
  291. type_or_value.GetSingleWordInOperand(1))));
  292. } break;
  293. case SpvOpTypeRuntimeArray: {
  294. // A runtime array is allowed as the final member of an SSBO. During
  295. // donation we turn runtime arrays into fixed-size arrays. For dead
  296. // code donations this is OK because the array is never indexed into at
  297. // runtime, so it does not matter what its size is. For live-safe code,
  298. // all accesses are made in-bounds, so this is also OK.
  299. //
  300. // The special OpArrayLength instruction, which works on runtime arrays,
  301. // is rewritten to yield the fixed length that is used for the array.
  302. uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
  303. if (!original_id_to_donated_id->count(component_type_id)) {
  304. // We did not donate the component type of this runtime array type, so
  305. // we cannot donate it as a fixed-size array.
  306. return;
  307. }
  308. new_result_id = GetFuzzerContext()->GetFreshId();
  309. ApplyTransformation(TransformationAddTypeArray(
  310. new_result_id, original_id_to_donated_id->at(component_type_id),
  311. FindOrCreateIntegerConstant(
  312. {GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false)));
  313. } break;
  314. case SpvOpTypeStruct: {
  315. // Similar to SpvOpTypeArray.
  316. std::vector<uint32_t> member_type_ids;
  317. for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
  318. auto component_type_id = type_or_value.GetSingleWordInOperand(i);
  319. if (!original_id_to_donated_id->count(component_type_id)) {
  320. // We did not donate every member type for this struct type, so we
  321. // cannot donate the struct type.
  322. return;
  323. }
  324. member_type_ids.push_back(
  325. original_id_to_donated_id->at(component_type_id));
  326. }
  327. new_result_id = GetFuzzerContext()->GetFreshId();
  328. ApplyTransformation(
  329. TransformationAddTypeStruct(new_result_id, member_type_ids));
  330. } break;
  331. case SpvOpTypePointer: {
  332. // Similar to SpvOpTypeArray.
  333. uint32_t pointee_type_id = type_or_value.GetSingleWordInOperand(1);
  334. if (!original_id_to_donated_id->count(pointee_type_id)) {
  335. // We did not donate the pointee type for this pointer type, so we
  336. // cannot donate the pointer type.
  337. return;
  338. }
  339. new_result_id = GetFuzzerContext()->GetFreshId();
  340. ApplyTransformation(TransformationAddTypePointer(
  341. new_result_id,
  342. AdaptStorageClass(static_cast<SpvStorageClass>(
  343. type_or_value.GetSingleWordInOperand(0))),
  344. original_id_to_donated_id->at(pointee_type_id)));
  345. } break;
  346. case SpvOpTypeFunction: {
  347. // It is not OK to have multiple function types that use identical ids
  348. // for their return and parameter types. We thus go through all
  349. // existing function types to look for a match. We do not use the
  350. // type manager here because we want to regard two function types that
  351. // are structurally identical but that differ with respect to the
  352. // actual ids used for pointer types as different.
  353. //
  354. // Example:
  355. //
  356. // %1 = OpTypeVoid
  357. // %2 = OpTypeInt 32 0
  358. // %3 = OpTypePointer Function %2
  359. // %4 = OpTypePointer Function %2
  360. // %5 = OpTypeFunction %1 %3
  361. // %6 = OpTypeFunction %1 %4
  362. //
  363. // We regard %5 and %6 as distinct function types here, even though
  364. // they both have the form "uint32* -> void"
  365. std::vector<uint32_t> return_and_parameter_types;
  366. for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
  367. uint32_t return_or_parameter_type =
  368. type_or_value.GetSingleWordInOperand(i);
  369. if (!original_id_to_donated_id->count(return_or_parameter_type)) {
  370. // We did not donate every return/parameter type for this function
  371. // type, so we cannot donate the function type.
  372. return;
  373. }
  374. return_and_parameter_types.push_back(
  375. original_id_to_donated_id->at(return_or_parameter_type));
  376. }
  377. uint32_t existing_function_id = fuzzerutil::FindFunctionType(
  378. GetIRContext(), return_and_parameter_types);
  379. if (existing_function_id) {
  380. new_result_id = existing_function_id;
  381. } else {
  382. // No match was found, so add a remapped version of the function type
  383. // to the module, with a fresh id.
  384. new_result_id = GetFuzzerContext()->GetFreshId();
  385. std::vector<uint32_t> argument_type_ids;
  386. for (uint32_t i = 1; i < type_or_value.NumInOperands(); i++) {
  387. argument_type_ids.push_back(original_id_to_donated_id->at(
  388. type_or_value.GetSingleWordInOperand(i)));
  389. }
  390. ApplyTransformation(TransformationAddTypeFunction(
  391. new_result_id,
  392. original_id_to_donated_id->at(
  393. type_or_value.GetSingleWordInOperand(0)),
  394. argument_type_ids));
  395. }
  396. } break;
  397. case SpvOpSpecConstantOp: {
  398. new_result_id = GetFuzzerContext()->GetFreshId();
  399. auto type_id = original_id_to_donated_id->at(type_or_value.type_id());
  400. auto opcode = static_cast<SpvOp>(type_or_value.GetSingleWordInOperand(0));
  401. // Make sure we take into account |original_id_to_donated_id| when
  402. // computing operands for OpSpecConstantOp.
  403. opt::Instruction::OperandList operands;
  404. for (uint32_t i = 1; i < type_or_value.NumInOperands(); ++i) {
  405. const auto& operand = type_or_value.GetInOperand(i);
  406. auto data =
  407. operand.type == SPV_OPERAND_TYPE_ID
  408. ? opt::Operand::OperandData{original_id_to_donated_id->at(
  409. operand.words[0])}
  410. : operand.words;
  411. operands.push_back({operand.type, std::move(data)});
  412. }
  413. ApplyTransformation(TransformationAddSpecConstantOp(
  414. new_result_id, type_id, opcode, std::move(operands)));
  415. } break;
  416. case SpvOpSpecConstantTrue:
  417. case SpvOpSpecConstantFalse:
  418. case SpvOpConstantTrue:
  419. case SpvOpConstantFalse: {
  420. // It is OK to have duplicate definitions of True and False, so add
  421. // these to the module, using a remapped Bool type.
  422. new_result_id = GetFuzzerContext()->GetFreshId();
  423. auto value = type_or_value.opcode() == SpvOpConstantTrue ||
  424. type_or_value.opcode() == SpvOpSpecConstantTrue;
  425. ApplyTransformation(
  426. TransformationAddConstantBoolean(new_result_id, value));
  427. } break;
  428. case SpvOpSpecConstant:
  429. case SpvOpConstant: {
  430. // It is OK to have duplicate constant definitions, so add this to the
  431. // module using a remapped result type.
  432. new_result_id = GetFuzzerContext()->GetFreshId();
  433. std::vector<uint32_t> data_words;
  434. type_or_value.ForEachInOperand([&data_words](const uint32_t* in_operand) {
  435. data_words.push_back(*in_operand);
  436. });
  437. ApplyTransformation(TransformationAddConstantScalar(
  438. new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
  439. data_words));
  440. } break;
  441. case SpvOpSpecConstantComposite:
  442. case SpvOpConstantComposite: {
  443. assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
  444. "Composite types for which it is possible to create a constant "
  445. "should have been donated.");
  446. // It is OK to have duplicate constant composite definitions, so add
  447. // this to the module using remapped versions of all consituent ids and
  448. // the result type.
  449. new_result_id = GetFuzzerContext()->GetFreshId();
  450. std::vector<uint32_t> constituent_ids;
  451. type_or_value.ForEachInId([&constituent_ids, &original_id_to_donated_id](
  452. const uint32_t* constituent_id) {
  453. assert(original_id_to_donated_id->count(*constituent_id) &&
  454. "The constants used to construct this composite should "
  455. "have been donated.");
  456. constituent_ids.push_back(
  457. original_id_to_donated_id->at(*constituent_id));
  458. });
  459. ApplyTransformation(TransformationAddConstantComposite(
  460. new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
  461. constituent_ids));
  462. } break;
  463. case SpvOpConstantNull: {
  464. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  465. // We did not donate the type associated with this null constant, so
  466. // we cannot donate the null constant.
  467. return;
  468. }
  469. // It is fine to have multiple OpConstantNull instructions of the same
  470. // type, so we just add this to the recipient module.
  471. new_result_id = GetFuzzerContext()->GetFreshId();
  472. ApplyTransformation(TransformationAddConstantNull(
  473. new_result_id,
  474. original_id_to_donated_id->at(type_or_value.type_id())));
  475. } break;
  476. case SpvOpVariable: {
  477. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  478. // We did not donate the pointer type associated with this variable,
  479. // so we cannot donate the variable.
  480. return;
  481. }
  482. // This is a global variable that could have one of various storage
  483. // classes. However, we change all global variable pointer storage
  484. // classes (such as Uniform, Input and Output) to private when donating
  485. // pointer types, with the exception of the Workgroup storage class.
  486. //
  487. // Thus this variable's pointer type is guaranteed to have storage class
  488. // Private or Workgroup.
  489. //
  490. // We add a global variable with either Private or Workgroup storage
  491. // class, using remapped versions of the result type and initializer ids
  492. // for the global variable in the donor.
  493. //
  494. // We regard the added variable as having an irrelevant value. This
  495. // means that future passes can add stores to the variable in any
  496. // way they wish, and pass them as pointer parameters to functions
  497. // without worrying about whether their data might get modified.
  498. new_result_id = GetFuzzerContext()->GetFreshId();
  499. uint32_t remapped_pointer_type =
  500. original_id_to_donated_id->at(type_or_value.type_id());
  501. uint32_t initializer_id;
  502. SpvStorageClass storage_class =
  503. static_cast<SpvStorageClass>(type_or_value.GetSingleWordInOperand(
  504. 0)) == SpvStorageClassWorkgroup
  505. ? SpvStorageClassWorkgroup
  506. : SpvStorageClassPrivate;
  507. if (type_or_value.NumInOperands() == 1) {
  508. // The variable did not have an initializer. Initialize it to zero
  509. // if it has Private storage class (to limit problems associated with
  510. // uninitialized data), and leave it uninitialized if it has Workgroup
  511. // storage class (as Workgroup variables cannot have initializers).
  512. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3275): we
  513. // could initialize Workgroup variables at the start of an entry
  514. // point, and should do so if their uninitialized nature proves
  515. // problematic.
  516. initializer_id = storage_class == SpvStorageClassWorkgroup
  517. ? 0
  518. : FindOrCreateZeroConstant(
  519. fuzzerutil::GetPointeeTypeIdFromPointerType(
  520. GetIRContext(), remapped_pointer_type));
  521. } else {
  522. // The variable already had an initializer; use its remapped id.
  523. initializer_id = original_id_to_donated_id->at(
  524. type_or_value.GetSingleWordInOperand(1));
  525. }
  526. ApplyTransformation(
  527. TransformationAddGlobalVariable(new_result_id, remapped_pointer_type,
  528. storage_class, initializer_id, true));
  529. } break;
  530. case SpvOpUndef: {
  531. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  532. // We did not donate the type associated with this undef, so we cannot
  533. // donate the undef.
  534. return;
  535. }
  536. // It is fine to have multiple Undef instructions of the same type, so
  537. // we just add this to the recipient module.
  538. new_result_id = GetFuzzerContext()->GetFreshId();
  539. ApplyTransformation(TransformationAddGlobalUndef(
  540. new_result_id,
  541. original_id_to_donated_id->at(type_or_value.type_id())));
  542. } break;
  543. default: {
  544. assert(0 && "Unknown type/value.");
  545. new_result_id = 0;
  546. } break;
  547. }
  548. // Update the id mapping to associate the instruction's result id with its
  549. // corresponding id in the recipient.
  550. original_id_to_donated_id->insert({type_or_value.result_id(), new_result_id});
  551. }
  552. void FuzzerPassDonateModules::HandleFunctions(
  553. opt::IRContext* donor_ir_context,
  554. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  555. bool make_livesafe) {
  556. // Get the ids of functions in the donor module, topologically sorted
  557. // according to the donor's call graph.
  558. auto topological_order =
  559. GetFunctionsInCallGraphTopologicalOrder(donor_ir_context);
  560. // Donate the functions in reverse topological order. This ensures that a
  561. // function gets donated before any function that depends on it. This allows
  562. // donation of the functions to be separated into a number of transformations,
  563. // each adding one function, such that every prefix of transformations leaves
  564. // the module valid.
  565. for (auto function_id = topological_order.rbegin();
  566. function_id != topological_order.rend(); ++function_id) {
  567. // Find the function to be donated.
  568. opt::Function* function_to_donate = nullptr;
  569. for (auto& function : *donor_ir_context->module()) {
  570. if (function.result_id() == *function_id) {
  571. function_to_donate = &function;
  572. break;
  573. }
  574. }
  575. assert(function_to_donate && "Function to be donated was not found.");
  576. if (!original_id_to_donated_id->count(
  577. function_to_donate->DefInst().GetSingleWordInOperand(1))) {
  578. // We were not able to donate this function's type, so we cannot donate
  579. // the function.
  580. continue;
  581. }
  582. // We will collect up protobuf messages representing the donor function's
  583. // instructions here, and use them to create an AddFunction transformation.
  584. std::vector<protobufs::Instruction> donated_instructions;
  585. // This set tracks the ids of those instructions for which donation was
  586. // completely skipped: neither the instruction nor a substitute for it was
  587. // donated.
  588. std::set<uint32_t> skipped_instructions;
  589. // Consider every instruction of the donor function.
  590. function_to_donate->ForEachInst(
  591. [this, &donated_instructions, donor_ir_context,
  592. &original_id_to_donated_id,
  593. &skipped_instructions](const opt::Instruction* instruction) {
  594. if (instruction->opcode() == SpvOpArrayLength) {
  595. // We treat OpArrayLength specially.
  596. HandleOpArrayLength(*instruction, original_id_to_donated_id,
  597. &donated_instructions);
  598. } else if (!CanDonateInstruction(donor_ir_context, *instruction,
  599. *original_id_to_donated_id,
  600. skipped_instructions)) {
  601. // This is an instruction that we cannot directly donate.
  602. HandleDifficultInstruction(*instruction, original_id_to_donated_id,
  603. &donated_instructions,
  604. &skipped_instructions);
  605. } else {
  606. PrepareInstructionForDonation(*instruction, donor_ir_context,
  607. original_id_to_donated_id,
  608. &donated_instructions);
  609. }
  610. });
  611. if (make_livesafe) {
  612. // Make the function livesafe and then add it.
  613. AddLivesafeFunction(*function_to_donate, donor_ir_context,
  614. *original_id_to_donated_id, donated_instructions);
  615. } else {
  616. // Add the function in a non-livesafe manner.
  617. ApplyTransformation(TransformationAddFunction(donated_instructions));
  618. }
  619. }
  620. }
  621. bool FuzzerPassDonateModules::CanDonateInstruction(
  622. opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
  623. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  624. const std::set<uint32_t>& skipped_instructions) const {
  625. if (instruction.type_id() &&
  626. !original_id_to_donated_id.count(instruction.type_id())) {
  627. // We could not donate the result type of this instruction, so we cannot
  628. // donate the instruction.
  629. return false;
  630. }
  631. // Now consider instructions we specifically want to skip because we do not
  632. // yet support them.
  633. switch (instruction.opcode()) {
  634. case SpvOpAtomicLoad:
  635. case SpvOpAtomicStore:
  636. case SpvOpAtomicExchange:
  637. case SpvOpAtomicCompareExchange:
  638. case SpvOpAtomicCompareExchangeWeak:
  639. case SpvOpAtomicIIncrement:
  640. case SpvOpAtomicIDecrement:
  641. case SpvOpAtomicIAdd:
  642. case SpvOpAtomicISub:
  643. case SpvOpAtomicSMin:
  644. case SpvOpAtomicUMin:
  645. case SpvOpAtomicSMax:
  646. case SpvOpAtomicUMax:
  647. case SpvOpAtomicAnd:
  648. case SpvOpAtomicOr:
  649. case SpvOpAtomicXor:
  650. // We conservatively ignore all atomic instructions at present.
  651. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3276): Consider
  652. // being less conservative here.
  653. case SpvOpImageSampleImplicitLod:
  654. case SpvOpImageSampleExplicitLod:
  655. case SpvOpImageSampleDrefImplicitLod:
  656. case SpvOpImageSampleDrefExplicitLod:
  657. case SpvOpImageSampleProjImplicitLod:
  658. case SpvOpImageSampleProjExplicitLod:
  659. case SpvOpImageSampleProjDrefImplicitLod:
  660. case SpvOpImageSampleProjDrefExplicitLod:
  661. case SpvOpImageFetch:
  662. case SpvOpImageGather:
  663. case SpvOpImageDrefGather:
  664. case SpvOpImageRead:
  665. case SpvOpImageWrite:
  666. case SpvOpImageSparseSampleImplicitLod:
  667. case SpvOpImageSparseSampleExplicitLod:
  668. case SpvOpImageSparseSampleDrefImplicitLod:
  669. case SpvOpImageSparseSampleDrefExplicitLod:
  670. case SpvOpImageSparseSampleProjImplicitLod:
  671. case SpvOpImageSparseSampleProjExplicitLod:
  672. case SpvOpImageSparseSampleProjDrefImplicitLod:
  673. case SpvOpImageSparseSampleProjDrefExplicitLod:
  674. case SpvOpImageSparseFetch:
  675. case SpvOpImageSparseGather:
  676. case SpvOpImageSparseDrefGather:
  677. case SpvOpImageSparseRead:
  678. case SpvOpImageSampleFootprintNV:
  679. case SpvOpImage:
  680. case SpvOpImageQueryFormat:
  681. case SpvOpImageQueryLevels:
  682. case SpvOpImageQueryLod:
  683. case SpvOpImageQueryOrder:
  684. case SpvOpImageQuerySamples:
  685. case SpvOpImageQuerySize:
  686. case SpvOpImageQuerySizeLod:
  687. case SpvOpSampledImage:
  688. // We ignore all instructions related to accessing images, since we do not
  689. // donate images.
  690. return false;
  691. case SpvOpLoad:
  692. switch (donor_ir_context->get_def_use_mgr()
  693. ->GetDef(instruction.type_id())
  694. ->opcode()) {
  695. case SpvOpTypeImage:
  696. case SpvOpTypeSampledImage:
  697. case SpvOpTypeSampler:
  698. // Again, we ignore instructions that relate to accessing images.
  699. return false;
  700. default:
  701. break;
  702. }
  703. default:
  704. break;
  705. }
  706. // Examine each id input operand to the instruction. If it turns out that we
  707. // have skipped any of these operands then we cannot donate the instruction.
  708. bool result = true;
  709. instruction.WhileEachInId(
  710. [donor_ir_context, &original_id_to_donated_id, &result,
  711. &skipped_instructions](const uint32_t* in_id) -> bool {
  712. if (!original_id_to_donated_id.count(*in_id)) {
  713. // We do not have a mapped result id for this id operand. That either
  714. // means that it is a forward reference (which is OK), that we skipped
  715. // the instruction that generated it (which is not OK), or that it is
  716. // the id of a function or global value that we did not donate (which
  717. // is not OK). We check for the latter two cases.
  718. if (skipped_instructions.count(*in_id) ||
  719. // A function or global value does not have an associated basic
  720. // block.
  721. !donor_ir_context->get_instr_block(*in_id)) {
  722. result = false;
  723. return false;
  724. }
  725. }
  726. return true;
  727. });
  728. return result;
  729. }
  730. bool FuzzerPassDonateModules::IsBasicType(
  731. const opt::Instruction& instruction) const {
  732. switch (instruction.opcode()) {
  733. case SpvOpTypeArray:
  734. case SpvOpTypeFloat:
  735. case SpvOpTypeInt:
  736. case SpvOpTypeMatrix:
  737. case SpvOpTypeStruct:
  738. case SpvOpTypeVector:
  739. return true;
  740. default:
  741. return false;
  742. }
  743. }
  744. std::vector<uint32_t>
  745. FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
  746. opt::IRContext* context) {
  747. CallGraph call_graph(context);
  748. // This is an implementation of Kahn’s algorithm for topological sorting.
  749. // This is the sorted order of function ids that we will eventually return.
  750. std::vector<uint32_t> result;
  751. // Get a copy of the initial in-degrees of all functions. The algorithm
  752. // involves decrementing these values, hence why we work on a copy.
  753. std::map<uint32_t, uint32_t> function_in_degree =
  754. call_graph.GetFunctionInDegree();
  755. // Populate a queue with all those function ids with in-degree zero.
  756. std::queue<uint32_t> queue;
  757. for (auto& entry : function_in_degree) {
  758. if (entry.second == 0) {
  759. queue.push(entry.first);
  760. }
  761. }
  762. // Pop ids from the queue, adding them to the sorted order and decreasing the
  763. // in-degrees of their successors. A successor who's in-degree becomes zero
  764. // gets added to the queue.
  765. while (!queue.empty()) {
  766. auto next = queue.front();
  767. queue.pop();
  768. result.push_back(next);
  769. for (auto successor : call_graph.GetDirectCallees(next)) {
  770. assert(function_in_degree.at(successor) > 0 &&
  771. "The in-degree cannot be zero if the function is a successor.");
  772. function_in_degree[successor] = function_in_degree.at(successor) - 1;
  773. if (function_in_degree.at(successor) == 0) {
  774. queue.push(successor);
  775. }
  776. }
  777. }
  778. assert(result.size() == function_in_degree.size() &&
  779. "Every function should appear in the sort.");
  780. return result;
  781. }
  782. void FuzzerPassDonateModules::HandleOpArrayLength(
  783. const opt::Instruction& instruction,
  784. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  785. std::vector<protobufs::Instruction>* donated_instructions) const {
  786. assert(instruction.opcode() == SpvOpArrayLength &&
  787. "Precondition: instruction must be OpArrayLength.");
  788. uint32_t donated_variable_id =
  789. original_id_to_donated_id->at(instruction.GetSingleWordInOperand(0));
  790. auto donated_variable_instruction =
  791. GetIRContext()->get_def_use_mgr()->GetDef(donated_variable_id);
  792. auto pointer_to_struct_instruction =
  793. GetIRContext()->get_def_use_mgr()->GetDef(
  794. donated_variable_instruction->type_id());
  795. assert(pointer_to_struct_instruction->opcode() == SpvOpTypePointer &&
  796. "Type of variable must be pointer.");
  797. auto donated_struct_type_instruction =
  798. GetIRContext()->get_def_use_mgr()->GetDef(
  799. pointer_to_struct_instruction->GetSingleWordInOperand(1));
  800. assert(donated_struct_type_instruction->opcode() == SpvOpTypeStruct &&
  801. "Pointee type of pointer used by OpArrayLength must be struct.");
  802. assert(donated_struct_type_instruction->NumInOperands() ==
  803. instruction.GetSingleWordInOperand(1) + 1 &&
  804. "OpArrayLength must refer to the final member of the given "
  805. "struct.");
  806. uint32_t fixed_size_array_type_id =
  807. donated_struct_type_instruction->GetSingleWordInOperand(
  808. donated_struct_type_instruction->NumInOperands() - 1);
  809. auto fixed_size_array_type_instruction =
  810. GetIRContext()->get_def_use_mgr()->GetDef(fixed_size_array_type_id);
  811. assert(fixed_size_array_type_instruction->opcode() == SpvOpTypeArray &&
  812. "The donated array type must be fixed-size.");
  813. auto array_size_id =
  814. fixed_size_array_type_instruction->GetSingleWordInOperand(1);
  815. if (instruction.result_id() &&
  816. !original_id_to_donated_id->count(instruction.result_id())) {
  817. original_id_to_donated_id->insert(
  818. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  819. }
  820. donated_instructions->push_back(MakeInstructionMessage(
  821. SpvOpCopyObject, original_id_to_donated_id->at(instruction.type_id()),
  822. original_id_to_donated_id->at(instruction.result_id()),
  823. opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {array_size_id}}})));
  824. }
  825. void FuzzerPassDonateModules::HandleDifficultInstruction(
  826. const opt::Instruction& instruction,
  827. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  828. std::vector<protobufs::Instruction>* donated_instructions,
  829. std::set<uint32_t>* skipped_instructions) {
  830. if (!instruction.result_id()) {
  831. // It does not generate a result id, so it can be ignored.
  832. return;
  833. }
  834. if (!original_id_to_donated_id->count(instruction.type_id())) {
  835. // We cannot handle this instruction's result type, so we need to skip it
  836. // all together.
  837. skipped_instructions->insert(instruction.result_id());
  838. return;
  839. }
  840. // We now attempt to replace the instruction with an OpCopyObject.
  841. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3278): We could do
  842. // something more refined here - we could check which operands to the
  843. // instruction could not be donated and replace those operands with
  844. // references to other ids (such as constants), so that we still get an
  845. // instruction with the opcode and easy-to-handle operands of the donor
  846. // instruction.
  847. auto remapped_type_id = original_id_to_donated_id->at(instruction.type_id());
  848. if (!IsBasicType(
  849. *GetIRContext()->get_def_use_mgr()->GetDef(remapped_type_id))) {
  850. // The instruction has a non-basic result type, so we cannot replace it with
  851. // an object copy of a constant. We thus skip it completely.
  852. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3279): We could
  853. // instead look for an available id of the right type and generate an
  854. // OpCopyObject of that id.
  855. skipped_instructions->insert(instruction.result_id());
  856. return;
  857. }
  858. // We are going to add an OpCopyObject instruction. Add a mapping for the
  859. // result id of the original instruction if does not already exist (it may
  860. // exist in the case that it has been forward-referenced).
  861. if (!original_id_to_donated_id->count(instruction.result_id())) {
  862. original_id_to_donated_id->insert(
  863. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  864. }
  865. // We find or add a zero constant to the receiving module for the type in
  866. // question, and add an OpCopyObject instruction that copies this zero.
  867. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
  868. // Using this particular constant is arbitrary, so if we have a
  869. // mechanism for noting that an id use is arbitrary and could be
  870. // fuzzed we should use it here.
  871. auto zero_constant = FindOrCreateZeroConstant(remapped_type_id);
  872. donated_instructions->push_back(MakeInstructionMessage(
  873. SpvOpCopyObject, remapped_type_id,
  874. original_id_to_donated_id->at(instruction.result_id()),
  875. opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {zero_constant}}})));
  876. }
  877. void FuzzerPassDonateModules::PrepareInstructionForDonation(
  878. const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
  879. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  880. std::vector<protobufs::Instruction>* donated_instructions) {
  881. // Get the instruction's input operands into donation-ready form,
  882. // remapping any id uses in the process.
  883. opt::Instruction::OperandList input_operands;
  884. // Consider each input operand in turn.
  885. for (uint32_t in_operand_index = 0;
  886. in_operand_index < instruction.NumInOperands(); in_operand_index++) {
  887. std::vector<uint32_t> operand_data;
  888. const opt::Operand& in_operand = instruction.GetInOperand(in_operand_index);
  889. // Check whether this operand is an id.
  890. if (spvIsIdType(in_operand.type)) {
  891. // This is an id operand - it consists of a single word of data,
  892. // which needs to be remapped so that it is replaced with the
  893. // donated form of the id.
  894. auto operand_id = in_operand.words[0];
  895. if (!original_id_to_donated_id->count(operand_id)) {
  896. // This is a forward reference. We will choose a corresponding
  897. // donor id for the referenced id and update the mapping to
  898. // reflect it.
  899. // Keep release compilers happy because |donor_ir_context| is only used
  900. // in this assertion.
  901. (void)(donor_ir_context);
  902. assert((donor_ir_context->get_def_use_mgr()
  903. ->GetDef(operand_id)
  904. ->opcode() == SpvOpLabel ||
  905. instruction.opcode() == SpvOpPhi) &&
  906. "Unsupported forward reference.");
  907. original_id_to_donated_id->insert(
  908. {operand_id, GetFuzzerContext()->GetFreshId()});
  909. }
  910. operand_data.push_back(original_id_to_donated_id->at(operand_id));
  911. } else {
  912. // For non-id operands, we just add each of the data words.
  913. for (auto word : in_operand.words) {
  914. operand_data.push_back(word);
  915. }
  916. }
  917. input_operands.push_back({in_operand.type, operand_data});
  918. }
  919. if (instruction.opcode() == SpvOpVariable &&
  920. instruction.NumInOperands() == 1) {
  921. // This is an uninitialized local variable. Initialize it to zero.
  922. input_operands.push_back(
  923. {SPV_OPERAND_TYPE_ID,
  924. {FindOrCreateZeroConstant(fuzzerutil::GetPointeeTypeIdFromPointerType(
  925. GetIRContext(),
  926. original_id_to_donated_id->at(instruction.type_id())))}});
  927. }
  928. if (instruction.result_id() &&
  929. !original_id_to_donated_id->count(instruction.result_id())) {
  930. original_id_to_donated_id->insert(
  931. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  932. }
  933. // Remap the result type and result id (if present) of the
  934. // instruction, and turn it into a protobuf message.
  935. donated_instructions->push_back(MakeInstructionMessage(
  936. instruction.opcode(),
  937. instruction.type_id()
  938. ? original_id_to_donated_id->at(instruction.type_id())
  939. : 0,
  940. instruction.result_id()
  941. ? original_id_to_donated_id->at(instruction.result_id())
  942. : 0,
  943. input_operands));
  944. }
  945. void FuzzerPassDonateModules::AddLivesafeFunction(
  946. const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
  947. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  948. const std::vector<protobufs::Instruction>& donated_instructions) {
  949. // Various types and constants must be in place for a function to be made
  950. // live-safe. Add them if not already present.
  951. FindOrCreateBoolType(); // Needed for comparisons
  952. FindOrCreatePointerToIntegerType(
  953. 32, false, SpvStorageClassFunction); // Needed for adding loop limiters
  954. FindOrCreateIntegerConstant({0}, 32,
  955. false); // Needed for initializing loop limiters
  956. FindOrCreateIntegerConstant({1}, 32,
  957. false); // Needed for incrementing loop limiters
  958. // Get a fresh id for the variable that will be used as a loop limiter.
  959. const uint32_t loop_limiter_variable_id = GetFuzzerContext()->GetFreshId();
  960. // Choose a random loop limit, and add the required constant to the
  961. // module if not already there.
  962. const uint32_t loop_limit = FindOrCreateIntegerConstant(
  963. {GetFuzzerContext()->GetRandomLoopLimit()}, 32, false);
  964. // Consider every loop header in the function to donate, and create a
  965. // structure capturing the ids to be used for manipulating the loop
  966. // limiter each time the loop is iterated.
  967. std::vector<protobufs::LoopLimiterInfo> loop_limiters;
  968. for (auto& block : function_to_donate) {
  969. if (block.IsLoopHeader()) {
  970. protobufs::LoopLimiterInfo loop_limiter;
  971. // Grab the loop header's id, mapped to its donated value.
  972. loop_limiter.set_loop_header_id(original_id_to_donated_id.at(block.id()));
  973. // Get fresh ids that will be used to load the loop limiter, increment
  974. // it, compare it with the loop limit, and an id for a new block that
  975. // will contain the loop's original terminator.
  976. loop_limiter.set_load_id(GetFuzzerContext()->GetFreshId());
  977. loop_limiter.set_increment_id(GetFuzzerContext()->GetFreshId());
  978. loop_limiter.set_compare_id(GetFuzzerContext()->GetFreshId());
  979. loop_limiter.set_logical_op_id(GetFuzzerContext()->GetFreshId());
  980. loop_limiters.emplace_back(loop_limiter);
  981. }
  982. }
  983. // Consider every access chain in the function to donate, and create a
  984. // structure containing the ids necessary to clamp the access chain
  985. // indices to be in-bounds.
  986. std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
  987. for (auto& block : function_to_donate) {
  988. for (auto& inst : block) {
  989. switch (inst.opcode()) {
  990. case SpvOpAccessChain:
  991. case SpvOpInBoundsAccessChain: {
  992. protobufs::AccessChainClampingInfo clamping_info;
  993. clamping_info.set_access_chain_id(
  994. original_id_to_donated_id.at(inst.result_id()));
  995. auto base_object = donor_ir_context->get_def_use_mgr()->GetDef(
  996. inst.GetSingleWordInOperand(0));
  997. assert(base_object && "The base object must exist.");
  998. auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef(
  999. base_object->type_id());
  1000. assert(pointer_type && pointer_type->opcode() == SpvOpTypePointer &&
  1001. "The base object must have pointer type.");
  1002. auto should_be_composite_type =
  1003. donor_ir_context->get_def_use_mgr()->GetDef(
  1004. pointer_type->GetSingleWordInOperand(1));
  1005. // Walk the access chain, creating fresh ids to facilitate
  1006. // clamping each index. For simplicity we do this for every
  1007. // index, even though constant indices will not end up being
  1008. // clamped.
  1009. for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
  1010. auto compare_and_select_ids =
  1011. clamping_info.add_compare_and_select_ids();
  1012. compare_and_select_ids->set_first(GetFuzzerContext()->GetFreshId());
  1013. compare_and_select_ids->set_second(
  1014. GetFuzzerContext()->GetFreshId());
  1015. // Get the bound for the component being indexed into.
  1016. uint32_t bound;
  1017. if (should_be_composite_type->opcode() == SpvOpTypeRuntimeArray) {
  1018. // The donor is indexing into a runtime array. We do not
  1019. // donate runtime arrays. Instead, we donate a corresponding
  1020. // fixed-size array for every runtime array. We should thus
  1021. // find that donor composite type's result id maps to a fixed-
  1022. // size array.
  1023. auto fixed_size_array_type =
  1024. GetIRContext()->get_def_use_mgr()->GetDef(
  1025. original_id_to_donated_id.at(
  1026. should_be_composite_type->result_id()));
  1027. assert(fixed_size_array_type->opcode() == SpvOpTypeArray &&
  1028. "A runtime array type in the donor should have been "
  1029. "replaced by a fixed-sized array in the recipient.");
  1030. // The size of this fixed-size array is a suitable bound.
  1031. bound = TransformationAddFunction::GetBoundForCompositeIndex(
  1032. GetIRContext(), *fixed_size_array_type);
  1033. } else {
  1034. bound = TransformationAddFunction::GetBoundForCompositeIndex(
  1035. donor_ir_context, *should_be_composite_type);
  1036. }
  1037. const uint32_t index_id = inst.GetSingleWordInOperand(index);
  1038. auto index_inst =
  1039. donor_ir_context->get_def_use_mgr()->GetDef(index_id);
  1040. auto index_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(
  1041. index_inst->type_id());
  1042. assert(index_type_inst->opcode() == SpvOpTypeInt);
  1043. opt::analysis::Integer* index_int_type =
  1044. donor_ir_context->get_type_mgr()
  1045. ->GetType(index_type_inst->result_id())
  1046. ->AsInteger();
  1047. if (index_inst->opcode() != SpvOpConstant) {
  1048. // We will have to clamp this index, so we need a constant
  1049. // whose value is one less than the bound, to compare
  1050. // against and to use as the clamped value.
  1051. FindOrCreateIntegerConstant({bound - 1}, 32,
  1052. index_int_type->IsSigned());
  1053. }
  1054. should_be_composite_type =
  1055. TransformationAddFunction::FollowCompositeIndex(
  1056. donor_ir_context, *should_be_composite_type, index_id);
  1057. }
  1058. access_chain_clamping_info.push_back(clamping_info);
  1059. break;
  1060. }
  1061. default:
  1062. break;
  1063. }
  1064. }
  1065. }
  1066. // If the function contains OpKill or OpUnreachable instructions, and has
  1067. // non-void return type, then we need a value %v to use in order to turn
  1068. // these into instructions of the form OpReturn %v.
  1069. uint32_t kill_unreachable_return_value_id;
  1070. auto function_return_type_inst =
  1071. donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
  1072. if (function_return_type_inst->opcode() == SpvOpTypeVoid) {
  1073. // The return type is void, so we don't need a return value.
  1074. kill_unreachable_return_value_id = 0;
  1075. } else {
  1076. // We do need a return value; we use zero.
  1077. assert(function_return_type_inst->opcode() != SpvOpTypePointer &&
  1078. "Function return type must not be a pointer.");
  1079. kill_unreachable_return_value_id = FindOrCreateZeroConstant(
  1080. original_id_to_donated_id.at(function_return_type_inst->result_id()));
  1081. }
  1082. // Add the function in a livesafe manner.
  1083. ApplyTransformation(TransformationAddFunction(
  1084. donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
  1085. kill_unreachable_return_value_id, access_chain_clamping_info));
  1086. }
  1087. } // namespace fuzz
  1088. } // namespace spvtools