@zigc/lib 0.16.0-test.1 → 0.17.0-dev.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/LICENSE +19 -0
  2. package/c/math.zig +148 -35
  3. package/c/stropts.zig +17 -0
  4. package/c.zig +1 -0
  5. package/compiler/aro/aro/Attribute/names.zig +604 -589
  6. package/compiler/aro/aro/Attribute.zig +202 -116
  7. package/compiler/aro/aro/Builtins/common.zig +874 -863
  8. package/compiler/aro/aro/Builtins/eval.zig +15 -7
  9. package/compiler/aro/aro/Builtins.zig +0 -1
  10. package/compiler/aro/aro/CodeGen.zig +3 -1
  11. package/compiler/aro/aro/Compilation.zig +120 -97
  12. package/compiler/aro/aro/Diagnostics.zig +21 -17
  13. package/compiler/aro/aro/Driver/GCCDetector.zig +635 -0
  14. package/compiler/aro/aro/Driver.zig +124 -50
  15. package/compiler/aro/aro/LangOpts.zig +12 -2
  16. package/compiler/aro/aro/Parser/Diagnostic.zig +79 -19
  17. package/compiler/aro/aro/Parser.zig +336 -142
  18. package/compiler/aro/aro/Preprocessor/Diagnostic.zig +21 -0
  19. package/compiler/aro/aro/Preprocessor.zig +127 -56
  20. package/compiler/aro/aro/Target.zig +17 -12
  21. package/compiler/aro/aro/Tokenizer.zig +31 -14
  22. package/compiler/aro/aro/Toolchain.zig +4 -7
  23. package/compiler/aro/aro/Tree.zig +178 -148
  24. package/compiler/aro/aro/TypeStore.zig +82 -24
  25. package/compiler/aro/aro/Value.zig +13 -17
  26. package/compiler/aro/aro/features.zig +1 -0
  27. package/compiler/aro/aro/pragmas/once.zig +0 -1
  28. package/compiler/aro/aro/record_layout.zig +3 -3
  29. package/compiler/aro/assembly_backend/x86_64.zig +3 -4
  30. package/compiler/aro/backend/Assembly.zig +1 -2
  31. package/compiler/aro/backend/Interner.zig +2 -2
  32. package/compiler/aro/backend/Ir.zig +100 -92
  33. package/compiler/aro/include/ptrcheck.h +49 -0
  34. package/compiler/aro/main.zig +26 -10
  35. package/compiler/build_runner.zig +1 -0
  36. package/compiler/objdump.zig +93 -0
  37. package/compiler/reduce.zig +5 -1
  38. package/compiler/resinator/compile.zig +2 -2
  39. package/compiler/resinator/main.zig +7 -1
  40. package/compiler/resinator/preprocess.zig +1 -3
  41. package/compiler/std-docs.zig +8 -1
  42. package/compiler/test_runner.zig +194 -62
  43. package/compiler/translate-c/MacroTranslator.zig +80 -11
  44. package/compiler/translate-c/PatternList.zig +1 -9
  45. package/compiler/translate-c/Scope.zig +43 -6
  46. package/compiler/translate-c/Translator.zig +364 -126
  47. package/compiler/translate-c/ast.zig +19 -11
  48. package/compiler/translate-c/main.zig +75 -16
  49. package/compiler_rt/cos.zig +141 -52
  50. package/compiler_rt/divmodei4.zig +40 -17
  51. package/compiler_rt/exp.zig +1 -4
  52. package/compiler_rt/exp2.zig +1 -4
  53. package/compiler_rt/exp_f128.zig +377 -0
  54. package/compiler_rt/limb64.zig +1126 -0
  55. package/compiler_rt/long_double.zig +37 -0
  56. package/compiler_rt/mulXi3.zig +1 -1
  57. package/compiler_rt/mulo.zig +6 -1
  58. package/compiler_rt/rem_pio2l.zig +173 -0
  59. package/compiler_rt/sin.zig +140 -55
  60. package/compiler_rt/sincos.zig +279 -72
  61. package/compiler_rt/ssp.zig +1 -1
  62. package/compiler_rt/tan.zig +118 -47
  63. package/compiler_rt/trig.zig +256 -6
  64. package/compiler_rt/udivmodei4.zig +28 -0
  65. package/compiler_rt.zig +2 -0
  66. package/fuzzer.zig +855 -307
  67. package/libc/musl/src/math/pow.c +343 -0
  68. package/package.json +1 -1
  69. package/std/Build/Fuzz.zig +6 -19
  70. package/std/Build/Module.zig +1 -1
  71. package/std/Build/Step/CheckObject.zig +3 -3
  72. package/std/Build/Step/Compile.zig +18 -0
  73. package/std/Build/Step/ConfigHeader.zig +49 -33
  74. package/std/Build/Step/InstallArtifact.zig +18 -0
  75. package/std/Build/Step/Run.zig +536 -87
  76. package/std/Build/Step/TranslateC.zig +0 -6
  77. package/std/Build/Step.zig +8 -15
  78. package/std/Build/WebServer.zig +29 -17
  79. package/std/Build/abi.zig +47 -11
  80. package/std/Build.zig +17 -14
  81. package/std/Io/Dispatch.zig +2 -0
  82. package/std/Io/File/Reader.zig +3 -1
  83. package/std/Io/File.zig +1 -0
  84. package/std/Io/Kqueue.zig +2 -2
  85. package/std/Io/Threaded.zig +181 -143
  86. package/std/Io/Uring.zig +2 -1
  87. package/std/Io/Writer.zig +41 -41
  88. package/std/Io.zig +970 -2
  89. package/std/Target.zig +3 -2
  90. package/std/Thread.zig +8 -3
  91. package/std/array_hash_map.zig +96 -555
  92. package/std/array_list.zig +22 -31
  93. package/std/bit_set.zig +22 -6
  94. package/std/builtin/assembly.zig +68 -0
  95. package/std/c.zig +17 -17
  96. package/std/compress/flate/Compress.zig +3 -3
  97. package/std/crypto/Certificate/Bundle.zig +15 -1
  98. package/std/crypto/codecs/asn1.zig +33 -18
  99. package/std/crypto/codecs/base64_hex_ct.zig +14 -4
  100. package/std/debug/Dwarf.zig +29 -9
  101. package/std/debug/Info.zig +4 -0
  102. package/std/debug/MachOFile.zig +46 -8
  103. package/std/debug/Pdb.zig +539 -36
  104. package/std/debug/SelfInfo/Elf.zig +19 -18
  105. package/std/debug/SelfInfo/MachO.zig +18 -7
  106. package/std/debug/SelfInfo/Windows.zig +138 -36
  107. package/std/debug.zig +179 -65
  108. package/std/enums.zig +25 -19
  109. package/std/heap/ArenaAllocator.zig +145 -154
  110. package/std/heap/debug_allocator.zig +7 -7
  111. package/std/http/Client.zig +10 -6
  112. package/std/http.zig +11 -9
  113. package/std/json/Stringify.zig +3 -3
  114. package/std/json/dynamic.zig +4 -4
  115. package/std/math/big/int.zig +16 -17
  116. package/std/mem/Allocator.zig +4 -5
  117. package/std/mem.zig +48 -0
  118. package/std/os/emscripten.zig +2 -18
  119. package/std/os/linux/arc.zig +144 -0
  120. package/std/os/linux.zig +21 -4
  121. package/std/os/windows.zig +2 -2
  122. package/std/pdb.zig +143 -4
  123. package/std/posix.zig +6 -12
  124. package/std/priority_dequeue.zig +13 -12
  125. package/std/priority_queue.zig +5 -4
  126. package/std/process/Child.zig +1 -1
  127. package/std/process/Environ.zig +1 -1
  128. package/std/start.zig +17 -4
  129. package/std/std.zig +19 -6
  130. package/std/testing/FailingAllocator.zig +4 -4
  131. package/std/testing/Smith.zig +37 -2
  132. package/std/zig/Ast/Render.zig +186 -458
  133. package/std/zig/Ast.zig +0 -4
  134. package/std/zig/AstGen.zig +44 -7
  135. package/std/zig/AstSmith.zig +2602 -0
  136. package/std/zig/Client.zig +8 -3
  137. package/std/zig/Parse.zig +83 -74
  138. package/std/zig/Server.zig +26 -0
  139. package/std/zig/Zir.zig +17 -0
  140. package/std/zig/c_translation/helpers.zig +14 -9
  141. package/std/zig/llvm/Builder.zig +107 -48
  142. package/std/zig/system.zig +20 -4
  143. package/std/zig/tokenizer.zig +2 -1
  144. package/std/zig.zig +6 -0
  145. package/compiler/aro/aro/Driver/Filesystem.zig +0 -241
  146. package/libc/mingw/complex/cabs.c +0 -48
  147. package/libc/mingw/complex/cabsf.c +0 -48
  148. package/libc/mingw/complex/cacos.c +0 -50
  149. package/libc/mingw/complex/cacosf.c +0 -50
  150. package/libc/mingw/complex/carg.c +0 -48
  151. package/libc/mingw/complex/cargf.c +0 -48
  152. package/libc/mingw/complex/casin.c +0 -50
  153. package/libc/mingw/complex/casinf.c +0 -50
  154. package/libc/mingw/complex/catan.c +0 -50
  155. package/libc/mingw/complex/catanf.c +0 -50
  156. package/libc/mingw/complex/ccos.c +0 -50
  157. package/libc/mingw/complex/ccosf.c +0 -50
  158. package/libc/mingw/complex/cexp.c +0 -48
  159. package/libc/mingw/complex/cexpf.c +0 -48
  160. package/libc/mingw/complex/cimag.c +0 -48
  161. package/libc/mingw/complex/cimagf.c +0 -48
  162. package/libc/mingw/complex/clog.c +0 -48
  163. package/libc/mingw/complex/clog10.c +0 -49
  164. package/libc/mingw/complex/clog10f.c +0 -49
  165. package/libc/mingw/complex/clogf.c +0 -48
  166. package/libc/mingw/complex/conj.c +0 -48
  167. package/libc/mingw/complex/conjf.c +0 -48
  168. package/libc/mingw/complex/cpow.c +0 -48
  169. package/libc/mingw/complex/cpowf.c +0 -48
  170. package/libc/mingw/complex/cproj.c +0 -48
  171. package/libc/mingw/complex/cprojf.c +0 -48
  172. package/libc/mingw/complex/creal.c +0 -48
  173. package/libc/mingw/complex/crealf.c +0 -48
  174. package/libc/mingw/complex/csin.c +0 -50
  175. package/libc/mingw/complex/csinf.c +0 -50
  176. package/libc/mingw/complex/csqrt.c +0 -48
  177. package/libc/mingw/complex/csqrtf.c +0 -48
  178. package/libc/mingw/complex/ctan.c +0 -50
  179. package/libc/mingw/complex/ctanf.c +0 -50
  180. package/libc/mingw/math/arm/s_rint.c +0 -86
  181. package/libc/mingw/math/arm/s_rintf.c +0 -51
  182. package/libc/mingw/math/arm/sincos.S +0 -30
  183. package/libc/mingw/math/arm-common/sincosl.c +0 -13
  184. package/libc/mingw/math/arm64/rint.c +0 -12
  185. package/libc/mingw/math/arm64/rintf.c +0 -12
  186. package/libc/mingw/math/arm64/sincos.S +0 -32
  187. package/libc/mingw/math/bsd_private_base.h +0 -148
  188. package/libc/mingw/math/fdiml.c +0 -24
  189. package/libc/mingw/math/frexpf.c +0 -13
  190. package/libc/mingw/math/frexpl.c +0 -71
  191. package/libc/mingw/math/x86/acosf.c +0 -29
  192. package/libc/mingw/math/x86/atanf.c +0 -23
  193. package/libc/mingw/math/x86/atanl.c +0 -18
  194. package/libc/mingw/math/x86/cos.def.h +0 -65
  195. package/libc/mingw/math/x86/cosl.c +0 -46
  196. package/libc/mingw/math/x86/cosl_internal.S +0 -55
  197. package/libc/mingw/math/x86/ldexp.c +0 -23
  198. package/libc/mingw/math/x86/scalbn.S +0 -41
  199. package/libc/mingw/math/x86/scalbnf.S +0 -40
  200. package/libc/mingw/math/x86/sin.def.h +0 -65
  201. package/libc/mingw/math/x86/sinl.c +0 -46
  202. package/libc/mingw/math/x86/sinl_internal.S +0 -58
  203. package/libc/mingw/math/x86/tanl.S +0 -62
  204. package/libc/mingw/misc/btowc.c +0 -28
  205. package/libc/mingw/misc/wcstof.c +0 -66
  206. package/libc/mingw/misc/wcstoimax.c +0 -132
  207. package/libc/mingw/misc/wcstoumax.c +0 -126
  208. package/libc/mingw/misc/wctob.c +0 -29
  209. package/libc/mingw/misc/winbs_uint64.c +0 -6
  210. package/libc/mingw/misc/winbs_ulong.c +0 -6
  211. package/libc/mingw/misc/winbs_ushort.c +0 -6
  212. package/libc/mingw/stdio/_Exit.c +0 -10
  213. package/libc/mingw/stdio/_findfirst64i32.c +0 -21
  214. package/libc/mingw/stdio/_findnext64i32.c +0 -21
  215. package/libc/mingw/stdio/_fstat64i32.c +0 -37
  216. package/libc/mingw/stdio/_stat64i32.c +0 -37
  217. package/libc/mingw/stdio/_wfindfirst64i32.c +0 -21
  218. package/libc/mingw/stdio/_wfindnext64i32.c +0 -21
  219. package/libc/mingw/stdio/_wstat64i32.c +0 -37
  220. package/libc/musl/src/legacy/isastream.c +0 -7
  221. package/libc/musl/src/legacy/valloc.c +0 -8
  222. package/libc/musl/src/math/__cosl.c +0 -96
  223. package/libc/musl/src/math/__sinl.c +0 -78
  224. package/libc/musl/src/math/__tanl.c +0 -143
  225. package/libc/musl/src/math/aarch64/lrint.c +0 -10
  226. package/libc/musl/src/math/aarch64/lrintf.c +0 -10
  227. package/libc/musl/src/math/aarch64/rintf.c +0 -7
  228. package/libc/musl/src/math/cosl.c +0 -39
  229. package/libc/musl/src/math/fdim.c +0 -10
  230. package/libc/musl/src/math/fdimf.c +0 -10
  231. package/libc/musl/src/math/fdiml.c +0 -18
  232. package/libc/musl/src/math/finite.c +0 -7
  233. package/libc/musl/src/math/finitef.c +0 -7
  234. package/libc/musl/src/math/frexp.c +0 -23
  235. package/libc/musl/src/math/frexpf.c +0 -23
  236. package/libc/musl/src/math/frexpl.c +0 -29
  237. package/libc/musl/src/math/i386/lrint.c +0 -8
  238. package/libc/musl/src/math/i386/lrintf.c +0 -8
  239. package/libc/musl/src/math/i386/rintf.c +0 -7
  240. package/libc/musl/src/math/lrint.c +0 -72
  241. package/libc/musl/src/math/lrintf.c +0 -8
  242. package/libc/musl/src/math/powerpc64/lrint.c +0 -16
  243. package/libc/musl/src/math/powerpc64/lrintf.c +0 -16
  244. package/libc/musl/src/math/rintf.c +0 -30
  245. package/libc/musl/src/math/s390x/rintf.c +0 -15
  246. package/libc/musl/src/math/sincosl.c +0 -60
  247. package/libc/musl/src/math/sinl.c +0 -41
  248. package/libc/musl/src/math/tanl.c +0 -29
  249. package/libc/musl/src/math/x32/lrint.s +0 -5
  250. package/libc/musl/src/math/x32/lrintf.s +0 -5
  251. package/libc/musl/src/math/x86_64/lrint.c +0 -8
  252. package/libc/musl/src/math/x86_64/lrintf.c +0 -8
  253. package/libc/wasi/libc-bottom-half/sources/reallocarray.c +0 -14
package/fuzzer.zig CHANGED
@@ -13,7 +13,7 @@ pub const std_options = std.Options{
13
13
  .logFn = logOverride,
14
14
  };
15
15
 
16
- const io = std.Io.Threaded.global_single_threaded.io();
16
+ const io = Io.Threaded.global_single_threaded.io();
17
17
 
18
18
  fn logOverride(
19
19
  comptime level: std.log.Level,
@@ -77,23 +77,27 @@ const Executable = struct {
77
77
  panic("failed to create directory 'v': {t}", .{e});
78
78
  defer v.close(io);
79
79
 
80
- const coverage_file, const populate = if (v.createFile(io, &file_name, .{
80
+ // Since acquiring locks in createFile is not gauraunteed to be atomic, it is not possible
81
+ // to ensure if we create the file we obtain an exclusive lock to populate it since another
82
+ // process may acquire a shared lock between the file being created and the lock request.
83
+ //
84
+ // Instead, the length will be used to determine if the file needs populated, and no
85
+ // process will acquire a shared lock before the coverage file is known to have been
86
+ // exclusively locked (i.e. is already locked). This means another process than the
87
+ // one which created the file could populate it, which is fine.
88
+ const coverage_file = v.createFile(io, &file_name, .{
81
89
  .read = true,
82
- // If we create the file, we want to block other processes while we populate it
83
- .lock = .exclusive,
84
- .exclusive = true,
85
- })) |f|
86
- .{ f, true }
87
- else |e| switch (e) {
88
- error.PathAlreadyExists => .{ v.openFile(io, &file_name, .{
89
- .mode = .read_write,
90
- .lock = .shared,
91
- }) catch |e2| panic(
92
- "failed to open existing coverage file '{s}': {t}",
93
- .{ &file_name, e2 },
94
- ), false },
95
- else => panic("failed to create coverage file '{s}': {t}", .{ &file_name, e }),
96
- };
90
+ .truncate = false,
91
+ }) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });
92
+
93
+ const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
94
+ "failed to acquire exclusive lock coverage file '{s}': {t}",
95
+ .{ &file_name, e },
96
+ );
97
+ if (!maybe_populate) {
98
+ coverage_file.lock(io, .shared) catch |e|
99
+ panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
100
+ }
97
101
 
98
102
  comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
99
103
  comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
@@ -102,16 +106,21 @@ const Executable = struct {
102
106
  pc_bitset_usizes * @sizeOf(usize) +
103
107
  pcs.len * @sizeOf(usize);
104
108
 
105
- if (populate) {
109
+ var populate: bool = false;
110
+ const size = coverage_file.length(io) catch |e|
111
+ panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
112
+ if (size == 0 and maybe_populate) {
106
113
  coverage_file.setLength(io, coverage_file_len) catch |e|
107
114
  panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
108
- } else {
109
- const size = coverage_file.length(io) catch |e|
110
- panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
111
- if (size != coverage_file_len) panic(
115
+ populate = true;
116
+ } else if (size != coverage_file_len) {
117
+ panic(
112
118
  "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
113
119
  .{ &file_name, size, coverage_file_len },
114
120
  );
121
+ } else if (maybe_populate) {
122
+ coverage_file.lock(io, .shared) catch |e|
123
+ panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e });
115
124
  }
116
125
 
117
126
  var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
@@ -228,6 +237,13 @@ const Executable = struct {
228
237
  return self;
229
238
  }
230
239
 
240
+ /// Asserts `buf[0..2]` is "in"
241
+ fn inputFileName(buf: *[10]u8, i: u32) []u8 {
242
+ assert(buf[0..2].* == "in".*);
243
+ const hex = std.fmt.bufPrint(buf[2..], "{x}", .{i}) catch unreachable;
244
+ return buf[0 .. 2 + hex.len];
245
+ }
246
+
231
247
  pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
232
248
  return .{ .pc_counters = self.pc_counters };
233
249
  }
@@ -263,32 +279,16 @@ const Executable = struct {
263
279
  };
264
280
 
265
281
  const Fuzzer = struct {
282
+ tests: []Test,
283
+ test_i: u32,
284
+ test_one: abi.TestOne,
285
+
266
286
  // The default PRNG is not used here since going through `Random` can be very expensive
267
287
  // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
268
288
  // is simpler since integers are not serialized then deserialized in the random stream.
269
289
  //
270
290
  // This acounts for a 30% performance improvement with LLVM 21.
271
291
  xoshiro: std.Random.Xoshiro256,
272
- test_one: abi.TestOne,
273
-
274
- seen_pcs: []usize,
275
- bests: struct {
276
- len: u32,
277
- quality_buf: []Input.Best,
278
- input_buf: []Input.Best.Map,
279
- },
280
- seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
281
- slices: union {
282
- ints: std.ArrayList([]u64),
283
- bytes: std.ArrayList(Input.Data.Bytes),
284
- },
285
- }, Uid.hashmap_ctx, false),
286
-
287
- /// Past inputs leading to new pc or uid hits.
288
- /// These are randomly mutated in round-robin fashion.
289
- corpus: std.MultiArrayList(Input),
290
- corpus_pos: Input.Index,
291
-
292
292
  bytes_input: std.testing.Smith,
293
293
  input_builder: Input.Builder,
294
294
  /// Number of data calls the current run has made.
@@ -319,13 +319,140 @@ const Fuzzer = struct {
319
319
  },
320
320
 
321
321
  /// As values are provided to the Smith, they are appended to this. If the test
322
- /// crashes, this can be recovered and used to obtain the crashing values.
322
+ /// crashes, this can be recovered and used to obtain the crashing values. It is
323
+ /// also used to rerun fresh inputs.
323
324
  mmap_input: MemoryMappedInput,
324
- /// Filesystem directory containing found inputs for future runs
325
- corpus_dir: Io.Dir,
326
- /// The values in `corpus` past this point directly correspond to what is found
327
- /// in `corpus_dir`.
328
- start_corpus_dir: u32,
325
+ /// The instance is responsible for updating the filesystem corpus.
326
+ ///
327
+ /// Since different fuzzer instances can be out of sync due to finding inputs before recieving
328
+ /// others and nondeterministic tests, the filesystem is only based off the first instance.
329
+ main_instance: bool,
330
+
331
+ const Test = struct {
332
+ const NameHash = u64;
333
+ const dirname_len = @sizeOf(NameHash) * 2;
334
+
335
+ seen_pcs: []usize,
336
+ bests: struct {
337
+ len: u32,
338
+ quality_buf: []Input.Best,
339
+ input_buf: []Input.Best.Map,
340
+ },
341
+ seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
342
+ slices: union {
343
+ ints: std.ArrayList([]u64),
344
+ bytes: std.ArrayList(Input.Data.Bytes),
345
+ },
346
+ }, Uid.hashmap_ctx, false),
347
+
348
+ /// Past inputs leading to new pc or uid hits.
349
+ /// These are randomly mutated in round-robin fashion.
350
+ corpus: std.MultiArrayList(Input),
351
+ corpus_pos: Input.Index,
352
+ /// If this is `math.maxInt(u32)` (reserved), it means the corpus has not been loaded from
353
+ /// the filesystem.
354
+ ///
355
+ /// If `main_instance` is set, the values in `corpus` after this are mirrored to the
356
+ /// filesystem.
357
+ start_mut_corpus: u32,
358
+ dirname: [dirname_len]u8,
359
+ /// Ensures only one fuzzer writes to the corpus.
360
+ ///
361
+ /// Undefined if this is not the main instance.
362
+ lock_file: Io.File,
363
+ received: Received,
364
+
365
+ limit: ?u64,
366
+ /// A batch is the amount of cycles approximently for one second of runtime.
367
+ ///
368
+ /// This value is set to the previous batch's runs per second or run limit.
369
+ batch_cycles: u32,
370
+ batches: u64,
371
+ batches_since_find: u64,
372
+ seen_pc_count: u32,
373
+ };
374
+
375
+ const Received = struct {
376
+ state: State,
377
+ /// Stream of inputs with each prefixed with a u32 length
378
+ inputs: std.ArrayList(u8),
379
+
380
+ pub const empty: Received = .{
381
+ .state = .{
382
+ .pending = false,
383
+ .read_lock = false,
384
+ .write_lock = false,
385
+ },
386
+ .inputs = .empty,
387
+ };
388
+
389
+ pub const State = packed struct(u32) {
390
+ pending: bool,
391
+ read_lock: bool,
392
+ /// If set in conjucation with `read_lock`, then there is a waiter on state.
393
+ write_lock: bool,
394
+ _: u29 = 0,
395
+
396
+ pub fn hasPending(s: *State) bool {
397
+ return @atomicLoad(State, s, .monotonic).pending;
398
+ }
399
+
400
+ pub fn startReadIfPending(s: *State) bool {
401
+ return @cmpxchgWeak(
402
+ State,
403
+ s,
404
+ .{ .pending = true, .read_lock = false, .write_lock = false },
405
+ .{ .pending = true, .read_lock = true, .write_lock = false },
406
+ .acquire,
407
+ .monotonic,
408
+ ) == null;
409
+ }
410
+
411
+ pub fn finishRead(s: *State) void {
412
+ const prev = @atomicRmw(State, s, .And, .{
413
+ .pending = false,
414
+ .read_lock = false,
415
+ .write_lock = true,
416
+ }, .release);
417
+ assert(prev.read_lock);
418
+ if (prev.write_lock) {
419
+ abi.runner_futex_wake(@ptrCast(s), 1);
420
+ }
421
+ }
422
+
423
+ /// Returns if cancelation is requested.
424
+ pub fn startWrite(s: *State) bool {
425
+ var prev = @atomicRmw(State, s, .Or, .{
426
+ .pending = false,
427
+ .read_lock = false,
428
+ .write_lock = true,
429
+ }, .acquire);
430
+ assert(!prev.write_lock);
431
+ while (prev.read_lock) {
432
+ if (abi.runner_futex_wait(@ptrCast(s), @bitCast(prev))) {
433
+ s.* = undefined; // fuzzer is exiting
434
+ return true;
435
+ }
436
+ // Still need `.acquire` ordering so @atomicRmw is necessary
437
+ prev = @atomicRmw(State, s, .Or, .{
438
+ .pending = false,
439
+ .read_lock = false,
440
+ .write_lock = false,
441
+ }, .acquire);
442
+ assert(prev.write_lock);
443
+ }
444
+ return false;
445
+ }
446
+
447
+ pub fn finishWrite(s: *State) void {
448
+ @atomicStore(State, s, .{
449
+ .pending = true,
450
+ .read_lock = false,
451
+ .write_lock = false,
452
+ }, .release);
453
+ }
454
+ };
455
+ };
329
456
 
330
457
  const SeqCopy = union {
331
458
  order_i: u32,
@@ -480,7 +607,9 @@ const Fuzzer = struct {
480
607
  .total_ints = 0,
481
608
  .total_bytes = 0,
482
609
  .weighted_len = 0,
483
- .smithed_len = 4,
610
+ // The - 1 is because we check that `smithed_len` does not overflow a u32;
611
+ // however, `MemoryMappedInput` allows up to `1 << 32`.
612
+ .smithed_len = @sizeOf(abi.MmapInputHeader) - 1,
484
613
  };
485
614
 
486
615
  pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
@@ -591,7 +720,7 @@ const Fuzzer = struct {
591
720
  b.total_ints = 0;
592
721
  b.total_bytes = 0;
593
722
  b.weighted_len = 0;
594
- b.smithed_len = 4;
723
+ b.smithed_len = Builder.init.smithed_len;
595
724
  return input;
596
725
  }
597
726
 
@@ -604,31 +733,128 @@ const Fuzzer = struct {
604
733
  }
605
734
  }
606
735
  b.uid_slices.clearRetainingCapacity();
736
+ b.bytes_table.clearRetainingCapacity();
607
737
  b.total_ints = 0;
608
738
  b.total_bytes = 0;
609
739
  b.weighted_len = 0;
610
- b.smithed_len = 4;
740
+ b.smithed_len = Builder.init.smithed_len;
741
+ }
742
+
743
+ /// Asserts the structure is reset
744
+ pub fn deinit(b: *Builder) void {
745
+ assert(b.uid_slices.entries.len == 0);
746
+ b.uid_slices.deinit(gpa);
747
+ b.bytes_table.deinit(gpa);
748
+ b.* = undefined;
611
749
  }
612
750
  };
613
751
  };
614
752
 
615
- pub fn init() Fuzzer {
616
- if (exec.pc_counters.len > math.maxInt(u32)) @panic("too many pcs");
617
- const f: Fuzzer = .{
618
- .xoshiro = .init(0),
619
- .test_one = undefined,
753
+ pub fn init(n_tests: u32, seed: u64, instance_id: u32, limit: ?u64) Fuzzer {
754
+ const pcs = exec.pc_counters.len;
755
+ if (pcs > math.maxInt(u32)) @panic("too many pcs");
756
+
757
+ const mmap_input = map: {
758
+ // Find a free input file. `instance_id` should give one that is not in use;
759
+ // however, this may not be the case if there are multiple libfuzzers running.
760
+ var input_i = instance_id;
761
+ const input_f = while (true) {
762
+ var name_buf: [10]u8 = undefined;
763
+ name_buf[0..2].* = "in".*;
764
+ const hex = std.fmt.bufPrint(name_buf[2..], "{x}", .{input_i}) catch unreachable;
765
+ const name = name_buf[0 .. 2 + hex.len];
766
+
767
+ if (exec.cache_f.createFile(io, name, .{
768
+ .read = true,
769
+ .truncate = false,
770
+ .lock = .exclusive,
771
+ .lock_nonblocking = true,
772
+ })) |f| {
773
+ break f;
774
+ } else |e| switch (e) {
775
+ // To ensure no input file is unused to avoid the number of input files
776
+ // growing indefinitely across runs, they are linearly searched through.
777
+ //
778
+ // This could be avoided by creating a shared file holding the current number
779
+ // of input files in use; however, using multiple libfuzzers is uncommon and
780
+ // there should not be that many input files to search through anyways.
781
+ error.WouldBlock => input_i += 1,
782
+ else => panic("failed to create file '{s}': {t}", .{ name, e }),
783
+ }
784
+ };
785
+ break :map MemoryMappedInput.init(input_f, instance_id, input_i);
786
+ };
620
787
 
621
- .seen_pcs = gpa.alloc(usize, bitsetUsizes(exec.pc_counters.len)) catch @panic("OOM"),
622
- .bests = .{
623
- .len = 0,
624
- .quality_buf = gpa.alloc(Input.Best, exec.pc_counters.len) catch @panic("OOM"),
625
- .input_buf = gpa.alloc(Input.Best.Map, exec.pc_counters.len) catch @panic("OOM"),
626
- },
627
- .seen_uids = .empty,
788
+ const tests = gpa.alloc(Test, n_tests) catch @panic("OOM");
789
+ const seen_pcs_len = bitsetUsizes(pcs);
790
+ var seen_pcs_bufs = gpa.alloc(usize, seen_pcs_len * n_tests) catch @panic("OOM");
791
+ var best_quality_bufs = gpa.alloc(Input.Best, pcs * n_tests) catch @panic("OOM");
792
+ var best_input_bufs = gpa.alloc(Input.Best.Map, pcs * n_tests) catch @panic("OOM");
793
+ @memset(seen_pcs_bufs, 0);
794
+ for (0.., tests) |i, *t| {
795
+ const name = abi.runner_test_name(@intCast(i)).toSlice();
796
+ // A hash is used as the dirname instead of the actual test name since the test name
797
+ // may be not allowed by the filesystem or have a special meaning (e.g. absolute /
798
+ // relative paths).
799
+ const dirname = std.fmt.hex(std.hash.Wyhash.hash(0, name));
800
+
801
+ const lock_file = file: {
802
+ if (instance_id != 0) break :file undefined;
803
+
804
+ exec.cache_f.createDir(io, &dirname, .default_dir) catch |e| switch (e) {
805
+ error.PathAlreadyExists => {},
806
+ else => panic("failed to create directory '{s}': {t}", .{ &dirname, e }),
807
+ };
808
+
809
+ var cname: CorpusFileName = .fromTest(dirname);
810
+ const lock_name = cname.syncLockName();
811
+ break :file exec.cache_f.createFile(io, lock_name, .{
812
+ .truncate = false,
813
+ .lock = .exclusive,
814
+ .lock_nonblocking = true,
815
+ }) catch |e| switch (e) {
816
+ error.WouldBlock => panic("corpus of '{s}' is in use by another fuzzer", .{name}),
817
+ else => panic("failed to create file '{s}': {t}", .{ lock_name, e }),
818
+ };
819
+ };
820
+
821
+ t.* = .{
822
+ .seen_pcs = seen_pcs_bufs[0..seen_pcs_len],
823
+ .bests = .{
824
+ .len = 0,
825
+ .quality_buf = best_quality_bufs[0..pcs],
826
+ .input_buf = best_input_bufs[0..pcs],
827
+ },
828
+ .seen_uids = .empty,
829
+
830
+ .corpus = .empty,
831
+ .corpus_pos = @enumFromInt(0),
832
+ .start_mut_corpus = math.maxInt(u32),
833
+ .dirname = dirname,
834
+ .lock_file = lock_file,
835
+ .received = .empty,
836
+
837
+ .limit = limit,
838
+ .batch_cycles = 1,
839
+ .batches = 0,
840
+ .batches_since_find = 0,
841
+ .seen_pc_count = 0,
842
+ };
843
+ t.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
844
+ seen_pcs_bufs = seen_pcs_bufs[seen_pcs_len..];
845
+ best_quality_bufs = best_quality_bufs[pcs..];
846
+ best_input_bufs = best_input_bufs[pcs..];
847
+ }
848
+ assert(seen_pcs_bufs.len == 0);
849
+ assert(best_quality_bufs.len == 0);
850
+ assert(best_input_bufs.len == 0);
628
851
 
629
- .corpus = .empty,
630
- .corpus_pos = undefined,
852
+ return .{
853
+ .tests = tests,
854
+ .test_i = undefined,
855
+ .test_one = undefined,
631
856
 
857
+ .xoshiro = .init(seed),
632
858
  .bytes_input = undefined,
633
859
  .input_builder = .init,
634
860
  .req_values = undefined,
@@ -636,97 +862,144 @@ const Fuzzer = struct {
636
862
  .uid_data_i = .empty,
637
863
  .mut_data = undefined,
638
864
 
639
- .mmap_input = undefined,
640
- .corpus_dir = undefined,
641
- .start_corpus_dir = undefined,
865
+ .mmap_input = mmap_input,
866
+ .main_instance = instance_id == 0,
642
867
  };
643
- @memset(f.seen_pcs, 0);
644
- return f;
645
868
  }
646
869
 
647
- /// May only be called after `f.setTest` has been called
648
- pub fn reset(f: *Fuzzer) void {
649
- f.test_one = undefined;
650
-
651
- @memset(f.seen_pcs, 0);
652
- f.bests.len = 0;
653
- @memset(f.bests.quality_buf, undefined);
654
- @memset(f.bests.input_buf, undefined);
655
- for (f.seen_uids.keys(), f.seen_uids.values()) |uid, *u| {
656
- switch (uid.kind) {
657
- .int => u.slices.ints.deinit(gpa),
658
- .bytes => u.slices.bytes.deinit(gpa),
870
+ pub fn deinit(f: *Fuzzer) void {
871
+ const pcs = exec.pc_counters.len;
872
+ const n_tests = f.tests.len;
873
+ gpa.free(f.tests[0].seen_pcs.ptr[0 .. bitsetUsizes(pcs) * n_tests]);
874
+ gpa.free(f.tests[0].bests.quality_buf.ptr[0 .. pcs * n_tests]);
875
+ gpa.free(f.tests[0].bests.input_buf.ptr[0 .. pcs * n_tests]);
876
+ for (f.tests) |*t| {
877
+ const seen_uids = t.seen_uids.entries.slice();
878
+ for (seen_uids.items(.key), seen_uids.items(.value)) |uid, *data| {
879
+ switch (uid.kind) {
880
+ .int => data.slices.ints.deinit(gpa),
881
+ .bytes => data.slices.bytes.deinit(gpa),
882
+ }
883
+ }
884
+ t.seen_uids.deinit(gpa);
885
+ const corpus = t.corpus.slice();
886
+ // The first input is `Input.none` and so is skipped as `deinit` is illegal.
887
+ for (1..corpus.len) |i| {
888
+ var in = corpus.get(i);
889
+ in.deinit();
659
890
  }
891
+ if (f.main_instance) {
892
+ t.lock_file.close(io);
893
+ }
894
+ t.received.inputs.deinit(gpa);
660
895
  }
661
- f.seen_uids.clearRetainingCapacity();
896
+ gpa.free(f.tests);
897
+ f.input_builder.deinit();
898
+ f.mmap_input.deinit();
899
+ f.* = undefined;
900
+ }
662
901
 
663
- f.corpus.clearRetainingCapacity();
664
- f.corpus_pos = undefined;
902
+ pub fn ensureCorpusLoaded(f: *Fuzzer) void {
903
+ const t = &f.tests[f.test_i];
904
+ if (t.start_mut_corpus != math.maxInt(u32)) return;
665
905
 
666
- f.uid_data_i.clearRetainingCapacity();
906
+ const start_mut: u32 = @intCast(t.corpus.len);
907
+ if (!f.main_instance) {
908
+ // Inputs can be culled as added since filesystem synchronacy is not required
909
+ t.start_mut_corpus = start_mut;
910
+ }
667
911
 
668
- f.mmap_input.deinit();
669
- f.corpus_dir.close(io);
670
- f.start_corpus_dir = undefined;
671
- }
912
+ read_corpus: {
913
+ var cname: CorpusFileName = .fromTest(t.dirname);
672
914
 
673
- pub fn setTest(f: *Fuzzer, test_one: abi.TestOne, unit_test_name: []const u8) void {
674
- f.test_one = test_one;
675
- f.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e|
676
- panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
677
- f.mmap_input = map: {
678
- const input = f.corpus_dir.createFile(io, "in", .{
679
- .read = true,
915
+ const readlock_name = cname.readLockName();
916
+ const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
680
917
  .truncate = false,
681
- // In case any other fuzz tests are running under the same test name,
682
- // the input file is exclusively locked to ensures only one proceeds.
683
- .lock = .exclusive,
684
- .lock_nonblocking = true,
918
+ .lock = .shared,
685
919
  }) catch |e| switch (e) {
686
- error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),
687
- else => panic("failed to create input file 'in': {t}", .{e}),
920
+ // FileNotFound means the corpus directory does not exist, which means it is empty
921
+ error.FileNotFound => break :read_corpus,
922
+ else => panic("failed to open '{s}': {t}", .{ readlock_name, e }),
688
923
  };
924
+ defer readlock_file.close(io);
925
+
926
+ var input_buf: std.ArrayList(u8) = .empty;
927
+ defer input_buf.deinit(gpa);
928
+ var i: u32 = 0;
929
+ while (true) {
930
+ const name = cname.inputName(i);
931
+ const input_file = exec.cache_f.openFile(io, name, .{}) catch |e| switch (e) {
932
+ error.FileNotFound => break,
933
+ else => panic("failed to open input file '{s}': {t}", .{ name, e }),
934
+ };
935
+
936
+ const len = input_file.length(io) catch |e|
937
+ panic("failed to get length of '{s}': {t}", .{ name, e });
938
+ const ulen = math.cast(usize, len) orelse @panic("OOM");
939
+ input_buf.resize(gpa, ulen) catch @panic("OOM");
940
+
941
+ var r = input_file.readerStreaming(io, &.{});
942
+ r.interface.readSliceAll(input_buf.items) catch |e| switch (e) {
943
+ error.ReadFailed => panic(
944
+ "failed to read from input file '{s}': {t}",
945
+ .{ name, r.err.? },
946
+ ),
947
+ error.EndOfStream => panic(
948
+ "input file '{s}' ended before its reported length",
949
+ .{name},
950
+ ),
951
+ };
952
+ f.newInputExternal(input_buf.items);
689
953
 
690
- var size = input.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});
691
- if (size < std.heap.page_size_max) {
692
- size = std.heap.page_size_max;
693
- input.setLength(io, size) catch |e| panic("failed to resize input file 'in': {t}", .{e});
954
+ i += 1; // Cannot overflow due to corpus 32-bit size limit
694
955
  }
956
+ }
695
957
 
696
- break :map MemoryMappedInput.init(input, size) catch |e|
697
- panic("failed to memmap input file 'in': {t}", .{e});
698
- };
958
+ if (f.main_instance) {
959
+ t.start_mut_corpus = start_mut;
699
960
 
700
- // Perform a dry-run of the stored input in case it might reproduce a crash.
701
- const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);
702
- if (len < f.mmap_input.mmap.memory[4..].len) {
703
- f.mmap_input.len = len;
704
- _ = f.runBytes(f.mmap_input.inputSlice(), .bytes_dry);
705
- f.mmap_input.clearRetainingCapacity();
961
+ // Cull old inputs
962
+ const ref = t.corpus.items(.ref);
963
+ var i: usize = t.start_mut_corpus;
964
+ while (i < t.corpus.len) {
965
+ if (ref[i].best_i_len == 0) {
966
+ f.removeInput(@enumFromInt(i));
967
+ } else {
968
+ i += 1;
969
+ }
970
+ }
706
971
  }
972
+
973
+ t.corpus_pos = @enumFromInt(0);
707
974
  }
708
975
 
709
- pub fn loadCorpus(f: *Fuzzer) void {
710
- f.corpus_pos = @enumFromInt(f.corpus.len);
711
- f.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
712
- f.start_corpus_dir = @intCast(f.corpus.len);
713
- while (true) {
714
- var name_buf: [8]u8 = undefined;
715
- const name = f.corpusFileName(&name_buf, @enumFromInt(f.corpus.len));
716
- const bytes = f.corpus_dir.readFileAlloc(io, name, gpa, .unlimited) catch |e| switch (e) {
717
- error.FileNotFound => break,
718
- else => panic("failed to read corpus file '{s}': {t}", .{ name, e }),
719
- };
720
- defer gpa.free(bytes);
721
- f.newInputExternal(bytes);
976
+ const CorpusFileName = struct {
977
+ buf: [Test.dirname_len + 9]u8,
978
+
979
+ pub fn fromTest(dirname: [Test.dirname_len]u8) CorpusFileName {
980
+ var n: CorpusFileName = undefined;
981
+ n.buf[0..dirname.len].* = dirname;
982
+ n.buf[dirname.len] = Io.Dir.path.sep;
983
+ return n;
722
984
  }
723
- f.corpus_pos = @enumFromInt(0);
724
- }
725
985
 
726
- fn corpusFileName(f: *Fuzzer, buf: *[8]u8, i: Input.Index) []u8 {
727
- const dir_i = @intFromEnum(i) - f.start_corpus_dir;
728
- return std.fmt.bufPrint(buf, "{x}", .{dir_i}) catch unreachable;
729
- }
986
+ pub fn readLockName(n: *CorpusFileName) []u8 {
987
+ const basename = "readlock";
988
+ n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
989
+ return n.buf[0 .. Test.dirname_len + 1 + basename.len];
990
+ }
991
+
992
+ pub fn syncLockName(n: *CorpusFileName) []u8 {
993
+ const basename = "synclock";
994
+ n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
995
+ return n.buf[0 .. Test.dirname_len + 1 + basename.len];
996
+ }
997
+
998
+ pub fn inputName(n: *CorpusFileName, i: u32) []u8 {
999
+ const hex = std.fmt.bufPrint(n.buf[Test.dirname_len + 1 ..][0..8], "{x}", .{i}) catch unreachable;
1000
+ return n.buf[0 .. Test.dirname_len + 1 + hex.len];
1001
+ }
1002
+ };
730
1003
 
731
1004
  fn rngInt(f: *Fuzzer, T: type) T {
732
1005
  comptime assert(@bitSizeOf(T) <= 64);
@@ -749,13 +1022,14 @@ const Fuzzer = struct {
749
1022
  };
750
1023
 
751
1024
  fn isFresh(f: *Fuzzer) bool {
1025
+ const t = &f.tests[f.test_i];
752
1026
  // Store as a bool instead of returning immediately to aid optimizations
753
1027
  // by reducing branching since a fresh input is the unlikely case.
754
1028
  var fresh: bool = false;
755
1029
 
756
1030
  var n_pcs: u32 = 0;
757
1031
  var hit_pcs = exec.pcBitsetIterator();
758
- for (f.seen_pcs) |seen| {
1032
+ for (t.seen_pcs) |seen| {
759
1033
  const hits = hit_pcs.next();
760
1034
  fresh |= hits & ~seen != 0;
761
1035
  n_pcs += @popCount(hits);
@@ -768,7 +1042,7 @@ const Fuzzer = struct {
768
1042
  .bytes = f.req_bytes,
769
1043
  },
770
1044
  };
771
- for (f.bests.quality_buf[0..f.bests.len]) |best| {
1045
+ for (t.bests.quality_buf[0..t.bests.len]) |best| {
772
1046
  if (exec.pc_counters[best.pc] == 0) continue;
773
1047
  fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);
774
1048
  }
@@ -776,12 +1050,15 @@ const Fuzzer = struct {
776
1050
  return fresh;
777
1051
  }
778
1052
 
1053
+ /// It is the callee's responsibility to reset the corpus pos
1054
+ ///
779
1055
  /// Returns if `error.SkipZigTest` was indicated
780
1056
  fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {
781
1057
  assert(mode == .bytes_dry or mode == .bytes_fresh);
782
1058
 
783
1059
  f.bytes_input = .{ .in = bytes };
784
- f.corpus_pos = mode;
1060
+ f.tests[f.test_i].corpus_pos = mode;
1061
+ defer f.tests[f.test_i].corpus_pos = undefined;
785
1062
  return f.run(0); // 0 since `f.uid_data` is unused
786
1063
  }
787
1064
 
@@ -791,89 +1068,112 @@ const Fuzzer = struct {
791
1068
  exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,
792
1069
  );
793
1070
 
1071
+ const t = &f.tests[f.test_i];
794
1072
  var hit_pcs = exec.pcBitsetIterator();
795
- for (f.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {
1073
+ for (t.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {
796
1074
  const new = hit_pcs.next() & ~seen.*;
797
1075
  if (new != 0) {
798
1076
  seen.* |= new;
799
1077
  _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);
1078
+ t.seen_pc_count += @popCount(new);
800
1079
  }
801
1080
  }
802
1081
  }
803
1082
 
804
- fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32, modify_fs_corpus: bool) void {
805
- const ref = &f.corpus.items(.ref)[@intFromEnum(i)];
1083
+ fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32) void {
1084
+ const t = &f.tests[f.test_i];
1085
+ const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
806
1086
  const list_i = mem.indexOfScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;
807
1087
  ref.best_i_len -= 1;
808
1088
  ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len];
809
1089
 
810
- if (ref.best_i_len == 0 and @intFromEnum(i) >= f.start_corpus_dir and modify_fs_corpus) {
1090
+ if (ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus) {
811
1091
  // The input is no longer valuable, so remove it.
812
- var removed_input = f.corpus.get(@intFromEnum(i));
813
- for (
814
- removed_input.data.uid_slices.keys(),
815
- removed_input.data.uid_slices.values(),
816
- removed_input.seen_uid_i,
817
- ) |uid, slice, seen_uid_i| {
818
- switch (uid.kind) {
819
- .int => {
820
- const seen_ints = &f.seen_uids.values()[seen_uid_i].slices.ints;
821
- const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
822
- _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
823
- if (removed_ints.ptr == ints.ptr) {
824
- assert(removed_ints.len == ints.len);
825
- break idx;
826
- }
827
- } else unreachable);
828
- },
829
- .bytes => {
830
- const seen_bytes = &f.seen_uids.values()[seen_uid_i].slices.bytes;
831
- const removed_bytes: Input.Data.Bytes = .{
832
- .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
833
- .table = removed_input.data.bytes.table,
834
- };
835
- _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
836
- if (removed_bytes.entries.ptr == bytes.entries.ptr) {
837
- assert(removed_bytes.entries.len == bytes.entries.len);
838
- assert(removed_bytes.table.ptr == bytes.table.ptr);
839
- assert(removed_bytes.table.len == bytes.table.len);
840
- break idx;
841
- }
842
- } else unreachable);
843
- },
844
- }
1092
+ f.removeInput(i);
1093
+ }
1094
+ }
1095
+
1096
+ fn removeInput(f: *Fuzzer, i: Input.Index) void {
1097
+ const t = &f.tests[f.test_i];
1098
+ const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
1099
+ assert(ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus);
1100
+
1101
+ var removed_input = t.corpus.get(@intFromEnum(i));
1102
+ for (
1103
+ removed_input.data.uid_slices.keys(),
1104
+ removed_input.data.uid_slices.values(),
1105
+ removed_input.seen_uid_i,
1106
+ ) |uid, slice, seen_uid_i| {
1107
+ switch (uid.kind) {
1108
+ .int => {
1109
+ const seen_ints = &t.seen_uids.values()[seen_uid_i].slices.ints;
1110
+ const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
1111
+ _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
1112
+ if (removed_ints.ptr == ints.ptr) {
1113
+ assert(removed_ints.len == ints.len);
1114
+ break idx;
1115
+ }
1116
+ } else unreachable);
1117
+ },
1118
+ .bytes => {
1119
+ const seen_bytes = &t.seen_uids.values()[seen_uid_i].slices.bytes;
1120
+ const removed_bytes: Input.Data.Bytes = .{
1121
+ .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
1122
+ .table = removed_input.data.bytes.table,
1123
+ };
1124
+ _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
1125
+ if (removed_bytes.entries.ptr == bytes.entries.ptr) {
1126
+ assert(removed_bytes.entries.len == bytes.entries.len);
1127
+ assert(removed_bytes.table.ptr == bytes.table.ptr);
1128
+ assert(removed_bytes.table.len == bytes.table.len);
1129
+ break idx;
1130
+ }
1131
+ } else unreachable);
1132
+ },
845
1133
  }
846
- removed_input.deinit();
847
- f.corpus.swapRemove(@intFromEnum(i));
1134
+ }
1135
+ removed_input.deinit();
1136
+ t.corpus.swapRemove(@intFromEnum(i));
848
1137
 
849
- var removed_name_buf: [8]u8 = undefined;
850
- const removed_name = f.corpusFileName(&removed_name_buf, i);
1138
+ if (@intFromEnum(i) != t.corpus.len) {
1139
+ // The last item was moved so its refs need updated.
1140
+ // `ref` can be reused since it was a swap remove.
1141
+ for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
1142
+ const best = &t.bests.input_buf[update_pc_i];
1143
+ assert(@intFromEnum(best.min) == t.corpus.len or
1144
+ @intFromEnum(best.max) == t.corpus.len);
851
1145
 
852
- if (@intFromEnum(i) == f.corpus.len) {
853
- f.corpus_dir.deleteFile(io, removed_name) catch |e| panic(
854
- "failed to remove corpus file '{s}': {t}",
855
- .{ removed_name, e },
856
- );
857
- return; // No item moved so no refs to update
1146
+ if (@intFromEnum(best.min) == t.corpus.len) best.min = i;
1147
+ if (@intFromEnum(best.max) == t.corpus.len) best.max = i;
858
1148
  }
1149
+ }
859
1150
 
860
- var swapped_name_buf: [8]u8 = undefined;
861
- const swapped_name = f.corpusFileName(&swapped_name_buf, @enumFromInt(f.corpus.len));
1151
+ if (!f.main_instance) return;
1152
+
1153
+ var removed_cname: CorpusFileName = .fromTest(t.dirname);
1154
+ // Temporarily use removed_name to construct the path to the lock
1155
+ const readlock_name = removed_cname.readLockName();
1156
+ const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
1157
+ .truncate = false,
1158
+ .lock = .exclusive,
1159
+ }) catch |e| panic("failed to open '{s}': {t}", .{ readlock_name, e });
1160
+ defer readlock_file.close(io);
1161
+
1162
+ const removed_name = removed_cname.inputName(@intFromEnum(i) - t.start_mut_corpus);
1163
+ if (@intFromEnum(i) == t.corpus.len) {
1164
+ exec.cache_f.deleteFile(io, removed_name) catch |e| panic(
1165
+ "failed to remove corpus file '{s}': {t}",
1166
+ .{ removed_name, e },
1167
+ );
1168
+ } else {
1169
+ var swapped_cname: CorpusFileName = .fromTest(t.dirname);
1170
+ const swapped_i: u32 = @intCast(t.corpus.len);
1171
+ const swapped_name = swapped_cname.inputName(swapped_i - t.start_mut_corpus);
862
1172
 
863
- f.corpus_dir.rename(swapped_name, f.corpus_dir, removed_name, io) catch |e| panic(
1173
+ exec.cache_f.rename(swapped_name, exec.cache_f, removed_name, io) catch |e| panic(
864
1174
  "failed to rename corpus file '{s}' to '{s}': {t}",
865
1175
  .{ swapped_name, removed_name, e },
866
1176
  );
867
-
868
- // Update refrences. `ref` can be reused since it was a swap remove
869
- for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
870
- const best = &f.bests.input_buf[update_pc_i];
871
- assert(@intFromEnum(best.min) == f.corpus.len or
872
- @intFromEnum(best.max) == f.corpus.len);
873
-
874
- if (@intFromEnum(best.min) == f.corpus.len) best.min = i;
875
- if (@intFromEnum(best.max) == f.corpus.len) best.max = i;
876
- }
877
1177
  }
878
1178
  }
879
1179
 
@@ -881,51 +1181,30 @@ const Fuzzer = struct {
881
1181
  // All inputs including the corpus are required to go through the memory
882
1182
  // mapped input in case they cause a crash so they can be identified.
883
1183
  f.mmap_input.appendSlice(bytes);
884
- f.newInput(false);
1184
+ f.newInput();
885
1185
  f.mmap_input.clearRetainingCapacity();
886
1186
  }
887
1187
 
888
- fn newInput(f: *Fuzzer, modify_fs_corpus: bool) void {
1188
+ fn newInput(f: *Fuzzer) void {
1189
+ const t = &f.tests[f.test_i];
1190
+ const new_is_mut = t.start_mut_corpus != math.maxInt(u32);
1191
+ assert(new_is_mut == (t.corpus.len >= t.start_mut_corpus));
889
1192
  const bytes = f.mmap_input.inputSlice();
890
1193
  // `error.SkipZigTest` here can be from one of these causes:
891
- // * The test has changed and a previous corpus input is being used
892
- // * An input provided by the test results in it
1194
+ // * A previous corpus input after the test has changed
1195
+ // * An input provided by the test
893
1196
  // * The test is non-deterministic
894
1197
  if (f.runBytes(bytes, .bytes_fresh) and
895
- modify_fs_corpus // The input is not from the filesystem.
896
- // This is required to ensure the filesystem and process corpus are the same.
1198
+ new_is_mut // The corpus must be mutable at this point for the input to be
1199
+ // omitted (i.e. test corpus inputs and filesystem inputs cannot be dropped)
897
1200
  ) {
898
1201
  f.input_builder.reset();
899
- f.corpus_pos = @enumFromInt(0);
1202
+ t.corpus_pos = @enumFromInt(0);
900
1203
  return;
901
1204
  }
1205
+
902
1206
  f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
903
1207
  f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
904
- var input = f.input_builder.build();
905
-
906
- f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
907
- for (
908
- input.seen_uid_i,
909
- input.data.uid_slices.keys(),
910
- input.data.uid_slices.values(),
911
- ) |*i, uid, slice| {
912
- const gop = f.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
913
- .int => .{ .slices = .{ .ints = .empty } },
914
- .bytes => .{ .slices = .{ .bytes = .empty } },
915
- }) catch @panic("OOM");
916
- switch (uid.kind) {
917
- .int => f.seen_uids.values()[gop.index].slices.ints.append(
918
- gpa,
919
- input.data.ints[slice.base..][0..slice.len],
920
- ) catch @panic("OOM"),
921
- .bytes => f.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
922
- .entries = input.data.bytes.entries[slice.base..][0..slice.len],
923
- .table = input.data.bytes.table,
924
- }) catch @panic("OOM"),
925
- }
926
- i.* = @intCast(gop.index);
927
- }
928
-
929
1208
  const quality: Input.Best.Quality = .{
930
1209
  .n_pcs = n_pcs: {
931
1210
  @setRuntimeSafety(builtin.mode == .Debug); // Necessary for vectorization
@@ -942,7 +1221,7 @@ const Fuzzer = struct {
942
1221
  };
943
1222
 
944
1223
  var best_i_list: std.ArrayList(u32) = .empty;
945
- for (0.., f.bests.quality_buf[0..f.bests.len]) |best_i, best| {
1224
+ for (0.., t.bests.quality_buf[0..t.bests.len]) |best_i, best| {
946
1225
  if (exec.pc_counters[best.pc] == 0) continue;
947
1226
 
948
1227
  const better_min = quality.betterLess(best.min);
@@ -953,30 +1232,30 @@ const Fuzzer = struct {
953
1232
  }
954
1233
  best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM");
955
1234
 
956
- const map = &f.bests.input_buf[best_i];
1235
+ const map = &t.bests.input_buf[best_i];
957
1236
  if (map.min != map.max) {
958
1237
  if (better_min) {
959
- f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);
1238
+ f.removeBest(map.min, @intCast(best_i));
960
1239
  }
961
1240
  if (better_max) {
962
- f.removeBest(map.max, @intCast(best_i), modify_fs_corpus);
1241
+ f.removeBest(map.max, @intCast(best_i));
963
1242
  }
964
1243
  } else {
965
1244
  if (better_min and better_max) {
966
- f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);
1245
+ f.removeBest(map.min, @intCast(best_i));
967
1246
  }
968
1247
  }
969
1248
  }
970
1249
 
971
1250
  // Must come after the above since some inputs may be removed
972
- const input_i: Input.Index = @enumFromInt(f.corpus.len);
1251
+ const input_i: Input.Index = @enumFromInt(t.corpus.len);
973
1252
  if (input_i == Input.Index.reserved_start) {
974
1253
  @panic("corpus size limit exceeded");
975
1254
  }
976
1255
 
977
1256
  for (best_i_list.items) |i| {
978
- const best_qual = &f.bests.quality_buf[i];
979
- const best_map = &f.bests.input_buf[i];
1257
+ const best_qual = &t.bests.quality_buf[i];
1258
+ const best_map = &t.bests.input_buf[i];
980
1259
 
981
1260
  if (quality.betterLess(best_qual.min)) {
982
1261
  best_qual.min = quality;
@@ -994,42 +1273,74 @@ const Fuzzer = struct {
994
1273
  continue;
995
1274
  }
996
1275
 
997
- if ((f.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {
1276
+ if ((t.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {
998
1277
  @branchHint(.unlikely);
999
- best_i_list.append(gpa, f.bests.len) catch @panic("OOM");
1000
- f.bests.quality_buf[f.bests.len] = .{
1278
+ best_i_list.append(gpa, t.bests.len) catch @panic("OOM");
1279
+ t.bests.quality_buf[t.bests.len] = .{
1001
1280
  .pc = @intCast(i),
1002
1281
  .min = quality,
1003
1282
  .max = quality,
1004
1283
  };
1005
- f.bests.input_buf[f.bests.len] = .{ .min = input_i, .max = input_i };
1006
- f.bests.len += 1;
1284
+ t.bests.input_buf[t.bests.len] = .{ .min = input_i, .max = input_i };
1285
+ t.bests.len += 1;
1007
1286
  }
1008
1287
  }
1009
1288
 
1010
- if (best_i_list.items.len == 0 and
1011
- modify_fs_corpus // Found by freshness; otherwise, it does not need to be better
1012
- ) {
1013
- @branchHint(.cold); // Nondeterministic test
1014
- std.log.warn("nondeterministic rerun", .{});
1289
+ // Having no best qualities could be from one of these causes:
1290
+ // * A previous corpus input after the test has changed
1291
+ // * An input provided by the test
1292
+ // * The test is non-deterministic
1293
+ if (best_i_list.items.len == 0 and new_is_mut) {
1294
+ assert(best_i_list.capacity == 0);
1295
+ f.input_builder.reset();
1296
+ t.corpus_pos = @enumFromInt(0);
1015
1297
  return;
1016
1298
  }
1017
1299
 
1300
+ var input = f.input_builder.build();
1301
+ f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
1302
+ for (
1303
+ input.seen_uid_i,
1304
+ input.data.uid_slices.keys(),
1305
+ input.data.uid_slices.values(),
1306
+ ) |*i, uid, slice| {
1307
+ const gop = t.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
1308
+ .int => .{ .slices = .{ .ints = .empty } },
1309
+ .bytes => .{ .slices = .{ .bytes = .empty } },
1310
+ }) catch @panic("OOM");
1311
+ switch (uid.kind) {
1312
+ .int => t.seen_uids.values()[gop.index].slices.ints.append(
1313
+ gpa,
1314
+ input.data.ints[slice.base..][0..slice.len],
1315
+ ) catch @panic("OOM"),
1316
+ .bytes => t.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
1317
+ .entries = input.data.bytes.entries[slice.base..][0..slice.len],
1318
+ .table = input.data.bytes.table,
1319
+ }) catch @panic("OOM"),
1320
+ }
1321
+ i.* = @intCast(gop.index);
1322
+ }
1323
+
1018
1324
  input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");
1019
1325
  input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);
1020
- f.corpus.append(gpa, input) catch @panic("OOM");
1021
- f.corpus_pos = input_i;
1326
+ t.corpus.append(gpa, input) catch @panic("OOM");
1327
+ t.corpus_pos = input_i;
1022
1328
 
1023
1329
  // Must come after the above since `seen_pcs` is used
1024
1330
  f.updateSeenPcs();
1025
1331
 
1026
- if (!modify_fs_corpus) return;
1027
-
1028
- // Write new input to cache
1029
- var name_buf: [8]u8 = undefined;
1030
- const name = f.corpusFileName(&name_buf, input_i);
1031
- f.corpus_dir.writeFile(io, .{ .sub_path = name, .data = bytes }) catch |e|
1032
- panic("failed to write corpus file '{s}': {t}", .{ name, e });
1332
+ t.batches_since_find = 0;
1333
+ if (f.main_instance and new_is_mut) {
1334
+ // Only the main instance increments the number of unique runs since it is likely
1335
+ // multiple instances find the same new input at the same time.
1336
+ _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1337
+ // Write new input to the cache
1338
+ var cname: CorpusFileName = .fromTest(t.dirname);
1339
+ const name = cname.inputName(@intFromEnum(input_i) - t.start_mut_corpus);
1340
+ exec.cache_f.writeFile(io, .{ .sub_path = name, .data = bytes, .flags = .{
1341
+ .exclusive = true,
1342
+ } }) catch |e| panic("failed to write corpus file '{s}': {t}", .{ name, e });
1343
+ }
1033
1344
  }
1034
1345
 
1035
1346
  /// Returns if `error.SkipZigTest` was indicated
@@ -1061,8 +1372,10 @@ const Fuzzer = struct {
1061
1372
 
1062
1373
  pub fn cycle(f: *Fuzzer) void {
1063
1374
  assert(f.mmap_input.len == 0);
1064
- const corpus = f.corpus.slice();
1065
- const corpus_i = @intFromEnum(f.corpus_pos);
1375
+
1376
+ const t = &f.tests[f.test_i];
1377
+ const corpus = t.corpus.slice();
1378
+ const corpus_i = @intFromEnum(t.corpus_pos);
1066
1379
 
1067
1380
  var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1068
1381
  var n_mutate = mutCount(small_entronopy.take(u16));
@@ -1118,13 +1431,181 @@ const Fuzzer = struct {
1118
1431
  if (!skip and f.isFresh()) {
1119
1432
  @branchHint(.unlikely);
1120
1433
 
1121
- _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1122
- f.newInput(true);
1434
+ abi.runner_broadcast_input(f.test_i, .fromSlice(f.mmap_input.inputSlice()));
1435
+ f.newInput();
1436
+ } else {
1437
+ assert(@intFromEnum(t.corpus_pos) < t.corpus.len);
1438
+ t.corpus_pos = @enumFromInt((@intFromEnum(t.corpus_pos) + 1) % t.corpus.len);
1123
1439
  }
1124
1440
  f.mmap_input.clearRetainingCapacity();
1441
+ }
1442
+
1443
+ fn takeReceived(f: *Fuzzer) void {
1444
+ const t = &f.tests[f.test_i];
1445
+ if (t.received.state.startReadIfPending()) {
1446
+ defer t.received.state.finishRead();
1447
+ const inputs = &t.received.inputs;
1448
+ var rem = inputs.items;
1449
+
1450
+ while (true) {
1451
+ const len: u32 = @bitCast(rem[0..4].*);
1452
+ rem = rem[4..];
1453
+ const bytes = rem[0..len];
1454
+ rem = rem[len..];
1455
+
1456
+ f.mmap_input.appendSlice(bytes);
1457
+ f.newInput();
1458
+ f.mmap_input.clearRetainingCapacity();
1459
+
1460
+ if (rem.len == 0) break;
1461
+ }
1462
+
1463
+ inputs.clearRetainingCapacity();
1464
+ }
1465
+ }
1466
+
1467
+ pub fn batch(f: *Fuzzer) void {
1468
+ const t = &f.tests[f.test_i];
1469
+ assert(t.limit != 0);
1470
+ t.batches += 1;
1471
+ t.batches_since_find += 1;
1472
+ if (f.tests.len != 1) {
1473
+ // Use cpu_process since some fuzz tests may spawn
1474
+ // other threads and give all the work to them.
1475
+ const start: Io.Timestamp = .now(io, .cpu_process);
1476
+ var completed_cycles: u32 = 0;
1477
+ var total_cycles: u32 = t.batch_cycles;
1478
+
1479
+ while (true) {
1480
+ assert(completed_cycles != total_cycles);
1481
+ while (completed_cycles < total_cycles) {
1482
+ f.takeReceived();
1483
+ f.cycle();
1484
+ completed_cycles += 1;
1485
+ }
1486
+
1487
+ const duration = start.untilNow(io, .cpu_process);
1488
+ const ns = @min(@max(1, duration.nanoseconds), math.maxInt(u64));
1489
+ const speed = @as(u64, t.batch_cycles) * std.time.ns_per_s / ns;
1490
+ // @min avoids large increases in batch_cycles due to just a few cycles running
1491
+ // fast. For example, if batch_cycles is only 2, and both run very fast due to
1492
+ // unlucky rng, this avoids a large runtime on the next batch. This also avoids
1493
+ // timer inprecision giving large values.
1494
+ t.batch_cycles = @max(1, @min(speed, t.batch_cycles *| 2));
1495
+
1496
+ if (ns < std.time.ns_per_s * 7 / 8) {
1497
+ // Keep running the test to get closer to a second. This will almost always
1498
+ // be the case for the first batch as the default batch_cycles is 1.
1499
+ if (t.limit == total_cycles) break;
1500
+
1501
+ const rem_ns: u64 = @as(u32, std.time.ns_per_s) - ns;
1502
+ const extra: u32 = @intCast(rem_ns * t.batch_cycles / std.time.ns_per_s);
1503
+ if (extra == 0) break; // No better approximation of a second possible
1504
+ total_cycles += extra;
1505
+ if (t.limit) |limit| total_cycles = @min(total_cycles, limit);
1506
+ continue;
1507
+ }
1508
+
1509
+ break;
1510
+ }
1511
+
1512
+ assert(completed_cycles == total_cycles);
1513
+ if (t.limit) |prev| {
1514
+ t.limit = prev - total_cycles;
1515
+ t.batch_cycles = @min(t.batch_cycles, t.limit.?);
1516
+ }
1517
+ } else {
1518
+ while (true) {
1519
+ if (t.limit) |limit| {
1520
+ if (limit == 0) break;
1521
+ t.limit = limit - 1;
1522
+ }
1523
+ f.takeReceived();
1524
+ f.cycle();
1525
+ }
1526
+ }
1527
+ }
1528
+
1529
+ pub fn select(f: *Fuzzer) ?u32 {
1530
+ assert(f.tests.len > 1); // More efficiently handled by the callee
1531
+
1532
+ // The algorithm for selecting tests is such that:
1533
+ // - 1/4 are from the number of pcs as they give an indication of test complexity.
1534
+ // - 3/4 are from the recency of the last find as it gives an indication of the
1535
+ // effectiveness of fuzzing for the test.
1536
+ // - Tests finding fresh inputs are run 8x other tests.
1537
+ // - Since new tests are considered to have just found a fresh input, this means they
1538
+ // are also prioritized which allows their characteristics to be learnt.
1539
+ // When a test has a new input pending, it is treated as if it had just found a fresh
1540
+ // input instead of immediately being run. This avoids a test which is finding many new
1541
+ // inputs from being exclusively run.
1542
+ const new_batches = 16;
1543
+
1544
+ var n_with_new: u32 = 0;
1545
+ var n_seen_pcs: u64 = 0;
1546
+ var n_latest_find: u64 = 0;
1547
+
1548
+ for (f.tests) |*t| {
1549
+ const has_pending = t.received.state.hasPending();
1550
+ if (has_pending) {
1551
+ assert(t.limit == null); // If multiprocess limited fuzzing was to be added, then
1552
+ // `t.received.inputs.clearRetainingCapacity()` would need to be added after
1553
+ // `t.received.state.startReadIfPending()` when the limit has been reached.
1554
+ }
1555
+ if (t.limit == 0) continue;
1556
+
1557
+ const latest_find = t.batches - t.batches_since_find;
1558
+ n_with_new += @intFromBool(t.batches_since_find < new_batches or has_pending);
1559
+ n_seen_pcs += @max(t.seen_pc_count, 1);
1560
+ n_latest_find += @max(latest_find, 1);
1561
+ }
1562
+
1563
+ if (n_seen_pcs == 0) {
1564
+ assert(n_with_new == 0);
1565
+ assert(n_latest_find == 0);
1566
+ return null; // All fuzz tests have used up their limit
1567
+ }
1568
+
1569
+ const rng: packed struct(u64) {
1570
+ idx_rng: u32,
1571
+ from_new: u3,
1572
+ from_latest_find: u2,
1573
+ _: u27,
1574
+ } = @bitCast(f.rngInt(u64));
1575
+
1576
+ if (n_with_new != 0 and rng.from_new != 0) {
1577
+ var n = std.Random.limitRangeBiased(u32, rng.idx_rng, n_with_new);
1578
+ for (0.., f.tests) |i, *t| {
1579
+ if (t.limit == 0) continue;
1580
+ if (t.batches_since_find < new_batches or t.received.state.hasPending()) {
1581
+ if (n == 0) return @intCast(i);
1582
+ n -= 1;
1583
+ }
1584
+ }
1585
+ unreachable;
1586
+ }
1125
1587
 
1126
- assert(@intFromEnum(f.corpus_pos) < f.corpus.len);
1127
- f.corpus_pos = @enumFromInt((@intFromEnum(f.corpus_pos) + 1) % f.corpus.len);
1588
+ if (rng.from_latest_find != 0) {
1589
+ const total_weight = n_latest_find;
1590
+ var n = f.rngLessThan(u64, total_weight);
1591
+ for (0.., f.tests) |i, *t| {
1592
+ if (t.limit == 0) continue;
1593
+ const latest_find = @max(t.batches - t.batches_since_find, 1);
1594
+ if (n < latest_find) return @intCast(i);
1595
+ n -= latest_find;
1596
+ }
1597
+ unreachable;
1598
+ } else {
1599
+ const total_weight = n_seen_pcs;
1600
+ var n = f.rngLessThan(u64, total_weight);
1601
+ for (0.., f.tests) |i, *t| {
1602
+ if (t.limit == 0) continue;
1603
+ const seen_pc_count = @max(t.seen_pc_count, 1);
1604
+ if (n < seen_pc_count) return @intCast(i);
1605
+ n -= seen_pc_count;
1606
+ }
1607
+ unreachable;
1608
+ }
1128
1609
  }
1129
1610
 
1130
1611
  fn weightsContain(int: u64, weights: []const abi.Weight) bool {
@@ -1184,8 +1665,9 @@ const Fuzzer = struct {
1184
1665
  mutate: Untyped,
1185
1666
  fresh: void,
1186
1667
  } {
1187
- const corpus = f.corpus.slice();
1188
- const corpus_i = @intFromEnum(f.corpus_pos);
1668
+ const t = &f.tests[f.test_i];
1669
+ const corpus = t.corpus.slice();
1670
+ const corpus_i = @intFromEnum(t.corpus_pos);
1189
1671
  const data = &corpus.items(.data)[corpus_i];
1190
1672
  var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1191
1673
 
@@ -1276,7 +1758,7 @@ const Fuzzer = struct {
1276
1758
  data_slice.len,
1277
1759
  } else src: {
1278
1760
  const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1279
- const untyped_slices = f.seen_uids.values()[seen_uid_i].slices;
1761
+ const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
1280
1762
  switch (uid.kind) {
1281
1763
  .int => {
1282
1764
  const slices = untyped_slices.ints.items;
@@ -1404,7 +1886,7 @@ const Fuzzer = struct {
1404
1886
  }
1405
1887
  } else {
1406
1888
  const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1407
- const untyped_slices = f.seen_uids.values()[seen_uid_i].slices;
1889
+ const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
1408
1890
  switch (uid.kind) {
1409
1891
  .int => {
1410
1892
  const slices = untyped_slices.ints.items;
@@ -1432,11 +1914,12 @@ const Fuzzer = struct {
1432
1914
  }
1433
1915
 
1434
1916
  pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1917
+ const t = &f.tests[f.test_i];
1435
1918
  f.req_values += 1;
1436
- if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1919
+ if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1437
1920
  @branchHint(.unlikely);
1438
1921
  const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);
1439
- if (f.corpus_pos == .bytes_fresh) {
1922
+ if (t.corpus_pos == .bytes_fresh) {
1440
1923
  f.input_builder.checkSmithedLen(8);
1441
1924
  f.input_builder.addInt(uid, int);
1442
1925
  }
@@ -1455,11 +1938,12 @@ const Fuzzer = struct {
1455
1938
  }
1456
1939
 
1457
1940
  pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {
1941
+ const t = &f.tests[f.test_i];
1458
1942
  f.req_values += 1;
1459
- if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1943
+ if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1460
1944
  @branchHint(.unlikely);
1461
1945
  const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);
1462
- if (f.corpus_pos == .bytes_fresh) {
1946
+ if (t.corpus_pos == .bytes_fresh) {
1463
1947
  f.input_builder.checkSmithedLen(1);
1464
1948
  f.input_builder.addInt(uid, @intFromBool(eos));
1465
1949
  }
@@ -1569,13 +2053,14 @@ const Fuzzer = struct {
1569
2053
  }
1570
2054
 
1571
2055
  pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
2056
+ const t = &f.tests[f.test_i];
1572
2057
  f.req_values += 1;
1573
2058
  f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit
1574
2059
  // data limit is exceeded, so wrapping is fine.
1575
- if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
2060
+ if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1576
2061
  @branchHint(.unlikely);
1577
2062
  f.bytes_input.bytesWeightedWithHash(out, weights, undefined);
1578
- if (f.corpus_pos == .bytes_fresh) {
2063
+ if (t.corpus_pos == .bytes_fresh) {
1579
2064
  f.input_builder.checkSmithedLen(out.len);
1580
2065
  f.input_builder.addBytes(uid, out);
1581
2066
  }
@@ -1660,8 +2145,9 @@ const Fuzzer = struct {
1660
2145
  len_weights: []const abi.Weight,
1661
2146
  byte_weights: []const abi.Weight,
1662
2147
  ) u32 {
2148
+ const t = &f.tests[f.test_i];
1663
2149
  f.req_values += 1;
1664
- if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
2150
+ if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1665
2151
  @branchHint(.unlikely);
1666
2152
  const n = f.bytes_input.sliceWeightedWithHash(
1667
2153
  buf,
@@ -1669,7 +2155,7 @@ const Fuzzer = struct {
1669
2155
  byte_weights,
1670
2156
  undefined,
1671
2157
  );
1672
- if (f.corpus_pos == .bytes_fresh) {
2158
+ if (t.corpus_pos == .bytes_fresh) {
1673
2159
  f.input_builder.checkSmithedLen(@as(usize, 4) + n);
1674
2160
  f.input_builder.addBytes(uid, buf[0..n]);
1675
2161
  }
@@ -1686,7 +2172,6 @@ const Fuzzer = struct {
1686
2172
 
1687
2173
  export fn fuzzer_init(cache_dir_path: abi.Slice) void {
1688
2174
  exec = .init(cache_dir_path.toSlice());
1689
- fuzzer = .init();
1690
2175
  }
1691
2176
 
1692
2177
  export fn fuzzer_coverage() abi.Coverage {
@@ -1706,23 +2191,66 @@ export fn fuzzer_coverage() abi.Coverage {
1706
2191
  };
1707
2192
  }
1708
2193
 
1709
- export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {
1710
- current_test_name = unit_test_name.toSlice();
1711
- fuzzer.setTest(test_one, unit_test_name.toSlice());
2194
+ export fn fuzzer_main(
2195
+ n_tests: u32,
2196
+ seed: u32,
2197
+ limit_kind: abi.LimitKind,
2198
+ amount_or_instance: u64,
2199
+ ) void {
2200
+ fuzzer = .init(
2201
+ n_tests,
2202
+ seed ^ amount_or_instance, // seed is otherwise the same for all instances
2203
+ if (limit_kind == .forever) @as(u32, @intCast(amount_or_instance)) else 0,
2204
+ if (limit_kind == .forever) null else amount_or_instance,
2205
+ );
2206
+ defer fuzzer.deinit();
2207
+ abi.runner_start_input_poller();
2208
+ defer abi.runner_stop_input_poller();
2209
+
2210
+ if (n_tests == 1) {
2211
+ // no swapping between fuzz tests
2212
+ runTest(0);
2213
+ } else {
2214
+ while (fuzzer.select()) |i| {
2215
+ runTest(i);
2216
+ }
2217
+ }
2218
+ }
2219
+
2220
+ export fn fuzzer_receive_input(test_i: u32, bytes_slice: abi.Slice) bool {
2221
+ const recv = &fuzzer.tests[test_i].received;
2222
+ if (recv.state.startWrite()) return true;
2223
+ defer recv.state.finishWrite();
2224
+
2225
+ const bytes = bytes_slice.toSlice();
2226
+ const len: u32 = @intCast(bytes.len);
2227
+ recv.inputs.ensureUnusedCapacity(gpa, 4 + bytes.len) catch @panic("OOM");
2228
+ recv.inputs.appendSliceAssumeCapacity(@ptrCast(&len));
2229
+ recv.inputs.appendSliceAssumeCapacity(bytes);
2230
+
2231
+ return false;
2232
+ }
2233
+
2234
+ fn runTest(i: u32) void {
2235
+ fuzzer.test_i = i;
2236
+ fuzzer.mmap_input.setTest(i);
2237
+ current_test_name = abi.runner_test_name(i).toSlice();
2238
+ abi.runner_test_run(i);
2239
+ }
2240
+
2241
+ export fn fuzzer_set_test(test_one: abi.TestOne) void {
2242
+ fuzzer.test_one = test_one;
1712
2243
  }
1713
2244
 
1714
2245
  export fn fuzzer_new_input(bytes: abi.Slice) void {
1715
2246
  if (bytes.len == 0) return; // An entry of length zero is always present
2247
+ if (fuzzer.tests[fuzzer.test_i].start_mut_corpus != math.maxInt(u32)) return; // Test ran previously
1716
2248
  fuzzer.newInputExternal(bytes.toSlice());
1717
2249
  }
1718
2250
 
1719
- export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
1720
- fuzzer.loadCorpus();
1721
- switch (limit_kind) {
1722
- .forever => while (true) fuzzer.cycle(),
1723
- .iterations => for (0..amount) |_| fuzzer.cycle(),
1724
- }
1725
- fuzzer.reset();
2251
+ export fn fuzzer_start_test() void {
2252
+ fuzzer.ensureCorpusLoaded();
2253
+ fuzzer.batch();
1726
2254
  }
1727
2255
 
1728
2256
  export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
@@ -1786,26 +2314,43 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
1786
2314
  /// Reusable and recoverable input.
1787
2315
  ///
1788
2316
  /// Has a 32-bit limit on the input length. This has the nice side effect that `u32`
1789
- /// can be used in most placed in `fuzzer` with the last four values reserved.
2317
+ /// can be used in most placed in `fuzzer` with the last `@sizeOf(abi.MmapInputHeader)`
2318
+ /// values reserved.
1790
2319
  const MemoryMappedInput = struct {
2320
+ const Header = abi.MmapInputHeader;
2321
+
1791
2322
  len: u32,
1792
2323
  /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
1793
- ///
1794
- /// `memory` starts with the length of the input as a little-endian 32-bit integer.
1795
2324
  mmap: Io.File.MemoryMap,
2325
+ in_i: u32,
1796
2326
 
1797
2327
  /// `file` becomes owned by the returned `MemoryMappedInput`
1798
- pub fn init(file: Io.File, size: usize) !MemoryMappedInput {
1799
- assert(size >= 4);
2328
+ pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput {
2329
+ var size = file.length(io) catch |e|
2330
+ panic("failed to get length of 'in{x}': {t}", .{ in_i, e });
2331
+ if (size < std.heap.page_size_max) {
2332
+ size = std.heap.page_size_max;
2333
+ file.setLength(io, size) catch |e|
2334
+ panic("failed to resize 'in{x}': {t}", .{ in_i, e });
2335
+ }
2336
+ const map = file.createMemoryMap(io, .{ .len = size }) catch |e|
2337
+ panic("failed to memmap input file 'in{x}': {t}", .{ in_i, e });
2338
+ @as(*volatile Header, @ptrCast(map.memory)).* = .{
2339
+ .pc_digest = mem.nativeToLittle(u64, exec.pc_digest),
2340
+ .instance_id = mem.nativeToLittle(u32, instance_id),
2341
+ .test_i = 0,
2342
+ .len = 0,
2343
+ };
1800
2344
  return .{
1801
2345
  .len = 0,
1802
- .mmap = try file.createMemoryMap(io, .{ .len = size }),
2346
+ .mmap = map,
2347
+ .in_i = in_i,
1803
2348
  };
1804
2349
  }
1805
2350
 
1806
2351
  pub fn deinit(l: *MemoryMappedInput) void {
1807
2352
  const f = l.mmap.file;
1808
- l.mmap.write(io) catch |e| panic("failed to write memory map of 'in': {t}", .{e});
2353
+ l.mmap.write(io) catch |e| panic("failed to write memory map of 'in{x}': {t}", .{ l.in_i, e });
1809
2354
  l.mmap.destroy(io);
1810
2355
  f.close(io);
1811
2356
  l.* = undefined;
@@ -1815,40 +2360,36 @@ const MemoryMappedInput = struct {
1815
2360
  ///
1816
2361
  /// Invalidates element pointers if additional memory is needed.
1817
2362
  pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
1818
- return l.ensureTotalCapacity(4 + l.len + additional_count);
2363
+ return l.ensureSize(@sizeOf(Header) + l.len + additional_count);
1819
2364
  }
1820
2365
 
1821
- /// If the current capacity is less than `min_capacity`, this function will
1822
- /// modify the array so that it can hold at least `min_capacity` items.
1823
- ///
1824
- /// Invalidates element pointers if additional memory is needed.
1825
- pub fn ensureTotalCapacity(l: *MemoryMappedInput, min_capacity: usize) void {
2366
+ fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void {
1826
2367
  if (l.mmap.memory.len < min_capacity) {
1827
2368
  @branchHint(.unlikely);
1828
2369
 
1829
- const max_capacity = 1 << 32; // The size of the length header is not added
2370
+ const max_capacity = 1 << 32; // The size of the header is not added
1830
2371
  // in order to keep the capacity page aligned and to allow those values to
1831
2372
  // reserved for other places.
1832
2373
  if (min_capacity > max_capacity) @panic("too much smith data requested");
1833
2374
 
1834
2375
  const new_capacity = @min(growCapacity(min_capacity), max_capacity);
1835
2376
  l.mmap.file.setLength(io, new_capacity) catch |e|
1836
- panic("failed to resize 'in': {t}", .{e});
2377
+ panic("failed to resize 'in{x}': {t}", .{ l.in_i, e });
1837
2378
  l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
1838
2379
  error.OperationUnsupported => {
1839
2380
  const f = l.mmap.file;
1840
2381
  l.mmap.destroy(io);
1841
2382
  l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|
1842
- panic("failed to memory map 'in': {t}", .{e});
2383
+ panic("failed to memory map 'in{x}': {t}", .{ l.in_i, e });
1843
2384
  },
1844
- else => panic("failed to resize memory map of 'in': {t}", .{se}),
2385
+ else => panic("failed to resize memory map of 'in{x}': {t}", .{ l.in_i, se }),
1845
2386
  };
1846
2387
  }
1847
2388
  }
1848
2389
 
1849
2390
  // Only writing has side effects, so volatile is not needed
1850
2391
  pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
1851
- return l.mmap.memory[4..][0..l.len];
2392
+ return l.mmap.memory[@sizeOf(Header)..][0..l.len];
1852
2393
  }
1853
2394
 
1854
2395
  // Writing has side effectsd, so volatile is necessary
@@ -1857,7 +2398,13 @@ const MemoryMappedInput = struct {
1857
2398
  }
1858
2399
 
1859
2400
  fn writeLen(l: *MemoryMappedInput) void {
1860
- l.writeSlice()[0..4].* = @bitCast(mem.nativeToLittle(u32, l.len));
2401
+ l.writeSlice()[@offsetOf(Header, "len")..][0..4].* =
2402
+ @bitCast(mem.nativeToLittle(u32, l.len));
2403
+ }
2404
+
2405
+ pub fn setTest(l: *MemoryMappedInput, i: u32) void {
2406
+ l.writeSlice()[@offsetOf(Header, "test_i")..][0..4].* =
2407
+ @bitCast(mem.nativeToLittle(u32, i));
1861
2408
  }
1862
2409
 
1863
2410
  /// Invalidates all element pointers.
@@ -1871,7 +2418,7 @@ const MemoryMappedInput = struct {
1871
2418
  /// Invalidates item pointers if more space is required.
1872
2419
  pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
1873
2420
  l.ensureUnusedCapacity(items.len);
1874
- @memcpy(l.writeSlice()[4 + l.len ..][0..items.len], items);
2421
+ @memcpy(l.writeSlice()[@sizeOf(Header) + l.len ..][0..items.len], items);
1875
2422
  l.len += @as(u32, @intCast(items.len));
1876
2423
  l.writeLen();
1877
2424
  }
@@ -1881,7 +2428,8 @@ const MemoryMappedInput = struct {
1881
2428
  /// Invalidates item pointers if more space is required.
1882
2429
  pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
1883
2430
  l.ensureUnusedCapacity(@sizeOf(T));
1884
- l.writeSlice()[4 + l.len ..][0..@sizeOf(T)].* = @bitCast(mem.nativeToLittle(T, x));
2431
+ l.writeSlice()[@sizeOf(Header) + l.len ..][0..@sizeOf(T)].* =
2432
+ @bitCast(mem.nativeToLittle(T, x));
1885
2433
  l.len += @sizeOf(T);
1886
2434
  l.writeLen();
1887
2435
  }