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

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