fcoreutils 0.22.0

High-performance GNU coreutils replacement with SIMD and parallelism
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
; ============================================================================
;  fyes.asm — GNU-compatible "yes" in x86_64 Linux assembly (hardened)
;
;  WHAT IS THIS?
;  =============
;  A drop-in replacement for GNU coreutils `yes` written in pure x86_64
;  assembly. Produces a ~1.5KB static ELF binary with zero dependencies —
;  no libc, no dynamic linker, no runtime allocations. Achieves 2-10x
;  higher throughput than GNU yes while maintaining 100% behavioral
;  compatibility.
;
;  BUILD (manual):
;    nasm -f bin fyes.asm -o fyes && chmod +x fyes
;
;  BUILD (recommended — auto-detects your system's yes output):
;    python3 build.py
;
;  The build.py script captures your system's `yes --help`, `yes --version`,
;  and error message formats, then patches the DATA SECTION below so fyes
;  produces byte-identical output. This handles locale differences (ASCII
;  vs UTF-8 quotes) and coreutils version variations automatically.
;
;  GNU COMPATIBILITY
;  =================
;  Matches GNU coreutils 9.x `yes` behavior including:
;    - --help / --version recognized anywhere in argv (GNU permutation)
;    - "--" terminates option processing; first "--" stripped from output
;    - Unrecognized long options (--foo): error to stderr, exit 1
;    - Invalid short options (-x): error to stderr, exit 1
;    - "-" alone is a regular string, not an option
;    - Multiple args joined with spaces, repeated on stdout forever
;    - No args → outputs "y\n" forever
;    - SIGPIPE/EPIPE → print error to stderr, exit 1 (GNU behavior)
;    - EINTR on write → automatic retry
;
;  ARCHITECTURE OVERVIEW
;  =====================
;
;  Memory Layout (virtual addresses):
;  ┌─────────────────────┬──────────────────────────────────────────┐
;  │ 0x400000            │ ELF header + code + data (this file)    │
;  │                     │ Segment: PT_LOAD, R+X (read+execute)    │
;  ├─────────────────────┼──────────────────────────────────────────┤
;  │ 0x500000 (BUF)      │ Write buffer, 16KB                      │
;  │                     │ Filled with repeated copies of the      │
;  │                     │ output line, flushed in big writes       │
;  ├─────────────────────┼──────────────────────────────────────────┤
;  │ 0x504000 (ARGBUF)   │ Argument assembly buffer, 2MB           │
;  │                     │ Holds the single output line built from  │
;  │                     │ argv (args joined by spaces + newline)   │
;  │                     │ Segment: PT_LOAD, R+W (BSS-style)       │
;  └─────────────────────┴──────────────────────────────────────────┘
;
;  The stack is marked non-executable via PT_GNU_STACK (NX bit).
;  No heap, no mmap, no brk — all memory is compile-time fixed.
;
;  Execution Flow:
;  ┌──────────┐    ┌──────────────┐    ┌───────────┐    ┌───────────┐
;  │ _start   │───▶│ PASS 1:      │───▶│ Build     │───▶│ Write     │
;  │ pop argc │    │ Validate     │    │ output    │    │ loop      │
;  │ save argv│    │ all options  │    │ line in   │    │ (forever) │
;  └──────────┘    │ in argv      │    │ ARGBUF    │    │ BUF→fd 1  │
;                  └──────────────┘    │ Fill BUF  │    └───────────┘
;                         │            └───────────┘
;                    On --help/       On error (bad
;                    --version:       option):
;                    print & exit 0   print & exit 1
;
;  Syscall Surface (entire binary uses only 3 syscalls):
;    - SYS_RT_SIGPROCMASK (14): block SIGPIPE at startup
;    - SYS_WRITE (1): output to stdout/stderr
;    - SYS_EXIT  (60): terminate process
;
;  PERFORMANCE NOTES
;  =================
;  Key optimizations for high throughput:
;
;  1. BUFFERED WRITES: Instead of writing one "y\n" per syscall, we fill
;     a 16KB buffer with repeated copies of the output line, then write
;     the entire buffer in one syscall. This reduces syscall overhead by
;     ~8000x for the default "y\n" case.
;
;  2. LINE-ALIGNED BUFFER: The buffer is rounded down to complete lines
;     to prevent partial-line output at buffer boundaries. This means
;     every write() call outputs only whole lines.
;
;  3. FAST DEFAULT PATH: When no args are given (the most common case),
;     we use `rep stosw` to fill the buffer with "y\n" pairs (0x0A79)
;     in a tight loop, bypassing the general argument-joining code.
;
;  4. EINTR RETRY: The write loop retries on EINTR (-4) automatically,
;     which is critical for correct behavior under signal-heavy loads.
;
;  5. ZERO DYNAMIC ALLOCATION: All buffers are in the BSS segment at
;     fixed addresses. No malloc, no brk, no mmap — this means zero
;     allocation overhead and deterministic memory usage.
;
;  SECURITY PROPERTIES
;  ===================
;  - Non-executable stack (PT_GNU_STACK with NX)
;  - No RWX memory segments (W^X policy)
;  - No dynamic linker (immune to LD_PRELOAD attacks)
;  - No file I/O (never calls open/openat/creat)
;  - Minimal syscall surface (only rt_sigprocmask + write + exit)
;  - Compile-time fixed memory layout (no heap corruption possible)
;  - EINTR-safe write loop (no signal-related data loss)
;  - EPIPE/SIGPIPE handling (print diagnostic to stderr, exit 1)
;
;  HOW TO MODIFY
;  =============
;  Common modifications and where to make them:
;
;  - Change buffer size: Edit BUFSZ (line ~47). Larger = fewer syscalls
;    but more memory. 16KB is optimal for most systems (matches pipe buf).
;
;  - Change max argument length: Edit ARGBUFSZ (line ~48). Current 2MB
;    handles extremely long argument lists.
;
;  - Update help/version text: Run `python3 build.py` to auto-detect,
;    or manually edit the hex bytes between @@DATA_START@@ and
;    @@DATA_END@@ markers. Use `echo -n "text" | xxd -i` to convert.
;
;  - Add a new long option: Add a comparison block after .chk_ver
;    following the same pattern (compare dword/word/byte sequences).
;
;  - Change error message format: Edit err_unrec, err_inval, err_suffix
;    in the DATA SECTION. These are split into prefix + dynamic part +
;    suffix so the option name/char can be inserted at runtime.
;
;  REGISTER CONVENTIONS (during main execution)
;  =============================================
;  r14  = pointer to argv[0] on stack (set once at _start, never changed)
;  r15  = "past --" flag during option parsing (0=checking, 1=past --)
;  rbx  = current argv pointer during iteration
;  r8   = byte count in ARGBUF during line building
;  r9   = total bytes to write per iteration (line length or buffer size)
;  r12  = saved option string/char for error messages; "--" skip flag
;  r13  = "have we included any arg" flag (for space-joining logic)
;  rdi  = fd (1=stdout) during write loop; destination ptr during copy
;  rsi  = buffer address during write loop; source ptr during copy
;  rdx  = byte count for write syscall
; ============================================================================

BITS 64
org 0x400000

; ======================== ELF Header ========================================
;
; This is a hand-crafted ELF64 header. NASM's `-f bin` output format means
; we control every byte of the binary. No linker is involved.
;
; The ELF header tells the kernel:
;   - This is a 64-bit Linux executable
;   - Entry point is at _start
;   - Program headers follow immediately after this header
;   - There are 3 program header entries (code+data, BSS, GNU_STACK)

ehdr:
    db      0x7f, "ELF"            ; e_ident[0..3]: ELF magic number
    db      2, 1, 1, 0             ; 2=64-bit, 1=little-endian, 1=ELF v1, 0=SysV ABI
    dq      0                      ; e_ident padding (8 bytes)
    dw      2                      ; e_type:    ET_EXEC (executable)
    dw      0x3E                   ; e_machine: EM_X86_64
    dd      1                      ; e_version: EV_CURRENT
    dq      _start                 ; e_entry:   virtual address of entry point
    dq      phdr - ehdr            ; e_phoff:   program header table offset
    dq      0                      ; e_shoff:   no section headers (not needed for exec)
    dd      0                      ; e_flags:   no processor-specific flags
    dw      ehdr_end - ehdr        ; e_ehsize:  ELF header size (64 bytes)
    dw      phdr_size              ; e_phentsize: program header entry size (56 bytes)
    dw      3                      ; e_phnum:   3 program headers (see below)
    dw      0, 0, 0                ; e_shentsize, e_shnum, e_shstrndx: unused
ehdr_end:

; ======================== Buffer Configuration ===============================
;
; These constants define the memory layout for runtime buffers.
; Both buffers live in the BSS-style PT_LOAD segment at 0x500000.
;
; BUF (0x500000, 16KB):
;   The write buffer. Filled with repeated copies of the output line,
;   then written to stdout in one syscall. 16KB was chosen because:
;   - It matches the default Linux pipe buffer size
;   - It's large enough to amortize syscall overhead
;   - It's small enough to stay in L1 cache on most CPUs
;
; ARGBUF (0x504000, 2MB):
;   Temporary buffer for assembling the output line from argv.
;   Args are copied here with spaces between them, plus a trailing \n.
;   2MB is generous — the kernel's MAX_ARG_STRLEN is typically 128KB,
;   so real-world argument lines are always much shorter.
;
; ARGBUF_MAX:
;   Safety limit to prevent writing past ARGBUF. We stop copying args
;   2 bytes before the end to leave room for the trailing '\n'.

%define BUF         0x500000       ; Write buffer base address
%define ARGBUF      0x504000       ; Argument assembly buffer base address
%define BUFSZ       16384          ; Write buffer size (16KB)
%define ARGBUFSZ    2097152        ; Arg buffer size (2MB)
%define ARGBUF_MAX  (ARGBUFSZ - 2) ; Max usable bytes in ARGBUF (room for \n)

; ======================== Program Headers ===================================
;
; Program headers tell the kernel how to map the binary into memory.
; We have 3 segments:
;
; 1. PT_LOAD (R+X): The entire binary file — code, data, and read-only
;    strings. Mapped at 0x400000 with read+execute permissions.
;    This is the only segment loaded from the file.
;
; 2. PT_LOAD (R+W, BSS): Runtime buffers at 0x500000. File size is 0
;    (nothing loaded from disk), memory size is BUFSZ + ARGBUFSZ.
;    The kernel zero-fills this on exec. This is our "BSS" segment.
;
; 3. PT_GNU_STACK: Tells the kernel the stack should be non-executable.
;    Flags = R+W (no X) enforces the NX bit on the stack.
;    Without this header, some kernels default to executable stack.

phdr:
    ; --- Segment 1: Code + Data (loaded from file) ---
    dd      1                       ; p_type:  PT_LOAD
    dd      5                       ; p_flags: PF_R(4) | PF_X(1) = read+execute
    dq      0                       ; p_offset: start of file
    dq      0x400000                ; p_vaddr:  virtual address
    dq      0x400000                ; p_paddr:  physical address (same)
    dq      file_end - ehdr         ; p_filesz: entire file
    dq      file_end - ehdr         ; p_memsz:  same as filesz (no BSS in this seg)
    dq      0x1000                  ; p_align:  page-aligned (4KB)
phdr_size equ $ - phdr              ; Size of one program header entry (56 bytes)

    ; --- Segment 2: BSS (runtime buffers, zero-initialized) ---
    dd      1                       ; p_type:  PT_LOAD
    dd      6                       ; p_flags: PF_R(4) | PF_W(2) = read+write
    dq      0                       ; p_offset: 0 (no file content)
    dq      0x500000                ; p_vaddr:  buffer base address
    dq      0x500000                ; p_paddr:  same
    dq      0                       ; p_filesz: 0 (nothing loaded from file)
    dq      BUFSZ + ARGBUFSZ        ; p_memsz:  16KB + 2MB = total buffer space
    dq      0x1000                  ; p_align:  page-aligned

    ; --- Segment 3: GNU Stack (marks stack as non-executable) ---
    dd      0x6474E551              ; p_type:  PT_GNU_STACK
    dd      6                       ; p_flags: PF_R(4) | PF_W(2) = NX stack
    dq      0, 0, 0, 0, 0          ; p_offset, p_vaddr, p_paddr, p_filesz, p_memsz: unused
    dq      0x10                    ; p_align:  16-byte alignment

; ============================================================================
;                           CODE SECTION
; ============================================================================
;
; Entry point. The kernel sets up the stack as:
;   [rsp]     = argc
;   [rsp+8]   = argv[0] (program name)
;   [rsp+16]  = argv[1] (first argument)
;   ...
;   [rsp+8*N] = NULL (argv terminator)
;
; We pop argc into rcx and save the stack pointer (which now points to
; argv[0]) into r14 for later use.

_start:
    pop     rcx                     ; rcx = argc (argument count)
    mov     r14, rsp                ; r14 = &argv[0] (saved for build_line)

    ; ---- Block SIGPIPE so write() returns -EPIPE instead of killing us ----
    ; rt_sigprocmask(SIG_BLOCK=0, &sigset, NULL, 8)
    ; SIGPIPE=13; sigset bit = 1<<(13-1) = 1<<12 = 0x1000
    push    rcx                     ; save argc (rcx will be clobbered)
    sub     rsp, 16                 ; allocate 16 bytes for sigset_t on stack
    mov     qword [rsp], 0x1000     ; sigset: bit 12 = SIGPIPE
    mov     eax, 14                 ; SYS_RT_SIGPROCMASK = 14
    xor     edi, edi                ; rdi = 0 (SIG_BLOCK)
    mov     rsi, rsp                ; rsi = &new_set (on stack)
    xor     edx, edx                ; rdx = NULL (old_set, don't care)
    mov     r10d, 8                 ; r10 = sigsetsize = 8
    syscall
    add     rsp, 16                 ; free sigset_t
    pop     rcx                     ; restore argc

    cmp     ecx, 2
    jl      .default                ; argc < 2 → no args, use default "y\n"

    ; ================================================================
    ;  PASS 1: Option Validation
    ;
    ;  GNU yes uses parse_gnu_standard_options_only(), which checks
    ;  EVERY argv entry for --help/--version, even after non-option
    ;  arguments. This is "getopt permutation" behavior.
    ;
    ;  The only thing that stops option checking is "--", which means
    ;  "end of options". After "--", all remaining args are treated
    ;  as literal strings regardless of their content.
    ;
    ;  Any unrecognized option (--foo or -x) is an error.
    ;  Bare "-" is not an option — it's a literal string.
    ;
    ;  Register usage in this section:
    ;    r15 = "past --" flag (0 = still checking options)
    ;    rbx = pointer to current argv entry
    ;    rsi = pointer to current argument string
    ; ================================================================

    xor     r15d, r15d              ; r15 = 0: not past "--" yet
    lea     rbx, [r14 + 8]         ; rbx = &argv[1] (skip program name)

.opt_loop:
    mov     rsi, [rbx]              ; rsi = current argv string pointer
    test    rsi, rsi                ; NULL pointer?
    jz      .opt_done               ; yes → end of argv, all args valid

    test    r15d, r15d              ; already past "--"?
    jnz     .opt_next               ; yes → skip option checking

    ; --- Check if this arg starts with '-' ---
    cmp     byte [rsi], '-'
    jne     .opt_next               ; doesn't start with '-' → not an option

    cmp     byte [rsi+1], 0
    je      .opt_next               ; just "-" alone → literal string, not option

    ; --- Starts with '-'. Is it a long option (--xxx)? ---
    cmp     byte [rsi+1], '-'
    jne     .err_short_opt          ; single '-' + char (e.g. "-n") → invalid option

    ; Starts with "--". Is it exactly "--" (end-of-options marker)?
    cmp     byte [rsi+2], 0
    je      .opt_set_past           ; exactly "--" → set flag, stop checking

    ; --- Check for "--help" ---
    ; String bytes: '-','-','h','e','l','p','\0'
    ; In little-endian dword at [rsi]: 0x65682D2D = "eh--" reversed = "--he"
    cmp     dword [rsi], 0x65682D2D ; first 4 bytes = "--he"?
    jne     .chk_ver                ; no → try --version
    cmp     word [rsi+4], 0x706C    ; bytes 4-5 = "lp"?
    jne     .chk_ver
    cmp     byte [rsi+6], 0         ; byte 6 = null terminator?
    jne     .chk_ver

    ; Matched "--help" → print help text and exit 0
    mov     esi, help_text          ; pointer to help text data
    mov     edx, help_text_len      ; length of help text
    jmp     .print_exit_ok

.chk_ver:
    ; --- Check for "--version" ---
    ; String bytes: '-','-','v','e','r','s','i','o','n','\0'
    ; In little-endian: dword[0] = 0x65762D2D ("--ve")
    ;                   dword[4] = 0x6F697372 ("rsio")
    ;                   word[8]  = 0x006E     ("n\0")
    cmp     dword [rsi], 0x65762D2D ; "--ve"?
    jne     .err_long_opt           ; no → unrecognized long option
    cmp     dword [rsi+4], 0x6F697372 ; "rsio"?
    jne     .err_long_opt
    cmp     word [rsi+8], 0x006E    ; "n\0"?
    jne     .err_long_opt

    ; Matched "--version" → print version text and exit 0
    mov     esi, version_text
    mov     edx, version_text_len
    jmp     .print_exit_ok

    ; ============================================================
    ;  Error: Unrecognized long option (e.g. "--foo")
    ;
    ;  Output format (to stderr):
    ;    yes: unrecognized option '--foo'
    ;    Try 'yes --help' for more information.
    ;
    ;  This is assembled from 3 write() calls:
    ;    1. err_unrec prefix:  "yes: unrecognized option '"
    ;    2. The option string:  "--foo" (variable length)
    ;    3. err_suffix:         "'\nTry 'yes --help'..."
    ; ============================================================
.err_long_opt:
    mov     r12, rsi                ; save option string pointer for later

    ; Write prefix: "yes: unrecognized option '"
    mov     eax, 1                  ; SYS_WRITE = 1
    mov     edi, 2                  ; fd = 2 (stderr)
    mov     esi, err_unrec          ; buffer = error prefix
    mov     edx, err_unrec_len      ; length
    syscall

    ; Write the option string itself (we need strlen first)
    mov     rsi, r12                ; rsi = option string
    xor     ecx, ecx                ; ecx = 0 (length counter)
.sl1:                               ; strlen loop
    cmp     byte [rsi + rcx], 0     ; null terminator?
    je      .sl1d                   ; yes → done
    inc     ecx                     ; no → count this byte
    jmp     .sl1
.sl1d:
    mov     edx, ecx                ; edx = string length
    mov     rsi, r12                ; rsi = string pointer
    mov     eax, 1                  ; SYS_WRITE
    mov     edi, 2                  ; fd = stderr
    syscall

    ; Write suffix: "'\nTry 'yes --help' for more information.\n"
    mov     eax, 1
    mov     edi, 2
    mov     esi, err_suffix
    mov     edx, err_suffix_len
    syscall
    jmp     .exit_fail              ; exit with code 1

    ; ============================================================
    ;  Error: Invalid short option (e.g. "-n", "-x")
    ;
    ;  Output format (to stderr):
    ;    yes: invalid option -- 'n'
    ;    Try 'yes --help' for more information.
    ;
    ;  Assembled from 3 write() calls:
    ;    1. err_inval prefix:  "yes: invalid option -- '"
    ;    2. The single char:    "n" (1 byte, written from stack)
    ;    3. err_suffix:         "'\nTry 'yes --help'..."
    ; ============================================================
.err_short_opt:
    movzx   r12d, byte [rsi+1]     ; save the option character (e.g. 'n')

    ; Write prefix: "yes: invalid option -- '"
    mov     eax, 1
    mov     edi, 2
    mov     esi, err_inval
    mov     edx, err_inval_len
    syscall

    ; Write the single option character
    ; We push it onto the stack to get a writable memory address
    ; (the data section is in an R+X segment, can't write there)
    push    r12                     ; put char on stack
    mov     rsi, rsp                ; rsi = pointer to char on stack
    mov     edx, 1                  ; length = 1 byte
    mov     eax, 1                  ; SYS_WRITE
    mov     edi, 2                  ; fd = stderr
    syscall
    pop     r12                     ; restore stack

    ; Write suffix
    mov     eax, 1
    mov     edi, 2
    mov     esi, err_suffix
    mov     edx, err_suffix_len
    syscall
    jmp     .exit_fail              ; exit with code 1

.opt_set_past:
    mov     r15d, 1                 ; set "past --" flag
.opt_next:
    add     rbx, 8                  ; advance to next argv entry (8 bytes = 1 pointer)
    jmp     .opt_loop               ; continue checking

.opt_done:
    ; All argv entries validated — no errors found.
    ; Proceed to build the output line from arguments.
    jmp     .build_line


; ======================== Print and Exit (success) ==========================
;
; Used by --help and --version: write the text to stdout, then exit 0.
; At this point: esi = text pointer, edx = text length.

.print_exit_ok:
    push    1
    pop     rax                     ; rax = 1 (SYS_WRITE)
    mov     edi, eax                ; edi = 1 (fd = stdout)
    syscall                         ; write(stdout, text, len)
    jmp     .exit                   ; exit with code 0

; ======================== Exit with code 1 ==================================
;
; Used by error paths (unrecognized/invalid option).

.exit_fail:
    push    1
    pop     rdi                     ; rdi = 1 (exit code)
    push    60
    pop     rax                     ; rax = 60 (SYS_EXIT)
    syscall                         ; _exit(1)


; ======================== Default "y\n" Fast Path ===========================
;
; When no arguments are given (argc < 2), output "y\n" forever.
;
; Optimization: Instead of copying "y\n" one-by-one, we use `rep stosw`
; to fill the entire 16KB buffer with the word 0x0A79 ("y\n" in
; little-endian). This fills 8192 copies of "y\n" in a single tight loop.
;
; After filling, we jump to setup_write which writes the buffer to stdout
; in a loop forever.

.default:
    mov     edi, BUF                ; edi = destination (write buffer)
    mov     ecx, BUFSZ / 2         ; ecx = 8192 (number of words to store)
    mov     eax, 0x0A79             ; ax = "y\n" as a 16-bit word
    rep     stosw                   ; fill BUF with "y\n" repeated 8192 times
    mov     r9d, BUFSZ             ; r9 = 16384 bytes to write per iteration
    jmp     .setup_write


; ======================== Argument Joining ==================================
;
;  Build the output line from argv[1..N] into ARGBUF.
;
;  GNU yes behavior for "--":
;    - The FIRST "--" in argv is stripped (not included in output)
;    - Subsequent "--" entries ARE included in output
;    - Example: `yes -- a -- b` outputs "a -- b\n"
;
;  Register usage:
;    rbx = pointer walking through argv
;    edi = write cursor in ARGBUF
;    r8  = bytes written to ARGBUF so far
;    r12 = "--" skip flag (0 = haven't skipped yet, 1 = already skipped)
;    r13 = "any arg included" flag (for space-before-arg logic)
;
;  Output format: "arg1 arg2 arg3\n" (space-separated, newline-terminated)

.build_line:
    lea     rbx, [r14 + 8]         ; rbx = &argv[1]
    mov     edi, ARGBUF             ; edi = write cursor (start of ARGBUF)
    xor     r8d, r8d               ; r8 = 0 (byte count)
    xor     r12d, r12d             ; r12 = 0 (haven't skipped "--" yet)
    xor     r13d, r13d             ; r13 = 0 (no args included yet)

.bl_next:
    mov     rsi, [rbx]              ; rsi = current arg string
    test    rsi, rsi                ; NULL? (end of argv)
    jz      .bl_done
    add     rbx, 8                  ; advance argv pointer

    ; --- Should we skip this arg? (first "--" only) ---
    test    r12d, r12d              ; already skipped a "--"?
    jnz     .bl_include             ; yes → include everything now
    cmp     word [rsi], 0x2D2D      ; first two bytes = "--"?
    jne     .bl_include             ; no → include it
    cmp     byte [rsi+2], 0         ; third byte = null? (exactly "--")
    jne     .bl_include             ; no → it's "--something", include it
    ; This is "--" and we haven't skipped one yet → skip it
    mov     r12d, 1                 ; mark: we've skipped the first "--"
    jmp     .bl_next

.bl_include:
    ; --- Add space separator before this arg (unless it's the first) ---
    test    r13d, r13d              ; is this the first included arg?
    jz      .bl_first_arg           ; yes → no space needed
    cmp     r8d, ARGBUF_MAX         ; buffer full?
    jge     .bl_done                ; yes → stop
    mov     byte [rdi], 0x20        ; write ' ' (space separator)
    inc     edi
    inc     r8d
    jmp     .bl_copy

.bl_first_arg:
    mov     r13d, 1                 ; mark: we've started including args

.bl_copy:
    ; --- Copy bytes from current arg string to ARGBUF ---
    cmp     r8d, ARGBUF_MAX         ; buffer full?
    jge     .bl_skip_rest           ; yes → skip remaining bytes of this arg
    lodsb                           ; al = *rsi++ (load byte, advance source)
    test    al, al                  ; null terminator?
    jz      .bl_next                ; yes → move to next arg
    stosb                           ; *rdi++ = al (store byte, advance dest)
    inc     r8d                     ; count this byte
    jmp     .bl_copy

.bl_skip_rest:
    ; Buffer is full but we need to consume the rest of this arg
    ; (to properly advance to the next arg in the loop)
    lodsb
    test    al, al
    jnz     .bl_skip_rest
    jmp     .bl_next

.bl_done:
    ; --- All args processed. Was anything actually included? ---
    test    r13d, r13d
    jz      .default                ; no args included → use default "y\n"

    ; --- Append newline to complete the output line ---
    mov     byte [rdi], 0x0A        ; '\n'
    inc     r8d
    ; r8 now = total line length including '\n'

    ; ================================================================
    ;  Fill BUF with repeated copies of the output line.
    ;
    ;  This is the key performance optimization: instead of calling
    ;  write() once per line, we fill a 16KB buffer with as many
    ;  complete copies of the line as will fit, then write the
    ;  entire buffer in one syscall.
    ;
    ;  For a 2-byte line ("y\n"), this means 8192 lines per write.
    ;  For a 100-byte line, ~163 lines per write.
    ; ================================================================

    mov     esi, ARGBUF             ; esi = source (the output line)
    mov     edi, BUF                ; edi = destination (write buffer)
    mov     r9, r8                  ; r9 = line length (for later)
    xor     r10d, r10d             ; r10 = bytes filled so far

.fill_loop:
    mov     rcx, BUFSZ             ; rcx = remaining buffer space
    sub     rcx, r10
    jle     .fill_done              ; buffer full → done
    cmp     rcx, r9                 ; more space than one line?
    jle     .fill_copy              ; no → copy partial (won't happen if aligned)
    mov     rcx, r9                 ; yes → copy exactly one line

.fill_copy:
    mov     r11, rcx               ; save byte count (rep movsb zeroes rcx)
    push    rsi                    ; save source pointer (rep movsb advances it)
    rep     movsb                  ; copy rcx bytes: [rsi] → [rdi]
    pop     rsi                    ; restore source to start of line
    add     r10, r11               ; update total bytes filled
    cmp     r10, BUFSZ             ; buffer full?
    jb      .fill_loop             ; no → copy another line

.fill_done:
    ; --- Round down to complete lines ---
    ; If the line doesn't evenly divide BUFSZ, the last partial copy
    ; would produce a broken line. We trim the buffer to the last
    ; complete line boundary.
    cmp     r9, BUFSZ              ; is the line longer than the buffer?
    jg      .long_line             ; yes → special case (write from ARGBUF)

    mov     rax, r10               ; rax = total bytes in buffer
    xor     edx, edx               ; clear remainder
    div     r9                     ; rax = complete lines, rdx = leftover bytes
    sub     r10, rdx               ; trim to complete-line boundary
    mov     r9, r10                ; r9 = trimmed buffer size
    jmp     .setup_write

.long_line:
    ; Special case: output line is longer than BUF (>16KB).
    ; Skip the buffer entirely — write directly from ARGBUF.
    ; This is rare but handles pathological cases correctly.
    push    1
    pop     rdi                    ; fd = stdout
    mov     esi, ARGBUF            ; source = ARGBUF
    mov     rdx, r9                ; length = line length
    jmp     .write_loop


; ======================== Write Loop ========================================
;
; The hot loop — writes the buffer to stdout forever until an error occurs.
;
; This loop handles two special cases:
;   - EINTR (errno -4): The write was interrupted by a signal. Retry.
;   - EPIPE (errno -32): The pipe was closed. Print diagnostic to stderr
;     and exit with code 1 (GNU yes compatibility).
;   - Other errors (negative or zero return): Exit with code 1.
;
; Note: `mov eax, edi` is a 2-byte instruction that copies fd (1) to eax
; for the SYS_WRITE syscall number. This is smaller than `mov eax, 1` (5
; bytes) and works because SYS_WRITE == 1 == STDOUT_FILENO.

.setup_write:
    push    1
    pop     rdi                     ; rdi = 1 (fd = stdout)
    mov     esi, BUF                ; rsi = buffer address
    mov     rdx, r9                 ; rdx = buffer size (bytes to write)

.write_loop:
    mov     eax, edi                ; eax = 1 (SYS_WRITE, borrowed from fd)
    syscall                         ; write(stdout, buf, len)
    cmp     eax, -4                 ; returned -EINTR?
    je      .write_loop             ; yes → retry the write
    test    eax, eax                ; positive return (bytes written)?
    jg      .write_loop             ; yes → keep writing

    ; Zero or negative return — check if EPIPE for GNU-compatible diagnostic
    cmp     eax, -32                ; returned -EPIPE?
    jne     .write_exit_fail        ; not EPIPE → exit 1 without diagnostic

    ; EPIPE: write "yes: standard output: Broken pipe\n" to stderr
    mov     eax, 1                  ; SYS_WRITE
    mov     edi, 2                  ; fd = stderr
    mov     esi, broken_pipe_msg    ; buffer = error message
    mov     edx, broken_pipe_msg_len ; length
    syscall
    jmp     .exit_fail              ; exit with code 1

.write_exit_fail:
    jmp     .exit_fail              ; exit with code 1

; ======================== Exit (success, code 0) ============================
;
; Used by --help and --version after successful output.

.exit:
    xor     edi, edi                ; rdi = 0 (exit code)
    push    60
    pop     rax                     ; rax = 60 (SYS_EXIT)
    syscall                         ; _exit(0)


; ############################################################################
;                           DATA SECTION
;
;  This section contains the help text, version text, and error message
;  fragments. All data is stored as raw bytes (hex) because the exact
;  content depends on the local system's coreutils installation.
;
;  AUTOMATIC PATCHING:
;    Run `python3 build.py` to detect your system's GNU yes output and
;    replace this section with matching data. The build script looks for
;    the @@DATA_START@@ and @@DATA_END@@ markers and replaces everything
;    between them.
;
;  MANUAL EDITING:
;    If you need to edit by hand, convert your text to hex bytes:
;      echo -n "your text here" | xxd -i
;    Then replace the appropriate label's `db` lines.
;    IMPORTANT: Update the `_len equ $ - label` line too — NASM
;    computes the length automatically from the label position.
;
;  DATA LABELS:
;    help_text      — Full --help output (written to stdout)
;    version_text   — Full --version output (written to stdout)
;    err_unrec      — Error prefix: "yes: unrecognized option '"
;    err_inval      — Error prefix: "yes: invalid option -- '"
;    err_suffix     — Error suffix: "'\nTry 'yes --help' for more information.\n"
;
;  ERROR MESSAGE ASSEMBLY:
;    For unrecognized long option --foo:
;      write(stderr, err_unrec)   →  "yes: unrecognized option '"
;      write(stderr, "--foo")      →  "--foo"
;      write(stderr, err_suffix)   →  "'\nTry 'yes --help' for more information.\n"
;
;    For invalid short option -x:
;      write(stderr, err_inval)   →  "yes: invalid option -- '"
;      write(stderr, "x")         →  "x"
;      write(stderr, err_suffix)   →  "'\nTry 'yes --help' for more information.\n"
; ############################################################################

flag_help:      db "--help", 0      ; Used by build.py for reference (not by code)
flag_version:   db "--version", 0   ; Used by build.py for reference (not by code)

; @@DATA_START@@
help_text:      db 0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x79, 0x65, 0x73, 0x20, 0x5b, 0x53, 0x54, 0x52, 0x49
                db 0x4e, 0x47, 0x5d, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x6f, 0x72, 0x3a, 0x20, 0x20, 0x79, 0x65
                db 0x73, 0x20, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x0a, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65
                db 0x64, 0x6c, 0x79, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x6e
                db 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69
                db 0x66, 0x69, 0x65, 0x64, 0x20, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x28, 0x73, 0x29, 0x2c, 0x20
                db 0x6f, 0x72, 0x20, 0x27, 0x79, 0x27, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d
                db 0x2d, 0x68, 0x65, 0x6c, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73
                db 0x70, 0x6c, 0x61, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x20, 0x61
                db 0x6e, 0x64, 0x20, 0x65, 0x78, 0x69, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d
                db 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x70
                db 0x75, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72
                db 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x65, 0x78, 0x69, 0x74, 0x0a
                db 0x0a, 0x47, 0x4e, 0x55, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x75, 0x74, 0x69, 0x6c, 0x73, 0x20, 0x6f
                db 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x3a, 0x20, 0x3c, 0x68, 0x74, 0x74
                db 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6e, 0x75, 0x2e, 0x6f, 0x72, 0x67
                db 0x2f, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x75, 0x74
                db 0x69, 0x6c, 0x73, 0x2f, 0x3e, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d
                db 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a
                db 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6e, 0x75, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x73, 0x6f
                db 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x75, 0x74, 0x69, 0x6c, 0x73
                db 0x2f, 0x79, 0x65, 0x73, 0x3e, 0x0a, 0x6f, 0x72, 0x20, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62
                db 0x6c, 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x76, 0x69, 0x61, 0x3a, 0x20
                db 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x27, 0x28, 0x63, 0x6f, 0x72, 0x65, 0x75, 0x74, 0x69, 0x6c, 0x73
                db 0x29, 0x20, 0x79, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e
                db 0x27, 0x0a
help_text_len equ $ - help_text

version_text:   db 0x79, 0x65, 0x73, 0x20, 0x28, 0x47, 0x4e, 0x55, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x75, 0x74, 0x69
                db 0x6c, 0x73, 0x29, 0x20, 0x39, 0x2e, 0x37, 0x0a, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x64
                db 0x20, 0x62, 0x79, 0x20, 0x44, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x20, 0x28, 0x39, 0x2e, 0x37, 0x2d
                db 0x33, 0x29, 0x0a, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29
                db 0x20, 0x32, 0x30, 0x32, 0x35, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77
                db 0x61, 0x72, 0x65, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20
                db 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x47, 0x50, 0x4c
                db 0x76, 0x33, 0x2b, 0x3a, 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x50, 0x4c, 0x20, 0x76, 0x65, 0x72
                db 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x33, 0x20, 0x6f, 0x72, 0x20, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x20
                db 0x3c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x6e, 0x75, 0x2e, 0x6f, 0x72, 0x67
                db 0x2f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x2f, 0x67, 0x70, 0x6c, 0x2e, 0x68, 0x74
                db 0x6d, 0x6c, 0x3e, 0x2e, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x66, 0x72, 0x65
                db 0x65, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x3a, 0x20, 0x79, 0x6f, 0x75, 0x20
                db 0x61, 0x72, 0x65, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x68, 0x61, 0x6e
                db 0x67, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62
                db 0x75, 0x74, 0x65, 0x20, 0x69, 0x74, 0x2e, 0x0a, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, 0x69, 0x73
                db 0x20, 0x4e, 0x4f, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, 0x2c, 0x20, 0x74, 0x6f
                db 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x70, 0x65, 0x72, 0x6d
                db 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x6c, 0x61, 0x77, 0x2e, 0x0a, 0x0a, 0x57
                db 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x62, 0x79, 0x20, 0x44, 0x61, 0x76, 0x69, 0x64, 0x20
                db 0x4d, 0x61, 0x63, 0x4b, 0x65, 0x6e, 0x7a, 0x69, 0x65, 0x2e, 0x0a
version_text_len equ $ - version_text

err_unrec:      db 0x79, 0x65, 0x73, 0x3a, 0x20, 0x75, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65
                db 0x64, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x27
err_unrec_len equ $ - err_unrec

err_inval:      db 0x79, 0x65, 0x73, 0x3a, 0x20, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x6f, 0x70, 0x74
                db 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x20, 0x27
err_inval_len equ $ - err_inval

err_suffix:     db 0x27, 0x0a, 0x54, 0x72, 0x79, 0x20, 0x27, 0x79, 0x65, 0x73, 0x20, 0x2d, 0x2d, 0x68, 0x65, 0x6c
                db 0x70, 0x27, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x66, 0x6f
                db 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a
err_suffix_len equ $ - err_suffix

; @@DATA_END@@

; EPIPE diagnostic message (GNU yes compatibility — not patched by build.py)
; "yes: standard output: Broken pipe\n"
broken_pipe_msg:
                db "yes: standard output: Broken pipe", 0x0a
broken_pipe_msg_len equ $ - broken_pipe_msg

file_end:
; ============================================================================
;  End of binary. Everything past file_end is not loaded into memory.
;  Total binary size: ~1.5KB (computed as file_end - ehdr).
; ============================================================================