Module: system-internals Synopsis: Implementation of multi-buffered streams for dood. Author: Toby Weinberg Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define sealed primary class () slot size-for-buffers :: ; // all the same size slot buffer-shift-amount :: ; slot buffer-vector-size-mask :: ; slot buffer-preemption-index :: = 0; slot buffers :: ; // = limited(, of: ); end class; // $empty-buffer is used as fill for the buffers (vector) slot. It is // important for the use count to be 3 so that uninitialized buffers are // correctly preempted. define constant $empty-buffer = make-(size: 2, known-power-of-two-size: #t, use-count: 3); define constant $free-stream-ids :: = make(); define constant $stream-id-to-stream-map :: = make(); define function new-stream-id(the-stream :: ) => (new-id :: ) let new-id = if ( ~ empty?($free-stream-ids)) pop($free-stream-ids); else let new-id = size($stream-id-to-stream-map); size($stream-id-to-stream-map) := new-id + 1; new-id end if; $stream-id-to-stream-map[new-id] := the-stream; new-id end function; define constant = ; define constant = limited(, of: ); define constant $buffer-map-index-size = 24; // TODO: MACHINE INDEP define inline function buffer-map-entry-empty? (bme :: ) => (res :: ) ~logbit?(0, bme) end function; define inline function buffer-map-entry-deposit-empty (v :: , bme :: ) logbit-deposit(~v, 0, bme) end function; define inline function buffer-map-entry-dirty? (bme :: ) => (res :: ) logbit?(1, bme) end function; define inline function buffer-map-entry-deposit-dirty (v :: , bme :: ) logbit-deposit(v, 1, bme) end function; define inline function buffer-map-entry-index (bme :: ) => (res :: ) bit-field-extract(2, $buffer-map-index-size, bme) end function; define inline function buffer-map-entry-deposit-index (i :: , bme :: ) buffer-map-entry-deposit-dirty (#t, buffer-map-entry-deposit-empty (#f, bit-field-deposit(i, 2, $buffer-map-index-size, bme))) end function; define sealed primary class () constant slot buffer-map :: = make(); slot buffer-vector :: ; slot stream-id :: false-or(); end class; define sealed primary class (, ) inherited slot stream-element-type = ; keyword encoding:; end class; define sealed primary class (, ) inherited slot stream-element-type = ; keyword encoding:; end class; define sealed primary class (, ) inherited slot stream-element-type = ; keyword encoding:; end class; /// Creating file streams define sealed method initialize (the-vector :: , #rest initargs, #key buffer-size: requested-buffer-size :: false-or() = #f, number-of-buffers: requested-number-of-buffers :: = 32, stream :: false-or() = #f) => () next-method(); let (buffer-size, shift-amount) = round-to-power-of-two( if (requested-buffer-size) round-to-power-of-two(requested-buffer-size) elseif (stream) accessor-preferred-buffer-size(stream.accessor) else 1024 end); the-vector.size-for-buffers := buffer-size; the-vector.buffer-shift-amount := - shift-amount; let number-of-buffers = round-to-power-of-two(requested-number-of-buffers); the-vector.buffers := make(, // limited(, of: ); size: number-of-buffers, fill: $empty-buffer); the-vector.buffer-vector-size-mask := number-of-buffers - 1; end method initialize; define method multi-buffered-stream-direction (direction :: false-or()) => (res :: ) if (direction == #"input") #"input" else #"input-output" end if end method; define sealed method initialize (the-stream :: , #rest initargs, #key direction = #"input-output", buffer-vector: input-buffer-vector :: false-or() = #f, buffer-size: requested-buffer-size = #f, number-of-buffers: requested-number-of-buffers = 32) => () let direction = multi-buffered-stream-direction(direction); apply(next-method, the-stream, direction: direction, initargs); // This is wrong, shouldn't really be subclass of file streams sigh. The // initialize method creates the shared buffer which we don't want. the-stream.stream-shared-buffer := #f; // Get rid of the bum // Have to calculate the size again to get the shift amount--yuck the-stream.buffer-vector := if (input-buffer-vector) input-buffer-vector else make(, stream: the-stream, buffer-size: requested-buffer-size, number-of-buffers: requested-number-of-buffers); end if; the-stream.stream-id := new-stream-id(the-stream); end method initialize; define sealed method make (class == , #rest initargs, #key locator, direction, element-type = , encoding) => (stream :: ) let type = apply(type-for-multi-buffered-stream, locator, element-type, encoding, initargs); let direction = multi-buffered-stream-direction(direction); if (type == class) apply(next-method, class, direction: direction, initargs) else apply(make, type, direction: direction, initargs) end end method make; define sealed generic type-for-multi-buffered-stream (locator :: , element-type :: false-or(), encoding :: , #key, #all-keys) => (multi-buffered-stream-type /* ---*** :: subclass() */); define sealed method type-for-multi-buffered-stream (locator :: , element-type :: false-or(), encoding :: , #key) => (multi-buffered-stream-type /* ---*** :: subclass() */) end method type-for-multi-buffered-stream; define sealed method type-for-multi-buffered-stream (locator :: , element-type == , encoding :: , #key) => (multi-buffered-stream-type /* ---*** :: subclass() */) end method type-for-multi-buffered-stream; //---*** This equates with . Hmm... define sealed method type-for-multi-buffered-stream (locator :: , element-type == , encoding :: , #key) => (multi-buffered-stream-type /* ---*** :: subclass() */) end method type-for-multi-buffered-stream; define sealed method type-for-multi-buffered-stream (locator :: , element-type == , encoding :: , #key) => (multi-buffered-stream-type /* ---*** :: subclass() */) end method type-for-multi-buffered-stream; /// File stream implementation define sealed method stream-at-end? (stream :: ) => (at-end? :: ) stream-position(stream) >= stream.stream-size end method stream-at-end?; define sealed method stream-input-available? (stream :: ) => (available? :: ) stream-position(stream) < stream.stream-size end method stream-input-available?; define sealed method stream-size (the-stream :: ) => (the-size :: ); // If the last buffer for this stream is paged in and modified then // use the maximum of buffer-end for that last buffer and the // accessor file size, otherwise use the accessor file size. let last-buffer :: false-or() = if ((the-stream.buffer-map.size > 0) & ~buffer-map-entry-empty?(the-stream.buffer-map.last)) let index = buffer-map-entry-index(the-stream.buffer-map.last); the-stream.buffer-vector.buffers[index] end if; if (last-buffer & last-buffer.buffer-dirty?) let last-buffer :: = last-buffer; // HACK: TYPE ONLY max(last-buffer.buffer-position + last-buffer.buffer-end, the-stream.accessor.accessor-file-size) else the-stream.accessor.accessor-file-size end if end method; /// Aligned power of two buffer methods // Helper function for filling buffers from the accessor. // still good load-buffer, load-buffer-and-fill // Note that the setter methods are never used. define sealed method stream-shared-buffer-setter (value :: false-or(), stream :: ) => (input-buffer :: false-or()) next-method(); stream.actual-stream-input-buffer := value; stream.actual-stream-output-buffer := value; end method; define sealed method stream-input-buffer-setter (value :: false-or(), stream :: ) => (input-buffer :: false-or()) stream.stream-shared-buffer := value; end method; define sealed method stream-output-buffer-setter (value :: false-or(), stream :: ) => (input-buffer :: false-or()) stream.stream-shared-buffer := value; end method; // Only calls if stream-input-buffer is false, (stream-input-buffer is eq // to stream-shared-buffer) define sealed method do-get-input-buffer (the-stream :: , #key wait? = #t, bytes = 1) => (buffer :: false-or()) let(the-buffer, eof?) = do-get-buffer(the-stream, wait?: wait?, bytes: bytes); stream-shared-buffer(the-stream) := the-buffer; the-buffer.buffer-use-count := 0; if (eof?) #f else the-buffer end end method do-get-input-buffer; define sealed method do-get-buffer (the-stream :: , #key wait? = #t, bytes = 1) => (the-buffer :: , eof? :: ) let the-position :: = the-stream.stream-position; // current-position? let eof? = #f; let buffer-vector :: = the-stream.buffer-vector; let buffer-map-index :: = ash(the-position, buffer-vector.buffer-shift-amount); let buffer-map :: = the-stream.buffer-map; // Make sure we have a map entry unless (buffer-map-index < buffer-map.size) grow-buffer-map(the-stream, buffer-map-index); end unless; // Get a valid buffer index let map-entry = buffer-map[buffer-map-index]; let buffer-index :: = if (buffer-map-entry-empty?(map-entry)) let buffer-index = preempt-buffer(the-stream, the-position, wait?: wait?, bytes: bytes); unless (buffer-map-entry-dirty?(map-entry)) *multi-buffer-working-set* := *multi-buffer-working-set* + 1; end unless; buffer-map[buffer-map-index] := buffer-map-entry-deposit-index(buffer-index, map-entry); buffer-index else buffer-map-entry-index(map-entry) end; let the-buffer :: = buffer-vector.buffers[buffer-index]; // set buffer-next let new-buffer-next :: = logand(the-buffer.buffer-on-page-bits, the-position); the-buffer.buffer-next := new-buffer-next; if (new-buffer-next > the-buffer.buffer-end) // we are creating a new end of file by setting the position off the // end of file. the-buffer.buffer-dirty? := #t; the-buffer.buffer-end := new-buffer-next; eof? := #t; elseif (new-buffer-next = the-buffer.buffer-end) eof? := #t; end if; values(the-buffer, eof?) end method; define function grow-buffer-map (the-stream :: , new-index :: ) => () // This would be a whole lot simpler if I could just set the buffer-map size // and be sure that the fill would be #f. iterate loop (map-index :: = the-stream.buffer-map.size) unless (map-index > new-index) add!(the-stream.buffer-map, buffer-map-entry-deposit-empty (#t, buffer-map-entry-deposit-dirty(#f, 0))); loop(map-index + 1) end unless; end iterate; end function; define function multi-buffer-total-bytes () => (res :: ) *multi-buffer-bytes* end function; define variable *multi-buffer-reads* :: = 0; define function multi-buffer-total-reads () => (res :: ) *multi-buffer-reads* end function; define variable *multi-buffer-working-set* :: = 0; define function multi-buffer-total-working-set () => (res :: ) *multi-buffer-working-set* end function; define method multi-buffer-reads (stream :: ) => (res :: ) // TODO multi-buffer-total-reads() end method; define method multi-buffer-bytes (stream :: ) => (res :: ) // TODO multi-buffer-total-bytes() end method; define method multi-buffer-working-set (stream :: ) => (res :: ) let count :: = 0; for (map-entry in stream.buffer-map) when (buffer-map-entry-dirty?(map-entry)) count := count + 1; end when; end for; count end method; // Find an empty buffer slot or else preempt an existing buffer, load the // buffer from the underlying file and return the index of buffer in the // buffer-vector vector. define function preempt-buffer (the-stream :: , the-position :: , #key wait?, bytes) => (buffer-index :: ); if (closed?(the-stream)) error(make(, stream: the-stream, format-string: "Stream closed: Can't read, write or set-position")); end if; let the-buffer-vector :: = the-stream.buffer-vector; let buffer-index :: = logand(the-buffer-vector.buffer-preemption-index + 1, the-buffer-vector.buffer-vector-size-mask); let the-buffer :: = the-buffer-vector.buffers[buffer-index]; if (the-buffer.buffer-use-count < 3) the-buffer.buffer-use-count := the-buffer.buffer-use-count + 1; end if; let (buffer-index :: , the-buffer :: ) = iterate loop (index :: = buffer-index, buffer :: = the-buffer) if (buffer.buffer-use-count = 3) values(index, buffer) else let buffer-index :: = logand(index + 1, the-stream.buffer-vector.buffer-vector-size-mask); let the-buffer :: = the-buffer-vector.buffers[buffer-index]; if (the-buffer.buffer-use-count < 3) the-buffer.buffer-use-count := the-buffer.buffer-use-count + 1; end if; loop(buffer-index, the-buffer) end if end iterate; the-stream.buffer-vector.buffer-preemption-index := buffer-index; let new-buffer-position :: = 37; // doesn't matter if (the-buffer == $empty-buffer) the-buffer := make- (stream-id: the-stream.stream-id, size: the-stream.buffer-vector.size-for-buffers, known-power-of-two-size?: #t, fill: 0); the-buffer-vector.buffers[buffer-index] := the-buffer; new-buffer-position := logand(the-buffer.buffer-off-page-bits, the-position); let start = 0; let the-size = the-buffer.buffer-size; load-buffer(the-stream, the-buffer, new-buffer-position, start, the-size); else // Zero out the buffer-map entry in the stream which currently // owns the we are preempting. Of course maybe nobody owns the // buffer any more if the owning stream has been closed so we must // check first. if (the-buffer.buffer-owning-stream) let the-owning-stream = $stream-id-to-stream-map[the-buffer.buffer-owning-stream]; let map-index = ash(the-buffer.buffer-position, the-stream.buffer-vector.buffer-shift-amount); let buffer-map = the-owning-stream.buffer-map; buffer-map[map-index] := buffer-map-entry-deposit-empty(#t, buffer-map[map-index]); // Zero out the stream-shared-buffer in the stream which currently // owns the buffer we are preempting if the stream-shared-buffer is // the preempted buffer. This can't happen unless the // buffer-vector is being shared by more than o ne stream. if (the-owning-stream.stream-shared-buffer == the-buffer) // First save the position the-owning-stream.current-position := stream-position(the-owning-stream); the-owning-stream.stream-shared-buffer := #f; end if; if (the-buffer.buffer-dirty? & read-only?(the-owning-stream)) error(make(, stream: the-owning-stream, format-string: "Internal error: buffer for read-only" " (dood stream) was" "modified, can't write the modified buffer." )); end if; force-buffer(the-buffer, the-owning-stream); end if; // Claim the buffer for the current stream. the-buffer.buffer-owning-stream := the-stream.stream-id; new-buffer-position := logand(the-buffer.buffer-off-page-bits, the-position); let start = 0; let the-size = the-buffer.buffer-size; load-buffer-and-fill(the-stream, the-buffer, new-buffer-position, start, the-size); end if; *multi-buffer-reads* := *multi-buffer-reads* + 1; the-buffer.buffer-position := new-buffer-position; the-buffer.buffer-start := 0; the-buffer.buffer-dirty? := #f; the-buffer.buffer-use-count := 0; buffer-index end function; // This is only called when buffer-next is equal to buffer-end, that is // when buffer-next has been incremented 1 past the end of buffer by // read-element. define sealed method do-next-input-buffer (the-stream :: , #key wait? = #t, bytes = 1) => (buffer :: false-or()) ignore(wait?, bytes); let last-buffer :: = stream-input-buffer(the-stream); the-stream.stream-position := last-buffer.buffer-position + last-buffer.buffer-end; let(new-buffer, eof?) = do-get-buffer(the-stream, wait?: wait?, bytes: bytes); stream-shared-buffer(the-stream) := new-buffer; new-buffer.buffer-use-count := 0; if (eof?) #f else new-buffer end end method; // We get here only when 'stream-output-buffer' is #f. define sealed method do-get-output-buffer (the-stream :: , #key bytes = 1) => (the-buffer :: ) let (the-buffer, eof?) = do-get-buffer(the-stream, bytes: bytes); ignore(eof?); stream-shared-buffer(the-stream) := the-buffer; the-buffer.buffer-use-count := 0; the-buffer end method do-get-output-buffer; // This can only be called when 'stream-output-buffer' has a buffer in // it. define sealed method do-next-output-buffer (the-stream :: , #key bytes = 1) => (the-buffer :: ) ignore(bytes); let last-buffer :: = stream-input-buffer(the-stream); the-stream.stream-position := last-buffer.buffer-position + last-buffer.buffer-end; let(new-buffer, eof?) = do-get-buffer(the-stream, bytes: bytes); ignore(eof?); stream-shared-buffer(the-stream) := new-buffer; new-buffer.buffer-use-count := 0; new-buffer end method; // Helper functions for forcing output buffers. Since output buffers can // change to input buffers without been synched it is necessary to force // out any dirty buffer regardless of whether it is input or output. define sealed method force-output-buffers (stream :: ) => () unless (empty?(stream.buffer-map)) let map-entry = stream.buffer-map.last; let map-entry-index = buffer-map-entry-index(map-entry); when (~buffer-map-entry-empty?(map-entry) & stream.buffer-vector.buffers[map-entry-index].buffer-dirty? & stream.buffer-vector.buffers[map-entry-index].buffer-end = 0) // special case set beginning position to last buffer but didn't write // anything into that buffer. Make sure that the previous buffer is // marked dirty stream-position(stream) := stream.buffer-vector.buffers[map-entry-index].buffer-position - 1; let previous-buffer = do-get-buffer(stream); previous-buffer.buffer-dirty? := #t; end when; end unless; // Sort the dirty buffers by increasingIbuffer position to minimize // disk head movement. let sordid-buffers :: = make(); for (buffer in stream.buffer-vector.buffers) if ((buffer.buffer-owning-stream == stream.stream-id) & buffer.buffer-dirty?) sordid-buffers := add!(sordid-buffers, buffer); end; end for; if (read-only?(stream) & ~empty? (sordid-buffers)) error(make(, stream: stream, format-string: "Internal error closing stream: buffer for read-only" " (dood stream) was" "modified. Can't write the modified buffer." )); else sordid-buffers := sort!(sordid-buffers, test: method (buffer-1 :: , buffer-2 :: ) buffer-1.buffer-position < buffer-2.buffer-position end method); for (buffer in sordid-buffers) force-buffer(buffer, stream); end for; end if; values() end method force-output-buffers; define method close (stream :: , #rest keys, #key abort? = #f, wait?, synchronize?) => () ignore(wait?, synchronize?); if (stream-open?(stream)) if (abort?) for (buffer in stream.buffer-vector.buffers) if (buffer.buffer-owning-stream == stream.stream-id) buffer.buffer-dirty? := #f; end if; end for; end if; next-method(); // Now force all the stream-ids for buffers in the streams buffer // vector to be #f (and remember to check for false buffers everywhere) for (buffer in stream.buffer-vector.buffers) if (buffer.buffer-owning-stream == stream.stream-id) buffer.buffer-owning-stream := #f; end; end for; $stream-id-to-stream-map[stream.stream-id] := #f; push($free-stream-ids, stream.stream-id); stream.stream-id := #f; // now zero out the buffer map for (map-index from 0 below stream.buffer-map.size) stream.buffer-map[map-index] := buffer-map-entry-deposit-empty(#t, stream.buffer-map[map-index]); end for; // Now zero out the stream shared buffer buffers so that any attempt // to use the stream forces a call to do-get-buffer which in turn // will call preempt-buffer because we just zeroed out all of the // buffer-map entries. Preempt buffer will figure out what is // wrong if anybody tries to read or write to the stream. stream.stream-shared-buffer := #f; end if; end method close; define sealed method do-force-output-buffers (the-stream :: ) => () force-output-buffers(the-stream); values() end method do-force-output-buffers; /// Positioning methods define sealed method stream-position (stream :: ) => (position :: ) if (stream-shared-buffer(stream)) stream-shared-buffer(stream).buffer-position + stream-shared-buffer(stream).buffer-next else stream.current-position end end method stream-position; // Optimize this for setting position to current buffer or to current position define sealed method stream-position-setter (the-position :: , stream :: ) => (the-position :: ) let size-of-stream :: = stream-size(stream); if ((the-position >= 0) & (the-position <= size-of-stream)) // Don't call next-method() it just figures out the error cases again stream.current-position := the-position; stream-shared-buffer(stream) := #f; else if (closed?(stream)) error(make(, stream: stream, format-string: "Can't set position of closed stream")); else error(make(, stream: stream, size: stream.accessor.accessor-file-size, position: position)); end if; end; the-position end method; // Special for dood - avoids the overhead of adjust-stream-position, but // allows setting the position to extend the file, unlike // stream-position-setter. define function multi-buffered-stream-position-setter (the-position :: , stream :: ) => (the-position :: ) if (stream-shared-buffer(stream)) let the-buffer :: = stream-shared-buffer(stream); if((the-buffer.buffer-position + the-buffer.buffer-next) == the-position) /* do nothing, already there*/ elseif (logand(the-buffer.buffer-off-page-bits, the-position) == the-buffer.buffer-position) /* set position to same page */ the-buffer.buffer-next := logand(the-buffer.buffer-on-page-bits, the-position); elseif (the-position >= 0) stream.current-position := the-position; stream-shared-buffer(stream) := #f; else if (closed?(stream)) error(make(, stream: stream, format-string: "Can't set position of closed stream")); else error(make(, stream: stream, size: stream.accessor.accessor-file-size, position: position)); end if; end if; elseif (the-position >= 0) stream.current-position := the-position; else if (closed?(stream)) error(make(, stream: stream, format-string: "Can't set position of closed stream")); else error(make(, stream: stream, size: stream.accessor.accessor-file-size, position: position)); end if; end if; the-position end function; define sealed method adjust-stream-position (stream :: , delta :: , #key from = #"current") => (position :: ) let size-of-stream :: = stream-size(stream); let position-from-start = select (from) #"current" => stream-position(stream) + delta; #"start" => delta; #"end" => size-of-stream + delta; end; if (position-from-start < 0) if (closed?(stream)) error(make(, stream: stream, format-string: "Can't set position of closed stream")); else error(make(, stream: stream, size: stream.accessor.accessor-file-size, position: position)); end if; else // Don't call next-method() it just figures out everything above again stream.current-position := position-from-start; stream-shared-buffer(stream) := #f; end if; position-from-start end method adjust-stream-position; // These methods really belong to multi-buffered-streams however they // use low level primitives which differ in the native and emulator // versions so putting them here is convenient, if a bit of a hack. // Special read and write methods for dood // These functions assume that since the buffer is a power of 2 greater // than 8 and since the write is aligned on a full word boundary, that // if there is space for the first byte in the buffer than there is // space for the entire four or eight bytes. define inline function write-4-aligned-bytes-from-word (stream :: , word :: ) => () with-output-buffer (sb = stream) let sb :: = sb; // HACK: TYPE ONLY let bi :: = sb.buffer-next; primitive-element (primitive-repeated-slot-as-raw(sb, primitive-repeated-slot-offset(sb)), integer-as-raw(0), integer-as-raw(bi)) := primitive-cast-raw-as-pointer(primitive-unwrap-machine-word(word)); sb.buffer-next := bi + 4; sb.buffer-end := max(bi + 4, sb.buffer-end); sb.buffer-dirty? := #t; end; end function write-4-aligned-bytes-from-word; define inline function read-4-aligned-bytes-as-word (stream :: , #key on-end-of-stream = unsupplied()) => (word) with-input-buffer (sb = stream) if (sb) let sb :: = sb; // HACK: TYPE ONLY let bi :: = sb.buffer-next; sb.buffer-next := bi + 4; primitive-wrap-machine-word (primitive-cast-pointer-as-raw (primitive-element (primitive-repeated-slot-as-raw(sb, primitive-repeated-slot-offset(sb)), integer-as-raw(0), integer-as-raw(bi)))) else error("End of stream in read-4-aligned-bytes"); as(, 0) // HACK: AVOID BOXING end if end with-input-buffer end function read-4-aligned-bytes-as-word; define function write-4-aligned-bytes (stream :: , byte-1 :: , byte-2 :: , byte-3 :: , byte-4 :: ) => () with-output-buffer (sb = stream) let sb :: = sb; // HACK: TYPE ONLY let bi :: = sb.buffer-next; without-bounds-checks sb[bi] := byte-1; sb[bi + 1] := byte-2; sb[bi + 2] := byte-3; sb[bi + 3] := byte-4; end without-bounds-checks; sb.buffer-next := bi + 4; sb.buffer-end := max(bi + 4, sb.buffer-end); sb.buffer-dirty? := #t; end; end function write-4-aligned-bytes; define function write-8-aligned-bytes (stream :: , byte-1 :: , byte-2 :: , byte-3 :: , byte-4 :: , byte-5 :: , byte-6 :: , byte-7 :: , byte-8 :: ) => () with-output-buffer (sb = stream) let sb :: = sb; // HACK: TYPE ONLY let bi :: = sb.buffer-next; without-bounds-checks sb[bi] := byte-1; sb[bi + 1] := byte-2; sb[bi + 2] := byte-3; sb[bi + 3] := byte-4; sb[bi + 4] := byte-5; sb[bi + 5] := byte-6; sb[bi + 6] := byte-7; sb[bi + 7] := byte-8; end without-bounds-checks; sb.buffer-next := bi + 8; sb.buffer-end := max(bi + 8, sb.buffer-end); sb.buffer-dirty? := #t; end end function write-8-aligned-bytes; define function read-4-aligned-bytes (stream :: , #key on-end-of-stream = unsupplied()) => (byte-1 :: , byte-2 :: , byte-3 :: , byte-4 :: ) with-input-buffer (sb = stream) if (sb) *multi-buffer-bytes* := *multi-buffer-bytes* + 4; let sb :: = sb; // HACK: TYPE ONLY let bi :: = sb.buffer-next; sb.buffer-next := bi + 4; without-bounds-checks values(sb[bi], sb[bi + 1], sb[bi + 2], sb[bi + 3]) end without-bounds-checks; else error("End of stream in read-4-aligned-bytes"); end end end function read-4-aligned-bytes; define function read-8-aligned-bytes (stream :: , #key on-end-of-stream = unsupplied()) => (byte-1 :: , byte-2 :: , byte-3 :: , byte-4 :: , byte-5 :: , byte-6 :: , byte-7 :: , byte-8 :: ) with-input-buffer (sb = stream) if (sb) *multi-buffer-bytes* := *multi-buffer-bytes* + 8; let sb :: = sb; // HACK: TYPE ONLY let bi :: = sb.buffer-next; sb.buffer-next := bi + 8; without-bounds-checks values(sb[bi], sb[bi + 1], sb[bi + 2], sb[bi + 3], sb[bi + 4], sb[bi + 5], sb[bi + 6], sb[bi + 7]) end without-bounds-checks; else error("End of stream in read-8-aligned-bytes"); end end end function read-8-aligned-bytes;