fuzzer_pass_donate_modules.cpp 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220
  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. bool ignore_inapplicable_transformations,
  44. std::vector<fuzzerutil::ModuleSupplier> donor_suppliers)
  45. : FuzzerPass(ir_context, transformation_context, fuzzer_context,
  46. transformations, ignore_inapplicable_transformations),
  47. donor_suppliers_(std::move(donor_suppliers)) {}
  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<spv::Capability>(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. spv::StorageClass FuzzerPassDonateModules::AdaptStorageClass(
  114. spv::StorageClass donor_storage_class) {
  115. switch (donor_storage_class) {
  116. case spv::StorageClass::Function:
  117. case spv::StorageClass::Private:
  118. case spv::StorageClass::Workgroup:
  119. // We leave these alone
  120. return donor_storage_class;
  121. case spv::StorageClass::Input:
  122. case spv::StorageClass::Output:
  123. case spv::StorageClass::Uniform:
  124. case spv::StorageClass::UniformConstant:
  125. case spv::StorageClass::PushConstant:
  126. case spv::StorageClass::Image:
  127. case spv::StorageClass::StorageBuffer:
  128. // We change these to Private
  129. return spv::StorageClass::Private;
  130. default:
  131. // Handle other cases on demand.
  132. assert(false && "Currently unsupported storage class.");
  133. return spv::StorageClass::Max;
  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 spv::Op::OpTypeImage:
  188. case spv::Op::OpTypeSampledImage:
  189. case spv::Op::OpTypeSampler:
  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 spv::Op::OpTypeVoid: {
  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 spv::Op::OpTypeBool: {
  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 spv::Op::OpTypeInt: {
  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 spv::Op::OpTypeFloat: {
  234. // Similar to spv::Op::OpTypeInt.
  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 spv::Op::OpTypeVector: {
  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 spv::Op::OpTypeMatrix: {
  269. // Similar to spv::Op::OpTypeVector.
  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 spv::Op::OpTypeArray: {
  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 spv::Op::OpTypeRuntimeArray: {
  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 spv::Op::OpTypeStruct: {
  326. // Similar to spv::Op::OpTypeArray.
  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 spv::Op::OpTypePointer: {
  343. // Similar to spv::Op::OpTypeArray.
  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<spv::StorageClass>(
  354. type_or_value.GetSingleWordInOperand(0))),
  355. original_id_to_donated_id->at(pointee_type_id)));
  356. } break;
  357. case spv::Op::OpTypeFunction: {
  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 spv::Op::OpSpecConstantOp: {
  409. new_result_id = GetFuzzerContext()->GetFreshId();
  410. auto type_id = original_id_to_donated_id->at(type_or_value.type_id());
  411. auto opcode =
  412. static_cast<spv::Op>(type_or_value.GetSingleWordInOperand(0));
  413. // Make sure we take into account |original_id_to_donated_id| when
  414. // computing operands for OpSpecConstantOp.
  415. opt::Instruction::OperandList operands;
  416. for (uint32_t i = 1; i < type_or_value.NumInOperands(); ++i) {
  417. const auto& operand = type_or_value.GetInOperand(i);
  418. auto data =
  419. operand.type == SPV_OPERAND_TYPE_ID
  420. ? opt::Operand::OperandData{original_id_to_donated_id->at(
  421. operand.words[0])}
  422. : operand.words;
  423. operands.push_back({operand.type, std::move(data)});
  424. }
  425. ApplyTransformation(TransformationAddSpecConstantOp(
  426. new_result_id, type_id, opcode, std::move(operands)));
  427. } break;
  428. case spv::Op::OpSpecConstantTrue:
  429. case spv::Op::OpSpecConstantFalse:
  430. case spv::Op::OpConstantTrue:
  431. case spv::Op::OpConstantFalse: {
  432. // It is OK to have duplicate definitions of True and False, so add
  433. // these to the module, using a remapped Bool type.
  434. new_result_id = GetFuzzerContext()->GetFreshId();
  435. auto value = type_or_value.opcode() == spv::Op::OpConstantTrue ||
  436. type_or_value.opcode() == spv::Op::OpSpecConstantTrue;
  437. ApplyTransformation(
  438. TransformationAddConstantBoolean(new_result_id, value, false));
  439. } break;
  440. case spv::Op::OpSpecConstant:
  441. case spv::Op::OpConstant: {
  442. // It is OK to have duplicate constant definitions, so add this to the
  443. // module using a remapped result type.
  444. new_result_id = GetFuzzerContext()->GetFreshId();
  445. std::vector<uint32_t> data_words;
  446. type_or_value.ForEachInOperand([&data_words](const uint32_t* in_operand) {
  447. data_words.push_back(*in_operand);
  448. });
  449. ApplyTransformation(TransformationAddConstantScalar(
  450. new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
  451. data_words, false));
  452. } break;
  453. case spv::Op::OpSpecConstantComposite:
  454. case spv::Op::OpConstantComposite: {
  455. assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
  456. "Composite types for which it is possible to create a constant "
  457. "should have been donated.");
  458. // It is OK to have duplicate constant composite definitions, so add
  459. // this to the module using remapped versions of all constituent ids and
  460. // the result type.
  461. new_result_id = GetFuzzerContext()->GetFreshId();
  462. std::vector<uint32_t> constituent_ids;
  463. type_or_value.ForEachInId([&constituent_ids, &original_id_to_donated_id](
  464. const uint32_t* constituent_id) {
  465. assert(original_id_to_donated_id->count(*constituent_id) &&
  466. "The constants used to construct this composite should "
  467. "have been donated.");
  468. constituent_ids.push_back(
  469. original_id_to_donated_id->at(*constituent_id));
  470. });
  471. ApplyTransformation(TransformationAddConstantComposite(
  472. new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
  473. constituent_ids, false));
  474. } break;
  475. case spv::Op::OpConstantNull: {
  476. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  477. // We did not donate the type associated with this null constant, so
  478. // we cannot donate the null constant.
  479. return;
  480. }
  481. // It is fine to have multiple OpConstantNull instructions of the same
  482. // type, so we just add this to the recipient module.
  483. new_result_id = GetFuzzerContext()->GetFreshId();
  484. ApplyTransformation(TransformationAddConstantNull(
  485. new_result_id,
  486. original_id_to_donated_id->at(type_or_value.type_id())));
  487. } break;
  488. case spv::Op::OpVariable: {
  489. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  490. // We did not donate the pointer type associated with this variable,
  491. // so we cannot donate the variable.
  492. return;
  493. }
  494. // This is a global variable that could have one of various storage
  495. // classes. However, we change all global variable pointer storage
  496. // classes (such as Uniform, Input and Output) to private when donating
  497. // pointer types, with the exception of the Workgroup storage class.
  498. //
  499. // Thus this variable's pointer type is guaranteed to have storage class
  500. // Private or Workgroup.
  501. //
  502. // We add a global variable with either Private or Workgroup storage
  503. // class, using remapped versions of the result type and initializer ids
  504. // for the global variable in the donor.
  505. //
  506. // We regard the added variable as having an irrelevant value. This
  507. // means that future passes can add stores to the variable in any
  508. // way they wish, and pass them as pointer parameters to functions
  509. // without worrying about whether their data might get modified.
  510. new_result_id = GetFuzzerContext()->GetFreshId();
  511. uint32_t remapped_pointer_type =
  512. original_id_to_donated_id->at(type_or_value.type_id());
  513. uint32_t initializer_id;
  514. spv::StorageClass storage_class =
  515. static_cast<spv::StorageClass>(type_or_value.GetSingleWordInOperand(
  516. 0)) == spv::StorageClass::Workgroup
  517. ? spv::StorageClass::Workgroup
  518. : spv::StorageClass::Private;
  519. if (type_or_value.NumInOperands() == 1) {
  520. // The variable did not have an initializer. Initialize it to zero
  521. // if it has Private storage class (to limit problems associated with
  522. // uninitialized data), and leave it uninitialized if it has Workgroup
  523. // storage class (as Workgroup variables cannot have initializers).
  524. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3275): we
  525. // could initialize Workgroup variables at the start of an entry
  526. // point, and should do so if their uninitialized nature proves
  527. // problematic.
  528. initializer_id = storage_class == spv::StorageClass::Workgroup
  529. ? 0
  530. : FindOrCreateZeroConstant(
  531. fuzzerutil::GetPointeeTypeIdFromPointerType(
  532. GetIRContext(), remapped_pointer_type),
  533. false);
  534. } else {
  535. // The variable already had an initializer; use its remapped id.
  536. initializer_id = original_id_to_donated_id->at(
  537. type_or_value.GetSingleWordInOperand(1));
  538. }
  539. ApplyTransformation(
  540. TransformationAddGlobalVariable(new_result_id, remapped_pointer_type,
  541. storage_class, initializer_id, true));
  542. } break;
  543. case spv::Op::OpUndef: {
  544. if (!original_id_to_donated_id->count(type_or_value.type_id())) {
  545. // We did not donate the type associated with this undef, so we cannot
  546. // donate the undef.
  547. return;
  548. }
  549. // It is fine to have multiple Undef instructions of the same type, so
  550. // we just add this to the recipient module.
  551. new_result_id = GetFuzzerContext()->GetFreshId();
  552. ApplyTransformation(TransformationAddGlobalUndef(
  553. new_result_id,
  554. original_id_to_donated_id->at(type_or_value.type_id())));
  555. } break;
  556. default: {
  557. assert(0 && "Unknown type/value.");
  558. new_result_id = 0;
  559. } break;
  560. }
  561. // Update the id mapping to associate the instruction's result id with its
  562. // corresponding id in the recipient.
  563. original_id_to_donated_id->insert({type_or_value.result_id(), new_result_id});
  564. }
  565. void FuzzerPassDonateModules::HandleFunctions(
  566. opt::IRContext* donor_ir_context,
  567. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  568. bool make_livesafe) {
  569. // Get the ids of functions in the donor module, topologically sorted
  570. // according to the donor's call graph.
  571. auto topological_order =
  572. CallGraph(donor_ir_context).GetFunctionsInTopologicalOrder();
  573. // Donate the functions in reverse topological order. This ensures that a
  574. // function gets donated before any function that depends on it. This allows
  575. // donation of the functions to be separated into a number of transformations,
  576. // each adding one function, such that every prefix of transformations leaves
  577. // the module valid.
  578. for (auto function_id = topological_order.rbegin();
  579. function_id != topological_order.rend(); ++function_id) {
  580. // Find the function to be donated.
  581. opt::Function* function_to_donate = nullptr;
  582. for (auto& function : *donor_ir_context->module()) {
  583. if (function.result_id() == *function_id) {
  584. function_to_donate = &function;
  585. break;
  586. }
  587. }
  588. assert(function_to_donate && "Function to be donated was not found.");
  589. if (!original_id_to_donated_id->count(
  590. function_to_donate->DefInst().GetSingleWordInOperand(1))) {
  591. // We were not able to donate this function's type, so we cannot donate
  592. // the function.
  593. continue;
  594. }
  595. // We will collect up protobuf messages representing the donor function's
  596. // instructions here, and use them to create an AddFunction transformation.
  597. std::vector<protobufs::Instruction> donated_instructions;
  598. // This set tracks the ids of those instructions for which donation was
  599. // completely skipped: neither the instruction nor a substitute for it was
  600. // donated.
  601. std::set<uint32_t> skipped_instructions;
  602. // Consider every instruction of the donor function.
  603. function_to_donate->ForEachInst(
  604. [this, &donated_instructions, donor_ir_context,
  605. &original_id_to_donated_id,
  606. &skipped_instructions](const opt::Instruction* instruction) {
  607. if (instruction->opcode() == spv::Op::OpArrayLength) {
  608. // We treat OpArrayLength specially.
  609. HandleOpArrayLength(*instruction, original_id_to_donated_id,
  610. &donated_instructions);
  611. } else if (!CanDonateInstruction(donor_ir_context, *instruction,
  612. *original_id_to_donated_id,
  613. skipped_instructions)) {
  614. // This is an instruction that we cannot directly donate.
  615. HandleDifficultInstruction(*instruction, original_id_to_donated_id,
  616. &donated_instructions,
  617. &skipped_instructions);
  618. } else {
  619. PrepareInstructionForDonation(*instruction, donor_ir_context,
  620. original_id_to_donated_id,
  621. &donated_instructions);
  622. }
  623. });
  624. // If |make_livesafe| is true, try to add the function in a livesafe manner.
  625. // Otherwise (if |make_lifesafe| is false or an attempt to make the function
  626. // livesafe has failed), add the function in a non-livesafe manner.
  627. if (!make_livesafe ||
  628. !MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context,
  629. *original_id_to_donated_id,
  630. donated_instructions)) {
  631. ApplyTransformation(TransformationAddFunction(donated_instructions));
  632. }
  633. }
  634. }
  635. bool FuzzerPassDonateModules::CanDonateInstruction(
  636. opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
  637. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  638. const std::set<uint32_t>& skipped_instructions) const {
  639. if (instruction.type_id() &&
  640. !original_id_to_donated_id.count(instruction.type_id())) {
  641. // We could not donate the result type of this instruction, so we cannot
  642. // donate the instruction.
  643. return false;
  644. }
  645. // Now consider instructions we specifically want to skip because we do not
  646. // yet support them.
  647. switch (instruction.opcode()) {
  648. case spv::Op::OpAtomicLoad:
  649. case spv::Op::OpAtomicStore:
  650. case spv::Op::OpAtomicExchange:
  651. case spv::Op::OpAtomicCompareExchange:
  652. case spv::Op::OpAtomicCompareExchangeWeak:
  653. case spv::Op::OpAtomicIIncrement:
  654. case spv::Op::OpAtomicIDecrement:
  655. case spv::Op::OpAtomicIAdd:
  656. case spv::Op::OpAtomicISub:
  657. case spv::Op::OpAtomicSMin:
  658. case spv::Op::OpAtomicUMin:
  659. case spv::Op::OpAtomicSMax:
  660. case spv::Op::OpAtomicUMax:
  661. case spv::Op::OpAtomicAnd:
  662. case spv::Op::OpAtomicOr:
  663. case spv::Op::OpAtomicXor:
  664. // We conservatively ignore all atomic instructions at present.
  665. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3276): Consider
  666. // being less conservative here.
  667. case spv::Op::OpImageSampleImplicitLod:
  668. case spv::Op::OpImageSampleExplicitLod:
  669. case spv::Op::OpImageSampleDrefImplicitLod:
  670. case spv::Op::OpImageSampleDrefExplicitLod:
  671. case spv::Op::OpImageSampleProjImplicitLod:
  672. case spv::Op::OpImageSampleProjExplicitLod:
  673. case spv::Op::OpImageSampleProjDrefImplicitLod:
  674. case spv::Op::OpImageSampleProjDrefExplicitLod:
  675. case spv::Op::OpImageFetch:
  676. case spv::Op::OpImageGather:
  677. case spv::Op::OpImageDrefGather:
  678. case spv::Op::OpImageRead:
  679. case spv::Op::OpImageWrite:
  680. case spv::Op::OpImageSparseSampleImplicitLod:
  681. case spv::Op::OpImageSparseSampleExplicitLod:
  682. case spv::Op::OpImageSparseSampleDrefImplicitLod:
  683. case spv::Op::OpImageSparseSampleDrefExplicitLod:
  684. case spv::Op::OpImageSparseSampleProjImplicitLod:
  685. case spv::Op::OpImageSparseSampleProjExplicitLod:
  686. case spv::Op::OpImageSparseSampleProjDrefImplicitLod:
  687. case spv::Op::OpImageSparseSampleProjDrefExplicitLod:
  688. case spv::Op::OpImageSparseFetch:
  689. case spv::Op::OpImageSparseGather:
  690. case spv::Op::OpImageSparseDrefGather:
  691. case spv::Op::OpImageSparseRead:
  692. case spv::Op::OpImageSampleFootprintNV:
  693. case spv::Op::OpImage:
  694. case spv::Op::OpImageQueryFormat:
  695. case spv::Op::OpImageQueryLevels:
  696. case spv::Op::OpImageQueryLod:
  697. case spv::Op::OpImageQueryOrder:
  698. case spv::Op::OpImageQuerySamples:
  699. case spv::Op::OpImageQuerySize:
  700. case spv::Op::OpImageQuerySizeLod:
  701. case spv::Op::OpSampledImage:
  702. // We ignore all instructions related to accessing images, since we do not
  703. // donate images.
  704. return false;
  705. case spv::Op::OpLoad:
  706. switch (donor_ir_context->get_def_use_mgr()
  707. ->GetDef(instruction.type_id())
  708. ->opcode()) {
  709. case spv::Op::OpTypeImage:
  710. case spv::Op::OpTypeSampledImage:
  711. case spv::Op::OpTypeSampler:
  712. // Again, we ignore instructions that relate to accessing images.
  713. return false;
  714. default:
  715. break;
  716. }
  717. default:
  718. break;
  719. }
  720. // Examine each id input operand to the instruction. If it turns out that we
  721. // have skipped any of these operands then we cannot donate the instruction.
  722. bool result = true;
  723. instruction.WhileEachInId(
  724. [donor_ir_context, &original_id_to_donated_id, &result,
  725. &skipped_instructions](const uint32_t* in_id) -> bool {
  726. if (!original_id_to_donated_id.count(*in_id)) {
  727. // We do not have a mapped result id for this id operand. That either
  728. // means that it is a forward reference (which is OK), that we skipped
  729. // the instruction that generated it (which is not OK), or that it is
  730. // the id of a function or global value that we did not donate (which
  731. // is not OK). We check for the latter two cases.
  732. if (skipped_instructions.count(*in_id) ||
  733. // A function or global value does not have an associated basic
  734. // block.
  735. !donor_ir_context->get_instr_block(*in_id)) {
  736. result = false;
  737. return false;
  738. }
  739. }
  740. return true;
  741. });
  742. return result;
  743. }
  744. bool FuzzerPassDonateModules::IsBasicType(
  745. const opt::Instruction& instruction) const {
  746. switch (instruction.opcode()) {
  747. case spv::Op::OpTypeArray:
  748. case spv::Op::OpTypeBool:
  749. case spv::Op::OpTypeFloat:
  750. case spv::Op::OpTypeInt:
  751. case spv::Op::OpTypeMatrix:
  752. case spv::Op::OpTypeStruct:
  753. case spv::Op::OpTypeVector:
  754. return true;
  755. default:
  756. return false;
  757. }
  758. }
  759. void FuzzerPassDonateModules::HandleOpArrayLength(
  760. const opt::Instruction& instruction,
  761. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  762. std::vector<protobufs::Instruction>* donated_instructions) const {
  763. assert(instruction.opcode() == spv::Op::OpArrayLength &&
  764. "Precondition: instruction must be OpArrayLength.");
  765. uint32_t donated_variable_id =
  766. original_id_to_donated_id->at(instruction.GetSingleWordInOperand(0));
  767. auto donated_variable_instruction =
  768. GetIRContext()->get_def_use_mgr()->GetDef(donated_variable_id);
  769. auto pointer_to_struct_instruction =
  770. GetIRContext()->get_def_use_mgr()->GetDef(
  771. donated_variable_instruction->type_id());
  772. assert(pointer_to_struct_instruction->opcode() == spv::Op::OpTypePointer &&
  773. "Type of variable must be pointer.");
  774. auto donated_struct_type_instruction =
  775. GetIRContext()->get_def_use_mgr()->GetDef(
  776. pointer_to_struct_instruction->GetSingleWordInOperand(1));
  777. assert(donated_struct_type_instruction->opcode() == spv::Op::OpTypeStruct &&
  778. "Pointee type of pointer used by OpArrayLength must be struct.");
  779. assert(donated_struct_type_instruction->NumInOperands() ==
  780. instruction.GetSingleWordInOperand(1) + 1 &&
  781. "OpArrayLength must refer to the final member of the given "
  782. "struct.");
  783. uint32_t fixed_size_array_type_id =
  784. donated_struct_type_instruction->GetSingleWordInOperand(
  785. donated_struct_type_instruction->NumInOperands() - 1);
  786. auto fixed_size_array_type_instruction =
  787. GetIRContext()->get_def_use_mgr()->GetDef(fixed_size_array_type_id);
  788. assert(fixed_size_array_type_instruction->opcode() == spv::Op::OpTypeArray &&
  789. "The donated array type must be fixed-size.");
  790. auto array_size_id =
  791. fixed_size_array_type_instruction->GetSingleWordInOperand(1);
  792. if (instruction.result_id() &&
  793. !original_id_to_donated_id->count(instruction.result_id())) {
  794. original_id_to_donated_id->insert(
  795. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  796. }
  797. donated_instructions->push_back(MakeInstructionMessage(
  798. spv::Op::OpCopyObject,
  799. original_id_to_donated_id->at(instruction.type_id()),
  800. original_id_to_donated_id->at(instruction.result_id()),
  801. opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {array_size_id}}})));
  802. }
  803. void FuzzerPassDonateModules::HandleDifficultInstruction(
  804. const opt::Instruction& instruction,
  805. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  806. std::vector<protobufs::Instruction>* donated_instructions,
  807. std::set<uint32_t>* skipped_instructions) {
  808. if (!instruction.result_id()) {
  809. // It does not generate a result id, so it can be ignored.
  810. return;
  811. }
  812. if (!original_id_to_donated_id->count(instruction.type_id())) {
  813. // We cannot handle this instruction's result type, so we need to skip it
  814. // all together.
  815. skipped_instructions->insert(instruction.result_id());
  816. return;
  817. }
  818. // We now attempt to replace the instruction with an OpCopyObject.
  819. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3278): We could do
  820. // something more refined here - we could check which operands to the
  821. // instruction could not be donated and replace those operands with
  822. // references to other ids (such as constants), so that we still get an
  823. // instruction with the opcode and easy-to-handle operands of the donor
  824. // instruction.
  825. auto remapped_type_id = original_id_to_donated_id->at(instruction.type_id());
  826. if (!IsBasicType(
  827. *GetIRContext()->get_def_use_mgr()->GetDef(remapped_type_id))) {
  828. // The instruction has a non-basic result type, so we cannot replace it with
  829. // an object copy of a constant. We thus skip it completely.
  830. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3279): We could
  831. // instead look for an available id of the right type and generate an
  832. // OpCopyObject of that id.
  833. skipped_instructions->insert(instruction.result_id());
  834. return;
  835. }
  836. // We are going to add an OpCopyObject instruction. Add a mapping for the
  837. // result id of the original instruction if does not already exist (it may
  838. // exist in the case that it has been forward-referenced).
  839. if (!original_id_to_donated_id->count(instruction.result_id())) {
  840. original_id_to_donated_id->insert(
  841. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  842. }
  843. // We find or add a zero constant to the receiving module for the type in
  844. // question, and add an OpCopyObject instruction that copies this zero.
  845. //
  846. // We mark the constant as irrelevant so that we can replace it with a
  847. // more interesting value later.
  848. auto zero_constant = FindOrCreateZeroConstant(remapped_type_id, true);
  849. donated_instructions->push_back(MakeInstructionMessage(
  850. spv::Op::OpCopyObject, remapped_type_id,
  851. original_id_to_donated_id->at(instruction.result_id()),
  852. opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {zero_constant}}})));
  853. }
  854. void FuzzerPassDonateModules::PrepareInstructionForDonation(
  855. const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
  856. std::map<uint32_t, uint32_t>* original_id_to_donated_id,
  857. std::vector<protobufs::Instruction>* donated_instructions) {
  858. // Get the instruction's input operands into donation-ready form,
  859. // remapping any id uses in the process.
  860. opt::Instruction::OperandList input_operands;
  861. // Consider each input operand in turn.
  862. for (uint32_t in_operand_index = 0;
  863. in_operand_index < instruction.NumInOperands(); in_operand_index++) {
  864. std::vector<uint32_t> operand_data;
  865. const opt::Operand& in_operand = instruction.GetInOperand(in_operand_index);
  866. // Check whether this operand is an id.
  867. if (spvIsIdType(in_operand.type)) {
  868. // This is an id operand - it consists of a single word of data,
  869. // which needs to be remapped so that it is replaced with the
  870. // donated form of the id.
  871. auto operand_id = in_operand.words[0];
  872. if (!original_id_to_donated_id->count(operand_id)) {
  873. // This is a forward reference. We will choose a corresponding
  874. // donor id for the referenced id and update the mapping to
  875. // reflect it.
  876. // Keep release compilers happy because |donor_ir_context| is only used
  877. // in this assertion.
  878. (void)(donor_ir_context);
  879. assert((donor_ir_context->get_def_use_mgr()
  880. ->GetDef(operand_id)
  881. ->opcode() == spv::Op::OpLabel ||
  882. instruction.opcode() == spv::Op::OpPhi) &&
  883. "Unsupported forward reference.");
  884. original_id_to_donated_id->insert(
  885. {operand_id, GetFuzzerContext()->GetFreshId()});
  886. }
  887. operand_data.push_back(original_id_to_donated_id->at(operand_id));
  888. } else {
  889. // For non-id operands, we just add each of the data words.
  890. for (auto word : in_operand.words) {
  891. operand_data.push_back(word);
  892. }
  893. }
  894. input_operands.push_back({in_operand.type, operand_data});
  895. }
  896. if (instruction.opcode() == spv::Op::OpVariable &&
  897. instruction.NumInOperands() == 1) {
  898. // This is an uninitialized local variable. Initialize it to zero.
  899. input_operands.push_back(
  900. {SPV_OPERAND_TYPE_ID,
  901. {FindOrCreateZeroConstant(
  902. fuzzerutil::GetPointeeTypeIdFromPointerType(
  903. GetIRContext(),
  904. original_id_to_donated_id->at(instruction.type_id())),
  905. false)}});
  906. }
  907. if (instruction.result_id() &&
  908. !original_id_to_donated_id->count(instruction.result_id())) {
  909. original_id_to_donated_id->insert(
  910. {instruction.result_id(), GetFuzzerContext()->GetFreshId()});
  911. }
  912. // Remap the result type and result id (if present) of the
  913. // instruction, and turn it into a protobuf message.
  914. donated_instructions->push_back(MakeInstructionMessage(
  915. instruction.opcode(),
  916. instruction.type_id()
  917. ? original_id_to_donated_id->at(instruction.type_id())
  918. : 0,
  919. instruction.result_id()
  920. ? original_id_to_donated_id->at(instruction.result_id())
  921. : 0,
  922. input_operands));
  923. }
  924. bool FuzzerPassDonateModules::CreateLoopLimiterInfo(
  925. opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
  926. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  927. protobufs::LoopLimiterInfo* out) {
  928. assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header");
  929. // Grab the loop header's id, mapped to its donated value.
  930. out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id()));
  931. // Get fresh ids that will be used to load the loop limiter, increment
  932. // it, compare it with the loop limit, and an id for a new block that
  933. // will contain the loop's original terminator.
  934. out->set_load_id(GetFuzzerContext()->GetFreshId());
  935. out->set_increment_id(GetFuzzerContext()->GetFreshId());
  936. out->set_compare_id(GetFuzzerContext()->GetFreshId());
  937. out->set_logical_op_id(GetFuzzerContext()->GetFreshId());
  938. // We are creating a branch from the back-edge block to the merge block. Thus,
  939. // if merge block has any OpPhi instructions, we might need to adjust
  940. // them.
  941. // Note that the loop might have an unreachable back-edge block. This means
  942. // that the loop can't iterate, so we don't need to adjust anything.
  943. const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId(
  944. donor_ir_context, loop_header.id());
  945. if (!back_edge_block_id) {
  946. return true;
  947. }
  948. auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id);
  949. assert(back_edge_block && "|back_edge_block_id| is invalid");
  950. const auto* merge_block =
  951. donor_ir_context->cfg()->block(loop_header.MergeBlockId());
  952. assert(merge_block && "Loop header has invalid merge block id");
  953. // We don't need to adjust anything if there is already a branch from
  954. // the back-edge block to the merge block.
  955. if (back_edge_block->IsSuccessor(merge_block)) {
  956. return true;
  957. }
  958. // Adjust OpPhi instructions in the |merge_block|.
  959. for (const auto& inst : *merge_block) {
  960. if (inst.opcode() != spv::Op::OpPhi) {
  961. break;
  962. }
  963. // There is no simple way to ensure that a chosen operand for the OpPhi
  964. // instruction will never cause any problems (e.g. if we choose an
  965. // integer id, it might have a zero value when we branch from the back
  966. // edge block. This might cause a division by 0 later in the function.).
  967. // Thus, we ignore possible problems and proceed as follows:
  968. // - if any of the existing OpPhi operands dominates the back-edge
  969. // block - use it
  970. // - if OpPhi has a basic type (see IsBasicType method) - create
  971. // a zero constant
  972. // - otherwise, we can't add a livesafe function.
  973. uint32_t suitable_operand_id = 0;
  974. for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) {
  975. auto dependency_inst_id = inst.GetSingleWordInOperand(i);
  976. if (fuzzerutil::IdIsAvailableBeforeInstruction(
  977. donor_ir_context, back_edge_block->terminator(),
  978. dependency_inst_id)) {
  979. suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id);
  980. break;
  981. }
  982. }
  983. if (suitable_operand_id == 0 &&
  984. IsBasicType(
  985. *donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) {
  986. // We mark this constant as irrelevant so that we can replace it
  987. // with more interesting value later.
  988. suitable_operand_id = FindOrCreateZeroConstant(
  989. original_id_to_donated_id.at(inst.type_id()), true);
  990. }
  991. if (suitable_operand_id == 0) {
  992. return false;
  993. }
  994. out->add_phi_id(suitable_operand_id);
  995. }
  996. return true;
  997. }
  998. bool FuzzerPassDonateModules::MaybeAddLivesafeFunction(
  999. const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
  1000. const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
  1001. const std::vector<protobufs::Instruction>& donated_instructions) {
  1002. // Various types and constants must be in place for a function to be made
  1003. // live-safe. Add them if not already present.
  1004. FindOrCreateBoolType(); // Needed for comparisons
  1005. FindOrCreatePointerToIntegerType(
  1006. 32, false,
  1007. spv::StorageClass::Function); // Needed for adding loop limiters
  1008. FindOrCreateIntegerConstant({0}, 32, false,
  1009. false); // Needed for initializing loop limiters
  1010. FindOrCreateIntegerConstant({1}, 32, false,
  1011. false); // Needed for incrementing loop limiters
  1012. // Get a fresh id for the variable that will be used as a loop limiter.
  1013. const uint32_t loop_limiter_variable_id = GetFuzzerContext()->GetFreshId();
  1014. // Choose a random loop limit, and add the required constant to the
  1015. // module if not already there.
  1016. const uint32_t loop_limit = FindOrCreateIntegerConstant(
  1017. {GetFuzzerContext()->GetRandomLoopLimit()}, 32, false, false);
  1018. // Consider every loop header in the function to donate, and create a
  1019. // structure capturing the ids to be used for manipulating the loop
  1020. // limiter each time the loop is iterated.
  1021. std::vector<protobufs::LoopLimiterInfo> loop_limiters;
  1022. for (auto& block : function_to_donate) {
  1023. if (block.IsLoopHeader()) {
  1024. protobufs::LoopLimiterInfo loop_limiter;
  1025. if (!CreateLoopLimiterInfo(donor_ir_context, block,
  1026. original_id_to_donated_id, &loop_limiter)) {
  1027. return false;
  1028. }
  1029. loop_limiters.emplace_back(std::move(loop_limiter));
  1030. }
  1031. }
  1032. // Consider every access chain in the function to donate, and create a
  1033. // structure containing the ids necessary to clamp the access chain
  1034. // indices to be in-bounds.
  1035. std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
  1036. for (auto& block : function_to_donate) {
  1037. for (auto& inst : block) {
  1038. switch (inst.opcode()) {
  1039. case spv::Op::OpAccessChain:
  1040. case spv::Op::OpInBoundsAccessChain: {
  1041. protobufs::AccessChainClampingInfo clamping_info;
  1042. clamping_info.set_access_chain_id(
  1043. original_id_to_donated_id.at(inst.result_id()));
  1044. auto base_object = donor_ir_context->get_def_use_mgr()->GetDef(
  1045. inst.GetSingleWordInOperand(0));
  1046. assert(base_object && "The base object must exist.");
  1047. auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef(
  1048. base_object->type_id());
  1049. assert(pointer_type &&
  1050. pointer_type->opcode() == spv::Op::OpTypePointer &&
  1051. "The base object must have pointer type.");
  1052. auto should_be_composite_type =
  1053. donor_ir_context->get_def_use_mgr()->GetDef(
  1054. pointer_type->GetSingleWordInOperand(1));
  1055. // Walk the access chain, creating fresh ids to facilitate
  1056. // clamping each index. For simplicity we do this for every
  1057. // index, even though constant indices will not end up being
  1058. // clamped.
  1059. for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
  1060. auto compare_and_select_ids =
  1061. clamping_info.add_compare_and_select_ids();
  1062. compare_and_select_ids->set_first(GetFuzzerContext()->GetFreshId());
  1063. compare_and_select_ids->set_second(
  1064. GetFuzzerContext()->GetFreshId());
  1065. // Get the bound for the component being indexed into.
  1066. uint32_t bound;
  1067. if (should_be_composite_type->opcode() ==
  1068. spv::Op::OpTypeRuntimeArray) {
  1069. // The donor is indexing into a runtime array. We do not
  1070. // donate runtime arrays. Instead, we donate a corresponding
  1071. // fixed-size array for every runtime array. We should thus
  1072. // find that donor composite type's result id maps to a fixed-
  1073. // size array.
  1074. auto fixed_size_array_type =
  1075. GetIRContext()->get_def_use_mgr()->GetDef(
  1076. original_id_to_donated_id.at(
  1077. should_be_composite_type->result_id()));
  1078. assert(fixed_size_array_type->opcode() == spv::Op::OpTypeArray &&
  1079. "A runtime array type in the donor should have been "
  1080. "replaced by a fixed-sized array in the recipient.");
  1081. // The size of this fixed-size array is a suitable bound.
  1082. bound = fuzzerutil::GetBoundForCompositeIndex(
  1083. *fixed_size_array_type, GetIRContext());
  1084. } else {
  1085. bound = fuzzerutil::GetBoundForCompositeIndex(
  1086. *should_be_composite_type, donor_ir_context);
  1087. }
  1088. const uint32_t index_id = inst.GetSingleWordInOperand(index);
  1089. auto index_inst =
  1090. donor_ir_context->get_def_use_mgr()->GetDef(index_id);
  1091. auto index_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(
  1092. index_inst->type_id());
  1093. assert(index_type_inst->opcode() == spv::Op::OpTypeInt);
  1094. opt::analysis::Integer* index_int_type =
  1095. donor_ir_context->get_type_mgr()
  1096. ->GetType(index_type_inst->result_id())
  1097. ->AsInteger();
  1098. if (index_inst->opcode() != spv::Op::OpConstant) {
  1099. // We will have to clamp this index, so we need a constant
  1100. // whose value is one less than the bound, to compare
  1101. // against and to use as the clamped value.
  1102. FindOrCreateIntegerConstant({bound - 1}, 32,
  1103. index_int_type->IsSigned(), false);
  1104. }
  1105. should_be_composite_type =
  1106. TransformationAddFunction::FollowCompositeIndex(
  1107. donor_ir_context, *should_be_composite_type, index_id);
  1108. }
  1109. access_chain_clamping_info.push_back(clamping_info);
  1110. break;
  1111. }
  1112. default:
  1113. break;
  1114. }
  1115. }
  1116. }
  1117. // If |function_to_donate| has non-void return type and contains an
  1118. // OpKill/OpUnreachable instruction, then a value is needed in order to turn
  1119. // these into instructions of the form OpReturnValue %value_id.
  1120. uint32_t kill_unreachable_return_value_id = 0;
  1121. auto function_return_type_inst =
  1122. donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
  1123. if (function_return_type_inst->opcode() != spv::Op::OpTypeVoid &&
  1124. fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) {
  1125. kill_unreachable_return_value_id = FindOrCreateZeroConstant(
  1126. original_id_to_donated_id.at(function_return_type_inst->result_id()),
  1127. false);
  1128. }
  1129. // Try to add the function in a livesafe manner. This may fail due to edge
  1130. // cases, e.g. where adding loop limiters changes dominance such that the
  1131. // module becomes invalid. It would be ideal to handle all such edge cases,
  1132. // but as they are rare it is more pragmatic to bail out of making the
  1133. // function livesafe if the transformation's precondition fails to hold.
  1134. return MaybeApplyTransformation(TransformationAddFunction(
  1135. donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
  1136. kill_unreachable_return_value_id, access_chain_clamping_info));
  1137. }
  1138. } // namespace fuzz
  1139. } // namespace spvtools