@@ -112,7 +112,7 @@ pub fn main() !void {
112112    var  steps_menu  =  false ;
113113    var  output_tmp_nonce : ? [16 ]u8  =  null ;
114114    var  watch  =  false ;
115-     var  fuzz   =   false ;
115+     var  fuzz :  ? std.Build.Fuzz.Mode   =   null ;
116116    var  debounce_interval_ms : u16  =  50 ;
117117    var  webui_listen : ? std.net.Address  =  null ;
118118
@@ -274,10 +274,44 @@ pub fn main() !void {
274274                    webui_listen  =  std .net .Address .parseIp ("::1" , 0 ) catch  unreachable ;
275275                }
276276            } else  if  (mem .eql (u8 , arg , "--fuzz" )) {
277-                 fuzz  =  true ;
277+                 fuzz  =  .{ . forever   =   undefined  } ;
278278                if  (webui_listen  ==  null ) {
279279                    webui_listen  =  std .net .Address .parseIp ("::1" , 0 ) catch  unreachable ;
280280                }
281+             } else  if  (mem .startsWith (u8 , arg , "--fuzz=" )) {
282+                 const  value  =  arg ["--fuzz=" .len .. ];
283+                 if  (value .len  ==  0 ) fatal ("missing argument to --fuzz" , .{});
284+ 
285+                 const  unit : u8  =  value [value .len  -  1 ];
286+                 const  digits  =  switch  (unit ) {
287+                     '0' ... '9'  = >  value ,
288+                     'K' , 'M' , 'G'  = >  value [0  ..  value .len  -  1 ],
289+                     else  = >  fatal (
290+                         "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]" ,
291+                         .{},
292+                     ),
293+                 };
294+ 
295+                 const  amount  =  std .fmt .parseInt (u64 , digits , 10 ) catch  {
296+                     fatal (
297+                         "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]" ,
298+                         .{},
299+                     );
300+                 };
301+ 
302+                 const  normalized_amount  =  std .math .mul (u64 , amount , switch  (unit ) {
303+                     else  = >  unreachable ,
304+                     '0' ... '9'  = >  1 ,
305+                     'K'  = >  1000 ,
306+                     'M'  = >  1_000_000 ,
307+                     'G'  = >  1_000_000_000 ,
308+                 }) catch  fatal ("fuzzing limit amount overflows u64" , .{});
309+ 
310+                 fuzz  =  .{
311+                     .limit  =  .{
312+                         .amount  =  normalized_amount ,
313+                     },
314+                 };
281315            } else  if  (mem .eql (u8 , arg , "-fincremental" )) {
282316                graph .incremental  =  true ;
283317            } else  if  (mem .eql (u8 , arg , "-fno-incremental" )) {
@@ -476,6 +510,7 @@ pub fn main() !void {
476510            targets .items ,
477511            main_progress_node ,
478512            & run ,
513+             fuzz ,
479514        ) catch  | err |  switch  (err ) {
480515            error .UncleanExit  = >  {
481516                assert (! run .watch  and  run .web_server  ==  null );
@@ -485,7 +520,12 @@ pub fn main() !void {
485520        };
486521
487522        if  (run .web_server ) | * web_server |  {
488-             web_server .finishBuild (.{ .fuzz  =  fuzz  });
523+             if  (fuzz ) | mode |  if  (mode  !=  .forever ) fatal (
524+                 "error: limited fuzzing is not implemented yet for --webui" ,
525+                 .{},
526+             );
527+ 
528+             web_server .finishBuild (.{ .fuzz  =  fuzz  !=  null  });
489529        }
490530
491531        if  (! watch  and  run .web_server  ==  null ) {
@@ -651,6 +691,7 @@ fn runStepNames(
651691    step_names : []const  []const  u8 ,
652692    parent_prog_node : std.Progress.Node ,
653693    run : * Run ,
694+     fuzz : ? std.Build.Fuzz.Mode ,
654695) ! void  {
655696    const  gpa  =  run .gpa ;
656697    const  step_stack  =  & run .step_stack ;
@@ -676,6 +717,7 @@ fn runStepNames(
676717            });
677718        }
678719    }
720+ 
679721    assert (run .memory_blocked_steps .items .len  ==  0 );
680722
681723    var  test_skip_count : usize  =  0 ;
@@ -724,6 +766,45 @@ fn runStepNames(
724766        }
725767    }
726768
769+     const  ttyconf  =  run .ttyconf ;
770+ 
771+     if  (fuzz ) | mode |  blk : {
772+         switch  (builtin .os .tag ) {
773+             // Current implementation depends on two things that need to be ported to Windows: 
774+             // * Memory-mapping to share data between the fuzzer and build runner. 
775+             // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving 
776+             //   many addresses to source locations). 
777+             .windows  = >  fatal ("--fuzz not yet implemented for {s}" , .{@tagName (builtin .os .tag )}),
778+             else  = >  {},
779+         }
780+         if  (@bitSizeOf (usize ) !=  64 ) {
781+             // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, 
782+             // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case 
783+             // on 32-bit platforms. 
784+             // Affects or affected by issues #5185, #22523, and #22464. 
785+             fatal ("--fuzz not yet implemented on {d}-bit platforms" , .{@bitSizeOf (usize )});
786+         }
787+ 
788+         switch  (mode ) {
789+             .forever  = >  break  :blk ,
790+             .limit  = >  {},
791+         }
792+ 
793+         assert (mode  ==  .limit );
794+         var  f  =  std .Build .Fuzz .init (
795+             gpa ,
796+             thread_pool ,
797+             step_stack .keys (),
798+             parent_prog_node ,
799+             ttyconf ,
800+             mode ,
801+         ) catch  | err |  fatal ("failed to start fuzzer: {s}" , .{@errorName (err )});
802+         defer  f .deinit ();
803+ 
804+         f .start ();
805+         f .waitAndPrintReport ();
806+     }
807+ 
727808    // A proper command line application defaults to silently succeeding. 
728809    // The user may request verbose mode if they have a different preference. 
729810    const  failures_only  =  switch  (run .summary ) {
@@ -737,8 +818,6 @@ fn runStepNames(
737818        std .Progress .setStatus (.failure );
738819    }
739820
740-     const  ttyconf  =  run .ttyconf ;
741- 
742821    if  (run .summary  !=  .none ) {
743822        const  w  =  std .debug .lockStderrWriter (& stdio_buffer_allocation );
744823        defer  std .debug .unlockStderrWriter ();
@@ -1366,7 +1445,10 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
13661445        \\  --watch                      Continuously rebuild when source files are modified 
13671446        \\  --debounce <ms>              Delay before rebuilding after changed file detected 
13681447        \\  --webui[=ip]                 Enable the web interface on the given IP address 
1369-         \\  --fuzz                       Continuously search for unit test failures (implies '--webui') 
1448+         \\  --fuzz[=limit]               Continuously search for unit test failures with an optional  
1449+         \\                               limit to the max number of iterations. The argument supports 
1450+         \\                               an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies 
1451+         \\                               '--webui' when no limit is specified. 
13701452        \\  --time-report                Force full rebuild and provide detailed information on 
13711453        \\                               compilation time of Zig source code (implies '--webui') 
13721454        \\     -fincremental             Enable incremental compilation 
0 commit comments