···11+(lang dune 3.21)
22+(name bitstream)
33+44+(generate_opam_files true)
55+66+(license ISC)
77+(authors "Anil Madhavapeddy <anil@recoil.org>")
88+(maintainers "Anil Madhavapeddy <anil@recoil.org>")
99+(source (tangled anil.recoil.org/ocaml-bitstream))
1010+1111+(package
1212+ (name bitstream)
1313+ (synopsis "Bit-level I/O for binary format parsing and generation")
1414+ (description
1515+ "Forward and backward bitstream reading/writing for binary formats.
1616+Supports bit-level operations required by compression algorithms like
1717+FSE, ANS, and Huffman coding.")
1818+ (depends
1919+ (ocaml (>= 5.2.0))
2020+ (alcotest (and :with-test (>= 1.7.0)))))
+385
src/bitstream.ml
···11+(** Bitstream - Bit-level I/O for binary formats.
22+33+ Provides forward and backward bitstream reading and writing for parsing
44+ and generating binary formats that operate at the bit level.
55+66+ Forward streams read/write from the start of a buffer towards the end.
77+ Backward streams read/write from the end of a buffer towards the start,
88+ which is required by some compression algorithms (FSE, ANS). *)
99+1010+(** {1 Slice Type} *)
1111+1212+module Slice = struct
1313+ type t = {
1414+ bytes : bytes;
1515+ first : int;
1616+ length : int;
1717+ }
1818+1919+ let make bytes ~first ~length =
2020+ { bytes; first; length }
2121+2222+ let of_bytes ?first ?length bytes =
2323+ let first = Option.value first ~default:0 in
2424+ let length = Option.value length ~default:(Bytes.length bytes - first) in
2525+ { bytes; first; length }
2626+2727+ let to_bytes t =
2828+ Bytes.sub t.bytes t.first t.length
2929+3030+ let is_empty t =
3131+ t.length = 0
3232+3333+ let sub t ~first ~length =
3434+ { bytes = t.bytes; first = t.first + first; length }
3535+end
3636+3737+(** {1 Exceptions} *)
3838+3939+exception End_of_stream
4040+(** Raised when attempting to read past the end of the stream. *)
4141+4242+exception Invalid_state of string
4343+(** Raised when an operation requires a specific state (e.g., byte alignment). *)
4444+4545+exception Corrupted_stream of string
4646+(** Raised when stream data is malformed (e.g., invalid padding marker). *)
4747+4848+(** {1 Forward Bitstream Reader} *)
4949+5050+module Forward_reader = struct
5151+ type t = {
5252+ src : bytes;
5353+ start_pos : int;
5454+ limit : int;
5555+ mutable byte_pos : int;
5656+ mutable bit_pos : int; (* 0-7, bits consumed in current byte *)
5757+ }
5858+5959+ let of_slice (slice : Slice.t) =
6060+ { src = slice.bytes;
6161+ start_pos = slice.first;
6262+ limit = slice.first + slice.length;
6363+ byte_pos = slice.first;
6464+ bit_pos = 0 }
6565+6666+ let of_bytes src =
6767+ of_slice (Slice.of_bytes src)
6868+6969+ let create src ~pos ~len =
7070+ of_slice (Slice.make src ~first:pos ~length:len)
7171+7272+ let[@inline] remaining t =
7373+ (t.limit - t.byte_pos) * 8 - t.bit_pos
7474+7575+ let[@inline] is_byte_aligned t =
7676+ t.bit_pos = 0
7777+7878+ let[@inline] read_bits t n =
7979+ if n <= 0 then 0
8080+ else if n > 57 then invalid_arg "read_bits: n > 57"
8181+ else begin
8282+ let result = ref 0 in
8383+ let bits_read = ref 0 in
8484+ while !bits_read < n do
8585+ if t.byte_pos >= t.limit then
8686+ raise End_of_stream;
8787+ let byte = Bytes.get_uint8 t.src t.byte_pos in
8888+ let available = 8 - t.bit_pos in
8989+ let to_read = min available (n - !bits_read) in
9090+ let mask = (1 lsl to_read) - 1 in
9191+ let bits = (byte lsr t.bit_pos) land mask in
9292+ result := !result lor (bits lsl !bits_read);
9393+ bits_read := !bits_read + to_read;
9494+ t.bit_pos <- t.bit_pos + to_read;
9595+ if t.bit_pos >= 8 then begin
9696+ t.bit_pos <- 0;
9797+ t.byte_pos <- t.byte_pos + 1
9898+ end
9999+ done;
100100+ !result
101101+ end
102102+103103+ let[@inline] read_byte t =
104104+ if t.bit_pos <> 0 then
105105+ raise (Invalid_state "read_byte: not byte aligned");
106106+ if t.byte_pos >= t.limit then
107107+ raise End_of_stream;
108108+ let b = Bytes.get_uint8 t.src t.byte_pos in
109109+ t.byte_pos <- t.byte_pos + 1;
110110+ b
111111+112112+ let rewind_bits t n =
113113+ let total_bits = (t.byte_pos - t.start_pos) * 8 + t.bit_pos in
114114+ let new_total = total_bits - n in
115115+ if new_total < 0 then
116116+ raise End_of_stream;
117117+ t.byte_pos <- t.start_pos + new_total / 8;
118118+ t.bit_pos <- new_total mod 8
119119+120120+ let align t =
121121+ if t.bit_pos <> 0 then begin
122122+ t.bit_pos <- 0;
123123+ t.byte_pos <- t.byte_pos + 1
124124+ end
125125+126126+ let byte_position t =
127127+ if t.bit_pos <> 0 then
128128+ raise (Invalid_state "byte_position: not byte aligned");
129129+ t.byte_pos
130130+131131+ let get_slice t n : Slice.t =
132132+ if t.bit_pos <> 0 then
133133+ raise (Invalid_state "get_slice: not byte aligned");
134134+ if t.byte_pos + n > t.limit then
135135+ raise End_of_stream;
136136+ let result = Slice.make t.src ~first:t.byte_pos ~length:n in
137137+ t.byte_pos <- t.byte_pos + n;
138138+ result
139139+140140+ let get_bytes t n =
141141+ Slice.to_bytes (get_slice t n)
142142+143143+ let to_slice t : Slice.t =
144144+ if t.bit_pos <> 0 then
145145+ raise (Invalid_state "to_slice: not byte aligned");
146146+ Slice.make t.src ~first:t.byte_pos ~length:(t.limit - t.byte_pos)
147147+148148+ let advance t n =
149149+ if t.bit_pos <> 0 then
150150+ raise (Invalid_state "advance: not byte aligned");
151151+ if t.byte_pos + n > t.limit then
152152+ raise End_of_stream;
153153+ t.byte_pos <- t.byte_pos + n
154154+155155+ let sub t n =
156156+ if t.bit_pos <> 0 then
157157+ raise (Invalid_state "sub: not byte aligned");
158158+ if t.byte_pos + n > t.limit then
159159+ raise End_of_stream;
160160+ let result = of_slice (Slice.make t.src ~first:t.byte_pos ~length:n) in
161161+ t.byte_pos <- t.byte_pos + n;
162162+ result
163163+164164+ let remaining_bytes t =
165165+ if t.bit_pos <> 0 then
166166+ raise (Invalid_state "remaining_bytes: not byte aligned");
167167+ t.limit - t.byte_pos
168168+169169+ let skip_bits t n =
170170+ ignore (read_bits t n)
171171+end
172172+173173+(** {1 Backward Bitstream Reader}
174174+175175+ Reads bits from the end of a buffer towards the start. The stream
176176+ starts with a padding marker (highest 1-bit indicates start of data). *)
177177+178178+module Backward_reader = struct
179179+ type t = {
180180+ src : bytes;
181181+ start_pos : int;
182182+ mutable bit_offset : int; (* Bits remaining from end, decreasing *)
183183+ }
184184+185185+ let of_slice (slice : Slice.t) =
186186+ if slice.length = 0 then
187187+ raise End_of_stream;
188188+ let last_byte_pos = slice.first + slice.length - 1 in
189189+ let last_byte = Bytes.get_uint8 slice.bytes last_byte_pos in
190190+ if last_byte = 0 then
191191+ raise (Corrupted_stream "invalid padding marker");
192192+ (* Find the highest set bit - this is the padding marker *)
193193+ let rec find_marker byte bit =
194194+ if bit < 0 then 0
195195+ else if (byte land (1 lsl bit)) <> 0 then bit
196196+ else find_marker byte (bit - 1)
197197+ in
198198+ let padding = 8 - find_marker last_byte 7 in
199199+ let bit_offset = slice.length * 8 - padding in
200200+ { src = slice.bytes; start_pos = slice.first; bit_offset }
201201+202202+ let of_bytes src ~pos ~len =
203203+ of_slice (Slice.make src ~first:pos ~length:len)
204204+205205+ let[@inline] remaining t = t.bit_offset
206206+207207+ let[@inline] is_empty t = t.bit_offset <= 0
208208+209209+ let[@inline] read_bits t n =
210210+ if n <= 0 then 0
211211+ else if n > 57 then invalid_arg "read_bits: n > 57"
212212+ else begin
213213+ t.bit_offset <- t.bit_offset - n;
214214+ let actual_offset = max 0 t.bit_offset in
215215+ let actual_bits = if t.bit_offset < 0 then n + t.bit_offset else n in
216216+ if actual_bits <= 0 then 0
217217+ else begin
218218+ let byte_offset = t.start_pos + (actual_offset / 8) in
219219+ let bit_offset = actual_offset mod 8 in
220220+ let result = ref 0 in
221221+ let bits_read = ref 0 in
222222+ let current_byte = ref byte_offset in
223223+ let current_bit = ref bit_offset in
224224+ while !bits_read < actual_bits do
225225+ let byte = Bytes.get_uint8 t.src !current_byte in
226226+ let available = 8 - !current_bit in
227227+ let to_read = min available (actual_bits - !bits_read) in
228228+ let mask = (1 lsl to_read) - 1 in
229229+ let bits = (byte lsr !current_bit) land mask in
230230+ result := !result lor (bits lsl !bits_read);
231231+ bits_read := !bits_read + to_read;
232232+ current_bit := !current_bit + to_read;
233233+ if !current_bit >= 8 then begin
234234+ current_bit := 0;
235235+ incr current_byte
236236+ end
237237+ done;
238238+ (* If we read past the beginning, shift the result *)
239239+ if t.bit_offset < 0 then
240240+ !result lsl (-t.bit_offset)
241241+ else
242242+ !result
243243+ end
244244+ end
245245+246246+ let peek_bits t n =
247247+ let saved_offset = t.bit_offset in
248248+ let result = read_bits t n in
249249+ t.bit_offset <- saved_offset;
250250+ result
251251+end
252252+253253+(** {1 Forward Bitstream Writer} *)
254254+255255+module Forward_writer = struct
256256+ type t = {
257257+ dst : bytes;
258258+ start_pos : int;
259259+ mutable byte_pos : int;
260260+ mutable bit_pos : int; (* 0-7, bits written in current byte *)
261261+ mutable current_byte : int;
262262+ }
263263+264264+ let of_slice (slice : Slice.t) =
265265+ { dst = slice.bytes;
266266+ start_pos = slice.first;
267267+ byte_pos = slice.first;
268268+ bit_pos = 0;
269269+ current_byte = 0 }
270270+271271+ let of_bytes dst =
272272+ of_slice (Slice.of_bytes dst)
273273+274274+ let create dst ~pos =
275275+ of_slice (Slice.make dst ~first:pos ~length:(Bytes.length dst - pos))
276276+277277+ let flush t =
278278+ if t.bit_pos > 0 then begin
279279+ Bytes.set_uint8 t.dst t.byte_pos t.current_byte;
280280+ t.byte_pos <- t.byte_pos + 1;
281281+ t.bit_pos <- 0;
282282+ t.current_byte <- 0
283283+ end
284284+285285+ let[@inline] write_bits t value n =
286286+ if n <= 0 then ()
287287+ else if n > 57 then invalid_arg "write_bits: n > 57"
288288+ else begin
289289+ let value = ref value in
290290+ let remaining = ref n in
291291+292292+ while !remaining > 0 do
293293+ let available = 8 - t.bit_pos in
294294+ let to_write = min available !remaining in
295295+ let mask = (1 lsl to_write) - 1 in
296296+ t.current_byte <- t.current_byte lor ((!value land mask) lsl t.bit_pos);
297297+ value := !value lsr to_write;
298298+ remaining := !remaining - to_write;
299299+ t.bit_pos <- t.bit_pos + to_write;
300300+301301+ if t.bit_pos = 8 then begin
302302+ Bytes.set_uint8 t.dst t.byte_pos t.current_byte;
303303+ t.byte_pos <- t.byte_pos + 1;
304304+ t.bit_pos <- 0;
305305+ t.current_byte <- 0
306306+ end
307307+ done
308308+ end
309309+310310+ let write_byte t value =
311311+ if t.bit_pos <> 0 then flush t;
312312+ Bytes.set_uint8 t.dst t.byte_pos value;
313313+ t.byte_pos <- t.byte_pos + 1
314314+315315+ let write_slice t (slice : Slice.t) =
316316+ if t.bit_pos <> 0 then flush t;
317317+ Bytes.blit slice.bytes slice.first t.dst t.byte_pos slice.length;
318318+ t.byte_pos <- t.byte_pos + slice.length
319319+320320+ let write_bytes t src =
321321+ write_slice t (Slice.of_bytes src)
322322+323323+ let byte_position t =
324324+ if t.bit_pos > 0 then t.byte_pos + 1 else t.byte_pos
325325+326326+ let finalize t =
327327+ flush t;
328328+ t.byte_pos - t.start_pos
329329+330330+ let to_slice t : Slice.t =
331331+ flush t;
332332+ Slice.make t.dst ~first:t.start_pos ~length:(t.byte_pos - t.start_pos)
333333+end
334334+335335+(** {1 Backward Bitstream Writer}
336336+337337+ Accumulates bits to be read backwards. Used for FSE and Huffman encoding. *)
338338+339339+module Backward_writer = struct
340340+ type t = {
341341+ mutable bits : int64;
342342+ mutable num_bits : int;
343343+ buffer : bytes;
344344+ mutable buf_pos : int;
345345+ }
346346+347347+ let create size =
348348+ { bits = 0L; num_bits = 0; buffer = Bytes.create size; buf_pos = size }
349349+350350+ let[@inline] write_bits t value n =
351351+ if n > 0 then begin
352352+ t.bits <- Int64.logor t.bits (Int64.shift_left (Int64.of_int value) t.num_bits);
353353+ t.num_bits <- t.num_bits + n
354354+ end
355355+356356+ let flush_bytes t =
357357+ while t.num_bits >= 8 do
358358+ t.buf_pos <- t.buf_pos - 1;
359359+ Bytes.set_uint8 t.buffer t.buf_pos (Int64.to_int (Int64.logand t.bits 0xFFL));
360360+ t.bits <- Int64.shift_right_logical t.bits 8;
361361+ t.num_bits <- t.num_bits - 8
362362+ done
363363+364364+ let finalize_to_slice t : Slice.t =
365365+ write_bits t 1 1;
366366+ if t.num_bits mod 8 <> 0 then
367367+ t.num_bits <- ((t.num_bits + 7) / 8) * 8;
368368+ flush_bytes t;
369369+ let len = Bytes.length t.buffer - t.buf_pos in
370370+ (* Reverse bytes in place so marker ends up at the end *)
371371+ for i = 0 to len / 2 - 1 do
372372+ let j = t.buf_pos + i in
373373+ let k = t.buf_pos + len - 1 - i in
374374+ let tmp = Bytes.get t.buffer j in
375375+ Bytes.set t.buffer j (Bytes.get t.buffer k);
376376+ Bytes.set t.buffer k tmp
377377+ done;
378378+ Slice.make t.buffer ~first:t.buf_pos ~length:len
379379+380380+ let finalize t =
381381+ Slice.to_bytes (finalize_to_slice t)
382382+383383+ let current_size t =
384384+ Bytes.length t.buffer - t.buf_pos + (t.num_bits + 7) / 8
385385+end
+267
src/bitstream.mli
···11+(** Bitstream - Bit-level I/O for binary formats.
22+33+ This library provides efficient bit-level reading and writing for parsing
44+ and generating binary formats. It supports both forward (start-to-end) and
55+ backward (end-to-start) operations, as required by various compression
66+ algorithms.
77+88+ {1 Overview}
99+1010+ {[
1111+ (* Forward reading from a slice (zero-copy) *)
1212+ let slice = { Bitstream.Slice.bytes = data; first = 0; length = n } in
1313+ let r = Bitstream.Forward_reader.of_slice slice in
1414+ let magic = Bitstream.Forward_reader.read_bits r 32 in
1515+ let flags = Bitstream.Forward_reader.read_bits r 8 in
1616+1717+ (* Get remaining data as a slice (zero-copy) *)
1818+ let remaining = Bitstream.Forward_reader.to_slice r in
1919+2020+ (* Backward reading - for FSE/ANS entropy decoding *)
2121+ let r = Bitstream.Backward_reader.of_slice slice in
2222+ let symbol = Bitstream.Backward_reader.read_bits r num_bits
2323+ ]}
2424+2525+ {1 Bytesrw Compatibility}
2626+2727+ The {!Slice} type is structurally compatible with [Bytesrw.Bytes.Slice.t],
2828+ enabling zero-copy integration with bytesrw-based streaming. All reader
2929+ and writer constructors accept slices as the primary input type.
3030+3131+ {1 Error Handling}
3232+3333+ Operations raise exceptions on error:
3434+ - {!End_of_stream}: Reading past end of stream
3535+ - {!Invalid_state}: Operation requires specific state (e.g., byte alignment)
3636+ - {!Corrupted_stream}: Malformed stream data *)
3737+3838+(** {1 Slice Type}
3939+4040+ A slice is a view into a byte buffer. This type is structurally compatible
4141+ with [Bytesrw.Bytes.Slice.t], enabling zero-copy interop. *)
4242+4343+module Slice : sig
4444+ type t = {
4545+ bytes : bytes;
4646+ first : int;
4747+ length : int;
4848+ }
4949+ (** A slice referencing [length] bytes starting at [first] in [bytes].
5050+ This is structurally identical to [Bytesrw.Bytes.Slice.t]. *)
5151+5252+ val make : bytes -> first:int -> length:int -> t
5353+ (** [make bytes ~first ~length] creates a slice. *)
5454+5555+ val of_bytes : ?first:int -> ?length:int -> bytes -> t
5656+ (** [of_bytes bytes] creates a slice for the entire buffer.
5757+ Optional [first] and [length] can restrict the range. *)
5858+5959+ val to_bytes : t -> bytes
6060+ (** [to_bytes t] copies the slice contents to a new buffer. *)
6161+6262+ val is_empty : t -> bool
6363+ (** [is_empty t] returns true if the slice has zero length. *)
6464+6565+ val sub : t -> first:int -> length:int -> t
6666+ (** [sub t ~first ~length] creates a sub-slice. [first] is relative to [t]. *)
6767+end
6868+6969+(** {1 Exceptions} *)
7070+7171+exception End_of_stream
7272+(** Raised when attempting to read past the end of the stream. *)
7373+7474+exception Invalid_state of string
7575+(** Raised when an operation requires a specific state (e.g., byte alignment). *)
7676+7777+exception Corrupted_stream of string
7878+(** Raised when stream data is malformed (e.g., invalid padding marker). *)
7979+8080+(** {1 Forward Bitstream Reader} *)
8181+8282+module Forward_reader : sig
8383+ (** Forward bitstream reader state. *)
8484+ type t
8585+8686+ val of_slice : Slice.t -> t
8787+ (** [of_slice slice] creates a reader from a slice. Zero-copy. *)
8888+8989+ val of_bytes : bytes -> t
9090+ (** [of_bytes src] creates a reader for the entire byte buffer. *)
9191+9292+ val create : bytes -> pos:int -> len:int -> t
9393+ (** [create src ~pos ~len] creates a reader for [len] bytes starting at [pos]. *)
9494+9595+ val remaining : t -> int
9696+ (** [remaining t] returns the number of unread bits. *)
9797+9898+ val is_byte_aligned : t -> bool
9999+ (** [is_byte_aligned t] returns true if the reader is at a byte boundary. *)
100100+101101+ val read_bits : t -> int -> int
102102+ (** [read_bits t n] reads and returns [n] bits (1-57) in little-endian order.
103103+ @raise End_of_stream if not enough data available.
104104+ @raise Invalid_argument if [n > 57]. *)
105105+106106+ val read_byte : t -> int
107107+ (** [read_byte t] reads and returns the next byte (0-255).
108108+ @raise Invalid_state if not byte aligned.
109109+ @raise End_of_stream if at end of stream. *)
110110+111111+ val rewind_bits : t -> int -> unit
112112+ (** [rewind_bits t n] rewinds the stream by [n] bits.
113113+ @raise End_of_stream if rewinding past the start. *)
114114+115115+ val align : t -> unit
116116+ (** [align t] advances to the next byte boundary if not already aligned. *)
117117+118118+ val byte_position : t -> int
119119+ (** [byte_position t] returns the current byte position.
120120+ @raise Invalid_state if not byte aligned. *)
121121+122122+ val get_slice : t -> int -> Slice.t
123123+ (** [get_slice t n] returns the next [n] bytes as a slice (zero-copy).
124124+ The slice references the underlying buffer directly.
125125+ @raise Invalid_state if not byte aligned.
126126+ @raise End_of_stream if not enough data. *)
127127+128128+ val get_bytes : t -> int -> bytes
129129+ (** [get_bytes t n] reads and returns the next [n] bytes as a new buffer.
130130+ Equivalent to [Slice.to_bytes (get_slice t n)].
131131+ @raise Invalid_state if not byte aligned.
132132+ @raise End_of_stream if not enough data. *)
133133+134134+ val to_slice : t -> Slice.t
135135+ (** [to_slice t] returns the remaining data as a slice (zero-copy).
136136+ @raise Invalid_state if not byte aligned. *)
137137+138138+ val advance : t -> int -> unit
139139+ (** [advance t n] skips [n] bytes without returning them.
140140+ @raise Invalid_state if not byte aligned.
141141+ @raise End_of_stream if not enough data. *)
142142+143143+ val sub : t -> int -> t
144144+ (** [sub t n] creates a sub-reader for the next [n] bytes and advances [t].
145145+ @raise Invalid_state if not byte aligned.
146146+ @raise End_of_stream if not enough data. *)
147147+148148+ val remaining_bytes : t -> int
149149+ (** [remaining_bytes t] returns the number of unread bytes.
150150+ @raise Invalid_state if not byte aligned. *)
151151+152152+ val skip_bits : t -> int -> unit
153153+ (** [skip_bits t n] skips [n] bits without returning them.
154154+ @raise End_of_stream if not enough data. *)
155155+end
156156+157157+(** {1 Backward Bitstream Reader}
158158+159159+ Reads bits from the end of a buffer towards the start. The stream format
160160+ includes a padding marker: the highest 1-bit in the final byte indicates
161161+ where actual data begins.
162162+163163+ This format is used by FSE and ANS entropy coders. *)
164164+165165+module Backward_reader : sig
166166+ (** Backward bitstream reader state. *)
167167+ type t
168168+169169+ val of_slice : Slice.t -> t
170170+ (** [of_slice slice] creates a backward reader from a slice. Zero-copy.
171171+ @raise End_of_stream if slice is empty.
172172+ @raise Corrupted_stream if padding marker is invalid. *)
173173+174174+ val of_bytes : bytes -> pos:int -> len:int -> t
175175+ (** [of_bytes src ~pos ~len] creates a backward reader.
176176+ The stream is read from position [pos + len - 1] towards [pos].
177177+ @raise End_of_stream if [len = 0].
178178+ @raise Corrupted_stream if padding marker is invalid. *)
179179+180180+ val remaining : t -> int
181181+ (** [remaining t] returns the number of bits remaining. *)
182182+183183+ val is_empty : t -> bool
184184+ (** [is_empty t] returns true if no more bits are available. *)
185185+186186+ val read_bits : t -> int -> int
187187+ (** [read_bits t n] reads and returns [n] bits (1-57).
188188+ Returns 0 bits when reading past the beginning.
189189+ @raise Invalid_argument if [n > 57]. *)
190190+191191+ val peek_bits : t -> int -> int
192192+ (** [peek_bits t n] returns the next [n] bits without consuming them.
193193+ @raise Invalid_argument if [n > 57]. *)
194194+end
195195+196196+(** {1 Forward Bitstream Writer} *)
197197+198198+module Forward_writer : sig
199199+ (** Forward bitstream writer state. *)
200200+ type t
201201+202202+ val of_slice : Slice.t -> t
203203+ (** [of_slice slice] creates a writer into a slice. Zero-copy. *)
204204+205205+ val of_bytes : bytes -> t
206206+ (** [of_bytes dst] creates a writer starting at position 0. *)
207207+208208+ val create : bytes -> pos:int -> t
209209+ (** [create dst ~pos] creates a writer starting at [pos] in buffer [dst]. *)
210210+211211+ val write_bits : t -> int -> int -> unit
212212+ (** [write_bits t value n] writes the lower [n] bits (1-57) of [value]
213213+ in little-endian order.
214214+ @raise Invalid_argument if [n > 57]. *)
215215+216216+ val write_byte : t -> int -> unit
217217+ (** [write_byte t value] writes a single byte. Flushes any partial bits first. *)
218218+219219+ val write_slice : t -> Slice.t -> unit
220220+ (** [write_slice t slice] writes bytes from a slice. Flushes any partial bits first. *)
221221+222222+ val write_bytes : t -> bytes -> unit
223223+ (** [write_bytes t src] writes all bytes from [src]. Flushes any partial bits first. *)
224224+225225+ val byte_position : t -> int
226226+ (** [byte_position t] returns the current output position including any partial byte. *)
227227+228228+ val flush : t -> unit
229229+ (** [flush t] writes any accumulated bits as a partial byte. *)
230230+231231+ val finalize : t -> int
232232+ (** [finalize t] flushes and returns the total number of bytes written. *)
233233+234234+ val to_slice : t -> Slice.t
235235+ (** [to_slice t] flushes and returns the written data as a slice (zero-copy).
236236+ The slice references the underlying destination buffer. *)
237237+end
238238+239239+(** {1 Backward Bitstream Writer}
240240+241241+ Accumulates bits to produce output that will be read backwards.
242242+ Used for FSE and Huffman encoding. *)
243243+244244+module Backward_writer : sig
245245+ (** Backward bitstream writer state. *)
246246+ type t
247247+248248+ val create : int -> t
249249+ (** [create size] creates a writer with an internal buffer of [size] bytes. *)
250250+251251+ val write_bits : t -> int -> int -> unit
252252+ (** [write_bits t value n] accumulates [n] bits from [value]. *)
253253+254254+ val flush_bytes : t -> unit
255255+ (** [flush_bytes t] flushes complete bytes to the internal buffer. *)
256256+257257+ val finalize_to_slice : t -> Slice.t
258258+ (** [finalize_to_slice t] adds the padding marker, flushes, and returns output
259259+ as a slice (zero-copy). The slice references the internal buffer. *)
260260+261261+ val finalize : t -> bytes
262262+ (** [finalize t] adds the padding marker, flushes, and returns the output.
263263+ Equivalent to [Slice.to_bytes (finalize_to_slice t)]. *)
264264+265265+ val current_size : t -> int
266266+ (** [current_size t] returns the current output size estimate. *)
267267+end