fuse: encode readdir dirents directly into one sized buffer (P2)

ober

ed956e655fde8e5133019b7324a0dafffac7c1ce

diff --git a/lib/jerboa-fuse/codec.sls b/lib/jerboa-fuse/codec.sls
index a32de7f..3bc62a8 100644
--- a/lib/jerboa-fuse/codec.sls
+++ b/lib/jerboa-fuse/codec.sls
@@ -285,24 +285,38 @@
          (bytevector-copy! name-bv 0 bv 24 namelen)
          bv))
   (def (encode-dirents unique dirents max-size)
-       (let loop ([ds dirents] [chunks '()] [total-payload 0])
+       (let measure ([ds dirents] [items '()] [total-payload 0])
          (if (null? ds)
-             (finalize-dirents unique (reverse chunks) total-payload)
-             (let* ([encoded (encode-dirent (car ds))]
-                    [elen (bytevector-length encoded)]
-                    [new-total (+ total-payload elen)])
+             (build-dirents unique (reverse items) total-payload)
+             (let* ([d (car ds)]
+                    [name-bv (string->utf8 (fuse-dirent-name d))]
+                    [reclen (fuse-rec-align
+                              (+ FUSE-DIRENT-HEADER-SIZE
+                                 (bytevector-length name-bv)))]
+                    [new-total (+ total-payload reclen)])
                (if (> new-total max-size)
-                   (finalize-dirents unique (reverse chunks) total-payload)
-                   (loop (cdr ds) (cons encoded chunks) new-total))))))
-  (def (finalize-dirents unique chunks total-payload)
+                   (build-dirents unique (reverse items) total-payload)
+                   (measure
+                     (cdr ds)
+                     (cons (vector d name-bv reclen) items)
+                     new-total))))))
+  (def (build-dirents unique items total-payload)
        (let* ([total (+ FUSE-OUT-HEADER-SIZE total-payload)]
               [bv (make-bytevector total 0)])
          (u32-set! bv 0 total)
          (s32-set! bv 4 0)
          (u64-set! bv 8 unique)
-         (let loop ([cs chunks] [pos FUSE-OUT-HEADER-SIZE])
-           (unless (null? cs)
-             (let ([chunk (car cs)])
-               (bytevector-copy! chunk 0 bv pos (bytevector-length chunk))
-               (loop (cdr cs) (+ pos (bytevector-length chunk))))))
+         (let loop ([is items] [pos FUSE-OUT-HEADER-SIZE])
+           (unless (null? is)
+             (let* ([item (car is)]
+                    [d (vector-ref item 0)]
+                    [name-bv (vector-ref item 1)]
+                    [reclen (vector-ref item 2)]
+                    [namelen (bytevector-length name-bv)])
+               (u64-set! bv pos (fuse-dirent-ino d))
+               (u64-set! bv (+ pos 8) (fuse-dirent-off d))
+               (u32-set! bv (+ pos 16) namelen)
+               (u32-set! bv (+ pos 20) (fuse-dirent-type d))
+               (bytevector-copy! name-bv 0 bv (+ pos 24) namelen)
+               (loop (cdr is) (+ pos reclen)))))
          bv)))
diff --git a/src/jerboa-fuse/codec.ss b/src/jerboa-fuse/codec.ss
index 22a028e..ed226c4 100644
--- a/src/jerboa-fuse/codec.ss
+++ b/src/jerboa-fuse/codec.ss
@@ -444,29 +444,41 @@
 
   ;; Pack a list of fuse-dirent records into a single response bytevector
   ;; that fits within max-size bytes. Returns complete response (header + dirents).
+  ;; Measures the dirents that fit, then encodes them directly into one sized
+  ;; output buffer (no per-dirent temporary bytevector, no final copy).
   (def (encode-dirents unique dirents max-size)
-    (let loop ([ds dirents] [chunks '()] [total-payload 0])
+    (let measure ([ds dirents] [items '()] [total-payload 0])
       (if (null? ds)
-        (finalize-dirents unique (reverse chunks) total-payload)
-        (let* ([encoded (encode-dirent (car ds))]
-               [elen (bytevector-length encoded)]
-               [new-total (+ total-payload elen)])
+        (build-dirents unique (reverse items) total-payload)
+        (let* ([d        (car ds)]
+               [name-bv  (string->utf8 (fuse-dirent-name d))]
+               [reclen   (fuse-rec-align (+ FUSE-DIRENT-HEADER-SIZE (bytevector-length name-bv)))]
+               [new-total (+ total-payload reclen)])
           (if (> new-total max-size)
-            (finalize-dirents unique (reverse chunks) total-payload)
-            (loop (cdr ds) (cons encoded chunks) new-total))))))
+            (build-dirents unique (reverse items) total-payload)
+            (measure (cdr ds) (cons (vector d name-bv reclen) items) new-total))))))
 
-  ;; Assemble header + dirent chunks into a single bytevector.
-  (def (finalize-dirents unique chunks total-payload)
+  ;; Encode the measured dirents straight into one zeroed response buffer,
+  ;; matching encode-dirent's wire layout (ino@0 off@8 namelen@16 type@24… name).
+  (def (build-dirents unique items total-payload)
     (let* ([total (+ FUSE-OUT-HEADER-SIZE total-payload)]
            [bv (make-bytevector total 0)])
       (u32-set! bv 0 total)
       (s32-set! bv 4 0)
       (u64-set! bv 8 unique)
-      (let loop ([cs chunks] [pos FUSE-OUT-HEADER-SIZE])
-        (unless (null? cs)
-          (let ([chunk (car cs)])
-            (bytevector-copy! chunk 0 bv pos (bytevector-length chunk))
-            (loop (cdr cs) (+ pos (bytevector-length chunk))))))
+      (let loop ([is items] [pos FUSE-OUT-HEADER-SIZE])
+        (unless (null? is)
+          (let* ([item     (car is)]
+                 [d        (vector-ref item 0)]
+                 [name-bv  (vector-ref item 1)]
+                 [reclen   (vector-ref item 2)]
+                 [namelen  (bytevector-length name-bv)])
+            (u64-set! bv pos (fuse-dirent-ino d))
+            (u64-set! bv (+ pos 8) (fuse-dirent-off d))
+            (u32-set! bv (+ pos 16) namelen)
+            (u32-set! bv (+ pos 20) (fuse-dirent-type d))
+            (bytevector-copy! name-bv 0 bv (+ pos 24) namelen)
+            (loop (cdr is) (+ pos reclen)))))
       bv))