summaryrefslogtreecommitdiff
path: root/module/ice-9/suspendable-ports.scm
blob: a823f1d37cc8fba3a9277027be8b5c56523bfa93 (plain)
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
;;; Ports, implemented in Scheme
;;; Copyright (C) 2016, 2019 Free Software Foundation, Inc.
;;;
;;; This library is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU Lesser General Public License as
;;; published by the Free Software Foundation, either version 3 of the
;;; License, or (at your option) any later version.
;;;
;;; This library is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;; Lesser General Public License for more details.
;;;
;;; You should have received a copy of the GNU Lesser General Public
;;; License along with this program.  If not, see
;;; <http://www.gnu.org/licenses/>.

;;; Commentary:
;;;
;;; We would like to be able to implement green threads using delimited
;;; continuations.  When a green thread would block on I/O, it should
;;; suspend and arrange to be resumed when it can make progress.
;;;
;;; The problem is that the ports code is written in C.  A delimited
;;; continuation that captures a C activation can't be resumed, because
;;; Guile doesn't know about the internal structure of the C activation
;;; (stack frame) and so can't compose it with the current continuation.
;;; For that reason, to implement this desired future, we have to
;;; implement ports in Scheme.
;;;
;;; If Scheme were fast enough, we would just implement ports in Scheme
;;; early in Guile's boot, and that would be that.  However currently
;;; that's not the case: character-by-character I/O is about three or
;;; four times slower in Scheme than in C.  This is mostly bytecode
;;; overhead, though there are some ways that compiler improvements
;;; could help us too.
;;;
;;; Note that the difference between Scheme and C is much less for
;;; batched operations, like read-bytes or read-line.
;;;
;;; So the upshot is that we need to keep the C I/O routines around for
;;; performance reasons.  We can still have our Scheme routines
;;; available as a module, though, for use by people working with green
;;; threads.  That's this module.  People that want green threads can
;;; even replace the core bindings, which enables green threading over
;;; other generic routines like the HTTP server.
;;;
;;; Code:


(define-module (ice-9 suspendable-ports)
  #:use-module (rnrs bytevectors)
  #:use-module (ice-9 ports internal)
  #:use-module (ice-9 match)
  #:export (current-read-waiter
            current-write-waiter

            install-suspendable-ports!
            uninstall-suspendable-ports!))

(define (default-read-waiter port) (port-poll port "r"))
(define (default-write-waiter port) (port-poll port "w"))

(define current-read-waiter  (make-parameter default-read-waiter))
(define current-write-waiter (make-parameter default-write-waiter))

(define (wait-for-readable port) ((current-read-waiter) port))
(define (wait-for-writable port) ((current-write-waiter) port))

(define (read-bytes port dst start count)
  (cond
   (((port-read port) port dst start count)
    => (lambda (read)
         (unless (<= 0 read count)
           (error "bad return from port read function" read))
         read))
   (else
    (wait-for-readable port)
    (read-bytes port dst start count))))

(define (write-bytes port src start count)
  (cond
   (((port-write port) port src start count)
    => (lambda (written)
         (unless (<= 0 written count)
           (error "bad return from port write function" written))
         (when (< written count)
           (write-bytes port src (+ start written) (- count written)))))
   (else
    (wait-for-writable port)
    (write-bytes port src start count))))

(define (flush-input port)
  (let* ((buf (port-read-buffer port))
         (cur (port-buffer-cur buf))
         (end (port-buffer-end buf)))
    (when (< cur end)
      (set-port-buffer-cur! buf 0)
      (set-port-buffer-end! buf 0)
      (seek port (- cur end) SEEK_CUR))))

(define (flush-output port)
  (let* ((buf (port-write-buffer port))
         (cur (port-buffer-cur buf))
         (end (port-buffer-end buf)))
    (when (< cur end)
      ;; Update cursors before attempting to write, assuming that I/O
      ;; errors are sticky.  That way if the write throws an error,
      ;; causing the computation to abort, and possibly causing the port
      ;; to be collected by GC when it's open, any subsequent close-port
      ;; or force-output won't signal *another* error.
      (set-port-buffer-cur! buf 0)
      (set-port-buffer-end! buf 0)
      (write-bytes port (port-buffer-bytevector buf) cur (- end cur)))))

(define utf8-bom #vu8(#xEF #xBB #xBF))
(define utf16be-bom #vu8(#xFE #xFF))
(define utf16le-bom #vu8(#xFF #xFE))
(define utf32be-bom #vu8(#x00 #x00 #xFE #xFF))
(define utf32le-bom #vu8(#xFF #xFE #x00 #x00))

(define (clear-stream-start-for-bom-read port io-mode)
  (define (maybe-consume-bom bom)
    (and (eq? (peek-byte port) (bytevector-u8-ref bom 0))
         (call-with-values (lambda ()
                             (fill-input port (bytevector-length bom)))
           (lambda (buf cur buffered)
             (and (<= (bytevector-length bom) buffered)
                  (let ((bv (port-buffer-bytevector buf)))
                    (let lp ((i 1))
                      (if (= i (bytevector-length bom))
                          (begin
                            (set-port-buffer-cur! buf (+ cur i))
                            #t)
                          (and (eq? (bytevector-u8-ref bv (+ cur i))
                                    (bytevector-u8-ref bom i))
                               (lp (1+ i)))))))))))
  (when (and (port-clear-stream-start-for-bom-read port)
             (eq? io-mode 'text))
    (case (%port-encoding port)
      ((UTF-8)
       (maybe-consume-bom utf8-bom))
      ((UTF-16)
       (cond
        ((maybe-consume-bom utf16le-bom)
         (specialize-port-encoding! port 'UTF-16LE))
        (else
         (maybe-consume-bom utf16be-bom)
         (specialize-port-encoding! port 'UTF-16BE))))
      ((UTF-32)
       (cond
        ((maybe-consume-bom utf32le-bom)
         (specialize-port-encoding! port 'UTF-32LE))
        (else
         (maybe-consume-bom utf32be-bom)
         (specialize-port-encoding! port 'UTF-32BE)))))))

(define* (fill-input port #:optional (minimum-buffering 1) (io-mode 'text))
  (clear-stream-start-for-bom-read port io-mode)
  (let* ((buf (port-read-buffer port))
         (cur (port-buffer-cur buf))
         (buffered (max (- (port-buffer-end buf) cur) 0)))
    (cond
     ((or (<= minimum-buffering buffered) (port-buffer-has-eof? buf))
      (values buf cur buffered))
     (else
      (unless (input-port? port)
        (error "not an input port" port))
      (when (port-random-access? port)
        (flush-output port))
      (let ((bv (port-buffer-bytevector buf)))
        (cond
         ((< (bytevector-length bv) minimum-buffering)
          (expand-port-read-buffer! port minimum-buffering)
          (fill-input port minimum-buffering))
         (else
          (when (< 0 cur)
            (bytevector-copy! bv cur bv 0 buffered)
            (set-port-buffer-cur! buf 0)
            (set-port-buffer-end! buf buffered))
          (let ((buffering (max (port-read-buffering port) minimum-buffering)))
            (let lp ((buffered buffered))
              (let* ((count (- buffering buffered))
                     (read (read-bytes port bv buffered count)))
                (cond
                 ((zero? read)
                  (set-port-buffer-has-eof?! buf #t)
                  (values buf 0 buffered))
                 (else
                  (let ((buffered (+ buffered read)))
                    (set-port-buffer-end! buf buffered)
                    (if (< buffered minimum-buffering)
                        (lp buffered)
                        (values buf 0 buffered)))))))))))))))

(define* (force-output #:optional (port (current-output-port)))
  (unless (and (output-port? port) (not (port-closed? port)))
    (error "not an open output port" port))
  (flush-output port))

(define close-port
  (let ((%close-port (@ (guile) close-port)))
    (lambda (port)
      (cond
       ((port-closed? port) #f)
       (else
        (when (output-port? port) (flush-output port))
        (%close-port port))))))

(define-inlinable (peek-bytes port count kfast kslow)
  (let* ((buf (port-read-buffer port))
         (cur (port-buffer-cur buf))
         (buffered (- (port-buffer-end buf) cur)))
    (if (<= count buffered)
        (kfast buf (port-buffer-bytevector buf) cur buffered)
        (call-with-values (lambda () (fill-input port count))
          (lambda (buf cur buffered)
            (kslow buf (port-buffer-bytevector buf) cur buffered))))))

(define (peek-byte port)
  (peek-bytes port 1
              (lambda (buf bv cur buffered)
                (bytevector-u8-ref bv cur))
              (lambda (buf bv cur buffered)
                (and (> buffered 0)
                     (bytevector-u8-ref bv cur)))))

(define* (lookahead-u8 port)
  (define (fast-path buf bv cur buffered)
    (bytevector-u8-ref bv cur))
  (define (slow-path buf bv cur buffered)
    (if (zero? buffered)
        the-eof-object
        (fast-path buf bv cur buffered)))
  (peek-bytes port 1 fast-path slow-path))

(define* (get-u8 port)
  (define (fast-path buf bv cur buffered)
    (set-port-buffer-cur! buf (1+ cur))
    (bytevector-u8-ref bv cur))
  (define (slow-path buf bv cur buffered)
    (if (zero? buffered)
        (begin
          (set-port-buffer-has-eof?! buf #f)
          the-eof-object)
        (fast-path buf bv cur buffered)))
  (peek-bytes port 1 fast-path slow-path))

(define (get-bytevector-n! port bv start count)
  (define (port-buffer-take! pos buf cur to-copy)
    (bytevector-copy! (port-buffer-bytevector buf) cur
                      bv pos to-copy)
    (set-port-buffer-cur! buf (+ cur to-copy))
    (+ pos to-copy))
  (define (take-already-buffered)
    (let* ((buf (port-read-buffer port))
           (cur (port-buffer-cur buf))
           (buffered (max (- (port-buffer-end buf) cur) 0)))
      (port-buffer-take! start buf cur (min count buffered))))
  (define (buffer-and-fill pos)
    (call-with-values (lambda () (fill-input port 1 'binary))
      (lambda (buf cur buffered)
        (if (zero? buffered)
            ;; We found EOF, which is marked in the port read buffer.
            ;; If we haven't read any bytes yet, clear the EOF from the
            ;; buffer and return it.  Otherwise return the number of
            ;; bytes that we have read.
            (if (= pos start)
                (begin
                  (set-port-buffer-has-eof?! buf #f)
                  the-eof-object)
                (- pos start))
            (let ((pos (port-buffer-take! pos buf cur
                                          (min (- (+ start count) pos)
                                               buffered))))
              (if (= pos (+ start count))
                  count
                  (buffer-and-fill pos)))))))
  (define (fill-directly pos)
    (when (port-random-access? port)
      (flush-output port))
    (port-clear-stream-start-for-bom-read port)
    (let lp ((pos pos))
      (let ((read (read-bytes port bv pos (- (+ start count) pos))))
        (cond
         ((= (+ pos read) (+ start count))
          count)
         ((zero? read)
          ;; We found EOF.  If we haven't read any bytes yet, return
          ;; EOF.  Otherwise save the EOF in the port read buffer.
          (if (= pos start)
              the-eof-object
              (begin
                (set-port-buffer-has-eof?! (port-read-buffer port) #t)
                (- pos start))))
         (else (lp (+ pos read)))))))
  (let ((pos (take-already-buffered)))
    (cond
     ((= pos (+ start count))
      count)
     ((< (- (+ start count) pos) (port-read-buffering port))
      (buffer-and-fill pos))
     (else (fill-directly pos)))))

(define (get-bytevector-n port count)
  (let* ((bv (make-bytevector count))
         (result (get-bytevector-n! port bv 0 count)))
    (cond ((eof-object? result)
           result)
          ((= result count)
           bv)
          (else
           (let ((bv* (make-bytevector result)))
             (bytevector-copy! bv 0 bv* 0 result)
             bv*)))))

(define (get-bytevector-some port)
  (call-with-values (lambda () (fill-input port 1 'binary))
    (lambda (buf cur buffered)
      (if (zero? buffered)
          (begin
            (set-port-buffer-has-eof?! buf #f)
            the-eof-object)
          (let ((result (make-bytevector buffered)))
            (bytevector-copy! (port-buffer-bytevector buf) cur
                              result 0 buffered)
            (set-port-buffer-cur! buf (+ cur buffered))
            result)))))

(define (get-bytevector-some! port bv start count)
  (if (zero? count)
      0
      (call-with-values (lambda () (fill-input port 1 'binary))
        (lambda (buf cur buffered)
          (if (zero? buffered)
              (begin
                (set-port-buffer-has-eof?! buf #f)
                the-eof-object)
              (let ((transfer-size (min count buffered)))
                (bytevector-copy! (port-buffer-bytevector buf) cur
                                  bv start transfer-size)
                (set-port-buffer-cur! buf (+ cur transfer-size))
                transfer-size))))))

(define (put-u8 port byte)
  (let* ((buf (port-write-buffer port))
         (bv (port-buffer-bytevector buf))
         (end (port-buffer-end buf)))
    (unless (<= 0 end (bytevector-length bv))
      (error "not an output port" port))
    (when (and (eq? (port-buffer-cur buf) end) (port-random-access? port))
      (flush-input port))
    (cond
     ((= end (bytevector-length bv))
      ;; Multiple threads racing; race to flush, then retry.
      (flush-output port)
      (put-u8 port byte))
     (else
      (bytevector-u8-set! bv end byte)
      (set-port-buffer-end! buf (1+ end))
      (when (= (1+ end) (bytevector-length bv)) (flush-output port))))))

(define* (put-bytevector port src #:optional (start 0)
                         (count (- (bytevector-length src) start)))
  (unless (<= 0 start (+ start count) (bytevector-length src))
    (error "invalid start/count" start count))
  (let* ((buf (port-write-buffer port))
         (bv (port-buffer-bytevector buf))
         (size (bytevector-length bv))
         (cur (port-buffer-cur buf))
         (end (port-buffer-end buf))
         (buffered (max (- end cur) 0)))
    (when (and (eq? cur end) (port-random-access? port))
      (flush-input port))
    (cond
     ((<= size count)
      ;; The write won't fit in the buffer at all; write directly.
      ;; Write directly.  Flush write buffer first if needed.
      (when (< cur end) (flush-output port))
      (write-bytes port src start count))
     ((< (- size buffered) count)
      ;; The write won't fit into the buffer along with what's already
      ;; buffered.  Flush and fill.
      (flush-output port)
      (set-port-buffer-end! buf count)
      (bytevector-copy! src start bv 0 count))
     (else
      ;; The write will fit in the buffer, but we need to shuffle the
      ;; already-buffered bytes (if any) down.
      (set-port-buffer-cur! buf 0)
      (set-port-buffer-end! buf (+ buffered count))
      (bytevector-copy! bv cur bv 0 buffered)
      (bytevector-copy! src start bv buffered count)
      ;; If the buffer completely fills, we flush.
      (when (= (+ buffered count) size)
        (flush-output port))))))

(define (decoding-error subr port)
  ;; GNU definition; fixme?
  (define EILSEQ 84)
  (throw 'decoding-error subr "input decoding error" EILSEQ port))

(define-inlinable (decode-utf8 bv start avail u8_0 kt kf)
  (cond
   ((< u8_0 #x80)
    (kt (integer->char u8_0) 1))
   ((and (<= #xc2 u8_0 #xdf) (<= 2 avail))
    (let ((u8_1 (bytevector-u8-ref bv (1+ start))))
      (if (= (logand u8_1 #xc0) #x80)
          (kt (integer->char
               (logior (ash (logand u8_0 #x1f) 6)
                       (logand u8_1 #x3f)))
              2)
          (kf))))
   ((and (= (logand u8_0 #xf0) #xe0) (<= 3 avail))
    (let ((u8_1 (bytevector-u8-ref bv (+ start 1)))
          (u8_2 (bytevector-u8-ref bv (+ start 2))))
      (if (and (= (logand u8_1 #xc0) #x80)
               (= (logand u8_2 #xc0) #x80)
               (case u8_0
                 ((#xe0) (>= u8_1 #xa0))
                 ((#xed) (>= u8_1 #x9f))
                 (else #t)))
          (kt (integer->char
               (logior (ash (logand u8_0 #x0f) 12)
                       (ash (logand u8_1 #x3f) 6)
                       (logand u8_2 #x3f)))
              3)
          (kf))))
   ((and (<= #xf0 u8_0 #xf4) (<= 4 avail))
    (let ((u8_1 (bytevector-u8-ref bv (+ start 1)))
          (u8_2 (bytevector-u8-ref bv (+ start 2)))
          (u8_3 (bytevector-u8-ref bv (+ start 3))))
      (if (and (= (logand u8_1 #xc0) #x80)
               (= (logand u8_2 #xc0) #x80)
               (= (logand u8_3 #xc0) #x80)
               (case u8_0
                 ((#xf0) (>= u8_1 #x90))
                 ((#xf4) (>= u8_1 #x8f))
                 (else #t)))
          (kt (integer->char
               (logior (ash (logand u8_0 #x07) 18)
                       (ash (logand u8_1 #x3f) 12)
                       (ash (logand u8_2 #x3f) 6)
                       (logand u8_3 #x3f)))
              4)
          (kf))))
   (else (kf))))

(define (bad-utf8-len bv cur buffering first-byte)
  (define (ref n)
    (bytevector-u8-ref bv (+ cur n)))
  (cond
   ((< first-byte #x80) 0)
   ((<= #xc2 first-byte #xdf)
    (cond
     ((< buffering 2) 1)
     ((not (= (logand (ref 1) #xc0) #x80)) 1)
     (else 0)))
   ((= (logand first-byte #xf0) #xe0)
    (cond
     ((< buffering 2) 1)
     ((not (= (logand (ref 1) #xc0) #x80)) 1)
     ((and (eq? first-byte #xe0) (< (ref 1) #xa0)) 1)
     ((and (eq? first-byte #xed) (< (ref 1) #x9f)) 1)
     ((< buffering 3) 2)
     ((not (= (logand (ref 2) #xc0) #x80)) 2)
     (else 0)))
   ((<= #xf0 first-byte #xf4)
    (cond
     ((< buffering 2) 1)
     ((not (= (logand (ref 1) #xc0) #x80)) 1)
     ((and (eq? first-byte #xf0) (< (ref 1) #x90)) 1)
     ((and (eq? first-byte #xf4) (< (ref 1) #x8f)) 1)
     ((< buffering 3) 2)
     ((not (= (logand (ref 2) #xc0) #x80)) 2)
     ((< buffering 4) 3)
     ((not (= (logand (ref 3) #xc0) #x80)) 3)
     (else 0)))
   (else 1)))

(define (peek-char-and-next-cur/utf8 port buf cur first-byte)
  (if (< first-byte #x80)
      (values (integer->char first-byte) buf (+ cur 1))
      (call-with-values (lambda ()
                          (fill-input port
                                      (cond
                                       ((<= #xc2 first-byte #xdf) 2)
                                       ((= (logand first-byte #xf0) #xe0) 3)
                                       (else 4))))
        (lambda (buf cur buffering)
          (let ((bv (port-buffer-bytevector buf)))
            (define (bad-utf8)
              (let ((len (bad-utf8-len bv cur buffering first-byte)))
                (when (zero? len) (error "internal error"))
                (if (eq? (port-conversion-strategy port) 'substitute)
                    (values #\xFFFD buf (+ cur len))
                    (decoding-error "peek-char" port))))
            (decode-utf8 bv cur buffering first-byte
                         (lambda (char len)
                           (values char buf (+ cur len)))
                         bad-utf8))))))

(define (peek-char-and-next-cur/iso-8859-1 port buf cur first-byte)
  (values (integer->char first-byte) buf (+ cur 1)))

(define (peek-char-and-next-cur/iconv port)
  (let lp ((prev-input-size 0))
    (let ((input-size (1+ prev-input-size)))
      (call-with-values (lambda () (fill-input port input-size))
        (lambda (buf cur buffered)
          (cond
           ((< buffered input-size)
            ;; Buffer failed to fill; EOF, possibly premature.
            (cond
             ((zero? prev-input-size)
              (values the-eof-object buf cur))
             ((eq? (port-conversion-strategy port) 'substitute)
              (values #\xFFFD buf (+ cur prev-input-size)))
             (else
              (decoding-error "peek-char" port))))
           ((port-decode-char port (port-buffer-bytevector buf)
                              cur input-size)
            => (lambda (char)
                 (values char buf (+ cur input-size))))
           (else
            (lp input-size))))))))

(define (peek-char-and-next-cur port)
  (define (have-byte buf bv cur buffered)
    (let ((first-byte (bytevector-u8-ref bv cur)))
      (case (%port-encoding port)
        ((UTF-8)
         (peek-char-and-next-cur/utf8 port buf cur first-byte))
        ((ISO-8859-1)
         (peek-char-and-next-cur/iso-8859-1 port buf cur first-byte))
        (else
         (peek-char-and-next-cur/iconv port)))))
  (peek-bytes port 1 have-byte
              (lambda (buf bv cur buffered)
                (if (< 0 buffered)
                    (have-byte buf bv cur buffered)
                    (values the-eof-object buf cur)))))

(define* (peek-char #:optional (port (current-input-port)))
  (define (slow-path)
    (call-with-values (lambda () (peek-char-and-next-cur port))
      (lambda (char buf cur)
        char)))
  (define (fast-path buf bv cur buffered)
    (let ((u8 (bytevector-u8-ref bv cur))
          (enc (%port-encoding port)))
      (case enc
        ((UTF-8) (decode-utf8 bv cur buffered u8 (lambda (char len) char)
                              slow-path))
        ((ISO-8859-1) (integer->char u8))
        (else (slow-path)))))
  (peek-bytes port 1 fast-path
              (lambda (buf bv cur buffered) (slow-path))))

(define-inlinable (advance-port-position! pos char)
  ;; FIXME: this cond is a speed hack; really we should just compile
  ;; `case' better.
  (cond
   ;; FIXME: char>? et al should compile well.
   ((<= (char->integer #\space) (char->integer char))
    (set-port-position-column! pos (1+ (port-position-column pos))))
   (else
    (case char
      ((#\alarm) #t)                    ; No change.
      ((#\backspace)
       (let ((col (port-position-column pos)))
         (when (> col 0)
           (set-port-position-column! pos (1- col)))))
      ((#\newline)
       (set-port-position-line! pos (1+ (port-position-line pos)))
       (set-port-position-column! pos 0))
      ((#\return)
       (set-port-position-column! pos 0))
      ((#\tab)
       (let ((col (port-position-column pos)))
         (set-port-position-column! pos (- (+ col 8) (remainder col 8)))))
      (else
       (set-port-position-column! pos (1+ (port-position-column pos))))))))

(define* (read-char #:optional (port (current-input-port)))
  (define (finish buf char)
    (advance-port-position! (port-buffer-position buf) char)
    char)
  (define (slow-path)
    (call-with-values (lambda () (peek-char-and-next-cur port))
      (lambda (char buf cur)
        (set-port-buffer-cur! buf cur)
        (if (eq? char the-eof-object)
            (begin
              (set-port-buffer-has-eof?! buf #f)
              char)
            (finish buf char)))))
  (define (fast-path buf bv cur buffered)
    (let ((u8 (bytevector-u8-ref bv cur))
          (enc (%port-encoding port)))
      (case enc
        ((UTF-8)
         (decode-utf8 bv cur buffered u8
                      (lambda (char len)
                        (set-port-buffer-cur! buf (+ cur len))
                        (finish buf char))
                      slow-path))
        ((ISO-8859-1)
         (set-port-buffer-cur! buf (+ cur 1))
         (finish buf (integer->char u8)))
        (else (slow-path)))))
  (peek-bytes port 1 fast-path
              (lambda (buf bv cur buffered) (slow-path))))

(define-inlinable (port-fold-chars/iso-8859-1 port proc seed)
  (let* ((buf (port-read-buffer port))
         (cur (port-buffer-cur buf)))
    (let fold-buffer ((buf buf) (cur cur) (seed seed))
      (let ((bv (port-buffer-bytevector buf))
            (end (port-buffer-end buf)))
        (let fold-chars ((cur cur) (seed seed))
          (cond
           ((= end cur)
            (call-with-values (lambda () (fill-input port))
              (lambda (buf cur buffered)
                (if (zero? buffered)
                    (call-with-values (lambda () (proc the-eof-object seed))
                      (lambda (seed done?)
                        (if done? seed (fold-buffer buf cur seed))))
                    (fold-buffer buf cur seed)))))
           (else
            (let ((ch (integer->char (bytevector-u8-ref bv cur)))
                  (cur (1+ cur)))
              (set-port-buffer-cur! buf cur)
              (advance-port-position! (port-buffer-position buf) ch)
              (call-with-values (lambda () (proc ch seed))
                (lambda (seed done?)
                  (if done? seed (fold-chars cur seed))))))))))))

(define-inlinable (port-fold-chars port proc seed)
  (case (%port-encoding port)
    ((ISO-8859-1) (port-fold-chars/iso-8859-1 port proc seed))
    (else
     (let lp ((seed seed))
       (let ((ch (read-char port)))
         (call-with-values (lambda () (proc ch seed))
           (lambda (seed done?)
             (if done? seed (lp seed)))))))))

(define* (read-delimited delims #:optional (port (current-input-port))
                         (handle-delim 'trim))
  ;; Currently this function conses characters into a list, then uses
  ;; reverse-list->string.  It wastes 2 words per character but it still
  ;; seems to be the fastest thing at the moment.
  (define (finish delim chars)
    (define (->string chars)
      (if (and (null? chars) (not (char? delim)))
          the-eof-object
          (reverse-list->string chars)))
    (case handle-delim
      ((trim) (->string chars))
      ((split) (cons (->string chars) delim))
      ((concat)
       (->string (if (char? delim) (cons delim chars) chars)))
      ((peek)
       (when (char? delim) (unread-char delim port))
       (->string chars))
      (else
       (error "unexpected handle-delim value: " handle-delim))))
  (define-syntax-rule (make-folder delimiter?)
    (lambda (char chars)
      (if (or (not (char? char)) (delimiter? char))
          (values (finish char chars) #t)
          (values (cons char chars) #f))))
  (define-syntax-rule (specialized-fold delimiter?)
    (port-fold-chars port (make-folder delimiter?) '()))
  (case (string-length delims)
    ((0) (specialized-fold (lambda (char) #f)))
    ((1) (let ((delim (string-ref delims 0)))
           (specialized-fold (lambda (char) (eqv? char delim)))))
    (else => (lambda (ndelims)
               (specialized-fold
                (lambda (char)
                  (let lp ((i 0))
                    (and (< i ndelims)
                         (or (eqv? char (string-ref delims i))
                             (lp (1+ i)))))))))))

(define* (read-line #:optional (port (current-input-port))
                    (handle-delim 'trim))
  (read-delimited "\n" port handle-delim))

(define* (%read-line port)
  (read-line port 'split))

(define* (put-string port str #:optional (start 0)
                     (count (- (string-length str) start)))
  (let* ((aux (port-auxiliary-write-buffer port))
         (pos (port-buffer-position aux))
         (line (port-position-line pos)))
    (set-port-buffer-cur! aux 0)
    (port-clear-stream-start-for-bom-write port aux)
    (let lp ((encoded 0))
      (when (< encoded count)
        (let ((encoded (+ encoded
                          (port-encode-chars port aux str
                                             (+ start encoded)
                                             (- count encoded)))))
          (let ((end (port-buffer-end aux)))
            (set-port-buffer-end! aux 0)
            (put-bytevector port (port-buffer-bytevector aux) 0 end)
            (lp encoded)))))
    (when (and (not (eqv? line (port-position-line pos)))
               (port-line-buffered? port))
      (flush-output port))))

(define* (put-char port char)
  (let ((aux (port-auxiliary-write-buffer port)))
    (set-port-buffer-cur! aux 0)
    (port-clear-stream-start-for-bom-write port aux)
    (port-encode-char port aux char)
    (let ((end (port-buffer-end aux)))
      (set-port-buffer-end! aux 0)
      (put-bytevector port (port-buffer-bytevector aux) 0 end))
    (when (and (eqv? char #\newline) (port-line-buffered? port))
      (flush-output port))))

(define accept
  (let ((%accept (@ (guile) accept)))
    (lambda* (port #:optional (flags 0))
      (let lp ()
        (or (%accept port flags)
            (begin
              (wait-for-readable port)
              (lp)))))))

(define connect
  (let ((%connect (@ (guile) connect)))
    (lambda (port sockaddr . args)
      (unless (apply %connect port sockaddr args)
        ;; Clownshoes semantics; see connect(2).
        (wait-for-writable port)
        (let ((err (getsockopt port SOL_SOCKET SO_ERROR)))
          (unless (zero? err)
            (scm-error 'system-error "connect" "~A"
                       (list (strerror err)) #f)))))))

(define saved-port-bindings #f)
(define port-bindings
  '(((guile)
     read-char peek-char force-output close-port
     accept connect)
    ((ice-9 binary-ports)
     get-u8 lookahead-u8 get-bytevector-n get-bytevector-n!
     get-bytevector-some get-bytevector-some!
     put-u8 put-bytevector)
    ((ice-9 textual-ports)
     put-char put-string)
    ((ice-9 rdelim) %read-line read-line read-delimited)))
(define (install-suspendable-ports!)
  (unless saved-port-bindings
    (set! saved-port-bindings (make-hash-table))
    (let ((suspendable-ports (resolve-module '(ice-9 suspendable-ports))))
      (for-each
       (match-lambda
         ((mod . syms)
          (let ((mod (resolve-module mod)))
            (for-each (lambda (sym)
                        (hashq-set! saved-port-bindings sym
                                    (module-ref mod sym))
                        (module-set! mod sym
                                     (module-ref suspendable-ports sym)))
                      syms))))
       port-bindings))))

(define (uninstall-suspendable-ports!)
  (when saved-port-bindings
    (for-each
     (match-lambda
       ((mod . syms)
        (let ((mod (resolve-module mod)))
          (for-each (lambda (sym)
                      (let ((saved (hashq-ref saved-port-bindings sym)))
                        (module-set! mod sym saved)))
                    syms))))
     port-bindings)
    (set! saved-port-bindings #f)))