fuzzer_pass_donate_modules.cpp 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  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. false)));
  314. } break;
  315. case SpvOpTypeStruct: {
  316. // Similar to SpvOpTypeArray.
  317. std::vector<uint32_t> member_type_ids;
  318. for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
  319. auto component_type_id = type_or_value.GetSingleWordInOperand(i);
  320. if (!original_id_to_donated_id->count(component_type_id)) {
  321. // We did not donate every member type for this struct type, so we
  322. // cannot donate the struct type.
  323. return;
  324. }
  325. member_type_ids.push_back(
  326. original_id_to_donated_id->at(component_type_id));
  327. }
  328. new_result_id = GetFuzzerContext()->GetFreshId();
  329. ApplyTransformation(
  330. TransformationAddTypeStruct(new_result_id, member_type_ids));
  331. } break;
  332. case SpvOpTypePointer: {
  333. // Similar to SpvOpTypeArray.
  334. uint32_t pointee_type_id = type_or_value.GetSingleWordInOperand(1);
  335. if (!original_id_to_donated_id->count(pointee_type_id)) {
  336. // We did not donate the pointee type for this pointer type, so we
  337. // cannot donate the pointer type.
  338. return;
  339. }
  340. new_result_id = GetFuzzerContext()->GetFreshId();
  341. ApplyTransformation(TransformationAddTypePointer(
  342. new_result_id,
  343. AdaptStorageClass(static_cast<SpvStorageClass>(
  344. type_or_value.GetSingleWordInOperand(0))),
  345. original_id_to_donated_id->at(pointee_type_id)));
  346. } break;
  347. case SpvOpTypeFunction: {
  348. // It is not OK to have multiple function types that use identical ids
  349. // for their return and parameter types. We thus go through all
  350. // existing function types to look for a match. We do not use the
  351. // type manager here because we want to regard two function types that
  352. // are structurally identical but that differ with respect to the
  353. // actual ids used for pointer types as different.
  354. //
  355. // Example:
  356. //
  357. // %1 = OpTypeVoid
  358. // %2 = OpTypeInt 32 0
  359. // %3 = OpTypePointer Function %2
  360. // %4 = OpTypePointer Function %2
  361. // %5 = OpTypeFunction %1 %3
  362. // %6 = OpTypeFunction %1 %4
  363. //
  364. // We regard %5 and %6 as distinct function types here, even though
  365. // they both have the form "uint32* -> void"
  366. std::vector<uint32_t> return_and_parameter_types;
  367. for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
  368. uint32_t return_or_parameter_type =
  369. type_or_value.GetSingleWordInOperand(i);
  370. if (!original_id_to_donated_id->count(return_or_parameter_type)) {
  371. // We did not donate every return/parameter type for this function
  372. // type, so we cannot donate the function type.
  373. return;
  374. }
  375. return_and_parameter_types.push_back(
  376. original_id_to_donated_id->at(return_or_parameter_type));
  377. }
  378. uint32_t existing_function_id = fuzzerutil::FindFunctionType(
  379. GetIRContext(), return_and_parameter_types);
  380. if (existing_function_id) {
  381. new_result_id = existing_function_id;
  382. } else {
  383. // No match was found, so add a remapped version of the function type
  384. // to the module, with a fresh id.
  385. new_result_id = GetFuzzerContext()->GetFreshId();
  386. std::vector<uint32_t> argument_type_ids;
  387. for (uint32_t i = 1; i < type_or_value.NumInOperands(); i++) {
  388. argument_type_ids.push_back(original_id_to_donated_id->at(
  389. type_or_value.GetSingleWordInOperand(i)));
  390. }
  391. ApplyTransformation(TransformationAddTypeFunction(
  392. new_result_id,
  393. original_id_to_donated_id->at(
  394. type_or_value.GetSingleWordInOperand(0)),
  395. argument_type_ids));
  396. }
  397. } break;
  398. case SpvOpSpecConstantOp: {
  399. new_result_id = GetFuzzerContext()->GetFreshId();
  400. auto type_id = original_id_to_donated_id->at(type_or_value.type_id());
  401. auto opcode = static_cast<SpvOp>(type_or_value.GetSingleWordInOperand(0));
  402. // Make sure we take into account |original_id_to_donated_id| when
  403. // computing operands for OpSpecConstantOp.
  404. opt::Instruction::OperandList operands;
  405. for (uint32_t i = 1; i < type_or_value.NumInOperands(); ++i) {
  406. const auto& operand = type_or_value.GetInOperand(i);
  407. auto data =
  408. operand.type == SPV_OPERAND_TYPE_ID
  409. ? opt::Operand::OperandData{original_id_to_donated_id->at(
  410. operand.words[0])}
  411. : operand.words;
  412. operands.push_back({operand.type, std::move(data)});
  413. }
  414. ApplyTransformation(TransformationAddSpecConstantOp(
  415. new_result_id, type_id, opcode, std::move(operands)));
  416. } break;
  417. case SpvOpSpecConstantTrue:
  418. case SpvOpSpecConstantFalse:
  419. case SpvOpConstantTrue:
  420. case SpvOpConstantFalse: {
  421. // It is OK to have duplicate definitions of True and False, so add
  422. // these to the module, using a remapped Bool type.
  423. new_result_id = GetFuzzerContext()->GetFreshId();
  424. auto value = type_or_value.opcode() == SpvOpConstantTrue ||
  425. type_or_value.opcode() == SpvOpSpecConstantTrue;
  426. ApplyTransformation(
  427. TransformationAddConstantBoolean(new_result_id, value, false));
  428. } break;
  429. case SpvOpSpecConstant:
  430. case SpvOpConstant: {
  431. // It is OK to have duplicate constant definitions, so add this to the
  432. // module using a remapped result type.
  433. new_result_id = GetFuzzerContext()->GetFreshId();
  434. std::vector<uint32_t> data_words;
  435. type_or_value.ForEachInOperand([&data_words](const uint32_t* in_operand) {
  436. data_words.push_back(*in_operand);
  437. });
  438. ApplyTransformation(TransformationAddConstantScalar(
  439. new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
  440. data_words, false));
  441. } break;
  442. case SpvOpSpecConstantComposite:
  443. case SpvOpConstantComposite: {
  444. assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
  445. "Composite types for which it is possible to create a constant "
  446. "should have been donated.");
  447. // It is OK to have duplicate constant composite definitions, so add
  448. // this to the module using remapped versions of all consituent ids and
  449. // the result type.
  450. new_result_id = GetFuzzerContext()->GetFreshId();
  451. std::vector<uint32_t> constituent_ids;
  452. type_or_value.ForEachInId([&constituent_ids, &original_id_to_donated_id](
  453. const uint32_t* constituent_id) {
  454. assert(original_id_to_donated_id->count(*constituent_id) &&
  455. "The constants used to construct this composite should "
  456. "have been donated.");
  457. constituent_ids.push_back(
  458. original_id_to_donated_id->at(*constituent_id));
  459. });
  460. ApplyTransformation(TransformationAddConstantComposite(
  461. new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
  462. constituent_ids, false));
  463. } break;
  464. case SpvOpConstantNull: {
  465. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  466. // We did not donate the type associated with this null constant, so
  467. // we cannot donate the null constant.
  468. return;
  469. }
  470. // It is fine to have multiple OpConstantNull instructions of the same
  471. // type, so we just add this to the recipient module.
  472. new_result_id = GetFuzzerContext()->GetFreshId();
  473. ApplyTransformation(TransformationAddConstantNull(
  474. new_result_id,
  475. original_id_to_donated_id->at(type_or_value.type_id())));
  476. } break;
  477. case SpvOpVariable: {
  478. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  479. // We did not donate the pointer type associated with this variable,
  480. // so we cannot donate the variable.
  481. return;
  482. }
  483. // This is a global variable that could have one of various storage
  484. // classes. However, we change all global variable pointer storage
  485. // classes (such as Uniform, Input and Output) to private when donating
  486. // pointer types, with the exception of the Workgroup storage class.
  487. //
  488. // Thus this variable's pointer type is guaranteed to have storage class
  489. // Private or Workgroup.
  490. //
  491. // We add a global variable with either Private or Workgroup storage
  492. // class, using remapped versions of the result type and initializer ids
  493. // for the global variable in the donor.
  494. //
  495. // We regard the added variable as having an irrelevant value. This
  496. // means that future passes can add stores to the variable in any
  497. // way they wish, and pass them as pointer parameters to functions
  498. // without worrying about whether their data might get modified.
  499. new_result_id = GetFuzzerContext()->GetFreshId();
  500. uint32_t remapped_pointer_type =
  501. original_id_to_donated_id->at(type_or_value.type_id());
  502. uint32_t initializer_id;
  503. SpvStorageClass storage_class =
  504. static_cast<SpvStorageClass>(type_or_value.GetSingleWordInOperand(
  505. 0)) == SpvStorageClassWorkgroup
  506. ? SpvStorageClassWorkgroup
  507. : SpvStorageClassPrivate;
  508. if (type_or_value.NumInOperands() == 1) {
  509. // The variable did not have an initializer. Initialize it to zero
  510. // if it has Private storage class (to limit problems associated with
  511. // uninitialized data), and leave it uninitialized if it has Workgroup
  512. // storage class (as Workgroup variables cannot have initializers).
  513. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3275): we
  514. // could initialize Workgroup variables at the start of an entry
  515. // point, and should do so if their uninitialized nature proves
  516. // problematic.
  517. initializer_id = storage_class == SpvStorageClassWorkgroup
  518. ? 0
  519. : FindOrCreateZeroConstant(
  520. fuzzerutil::GetPointeeTypeIdFromPointerType(
  521. GetIRContext(), remapped_pointer_type),
  522. false);
  523. } else {
  524. // The variable already had an initializer; use its remapped id.
  525. initializer_id = original_id_to_donated_id->at(
  526. type_or_value.GetSingleWordInOperand(1));
  527. }
  528. ApplyTransformation(
  529. TransformationAddGlobalVariable(new_result_id, remapped_pointer_type,
  530. storage_class, initializer_id, true));
  531. } break;
  532. case SpvOpUndef: {
  533. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  534. // We did not donate the type associated with this undef, so we cannot
  535. // donate the undef.
  536. return;
  537. }
  538. // It is fine to have multiple Undef instructions of the same type, so
  539. // we just add this to the recipient module.
  540. new_result_id = GetFuzzerContext()->GetFreshId();
  541. ApplyTransformation(TransformationAddGlobalUndef(
  542. new_result_id,
  543. original_id_to_donated_id->at(type_or_value.type_id())));
  544. } break;
  545. default: {
  546. assert(0 && "Unknown type/value.");
  547. new_result_id = 0;
  548. } break;
  549. }
  550. // Update the id mapping to associate the instruction's result id with its
  551. // corresponding id in the recipient.
  552. original_id_to_donated_id->insert({type_or_value.result_id(), new_result_id});
  553. }
  554. void FuzzerPassDonateModules::HandleFunctions(
  555. opt::IRContext* donor_ir_context,
  556. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  557. bool make_livesafe) {
  558. // Get the ids of functions in the donor module, topologically sorted
  559. // according to the donor's call graph.
  560. auto topological_order =
  561. GetFunctionsInCallGraphTopologicalOrder(donor_ir_context);
  562. // Donate the functions in reverse topological order. This ensures that a
  563. // function gets donated before any function that depends on it. This allows
  564. // donation of the functions to be separated into a number of transformations,
  565. // each adding one function, such that every prefix of transformations leaves
  566. // the module valid.
  567. for (auto function_id = topological_order.rbegin();
  568. function_id != topological_order.rend(); ++function_id) {
  569. // Find the function to be donated.
  570. opt::Function* function_to_donate = nullptr;
  571. for (auto& function : *donor_ir_context->module()) {
  572. if (function.result_id() == *function_id) {
  573. function_to_donate = &function;
  574. break;
  575. }
  576. }
  577. assert(function_to_donate && "Function to be donated was not found.");
  578. if (!original_id_to_donated_id->count(
  579. function_to_donate->DefInst().GetSingleWordInOperand(1))) {
  580. // We were not able to donate this function's type, so we cannot donate
  581. // the function.
  582. continue;
  583. }
  584. // We will collect up protobuf messages representing the donor function's
  585. // instructions here, and use them to create an AddFunction transformation.
  586. std::vector<protobufs::Instruction> donated_instructions;
  587. // This set tracks the ids of those instructions for which donation was
  588. // completely skipped: neither the instruction nor a substitute for it was
  589. // donated.
  590. std::set<uint32_t> skipped_instructions;
  591. // Consider every instruction of the donor function.
  592. function_to_donate->ForEachInst(
  593. [this, &donated_instructions, donor_ir_context,
  594. &original_id_to_donated_id,
  595. &skipped_instructions](const opt::Instruction* instruction) {
  596. if (instruction->opcode() == SpvOpArrayLength) {
  597. // We treat OpArrayLength specially.
  598. HandleOpArrayLength(*instruction, original_id_to_donated_id,
  599. &donated_instructions);
  600. } else if (!CanDonateInstruction(donor_ir_context, *instruction,
  601. *original_id_to_donated_id,
  602. skipped_instructions)) {
  603. // This is an instruction that we cannot directly donate.
  604. HandleDifficultInstruction(*instruction, original_id_to_donated_id,
  605. &donated_instructions,
  606. &skipped_instructions);
  607. } else {
  608. PrepareInstructionForDonation(*instruction, donor_ir_context,
  609. original_id_to_donated_id,
  610. &donated_instructions);
  611. }
  612. });
  613. if (make_livesafe) {
  614. // Make the function livesafe and then add it.
  615. AddLivesafeFunction(*function_to_donate, donor_ir_context,
  616. *original_id_to_donated_id, donated_instructions);
  617. } else {
  618. // Add the function in a non-livesafe manner.
  619. ApplyTransformation(TransformationAddFunction(donated_instructions));
  620. }
  621. }
  622. }
  623. bool FuzzerPassDonateModules::CanDonateInstruction(
  624. opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
  625. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  626. const std::set<uint32_t>& skipped_instructions) const {
  627. if (instruction.type_id() &&
  628. !original_id_to_donated_id.count(instruction.type_id())) {
  629. // We could not donate the result type of this instruction, so we cannot
  630. // donate the instruction.
  631. return false;
  632. }
  633. // Now consider instructions we specifically want to skip because we do not
  634. // yet support them.
  635. switch (instruction.opcode()) {
  636. case SpvOpAtomicLoad:
  637. case SpvOpAtomicStore:
  638. case SpvOpAtomicExchange:
  639. case SpvOpAtomicCompareExchange:
  640. case SpvOpAtomicCompareExchangeWeak:
  641. case SpvOpAtomicIIncrement:
  642. case SpvOpAtomicIDecrement:
  643. case SpvOpAtomicIAdd:
  644. case SpvOpAtomicISub:
  645. case SpvOpAtomicSMin:
  646. case SpvOpAtomicUMin:
  647. case SpvOpAtomicSMax:
  648. case SpvOpAtomicUMax:
  649. case SpvOpAtomicAnd:
  650. case SpvOpAtomicOr:
  651. case SpvOpAtomicXor:
  652. // We conservatively ignore all atomic instructions at present.
  653. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3276): Consider
  654. // being less conservative here.
  655. case SpvOpImageSampleImplicitLod:
  656. case SpvOpImageSampleExplicitLod:
  657. case SpvOpImageSampleDrefImplicitLod:
  658. case SpvOpImageSampleDrefExplicitLod:
  659. case SpvOpImageSampleProjImplicitLod:
  660. case SpvOpImageSampleProjExplicitLod:
  661. case SpvOpImageSampleProjDrefImplicitLod:
  662. case SpvOpImageSampleProjDrefExplicitLod:
  663. case SpvOpImageFetch:
  664. case SpvOpImageGather:
  665. case SpvOpImageDrefGather:
  666. case SpvOpImageRead:
  667. case SpvOpImageWrite:
  668. case SpvOpImageSparseSampleImplicitLod:
  669. case SpvOpImageSparseSampleExplicitLod:
  670. case SpvOpImageSparseSampleDrefImplicitLod:
  671. case SpvOpImageSparseSampleDrefExplicitLod:
  672. case SpvOpImageSparseSampleProjImplicitLod:
  673. case SpvOpImageSparseSampleProjExplicitLod:
  674. case SpvOpImageSparseSampleProjDrefImplicitLod:
  675. case SpvOpImageSparseSampleProjDrefExplicitLod:
  676. case SpvOpImageSparseFetch:
  677. case SpvOpImageSparseGather:
  678. case SpvOpImageSparseDrefGather:
  679. case SpvOpImageSparseRead:
  680. case SpvOpImageSampleFootprintNV:
  681. case SpvOpImage:
  682. case SpvOpImageQueryFormat:
  683. case SpvOpImageQueryLevels:
  684. case SpvOpImageQueryLod:
  685. case SpvOpImageQueryOrder:
  686. case SpvOpImageQuerySamples:
  687. case SpvOpImageQuerySize:
  688. case SpvOpImageQuerySizeLod:
  689. case SpvOpSampledImage:
  690. // We ignore all instructions related to accessing images, since we do not
  691. // donate images.
  692. return false;
  693. case SpvOpLoad:
  694. switch (donor_ir_context->get_def_use_mgr()
  695. ->GetDef(instruction.type_id())
  696. ->opcode()) {
  697. case SpvOpTypeImage:
  698. case SpvOpTypeSampledImage:
  699. case SpvOpTypeSampler:
  700. // Again, we ignore instructions that relate to accessing images.
  701. return false;
  702. default:
  703. break;
  704. }
  705. default:
  706. break;
  707. }
  708. // Examine each id input operand to the instruction. If it turns out that we
  709. // have skipped any of these operands then we cannot donate the instruction.
  710. bool result = true;
  711. instruction.WhileEachInId(
  712. [donor_ir_context, &original_id_to_donated_id, &result,
  713. &skipped_instructions](const uint32_t* in_id) -> bool {
  714. if (!original_id_to_donated_id.count(*in_id)) {
  715. // We do not have a mapped result id for this id operand. That either
  716. // means that it is a forward reference (which is OK), that we skipped
  717. // the instruction that generated it (which is not OK), or that it is
  718. // the id of a function or global value that we did not donate (which
  719. // is not OK). We check for the latter two cases.
  720. if (skipped_instructions.count(*in_id) ||
  721. // A function or global value does not have an associated basic
  722. // block.
  723. !donor_ir_context->get_instr_block(*in_id)) {
  724. result = false;
  725. return false;
  726. }
  727. }
  728. return true;
  729. });
  730. return result;
  731. }
  732. bool FuzzerPassDonateModules::IsBasicType(
  733. const opt::Instruction& instruction) const {
  734. switch (instruction.opcode()) {
  735. case SpvOpTypeArray:
  736. case SpvOpTypeFloat:
  737. case SpvOpTypeInt:
  738. case SpvOpTypeMatrix:
  739. case SpvOpTypeStruct:
  740. case SpvOpTypeVector:
  741. return true;
  742. default:
  743. return false;
  744. }
  745. }
  746. std::vector<uint32_t>
  747. FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
  748. opt::IRContext* context) {
  749. CallGraph call_graph(context);
  750. // This is an implementation of Kahn’s algorithm for topological sorting.
  751. // This is the sorted order of function ids that we will eventually return.
  752. std::vector<uint32_t> result;
  753. // Get a copy of the initial in-degrees of all functions. The algorithm
  754. // involves decrementing these values, hence why we work on a copy.
  755. std::map<uint32_t, uint32_t> function_in_degree =
  756. call_graph.GetFunctionInDegree();
  757. // Populate a queue with all those function ids with in-degree zero.
  758. std::queue<uint32_t> queue;
  759. for (auto& entry : function_in_degree) {
  760. if (entry.second == 0) {
  761. queue.push(entry.first);
  762. }
  763. }
  764. // Pop ids from the queue, adding them to the sorted order and decreasing the
  765. // in-degrees of their successors. A successor who's in-degree becomes zero
  766. // gets added to the queue.
  767. while (!queue.empty()) {
  768. auto next = queue.front();
  769. queue.pop();
  770. result.push_back(next);
  771. for (auto successor : call_graph.GetDirectCallees(next)) {
  772. assert(function_in_degree.at(successor) > 0 &&
  773. "The in-degree cannot be zero if the function is a successor.");
  774. function_in_degree[successor] = function_in_degree.at(successor) - 1;
  775. if (function_in_degree.at(successor) == 0) {
  776. queue.push(successor);
  777. }
  778. }
  779. }
  780. assert(result.size() == function_in_degree.size() &&
  781. "Every function should appear in the sort.");
  782. return result;
  783. }
  784. void FuzzerPassDonateModules::HandleOpArrayLength(
  785. const opt::Instruction& instruction,
  786. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  787. std::vector<protobufs::Instruction>* donated_instructions) const {
  788. assert(instruction.opcode() == SpvOpArrayLength &&
  789. "Precondition: instruction must be OpArrayLength.");
  790. uint32_t donated_variable_id =
  791. original_id_to_donated_id->at(instruction.GetSingleWordInOperand(0));
  792. auto donated_variable_instruction =
  793. GetIRContext()->get_def_use_mgr()->GetDef(donated_variable_id);
  794. auto pointer_to_struct_instruction =
  795. GetIRContext()->get_def_use_mgr()->GetDef(
  796. donated_variable_instruction->type_id());
  797. assert(pointer_to_struct_instruction->opcode() == SpvOpTypePointer &&
  798. "Type of variable must be pointer.");
  799. auto donated_struct_type_instruction =
  800. GetIRContext()->get_def_use_mgr()->GetDef(
  801. pointer_to_struct_instruction->GetSingleWordInOperand(1));
  802. assert(donated_struct_type_instruction->opcode() == SpvOpTypeStruct &&
  803. "Pointee type of pointer used by OpArrayLength must be struct.");
  804. assert(donated_struct_type_instruction->NumInOperands() ==
  805. instruction.GetSingleWordInOperand(1) + 1 &&
  806. "OpArrayLength must refer to the final member of the given "
  807. "struct.");
  808. uint32_t fixed_size_array_type_id =
  809. donated_struct_type_instruction->GetSingleWordInOperand(
  810. donated_struct_type_instruction->NumInOperands() - 1);
  811. auto fixed_size_array_type_instruction =
  812. GetIRContext()->get_def_use_mgr()->GetDef(fixed_size_array_type_id);
  813. assert(fixed_size_array_type_instruction->opcode() == SpvOpTypeArray &&
  814. "The donated array type must be fixed-size.");
  815. auto array_size_id =
  816. fixed_size_array_type_instruction->GetSingleWordInOperand(1);
  817. if (instruction.result_id() &&
  818. !original_id_to_donated_id->count(instruction.result_id())) {
  819. original_id_to_donated_id->insert(
  820. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  821. }
  822. donated_instructions->push_back(MakeInstructionMessage(
  823. SpvOpCopyObject, original_id_to_donated_id->at(instruction.type_id()),
  824. original_id_to_donated_id->at(instruction.result_id()),
  825. opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {array_size_id}}})));
  826. }
  827. void FuzzerPassDonateModules::HandleDifficultInstruction(
  828. const opt::Instruction& instruction,
  829. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  830. std::vector<protobufs::Instruction>* donated_instructions,
  831. std::set<uint32_t>* skipped_instructions) {
  832. if (!instruction.result_id()) {
  833. // It does not generate a result id, so it can be ignored.
  834. return;
  835. }
  836. if (!original_id_to_donated_id->count(instruction.type_id())) {
  837. // We cannot handle this instruction's result type, so we need to skip it
  838. // all together.
  839. skipped_instructions->insert(instruction.result_id());
  840. return;
  841. }
  842. // We now attempt to replace the instruction with an OpCopyObject.
  843. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3278): We could do
  844. // something more refined here - we could check which operands to the
  845. // instruction could not be donated and replace those operands with
  846. // references to other ids (such as constants), so that we still get an
  847. // instruction with the opcode and easy-to-handle operands of the donor
  848. // instruction.
  849. auto remapped_type_id = original_id_to_donated_id->at(instruction.type_id());
  850. if (!IsBasicType(
  851. *GetIRContext()->get_def_use_mgr()->GetDef(remapped_type_id))) {
  852. // The instruction has a non-basic result type, so we cannot replace it with
  853. // an object copy of a constant. We thus skip it completely.
  854. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3279): We could
  855. // instead look for an available id of the right type and generate an
  856. // OpCopyObject of that id.
  857. skipped_instructions->insert(instruction.result_id());
  858. return;
  859. }
  860. // We are going to add an OpCopyObject instruction. Add a mapping for the
  861. // result id of the original instruction if does not already exist (it may
  862. // exist in the case that it has been forward-referenced).
  863. if (!original_id_to_donated_id->count(instruction.result_id())) {
  864. original_id_to_donated_id->insert(
  865. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  866. }
  867. // We find or add a zero constant to the receiving module for the type in
  868. // question, and add an OpCopyObject instruction that copies this zero.
  869. //
  870. // We mark the constant as irrelevant so that we can replace it with a
  871. // more interesting value later.
  872. auto zero_constant = FindOrCreateZeroConstant(remapped_type_id, true);
  873. donated_instructions->push_back(MakeInstructionMessage(
  874. SpvOpCopyObject, remapped_type_id,
  875. original_id_to_donated_id->at(instruction.result_id()),
  876. opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {zero_constant}}})));
  877. }
  878. void FuzzerPassDonateModules::PrepareInstructionForDonation(
  879. const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
  880. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  881. std::vector<protobufs::Instruction>* donated_instructions) {
  882. // Get the instruction's input operands into donation-ready form,
  883. // remapping any id uses in the process.
  884. opt::Instruction::OperandList input_operands;
  885. // Consider each input operand in turn.
  886. for (uint32_t in_operand_index = 0;
  887. in_operand_index < instruction.NumInOperands(); in_operand_index++) {
  888. std::vector<uint32_t> operand_data;
  889. const opt::Operand& in_operand = instruction.GetInOperand(in_operand_index);
  890. // Check whether this operand is an id.
  891. if (spvIsIdType(in_operand.type)) {
  892. // This is an id operand - it consists of a single word of data,
  893. // which needs to be remapped so that it is replaced with the
  894. // donated form of the id.
  895. auto operand_id = in_operand.words[0];
  896. if (!original_id_to_donated_id->count(operand_id)) {
  897. // This is a forward reference. We will choose a corresponding
  898. // donor id for the referenced id and update the mapping to
  899. // reflect it.
  900. // Keep release compilers happy because |donor_ir_context| is only used
  901. // in this assertion.
  902. (void)(donor_ir_context);
  903. assert((donor_ir_context->get_def_use_mgr()
  904. ->GetDef(operand_id)
  905. ->opcode() == SpvOpLabel ||
  906. instruction.opcode() == SpvOpPhi) &&
  907. "Unsupported forward reference.");
  908. original_id_to_donated_id->insert(
  909. {operand_id, GetFuzzerContext()->GetFreshId()});
  910. }
  911. operand_data.push_back(original_id_to_donated_id->at(operand_id));
  912. } else {
  913. // For non-id operands, we just add each of the data words.
  914. for (auto word : in_operand.words) {
  915. operand_data.push_back(word);
  916. }
  917. }
  918. input_operands.push_back({in_operand.type, operand_data});
  919. }
  920. if (instruction.opcode() == SpvOpVariable &&
  921. instruction.NumInOperands() == 1) {
  922. // This is an uninitialized local variable. Initialize it to zero.
  923. input_operands.push_back(
  924. {SPV_OPERAND_TYPE_ID,
  925. {FindOrCreateZeroConstant(
  926. fuzzerutil::GetPointeeTypeIdFromPointerType(
  927. GetIRContext(),
  928. original_id_to_donated_id->at(instruction.type_id())),
  929. false)}});
  930. }
  931. if (instruction.result_id() &&
  932. !original_id_to_donated_id->count(instruction.result_id())) {
  933. original_id_to_donated_id->insert(
  934. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  935. }
  936. // Remap the result type and result id (if present) of the
  937. // instruction, and turn it into a protobuf message.
  938. donated_instructions->push_back(MakeInstructionMessage(
  939. instruction.opcode(),
  940. instruction.type_id()
  941. ? original_id_to_donated_id->at(instruction.type_id())
  942. : 0,
  943. instruction.result_id()
  944. ? original_id_to_donated_id->at(instruction.result_id())
  945. : 0,
  946. input_operands));
  947. }
  948. void FuzzerPassDonateModules::AddLivesafeFunction(
  949. const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
  950. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  951. const std::vector<protobufs::Instruction>& donated_instructions) {
  952. // Various types and constants must be in place for a function to be made
  953. // live-safe. Add them if not already present.
  954. FindOrCreateBoolType(); // Needed for comparisons
  955. FindOrCreatePointerToIntegerType(
  956. 32, false, SpvStorageClassFunction); // Needed for adding loop limiters
  957. FindOrCreateIntegerConstant({0}, 32, false,
  958. false); // Needed for initializing loop limiters
  959. FindOrCreateIntegerConstant({1}, 32, false,
  960. false); // Needed for incrementing loop limiters
  961. // Get a fresh id for the variable that will be used as a loop limiter.
  962. const uint32_t loop_limiter_variable_id = GetFuzzerContext()->GetFreshId();
  963. // Choose a random loop limit, and add the required constant to the
  964. // module if not already there.
  965. const uint32_t loop_limit = FindOrCreateIntegerConstant(
  966. {GetFuzzerContext()->GetRandomLoopLimit()}, 32, false, false);
  967. // Consider every loop header in the function to donate, and create a
  968. // structure capturing the ids to be used for manipulating the loop
  969. // limiter each time the loop is iterated.
  970. std::vector<protobufs::LoopLimiterInfo> loop_limiters;
  971. for (auto& block : function_to_donate) {
  972. if (block.IsLoopHeader()) {
  973. protobufs::LoopLimiterInfo loop_limiter;
  974. // Grab the loop header's id, mapped to its donated value.
  975. loop_limiter.set_loop_header_id(original_id_to_donated_id.at(block.id()));
  976. // Get fresh ids that will be used to load the loop limiter, increment
  977. // it, compare it with the loop limit, and an id for a new block that
  978. // will contain the loop's original terminator.
  979. loop_limiter.set_load_id(GetFuzzerContext()->GetFreshId());
  980. loop_limiter.set_increment_id(GetFuzzerContext()->GetFreshId());
  981. loop_limiter.set_compare_id(GetFuzzerContext()->GetFreshId());
  982. loop_limiter.set_logical_op_id(GetFuzzerContext()->GetFreshId());
  983. loop_limiters.emplace_back(loop_limiter);
  984. }
  985. }
  986. // Consider every access chain in the function to donate, and create a
  987. // structure containing the ids necessary to clamp the access chain
  988. // indices to be in-bounds.
  989. std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
  990. for (auto& block : function_to_donate) {
  991. for (auto& inst : block) {
  992. switch (inst.opcode()) {
  993. case SpvOpAccessChain:
  994. case SpvOpInBoundsAccessChain: {
  995. protobufs::AccessChainClampingInfo clamping_info;
  996. clamping_info.set_access_chain_id(
  997. original_id_to_donated_id.at(inst.result_id()));
  998. auto base_object = donor_ir_context->get_def_use_mgr()->GetDef(
  999. inst.GetSingleWordInOperand(0));
  1000. assert(base_object && "The base object must exist.");
  1001. auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef(
  1002. base_object->type_id());
  1003. assert(pointer_type && pointer_type->opcode() == SpvOpTypePointer &&
  1004. "The base object must have pointer type.");
  1005. auto should_be_composite_type =
  1006. donor_ir_context->get_def_use_mgr()->GetDef(
  1007. pointer_type->GetSingleWordInOperand(1));
  1008. // Walk the access chain, creating fresh ids to facilitate
  1009. // clamping each index. For simplicity we do this for every
  1010. // index, even though constant indices will not end up being
  1011. // clamped.
  1012. for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
  1013. auto compare_and_select_ids =
  1014. clamping_info.add_compare_and_select_ids();
  1015. compare_and_select_ids->set_first(GetFuzzerContext()->GetFreshId());
  1016. compare_and_select_ids->set_second(
  1017. GetFuzzerContext()->GetFreshId());
  1018. // Get the bound for the component being indexed into.
  1019. uint32_t bound;
  1020. if (should_be_composite_type->opcode() == SpvOpTypeRuntimeArray) {
  1021. // The donor is indexing into a runtime array. We do not
  1022. // donate runtime arrays. Instead, we donate a corresponding
  1023. // fixed-size array for every runtime array. We should thus
  1024. // find that donor composite type's result id maps to a fixed-
  1025. // size array.
  1026. auto fixed_size_array_type =
  1027. GetIRContext()->get_def_use_mgr()->GetDef(
  1028. original_id_to_donated_id.at(
  1029. should_be_composite_type->result_id()));
  1030. assert(fixed_size_array_type->opcode() == SpvOpTypeArray &&
  1031. "A runtime array type in the donor should have been "
  1032. "replaced by a fixed-sized array in the recipient.");
  1033. // The size of this fixed-size array is a suitable bound.
  1034. bound = TransformationAddFunction::GetBoundForCompositeIndex(
  1035. GetIRContext(), *fixed_size_array_type);
  1036. } else {
  1037. bound = TransformationAddFunction::GetBoundForCompositeIndex(
  1038. donor_ir_context, *should_be_composite_type);
  1039. }
  1040. const uint32_t index_id = inst.GetSingleWordInOperand(index);
  1041. auto index_inst =
  1042. donor_ir_context->get_def_use_mgr()->GetDef(index_id);
  1043. auto index_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(
  1044. index_inst->type_id());
  1045. assert(index_type_inst->opcode() == SpvOpTypeInt);
  1046. opt::analysis::Integer* index_int_type =
  1047. donor_ir_context->get_type_mgr()
  1048. ->GetType(index_type_inst->result_id())
  1049. ->AsInteger();
  1050. if (index_inst->opcode() != SpvOpConstant) {
  1051. // We will have to clamp this index, so we need a constant
  1052. // whose value is one less than the bound, to compare
  1053. // against and to use as the clamped value.
  1054. FindOrCreateIntegerConstant({bound - 1}, 32,
  1055. index_int_type->IsSigned(), false);
  1056. }
  1057. should_be_composite_type =
  1058. TransformationAddFunction::FollowCompositeIndex(
  1059. donor_ir_context, *should_be_composite_type, index_id);
  1060. }
  1061. access_chain_clamping_info.push_back(clamping_info);
  1062. break;
  1063. }
  1064. default:
  1065. break;
  1066. }
  1067. }
  1068. }
  1069. // If the function contains OpKill or OpUnreachable instructions, and has
  1070. // non-void return type, then we need a value %v to use in order to turn
  1071. // these into instructions of the form OpReturn %v.
  1072. uint32_t kill_unreachable_return_value_id;
  1073. auto function_return_type_inst =
  1074. donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
  1075. if (function_return_type_inst->opcode() == SpvOpTypeVoid) {
  1076. // The return type is void, so we don't need a return value.
  1077. kill_unreachable_return_value_id = 0;
  1078. } else {
  1079. // We do need a return value; we use zero.
  1080. assert(function_return_type_inst->opcode() != SpvOpTypePointer &&
  1081. "Function return type must not be a pointer.");
  1082. kill_unreachable_return_value_id = FindOrCreateZeroConstant(
  1083. original_id_to_donated_id.at(function_return_type_inst->result_id()),
  1084. false);
  1085. }
  1086. // Add the function in a livesafe manner.
  1087. ApplyTransformation(TransformationAddFunction(
  1088. donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
  1089. kill_unreachable_return_value_id, access_chain_clamping_info));
  1090. }
  1091. } // namespace fuzz
  1092. } // namespace spvtools