fuzzer_pass_donate_modules.cpp 53 KB

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