twilic 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,766 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "twilic/core/errors"
4
+ require "twilic/core/model"
5
+ require "twilic/core/wire"
6
+
7
+ module Twilic
8
+ module Core
9
+ module Codec
10
+ MAX_U64 = 0xFFFFFFFFFFFFFFFF
11
+ MAX_I64 = 0x7FFFFFFFFFFFFFFF
12
+ MIN_I64 = -0x8000000000000000
13
+
14
+ SIMPLE8B_SLOTS = [
15
+ { count: 60, width: 1 },
16
+ { count: 30, width: 2 },
17
+ { count: 20, width: 3 },
18
+ { count: 15, width: 4 },
19
+ { count: 12, width: 5 },
20
+ { count: 10, width: 6 },
21
+ { count: 8, width: 7 },
22
+ { count: 7, width: 8 },
23
+ { count: 6, width: 10 },
24
+ { count: 5, width: 12 },
25
+ { count: 4, width: 15 },
26
+ { count: 3, width: 20 },
27
+ { count: 2, width: 30 },
28
+ { count: 1, width: 60 }
29
+ ].freeze
30
+
31
+ module_function
32
+
33
+ def encode_i64_vector(values, codec, out)
34
+ case codec
35
+ when Model::VectorCodec::RLE
36
+ encode_i64_rle(values, out)
37
+ when Model::VectorCodec::DIRECT_BITPACK
38
+ encode_i64_direct_bitpack(values, out)
39
+ when Model::VectorCodec::DELTA_BITPACK
40
+ deltas = delta(values)
41
+ encode_i64_direct_bitpack(deltas, out)
42
+ when Model::VectorCodec::FOR_BITPACK
43
+ if values.empty?
44
+ Wire.encode_varuint(0, out)
45
+ return
46
+ end
47
+ min_value = values[0]
48
+ values[1..].each do |v|
49
+ min_value = v if v < min_value
50
+ end
51
+ Wire.encode_varuint(Wire.encode_zigzag(min_value), out)
52
+ shifted = values.map { |v| v - min_value }
53
+ encode_i64_direct_bitpack(shifted, out)
54
+ when Model::VectorCodec::DELTA_FOR_BITPACK
55
+ deltas = delta(values)
56
+ if deltas.empty?
57
+ Wire.encode_varuint(0, out)
58
+ return
59
+ end
60
+ min_value = deltas[0]
61
+ deltas[1..].each do |v|
62
+ min_value = v if v < min_value
63
+ end
64
+ Wire.encode_varuint(Wire.encode_zigzag(min_value), out)
65
+ shifted = deltas.map { |v| v - min_value }
66
+ encode_i64_direct_bitpack(shifted, out)
67
+ when Model::VectorCodec::DELTA_DELTA_BITPACK
68
+ encode_i64_delta_delta(values, out)
69
+ when Model::VectorCodec::PATCHED_FOR
70
+ encode_i64_patched_for(values, out)
71
+ when Model::VectorCodec::SIMPLE8B
72
+ encode_i64_simple8b(values, out)
73
+ when Model::VectorCodec::PLAIN, Model::VectorCodec::DICTIONARY, Model::VectorCodec::STRING_REF,
74
+ Model::VectorCodec::PREFIX_DELTA, Model::VectorCodec::XOR_FLOAT
75
+ encode_i64_plain(values, out)
76
+ end
77
+ end
78
+
79
+ def decode_i64_vector(reader, codec)
80
+ case codec
81
+ when Model::VectorCodec::RLE
82
+ decode_i64_rle(reader)
83
+ when Model::VectorCodec::DIRECT_BITPACK
84
+ decode_i64_direct_bitpack(reader)
85
+ when Model::VectorCodec::DELTA_BITPACK
86
+ values = decode_i64_direct_bitpack(reader)
87
+ undelta(values)
88
+ when Model::VectorCodec::FOR_BITPACK
89
+ encoded_min = reader.read_varuint
90
+ min_value = Wire.decode_zigzag(encoded_min)
91
+ return [] if reader.eof?
92
+
93
+ shifted = decode_i64_direct_bitpack(reader)
94
+ shifted.map { |v| v + min_value }
95
+ when Model::VectorCodec::DELTA_FOR_BITPACK
96
+ encoded_min = reader.read_varuint
97
+ min_value = Wire.decode_zigzag(encoded_min)
98
+ return [] if reader.eof?
99
+
100
+ shifted = decode_i64_direct_bitpack(reader)
101
+ deltas = shifted.map { |v| v + min_value }
102
+ undelta(deltas)
103
+ when Model::VectorCodec::DELTA_DELTA_BITPACK
104
+ decode_i64_delta_delta(reader)
105
+ when Model::VectorCodec::PATCHED_FOR
106
+ decode_i64_patched_for(reader)
107
+ when Model::VectorCodec::SIMPLE8B
108
+ decode_i64_simple8b(reader)
109
+ when Model::VectorCodec::PLAIN, Model::VectorCodec::DICTIONARY, Model::VectorCodec::STRING_REF,
110
+ Model::VectorCodec::PREFIX_DELTA, Model::VectorCodec::XOR_FLOAT
111
+ decode_i64_plain(reader)
112
+ else
113
+ raise Errors.invalid_data("unsupported vector codec")
114
+ end
115
+ end
116
+
117
+ def encode_u64_vector(values, codec, out)
118
+ case codec
119
+ when Model::VectorCodec::RLE
120
+ encode_u64_rle(values, out)
121
+ when Model::VectorCodec::DIRECT_BITPACK
122
+ encode_u64_direct_bitpack(values, out)
123
+ when Model::VectorCodec::FOR_BITPACK
124
+ if values.empty?
125
+ Wire.encode_varuint(0, out)
126
+ return
127
+ end
128
+ min_value = values[0]
129
+ values[1..].each do |v|
130
+ min_value = v if v < min_value
131
+ end
132
+ Wire.encode_varuint(min_value, out)
133
+ shifted = values.map { |v| v - min_value }
134
+ encode_u64_direct_bitpack(shifted, out)
135
+ when Model::VectorCodec::PLAIN
136
+ encode_u64_plain(values, out)
137
+ when Model::VectorCodec::SIMPLE8B
138
+ encode_u64_simple8b(values, out)
139
+ when Model::VectorCodec::DICTIONARY, Model::VectorCodec::STRING_REF, Model::VectorCodec::PREFIX_DELTA,
140
+ Model::VectorCodec::XOR_FLOAT, Model::VectorCodec::DELTA_BITPACK, Model::VectorCodec::DELTA_FOR_BITPACK,
141
+ Model::VectorCodec::DELTA_DELTA_BITPACK, Model::VectorCodec::PATCHED_FOR
142
+ encode_u64_plain(values, out)
143
+ end
144
+ end
145
+
146
+ def decode_u64_vector(reader, codec)
147
+ case codec
148
+ when Model::VectorCodec::RLE
149
+ decode_u64_rle(reader)
150
+ when Model::VectorCodec::DIRECT_BITPACK
151
+ decode_u64_direct_bitpack(reader)
152
+ when Model::VectorCodec::FOR_BITPACK
153
+ min_value = reader.read_varuint
154
+ return [] if reader.eof?
155
+
156
+ shifted = decode_u64_direct_bitpack(reader)
157
+ out = []
158
+ shifted.each do |v|
159
+ sum, ok = checked_add_u64(v, min_value)
160
+ raise Errors.invalid_data("u64 FOR overflow") unless ok
161
+
162
+ out << sum
163
+ end
164
+ out
165
+ when Model::VectorCodec::PLAIN
166
+ decode_u64_plain(reader)
167
+ when Model::VectorCodec::SIMPLE8B
168
+ decode_u64_simple8b(reader)
169
+ when Model::VectorCodec::DICTIONARY, Model::VectorCodec::STRING_REF, Model::VectorCodec::PREFIX_DELTA,
170
+ Model::VectorCodec::XOR_FLOAT, Model::VectorCodec::DELTA_BITPACK, Model::VectorCodec::DELTA_FOR_BITPACK,
171
+ Model::VectorCodec::DELTA_DELTA_BITPACK, Model::VectorCodec::PATCHED_FOR
172
+ decode_u64_plain(reader)
173
+ else
174
+ raise Errors.invalid_data("unsupported vector codec")
175
+ end
176
+ end
177
+
178
+ def encode_f64_vector(values, codec, out)
179
+ if codec == Model::VectorCodec::XOR_FLOAT
180
+ encode_xor_float(values, out)
181
+ return
182
+ end
183
+ Wire.encode_varuint(values.length, out)
184
+ values.each { |v| Wire.append_f64_le(out, v) }
185
+ end
186
+
187
+ def decode_f64_vector(reader, codec)
188
+ return decode_xor_float(reader) if codec == Model::VectorCodec::XOR_FLOAT
189
+
190
+ length = reader.read_varuint
191
+ out = []
192
+ length.times do
193
+ out << Wire.read_f64_le(reader)
194
+ end
195
+ out
196
+ end
197
+
198
+ def encode_u64_plain(values, out)
199
+ Wire.encode_varuint(values.length, out)
200
+ values.each { |value| Wire.encode_varuint(value, out) }
201
+ end
202
+
203
+ def decode_u64_plain(reader)
204
+ length = reader.read_varuint
205
+ out = []
206
+ length.times do
207
+ out << reader.read_varuint
208
+ end
209
+ out
210
+ end
211
+
212
+ def encode_u64_rle(values, out)
213
+ runs = []
214
+ values.each do |value|
215
+ if !runs.empty? && runs[-1][:value] == value
216
+ runs[-1][:count] += 1
217
+ else
218
+ runs << { value: value, count: 1 }
219
+ end
220
+ end
221
+ Wire.encode_varuint(runs.length, out)
222
+ runs.each do |run|
223
+ Wire.encode_varuint(run[:value], out)
224
+ Wire.encode_varuint(run[:count], out)
225
+ end
226
+ end
227
+
228
+ def decode_u64_rle(reader)
229
+ runs_len = reader.read_varuint
230
+ out = []
231
+ runs_len.times do
232
+ value = reader.read_varuint
233
+ count = reader.read_varuint
234
+ count.times { out << value }
235
+ end
236
+ out
237
+ end
238
+
239
+ def encode_u64_direct_bitpack(values, out)
240
+ Wire.encode_varuint(values.length, out)
241
+ if values.empty?
242
+ out << 0.chr
243
+ return
244
+ end
245
+ width = 1
246
+ values.each do |v|
247
+ bw = bit_width(v)
248
+ width = bw if bw > width
249
+ end
250
+ out << width.chr
251
+ pack_u64_values(values, width, out)
252
+ end
253
+
254
+ def decode_u64_direct_bitpack(reader)
255
+ length = reader.read_varuint
256
+ width = reader.read_u8
257
+ return [] if length.zero?
258
+
259
+ raise Errors.invalid_data("bitpack width") if width.zero? || width > 64
260
+
261
+ unpack_u64_values(reader, length, width)
262
+ end
263
+
264
+ def encode_i64_plain(values, out)
265
+ Wire.encode_varuint(values.length, out)
266
+ values.each { |value| Wire.encode_varuint(Wire.encode_zigzag(value), out) }
267
+ end
268
+
269
+ def decode_i64_plain(reader)
270
+ length = reader.read_varuint
271
+ out = []
272
+ length.times do
273
+ v = reader.read_varuint
274
+ out << Wire.decode_zigzag(v)
275
+ end
276
+ out
277
+ end
278
+
279
+ def encode_i64_simple8b(values, out)
280
+ encoded = values.map { |v| Wire.encode_zigzag(v) }
281
+ encode_u64_simple8b_inner(encoded, out)
282
+ end
283
+
284
+ def decode_i64_simple8b(reader)
285
+ encoded = decode_u64_simple8b_inner(reader)
286
+ encoded.map { |v| Wire.decode_zigzag(v) }
287
+ end
288
+
289
+ def encode_u64_simple8b(values, out)
290
+ encode_u64_simple8b_inner(values, out)
291
+ end
292
+
293
+ def decode_u64_simple8b(reader)
294
+ decode_u64_simple8b_inner(reader)
295
+ end
296
+
297
+ def encode_u64_simple8b_inner(values, out)
298
+ Wire.encode_varuint(values.length, out)
299
+ return if values.empty?
300
+
301
+ max_value = 0
302
+ values.each { |v| max_value = v if v > max_value }
303
+ if max_value > ((1 << 60) - 1)
304
+ out << 0.chr
305
+ values.each { |value| Wire.encode_varuint(value, out) }
306
+ return
307
+ end
308
+
309
+ out << 1.chr
310
+ idx = 0
311
+ while idx < values.length
312
+ zero_run = 0
313
+ while idx + zero_run < values.length && values[idx + zero_run].zero? && zero_run < 240
314
+ zero_run += 1
315
+ end
316
+ if zero_run >= 120
317
+ take = zero_run >= 240 ? 240 : 120
318
+ word = (take == 240 ? 0 : (1 << 60))
319
+ Wire.append_u64_le(out, word)
320
+ idx += take
321
+ next
322
+ end
323
+
324
+ packed = false
325
+ SIMPLE8B_SLOTS.each_with_index do |slot, selector_idx|
326
+ next if idx + slot[:count] > values.length
327
+
328
+ max_encodable = (1 << slot[:width]) - 1
329
+ all_fit = true
330
+ values[idx, slot[:count]].each do |value|
331
+ if value > max_encodable
332
+ all_fit = false
333
+ break
334
+ end
335
+ end
336
+ next unless all_fit
337
+
338
+ selector = selector_idx + 2
339
+ payload = 0
340
+ shift = 0
341
+ values[idx, slot[:count]].each do |value|
342
+ payload |= (value << shift)
343
+ shift += slot[:width]
344
+ end
345
+ word = (selector << 60) | payload
346
+ Wire.append_u64_le(out, word & MAX_U64)
347
+ idx += slot[:count]
348
+ packed = true
349
+ break
350
+ end
351
+ next if packed
352
+
353
+ selector = 15
354
+ word = (selector << 60) | (values[idx] & ((1 << 60) - 1))
355
+ Wire.append_u64_le(out, word & MAX_U64)
356
+ idx += 1
357
+ end
358
+ end
359
+
360
+ def decode_u64_simple8b_inner(reader)
361
+ length = reader.read_varuint
362
+ return [] if length.zero?
363
+
364
+ mode = reader.read_u8
365
+ if mode.zero?
366
+ out = []
367
+ length.times do
368
+ out << reader.read_varuint
369
+ end
370
+ return out
371
+ end
372
+ raise Errors.invalid_data("simple8b mode") unless mode == 1
373
+
374
+ out = []
375
+ while out.length < length
376
+ packed = Wire.read_u64_le(reader)
377
+ selector = packed >> 60
378
+ payload = packed & ((1 << 60) - 1)
379
+ if selector == 0 || selector == 1
380
+ count = selector == 1 ? 120 : 240
381
+ remain = length - out.length
382
+ limit = remain < count ? remain : count
383
+ limit.times { out << 0 }
384
+ elsif selector >= 2 && selector <= 15
385
+ if selector == 15
386
+ count = 1
387
+ width = 60
388
+ else
389
+ slot = SIMPLE8B_SLOTS[selector - 2]
390
+ count = slot[:count]
391
+ width = slot[:width]
392
+ end
393
+ mask = (1 << width) - 1
394
+ shift = 0
395
+ remain = length - out.length
396
+ limit = remain < count ? remain : count
397
+ limit.times do
398
+ out << ((payload >> shift) & mask)
399
+ shift += width
400
+ end
401
+ else
402
+ raise Errors.invalid_data("simple8b selector")
403
+ end
404
+ end
405
+ out
406
+ end
407
+
408
+ def delta(values)
409
+ out = []
410
+ prev = 0
411
+ values.each_with_index do |value, i|
412
+ out << (i.zero? ? value : (value - prev))
413
+ prev = value
414
+ end
415
+ out
416
+ end
417
+
418
+ def undelta(values)
419
+ out = []
420
+ prev = 0
421
+ values.each_with_index do |value, i|
422
+ if i.zero?
423
+ out << value
424
+ prev = value
425
+ next
426
+ end
427
+ next_value, ok = checked_add_i64(prev, value)
428
+ raise Errors.invalid_data("delta overflow") unless ok
429
+
430
+ out << next_value
431
+ prev = next_value
432
+ end
433
+ out
434
+ end
435
+
436
+ def encode_i64_rle(values, out)
437
+ runs = []
438
+ values.each do |value|
439
+ if !runs.empty? && runs[-1][:value] == value
440
+ runs[-1][:count] += 1
441
+ else
442
+ runs << { value: value, count: 1 }
443
+ end
444
+ end
445
+ Wire.encode_varuint(runs.length, out)
446
+ runs.each do |run|
447
+ Wire.encode_varuint(Wire.encode_zigzag(run[:value]), out)
448
+ Wire.encode_varuint(run[:count], out)
449
+ end
450
+ end
451
+
452
+ def decode_i64_rle(reader)
453
+ runs_len = reader.read_varuint
454
+ out = []
455
+ runs_len.times do
456
+ value_encoded = reader.read_varuint
457
+ value = Wire.decode_zigzag(value_encoded)
458
+ count = reader.read_varuint
459
+ count.times { out << value }
460
+ end
461
+ out
462
+ end
463
+
464
+ def encode_i64_patched_for(values, out)
465
+ if values.empty?
466
+ Wire.encode_varuint(0, out)
467
+ return
468
+ end
469
+ base = values[0]
470
+ values[1..].each do |v|
471
+ base = v if v < base
472
+ end
473
+ shifted = values.map { |v| v - base }
474
+ Wire.encode_varuint(shifted.length, out)
475
+ Wire.encode_varuint(Wire.encode_zigzag(base), out)
476
+
477
+ max_value = 0
478
+ shifted.each { |value| max_value = value if value > max_value }
479
+ bw = bit_width(max_value & MAX_U64)
480
+ base_width = bw > 2 ? bw - 2 : 0
481
+ out << base_width.chr
482
+
483
+ patch_positions = []
484
+ main_values = []
485
+ shifted.each_with_index do |value, idx|
486
+ if bit_width(value & MAX_U64) > base_width
487
+ patch_positions << { pos: idx, value: value }
488
+ main = 0
489
+ if base_width.positive?
490
+ mask = (1 << base_width) - 1
491
+ main = value & mask
492
+ main = 0 if main.negative?
493
+ end
494
+ main_values << main
495
+ else
496
+ main_values << value
497
+ end
498
+ end
499
+ main_values.each do |value|
500
+ Wire.encode_varuint(value & MAX_U64, out)
501
+ end
502
+ Wire.encode_varuint(patch_positions.length, out)
503
+ patch_positions.each do |patch|
504
+ Wire.encode_varuint(patch[:pos], out)
505
+ Wire.encode_varuint(patch[:value] & MAX_U64, out)
506
+ end
507
+ end
508
+
509
+ def decode_i64_patched_for(reader)
510
+ length = reader.read_varuint
511
+ return [] if length.zero?
512
+
513
+ base_encoded = reader.read_varuint
514
+ base = Wire.decode_zigzag(base_encoded)
515
+ reader.read_u8
516
+ values = []
517
+ length.times do
518
+ v = reader.read_varuint
519
+ values << u64_to_i64(v)
520
+ end
521
+ patch_count = reader.read_varuint
522
+ patch_count.times do
523
+ pos = reader.read_varuint
524
+ patch = reader.read_varuint
525
+ values[pos] = u64_to_i64(patch) if pos < values.length
526
+ end
527
+ values.map { |v| v + base }
528
+ end
529
+
530
+ def encode_xor_float(values, out)
531
+ Wire.encode_varuint(values.length, out)
532
+ return if values.empty?
533
+
534
+ first_bits = f64_to_u64(values[0])
535
+ Wire.append_u64_le(out, first_bits)
536
+ prev = first_bits
537
+ values[1..].each do |value|
538
+ bits_value = f64_to_u64(value)
539
+ x = prev ^ bits_value
540
+ if x.zero?
541
+ out << 0.chr
542
+ else
543
+ out << 1.chr
544
+ leading = leading_zeros64(x)
545
+ trailing = trailing_zeros64(x)
546
+ width = 64 - (leading + trailing)
547
+ Wire.encode_varuint(leading, out)
548
+ Wire.encode_varuint(trailing, out)
549
+ Wire.encode_varuint(width, out)
550
+ payload = if width == 64
551
+ x
552
+ else
553
+ (x >> trailing) & ((1 << width) - 1)
554
+ end
555
+ Wire.encode_varuint(payload, out)
556
+ end
557
+ prev = bits_value
558
+ end
559
+ end
560
+
561
+ def decode_xor_float(reader)
562
+ length = reader.read_varuint
563
+ return [] if length.zero?
564
+
565
+ first_bits = Wire.read_u64_le(reader)
566
+ out = [u64_to_f64(first_bits)]
567
+ prev = first_bits
568
+ (length - 1).times do
569
+ flag = reader.read_u8
570
+ bits_value = prev
571
+ unless flag.zero?
572
+ leading = reader.read_varuint
573
+ trailing = reader.read_varuint
574
+ width = reader.read_varuint
575
+ payload = reader.read_varuint
576
+ raise Errors.invalid_data("xor-float bit widths") if leading + trailing + width > 64
577
+
578
+ x = width == 64 ? payload : (payload << trailing)
579
+ bits_value = prev ^ x
580
+ end
581
+ out << u64_to_f64(bits_value)
582
+ prev = bits_value
583
+ end
584
+ out
585
+ end
586
+
587
+ def encode_i64_direct_bitpack(values, out)
588
+ Wire.encode_varuint(values.length, out)
589
+ if values.empty?
590
+ out << 0.chr
591
+ return
592
+ end
593
+ encoded = []
594
+ width = 1
595
+ values.each do |v|
596
+ enc = Wire.encode_zigzag(v)
597
+ encoded << enc
598
+ bw = bit_width(enc)
599
+ width = bw if bw > width
600
+ end
601
+ out << width.chr
602
+ pack_u64_values(encoded, width, out)
603
+ end
604
+
605
+ def decode_i64_direct_bitpack(reader)
606
+ length = reader.read_varuint
607
+ width = reader.read_u8
608
+ return [] if length.zero?
609
+
610
+ raise Errors.invalid_data("bitpack width") if width.zero? || width > 64
611
+
612
+ encoded = unpack_u64_values(reader, length, width)
613
+ encoded.map { |v| Wire.decode_zigzag(v) }
614
+ end
615
+
616
+ def encode_i64_delta_delta(values, out)
617
+ Wire.encode_varuint(values.length, out)
618
+ return if values.empty?
619
+
620
+ Wire.encode_varuint(Wire.encode_zigzag(values[0]), out)
621
+ return if values.length == 1
622
+
623
+ d1 = values[1] - values[0]
624
+ Wire.encode_varuint(Wire.encode_zigzag(d1), out)
625
+ dd = []
626
+ prev_delta = d1
627
+ (1...(values.length - 1)).each do |i|
628
+ d = values[i + 1] - values[i]
629
+ dd << (d - prev_delta)
630
+ prev_delta = d
631
+ end
632
+ encode_i64_direct_bitpack(dd, out)
633
+ end
634
+
635
+ def decode_i64_delta_delta(reader)
636
+ length = reader.read_varuint
637
+ return [] if length.zero?
638
+
639
+ first_encoded = reader.read_varuint
640
+ first = Wire.decode_zigzag(first_encoded)
641
+ return [first] if length == 1
642
+
643
+ first_delta_encoded = reader.read_varuint
644
+ first_delta = Wire.decode_zigzag(first_delta_encoded)
645
+ dd = decode_i64_direct_bitpack(reader)
646
+ raise Errors.invalid_data("delta-delta length") if dd.length != length - 2
647
+
648
+ out = [first]
649
+ prev = first
650
+ second, ok = checked_add_i64(prev, first_delta)
651
+ raise Errors.invalid_data("delta-delta overflow") unless ok
652
+
653
+ out << second
654
+ prev = second
655
+ prev_delta = first_delta
656
+ dd.each do |ddv|
657
+ d, ok = checked_add_i64(prev_delta, ddv)
658
+ raise Errors.invalid_data("delta-delta overflow") unless ok
659
+
660
+ nxt, ok = checked_add_i64(prev, d)
661
+ raise Errors.invalid_data("delta-delta overflow") unless ok
662
+
663
+ out << nxt
664
+ prev = nxt
665
+ prev_delta = d
666
+ end
667
+ out
668
+ end
669
+
670
+ def pack_u64_values(values, width, out)
671
+ total_bits = values.length * width
672
+ byte_len = (total_bits + 7) / 8
673
+ bytes = Array.new(byte_len, 0)
674
+ bit_pos = 0
675
+ values.each do |value|
676
+ written = 0
677
+ while written < width
678
+ byte_idx = bit_pos / 8
679
+ bit_off = bit_pos % 8
680
+ room = 8 - bit_off
681
+ take = width - written
682
+ take = room if take > room
683
+ mask = (1 << take) - 1
684
+ part = (value >> written) & mask
685
+ bytes[byte_idx] |= (part << bit_off)
686
+ bit_pos += take
687
+ written += take
688
+ end
689
+ end
690
+ out << bytes.pack("C*")
691
+ end
692
+
693
+ def unpack_u64_values(reader, length, width)
694
+ total_bits = length * width
695
+ byte_len = (total_bits + 7) / 8
696
+ bytes = reader.read_exact(byte_len)
697
+ out = []
698
+ bit_pos = 0
699
+ length.times do
700
+ value = 0
701
+ written = 0
702
+ while written < width
703
+ byte_idx = bit_pos / 8
704
+ raise Errors.invalid_data("bitpack underflow") if byte_idx >= bytes.bytesize
705
+
706
+ bit_off = bit_pos % 8
707
+ room = 8 - bit_off
708
+ take = width - written
709
+ take = room if take > room
710
+ mask = (1 << take) - 1
711
+ part = (bytes.getbyte(byte_idx) >> bit_off) & mask
712
+ value |= (part << written)
713
+ bit_pos += take
714
+ written += take
715
+ end
716
+ out << value
717
+ end
718
+ out
719
+ end
720
+
721
+ def bit_width(v)
722
+ v &= MAX_U64
723
+ return 1 if v.zero?
724
+
725
+ 64 - leading_zeros64(v)
726
+ end
727
+
728
+ def checked_add_u64(a, b)
729
+ sum = a + b
730
+ [sum & MAX_U64, sum <= MAX_U64]
731
+ end
732
+
733
+ def checked_add_i64(a, b)
734
+ sum = a + b
735
+ return [0, false] if (b.positive? && sum < a) || (b.negative? && sum > a)
736
+ return [0, false] if sum < MIN_I64 || sum > MAX_I64
737
+
738
+ [sum, true]
739
+ end
740
+
741
+ def u64_to_i64(v)
742
+ (v & (1 << 63)).zero? ? v : (v - (1 << 64))
743
+ end
744
+
745
+ def f64_to_u64(value)
746
+ [value].pack("E").unpack1("Q<")
747
+ end
748
+
749
+ def u64_to_f64(bits)
750
+ [bits].pack("Q<").unpack1("E")
751
+ end
752
+
753
+ def leading_zeros64(v)
754
+ return 64 if v.zero?
755
+
756
+ 64 - v.bit_length
757
+ end
758
+
759
+ def trailing_zeros64(v)
760
+ return 64 if v.zero?
761
+
762
+ (v & -v).bit_length - 1
763
+ end
764
+ end
765
+ end
766
+ end