string_bits 0.1.0 → 0.2.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/string_bits/string_bits.c +692 -547
  4. metadata +1 -1
@@ -70,7 +70,7 @@ sb_popcount64(uint64_t x)
70
70
  /* ctz / clz helpers for set-bit iteration ---------------------------------- */
71
71
 
72
72
  static ID id_bracket;
73
- static VALUE sym_lsb_first, sym_lsb, sym_msb, sym_invert;
73
+ static VALUE sym_lsb_first, sym_invert;
74
74
 
75
75
  enum sb_kw_flag {
76
76
  SB_KW_INVERT = 1 << 0,
@@ -190,27 +190,10 @@ integer_to_bit_idx(VALUE n)
190
190
  UNREACHABLE_RETURN(0);
191
191
  }
192
192
 
193
- static ssize_t
194
- check_bit_index(VALUE self, VALUE n, int lsb_first)
195
- {
196
- if (!rb_integer_type_p(n)) {
197
- rb_raise(rb_eTypeError, "bit index must be an integer");
198
- }
199
- ssize_t idx = integer_to_bit_idx(n);
200
- ssize_t size = RSTRING_LEN(self) * 8;
201
- if (idx < 0 || idx >= size) {
202
- rb_raise(rb_eIndexError, "bit index out of range");
203
- }
204
- if (!lsb_first) idx = (idx & ~7L) | (7 - (idx & 7L));
205
- return idx;
206
- }
207
-
208
- static inline ssize_t
209
- physical_to_count_from(ssize_t physical, int lsb_first)
210
- {
211
- return lsb_first ? physical : ((physical & ~7L) | (7 - (physical & 7L)));
212
- }
213
-
193
+ /* Bit numbering between byte-with-LSB-as-bit-0 and byte-with-MSB-as-bit-0
194
+ * is an involution: swapping in either direction uses the same formula
195
+ * `(x & ~7) | (7 - (x & 7))`. logical_to_physical is therefore symmetric and
196
+ * is reused on the return path (physical -> logical) as well. */
214
197
  static inline ssize_t
215
198
  logical_to_physical(ssize_t logical, int lsb_first)
216
199
  {
@@ -237,6 +220,20 @@ logical_write_bit(unsigned char *ptr, ssize_t logical_index, int lsb_first, int
237
220
  physical_write_bit(ptr, logical_to_physical(logical_index, lsb_first), bit);
238
221
  }
239
222
 
223
+ static ssize_t
224
+ check_bit_index(VALUE self, VALUE n, int lsb_first)
225
+ {
226
+ if (!rb_integer_type_p(n)) {
227
+ rb_raise(rb_eTypeError, "bit index must be an integer");
228
+ }
229
+ ssize_t idx = integer_to_bit_idx(n);
230
+ ssize_t size = RSTRING_LEN(self) * 8;
231
+ if (idx < 0 || idx >= size) {
232
+ rb_raise(rb_eIndexError, "bit index out of range");
233
+ }
234
+ return logical_to_physical(idx, lsb_first);
235
+ }
236
+
240
237
  /* ssize_t-interface wrapper around rb_range_beg_len.
241
238
  *
242
239
  * rb_range_beg_len() takes (long *begp, long *lenp, long len), but this
@@ -266,6 +263,44 @@ sb_range_beg_len_call(VALUE arg)
266
263
  return rb_range_beg_len(a->range, a->lbegp, a->llenp, a->len, a->err);
267
264
  }
268
265
 
266
+ /* Validate Range endpoints for bit position arguments.
267
+ * Raises ArgumentError for:
268
+ * - any explicit (non-nil) Bignum endpoint: cannot address any real string,
269
+ * consistent with integer_to_bit_idx behavior for scalar indices.
270
+ * - any explicit (non-nil) negative endpoint: count-from-end semantics
271
+ * interact confusingly with lsb_first: true/false.
272
+ * RBIGNUM_NEGATIVE_P is used for the negativity check on Bignums to avoid
273
+ * calling NUM2LL on values that do not fit in long long.
274
+ *
275
+ * Porting to Ruby Core:
276
+ * Replace rb_range_values() with direct struct access:
277
+ * #include "internal/range.h"
278
+ * beg = RANGE_BEG(range);
279
+ * end = RANGE_END(range);
280
+ * excl = RANGE_EXCL(range);
281
+ */
282
+ static void
283
+ sb_range_validate_endpoints(VALUE range)
284
+ {
285
+ VALUE beg, end;
286
+ int excl;
287
+ rb_range_values(range, &beg, &end, &excl);
288
+ if (!NIL_P(beg) && rb_integer_type_p(beg)) {
289
+ if (!FIXNUM_P(beg))
290
+ rb_raise(rb_eArgError, "bit index out of representable range");
291
+ if (FIX2LONG(beg) < 0)
292
+ rb_raise(rb_eIndexError,
293
+ "negative Range endpoint is not allowed for bit positions");
294
+ }
295
+ if (!NIL_P(end) && rb_integer_type_p(end)) {
296
+ if (!FIXNUM_P(end))
297
+ rb_raise(rb_eArgError, "bit index out of representable range");
298
+ if (FIX2LONG(end) < 0)
299
+ rb_raise(rb_eIndexError,
300
+ "negative Range endpoint is not allowed for bit positions");
301
+ }
302
+ }
303
+
269
304
  static inline VALUE
270
305
  sb_range_beg_len(VALUE range, ssize_t *begp, ssize_t *lenp, ssize_t len, int err)
271
306
  {
@@ -325,53 +360,42 @@ parse_lsb_first_opt(VALUE opts)
325
360
  return parse_bool_opt(opts, sym_lsb_first, "lsb_first", 1);
326
361
  }
327
362
 
328
- static int
329
- parse_lsb_first(int argc, VALUE *argv)
363
+ /* Parse an optional start_offset positional argument (Qnil => 0).
364
+ * Raises ArgumentError for Bignum, IndexError for negative Fixnum. */
365
+ static ssize_t
366
+ parse_start_offset(VALUE v)
330
367
  {
331
- VALUE opts = Qnil;
332
- rb_scan_args(argc, argv, "0:", &opts);
333
- validate_option_hash(opts, SB_KW_LSB_FIRST);
334
- return parse_lsb_first_opt(opts);
368
+ if (NIL_P(v)) return 0;
369
+ ssize_t start_offset = integer_to_bit_idx(v); /* raises ArgumentError for Bignum */
370
+ if (start_offset < 0)
371
+ rb_raise(rb_eIndexError, "bit_offset must be non-negative");
372
+ return start_offset;
335
373
  }
336
374
 
337
375
  /* read -------------------------------------------------------------------- */
338
376
 
339
- /* String#bit_at(n, lsb_first: true) -> true or false
340
- *
341
- * bit_at uses flat/Arrow convention: byte_index = n/8 from start, bit = n%8 from LSB
342
- * e.g. "\xAA\xCC": bit 0..7 live in byte[0]=0xAA, bit 8..15 live in byte[1]=0xCC
343
- *
344
- * str = "\xFF\xAA" # 11111111 10101010
345
- * str.bit_at(0) # => true (1st bit is set)
346
- * str.bit_at(7) # => true (8th bit is set)
347
- * str.bit_at(8) # => false (9th bit is clear)
348
- * str.bit_at(9) # => true (10th bit is set)
349
- * str.bit_at(16) # => nil
350
- */
377
+ /* Return true/false/nil for the bit at flat position n. */
351
378
  static VALUE
352
379
  rb_str_bit_at(int argc, VALUE *argv, VALUE self)
353
380
  {
354
- VALUE n, opts;
355
- rb_scan_args(argc, argv, "1:", &n, &opts);
381
+ VALUE bit_offset_v, opts;
382
+ rb_scan_args(argc, argv, "1:", &bit_offset_v, &opts);
356
383
  validate_option_hash(opts, SB_KW_LSB_FIRST);
357
384
 
358
- if (!rb_integer_type_p(n)) {
385
+ if (!rb_integer_type_p(bit_offset_v)) {
359
386
  rb_raise(rb_eTypeError, "bit index must be an integer");
360
387
  }
361
- ssize_t idx = integer_to_bit_idx(n);
362
- if (idx < 0) {
363
- rb_raise(rb_eArgError, "bit index must be non-negative");
388
+ ssize_t bit_offset = integer_to_bit_idx(bit_offset_v);
389
+ if (bit_offset < 0) {
390
+ rb_raise(rb_eIndexError, "bit index out of range");
364
391
  }
365
392
  ssize_t size = RSTRING_LEN(self) * 8;
366
- if (size <= idx) {
393
+ if (size <= bit_offset) {
367
394
  return Qnil;
368
395
  }
369
396
 
370
397
  int lsb_first = parse_lsb_first_opt(opts);
371
-
372
- if (!lsb_first) {
373
- idx = (idx & ~7L) | (7 - (idx & 7L));
374
- }
398
+ ssize_t idx = logical_to_physical(bit_offset, lsb_first);
375
399
 
376
400
  if (test_bit(RSTRING_PTR(self), idx)) {
377
401
  return Qtrue;
@@ -380,19 +404,21 @@ rb_str_bit_at(int argc, VALUE *argv, VALUE self)
380
404
  }
381
405
  }
382
406
 
383
- static VALUE
384
- rb_str_bit_count(VALUE self)
407
+ /* count_set_bits: popcount over a raw byte buffer.
408
+ *
409
+ * Uses a 32-byte (4 x uint64_t) unrolled inner loop, falls back to 8-byte
410
+ * steps, and finally collects the partial trailing bytes into a single
411
+ * uint64_t for one more popcount. memcpy avoids unaligned-load issues on
412
+ * strict-alignment platforms (SPARC, MIPS); modern compilers fold the 8-byte
413
+ * memcpy into a single load on platforms that allow unaligned access. */
414
+ static ssize_t
415
+ count_set_bits(const unsigned char *str, ssize_t len)
385
416
  {
386
417
  ssize_t count = 0;
387
- ssize_t len = RSTRING_LEN(self);
388
- const char *str = RSTRING_PTR(self);
389
418
  ssize_t off = 0;
390
419
  ssize_t unrolled_end = len & ~31L;
391
420
  ssize_t aligned_end = len & ~7L;
392
421
 
393
- /* Use memcpy to avoid unaligned loads (SIGBUS on SPARC, MIPS, etc.)
394
- * and strict-aliasing violations. Modern compilers fold 8-byte memcpy
395
- * into a single load on platforms that allow unaligned access. */
396
422
  for (; off < unrolled_end; off += 32) {
397
423
  uint64_t w0, w1, w2, w3;
398
424
  memcpy(&w0, str + off, 8);
@@ -414,208 +440,373 @@ rb_str_bit_count(VALUE self)
414
440
  ssize_t remainder = len - aligned_end;
415
441
  if (remainder > 0) {
416
442
  uint64_t last = 0;
417
- const unsigned char *tail = (const unsigned char *)(str + aligned_end);
443
+ const unsigned char *tail = str + aligned_end;
418
444
  for (ssize_t i = 0; i < remainder; i++) {
419
445
  last |= (uint64_t)tail[i] << (i * 8);
420
446
  }
421
447
  count += sb_popcount64(last);
422
448
  }
423
449
 
424
- return SSIZET2NUM(count);
450
+ return count;
425
451
  }
426
452
 
427
- /* iterate bits ------------------------------------------------------------ */
453
+ /* count_set_bits_range: popcount over [start, start+length) in LSB-first numbering.
454
+ * Handles non-byte-aligned start and length by masking partial first/last bytes. */
455
+ static ssize_t
456
+ count_set_bits_range(const unsigned char *str, ssize_t total_bytes,
457
+ ssize_t start, ssize_t length)
458
+ {
459
+ if (length <= 0) return 0;
460
+ ssize_t total_bits = total_bytes * 8;
461
+ if (start >= total_bits) return 0;
462
+ if (start + length > total_bits) length = total_bits - start;
428
463
 
429
- static VALUE
430
- rb_str_each_bit(int argc, VALUE *argv, VALUE self)
464
+ ssize_t byte_start = start >> 3;
465
+ int bit_lo = (int)(start & 7);
466
+ ssize_t end_bit = start + length;
467
+ ssize_t last_byte = (end_bit - 1) >> 3;
468
+ int e_bit = (int)(end_bit & 7); /* bits to use in last byte; 0 means full byte */
469
+
470
+ if (byte_start == last_byte) {
471
+ unsigned int b = (unsigned int)str[byte_start] >> bit_lo;
472
+ b &= (1u << (unsigned)length) - 1u;
473
+ return (ssize_t)sb_popcount64(b);
474
+ }
475
+
476
+ ssize_t count = 0;
477
+ if (bit_lo != 0) {
478
+ count += sb_popcount64((unsigned int)str[byte_start] >> bit_lo);
479
+ byte_start++;
480
+ }
481
+ ssize_t full_last = (e_bit == 0) ? last_byte + 1 : last_byte;
482
+ count += count_set_bits(str + byte_start, full_last - byte_start);
483
+ if (e_bit != 0) {
484
+ unsigned int b = (unsigned int)str[last_byte] & ((1u << (unsigned)e_bit) - 1u);
485
+ count += sb_popcount64(b);
486
+ }
487
+ return count;
488
+ }
489
+
490
+ /* count_set_bits_range_msb: same as count_set_bits_range but for MSB-first numbering.
491
+ * In MSB-first, position 0 within a byte is physical bit 7 (the MSB). */
492
+ static ssize_t
493
+ count_set_bits_range_msb(const unsigned char *str, ssize_t total_bytes,
494
+ ssize_t start, ssize_t length)
431
495
  {
432
- RETURN_ENUMERATOR(self, argc, argv);
496
+ if (length <= 0) return 0;
497
+ ssize_t total_bits = total_bytes * 8;
498
+ if (start >= total_bits) return 0;
499
+ if (start + length > total_bits) length = total_bits - start;
433
500
 
434
- int lsb_first = parse_lsb_first(argc, argv);
435
- ssize_t len = RSTRING_LEN(self);
436
- const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
501
+ ssize_t byte_start = start >> 3;
502
+ int s_bit = (int)(start & 7); /* MSB-first within-byte start index */
503
+ ssize_t end_bit = start + length;
504
+ ssize_t last_byte = (end_bit - 1) >> 3;
505
+ int e_bit = (int)(end_bit & 7); /* bits to use in last byte; 0 means full byte */
437
506
 
438
- for (ssize_t i = 0; i < len; i++) {
439
- unsigned char b = str[i];
440
- if (lsb_first) {
441
- for (int j = 0; j < 8; j++) {
442
- rb_yield((b >> j) & 1 ? Qtrue : Qfalse);
443
- }
444
- } else {
445
- for (int j = 7; j >= 0; j--) {
446
- rb_yield((b >> j) & 1 ? Qtrue : Qfalse);
447
- }
448
- }
507
+ if (byte_start == last_byte) {
508
+ /* physical bits (7-s_bit) down to (7-s_bit-length+1) */
509
+ unsigned int b = (unsigned int)str[byte_start] >> (unsigned)(8 - s_bit - (int)length);
510
+ b &= (1u << (unsigned)length) - 1u;
511
+ return (ssize_t)sb_popcount64(b);
449
512
  }
450
513
 
451
- return self;
514
+ ssize_t count = 0;
515
+ /* partial first byte: MSB-first positions s_bit..7 = physical bits 0..(7-s_bit) */
516
+ if (s_bit != 0) {
517
+ unsigned int b = (unsigned int)str[byte_start] & ((1u << (unsigned)(8 - s_bit)) - 1u);
518
+ count += sb_popcount64(b);
519
+ byte_start++;
520
+ }
521
+ ssize_t full_last = (e_bit == 0) ? last_byte + 1 : last_byte;
522
+ count += count_set_bits(str + byte_start, full_last - byte_start);
523
+ /* partial last byte: MSB-first positions 0..(e_bit-1) = physical bits (8-e_bit)..7 */
524
+ if (e_bit != 0) {
525
+ unsigned int b = (unsigned int)str[last_byte] >> (unsigned)(8 - e_bit);
526
+ count += sb_popcount64(b);
527
+ }
528
+ return count;
452
529
  }
453
530
 
454
531
  static VALUE
455
- rb_str_bits(int argc, VALUE *argv, VALUE self)
532
+ rb_str_bit_count(int argc, VALUE *argv, VALUE self)
456
533
  {
457
- int lsb_first = parse_lsb_first(argc, argv);
458
- ssize_t len = RSTRING_LEN(self);
459
534
  const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
460
- ssize_t total_bits = len * 8;
461
- int have_block = rb_block_given_p();
535
+ ssize_t src_len = RSTRING_LEN(self);
462
536
 
463
- VALUE ary = have_block ? Qnil : rb_ary_new_capa(total_bits);
537
+ VALUE v0 = Qnil, v1 = Qnil, opts = Qnil;
538
+ rb_scan_args(argc, argv, "02:", &v0, &v1, &opts);
539
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
464
540
 
465
- for (ssize_t i = 0; i < len; i++) {
466
- unsigned char b = str[i];
467
- if (lsb_first) {
468
- for (int j = 0; j < 8; j++) {
469
- VALUE bit = (b >> j) & 1 ? Qtrue : Qfalse;
470
- have_block ? rb_yield(bit) : rb_ary_push(ary, bit);
471
- }
472
- } else {
473
- for (int j = 7; j >= 0; j--) {
474
- VALUE bit = (b >> j) & 1 ? Qtrue : Qfalse;
475
- have_block ? rb_yield(bit) : rb_ary_push(ary, bit);
476
- }
477
- }
541
+ /* No positional args: count the whole string; lsb_first: is ignored (order-independent) */
542
+ if (NIL_P(v0))
543
+ return SSIZET2NUM(count_set_bits(str, src_len));
544
+
545
+ int lsb_first = parse_lsb_first_opt(opts);
546
+ ssize_t total_bits = src_len * 8;
547
+ ssize_t bit_offset, bit_length;
548
+
549
+ if (rb_obj_is_kind_of(v0, rb_cRange)) {
550
+ if (!NIL_P(v1))
551
+ rb_raise(rb_eArgError, "wrong number of arguments");
552
+ sb_range_validate_endpoints(v0);
553
+ ssize_t beg, len;
554
+ if (!RTEST(sb_range_beg_len(v0, &beg, &len, total_bits, 0)))
555
+ return INT2FIX(0);
556
+ bit_offset = beg;
557
+ bit_length = len;
558
+ }
559
+ else if (!NIL_P(v1)) {
560
+ if (!rb_integer_type_p(v0))
561
+ rb_raise(rb_eTypeError, "bit_offset must be an integer");
562
+ if (!rb_integer_type_p(v1))
563
+ rb_raise(rb_eTypeError, "bit_length must be an integer");
564
+ bit_offset = integer_to_bit_idx(v0);
565
+ if (bit_offset < 0)
566
+ rb_raise(rb_eIndexError, "bit_offset must be non-negative");
567
+ bit_length = integer_to_bit_idx(v1);
568
+ if (bit_length < 0)
569
+ rb_raise(rb_eArgError, "bit_length must be non-negative");
570
+ }
571
+ else {
572
+ rb_raise(rb_eArgError,
573
+ "wrong number of arguments (given 1, expected 0, 1 Range, or 2)");
478
574
  }
479
575
 
480
- return have_block ? self : ary;
576
+ if (lsb_first)
577
+ return SSIZET2NUM(count_set_bits_range(str, src_len, bit_offset, bit_length));
578
+ else
579
+ return SSIZET2NUM(count_set_bits_range_msb(str, src_len, bit_offset, bit_length));
481
580
  }
482
581
 
483
- /* iterate set-bit positions ----------------------------------------------- */
582
+ /* iterate bits ------------------------------------------------------------ */
484
583
 
485
- static VALUE
486
- rb_str_each_set_bit_offset(int argc, VALUE *argv, VALUE self)
584
+ /* Unified emitter for each_bit / bits.
585
+ *
586
+ * Yields (when ary == Qnil) or pushes to a pre-allocated Array. lsb_first is
587
+ * hoisted outside the byte loop so the inner walk direction is straight-line
588
+ * code, removing a per-byte branch.
589
+ */
590
+ static void
591
+ emit_bits(const unsigned char *str, ssize_t len, int lsb_first, ssize_t start_offset, VALUE ary)
487
592
  {
488
- RETURN_ENUMERATOR(self, argc, argv);
593
+ if (start_offset >= len * 8) return;
594
+
595
+ #define SB_EMIT(v) \
596
+ do { VALUE _b = (v); \
597
+ if (ary == Qnil) rb_yield(_b); else rb_ary_push(ary, _b); } while (0)
598
+
599
+ ssize_t byte_start = start_offset >> 3;
600
+ int bit_start = (int)(start_offset & 7);
489
601
 
490
- int lsb_first = parse_lsb_first(argc, argv);
491
- ssize_t len = RSTRING_LEN(self);
492
- const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
493
602
  if (lsb_first) {
494
- /* LSB-first: ascending positions 0, 1, 2, ...
495
- * On little-endian, loading 8 bytes as a uint64_t preserves the flat
496
- * LSB-first bit numbering: word bit 0 == position 0, bit 63 == 63.
497
- * memcpy avoids unaligned-load SIGBUS on strict-alignment platforms. */
498
- #if SB_LITTLE_ENDIAN
499
- ssize_t n_words = len >> 3;
500
- for (ssize_t wi = 0; wi < n_words; wi++) {
501
- uint64_t w;
502
- memcpy(&w, str + wi * 8, 8);
503
- while (w != 0) {
504
- int bit = sb_ctzll(w);
505
- rb_yield(SSIZET2NUM(wi * 64 + bit));
506
- w &= w - 1;
507
- }
508
- }
509
- for (ssize_t bi = n_words << 3; bi < len; bi++) {
510
- unsigned int b = str[bi];
511
- while (b != 0) {
512
- int bit = sb_ctz8(b);
513
- rb_yield(SSIZET2NUM(bi * 8 + bit));
514
- b &= b - 1;
515
- }
516
- }
517
- #else
518
- for (ssize_t bi = 0; bi < len; bi++) {
519
- unsigned int b = str[bi];
520
- while (b != 0) {
521
- int bit = sb_ctz8(b);
522
- rb_yield(SSIZET2NUM(bi * 8 + bit));
523
- b &= b - 1;
603
+ for (ssize_t i = byte_start; i < len; i++) {
604
+ unsigned char b = str[i];
605
+ int j_start = (i == byte_start) ? bit_start : 0;
606
+ for (int j = j_start; j < 8; j++) {
607
+ SB_EMIT((b >> j) & 1 ? Qtrue : Qfalse);
524
608
  }
525
609
  }
526
- #endif
527
- }
528
- else {
529
- /* lsb_first: false => byte order preserved, bits 7..0 map to logical 0..7 */
530
- for (ssize_t bi = 0; bi < len; bi++) {
531
- unsigned int b = str[bi];
532
- while (b != 0) {
533
- int bit = sb_highest_bit8(b);
534
- ssize_t physical = bi * 8 + bit;
535
- rb_yield(SSIZET2NUM(physical_to_count_from(physical, 0)));
536
- b ^= (1u << bit); /* clear highest set bit */
610
+ } else {
611
+ for (ssize_t i = byte_start; i < len; i++) {
612
+ unsigned char b = str[i];
613
+ int j_end = (i == byte_start) ? (7 - bit_start) : 7;
614
+ for (int j = j_end; j >= 0; j--) {
615
+ SB_EMIT((b >> j) & 1 ? Qtrue : Qfalse);
537
616
  }
538
617
  }
539
618
  }
540
619
 
620
+ #undef SB_EMIT
621
+ }
622
+
623
+ static VALUE
624
+ rb_str_each_bit(int argc, VALUE *argv, VALUE self)
625
+ {
626
+ RETURN_ENUMERATOR(self, argc, argv);
627
+
628
+ VALUE start_offset_v = Qnil, opts = Qnil;
629
+ rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
630
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
631
+ int lsb_first = parse_lsb_first_opt(opts);
632
+ ssize_t start_offset = parse_start_offset(start_offset_v);
633
+
634
+ emit_bits((const unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self),
635
+ lsb_first, start_offset, Qnil);
541
636
  return self;
542
637
  }
543
638
 
544
639
  static VALUE
545
- rb_str_set_bit_offsets(int argc, VALUE *argv, VALUE self)
640
+ rb_str_bits(int argc, VALUE *argv, VALUE self)
546
641
  {
547
- int lsb_first = parse_lsb_first(argc, argv);
642
+ VALUE start_offset_v = Qnil, opts = Qnil;
643
+ rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
644
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
645
+ int lsb_first = parse_lsb_first_opt(opts);
646
+ ssize_t start_offset = parse_start_offset(start_offset_v);
548
647
  ssize_t len = RSTRING_LEN(self);
549
648
  const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
550
- int have_block = rb_block_given_p();
551
649
 
552
- VALUE ary;
553
- if (have_block) {
554
- ary = Qnil;
555
- }
556
- else {
557
- /* Pre-size the Array with popcount to avoid repeated reallocation.
558
- * memcpy avoids unaligned-load issues on strict-alignment platforms. */
559
- ssize_t count = 0;
560
- ssize_t nw = len >> 3;
561
- for (ssize_t wi = 0; wi < nw; wi++) {
562
- uint64_t w;
563
- memcpy(&w, str + wi * 8, 8);
564
- count += sb_popcount64(w);
565
- }
566
- for (ssize_t bi = nw << 3; bi < len; bi++)
567
- count += sb_popcount64((uint64_t)(unsigned char)str[bi]);
568
- ary = rb_ary_new_capa(count);
650
+ if (rb_block_given_p()) {
651
+ emit_bits(str, len, lsb_first, start_offset, Qnil);
652
+ return self;
569
653
  }
570
654
 
655
+ ssize_t nbits = (start_offset >= len * 8) ? 0 : (len * 8 - start_offset);
656
+ VALUE ary = rb_ary_new_capa(nbits);
657
+ emit_bits(str, len, lsb_first, start_offset, ary);
658
+ return ary;
659
+ }
660
+
661
+ /* iterate bit positions matching `bit` ------------------------------------ */
662
+
663
+ /* parse the required `bit` argument (true/false/1/0) to 0 or 1 */
664
+ static int
665
+ parse_bit_target(VALUE bit_val)
666
+ {
667
+ if (bit_val == Qtrue || bit_val == INT2FIX(1)) return 1;
668
+ if (bit_val == Qfalse || bit_val == INT2FIX(0)) return 0;
669
+ rb_raise(rb_eArgError, "bit must be 0, 1, false, or true");
670
+ UNREACHABLE_RETURN(0);
671
+ }
672
+
673
+ /* Unified scanner for each_bit_offset / bit_offsets.
674
+ *
675
+ * Emit each bit position equal to `target` either by yielding to the block
676
+ * (when ary == Qnil) or by pushing to the pre-allocated Array. Both call
677
+ * paths share the same hot loops; the only per-emit cost is one branch on
678
+ * (ary == Qnil), which the compiler can lift out of the inner while loop.
679
+ *
680
+ * LSB-first path: on little-endian, an 8-byte memcpy preserves the flat
681
+ * LSB-first bit numbering (word bit 0 == position 0), so we can scan 64 bits
682
+ * per ctzll. For target=0, invert the loaded word/byte; all 8/64 bits of the
683
+ * inverted unit are valid positions since each byte contributes exactly 8.
684
+ *
685
+ * MSB-first path: walk byte-by-byte with sb_highest_bit8, mapping each
686
+ * physical (LSB-first) bit position into the MSB-first count via
687
+ * logical_to_physical (the operation is its own inverse).
688
+ */
689
+ static void
690
+ emit_bit_offsets(const unsigned char *str, ssize_t len, int target, int lsb_first,
691
+ ssize_t start_offset, VALUE ary)
692
+ {
693
+ if (start_offset >= len * 8) return;
694
+
695
+ #define SB_EMIT(pos_val) \
696
+ do { VALUE _p = (pos_val); \
697
+ if (ary == Qnil) rb_yield(_p); else rb_ary_push(ary, _p); } while (0)
698
+
699
+ ssize_t byte_start = start_offset >> 3;
700
+ int bit_lo = (int)(start_offset & 7);
701
+
571
702
  if (lsb_first) {
703
+ /* Handle the partial first byte before aligning to byte boundary */
704
+ if (bit_lo != 0) {
705
+ unsigned int b = str[byte_start];
706
+ if (target == 0) b = (~b) & 0xFF;
707
+ b >>= bit_lo;
708
+ while (b != 0) {
709
+ int bit = sb_ctz8(b);
710
+ SB_EMIT(SSIZET2NUM(byte_start * 8 + bit_lo + bit));
711
+ b &= b - 1;
712
+ }
713
+ byte_start++;
714
+ }
572
715
  #if SB_LITTLE_ENDIAN
573
- ssize_t n_words = len >> 3;
716
+ ssize_t n_words = (len - byte_start) >> 3;
574
717
  for (ssize_t wi = 0; wi < n_words; wi++) {
575
718
  uint64_t w;
576
- memcpy(&w, str + wi * 8, 8);
719
+ memcpy(&w, str + byte_start + wi * 8, 8);
720
+ if (target == 0) w = ~w;
577
721
  while (w != 0) {
578
722
  int bit = sb_ctzll(w);
579
- VALUE pos = SSIZET2NUM(wi * 64 + bit);
580
- have_block ? rb_yield(pos) : rb_ary_push(ary, pos);
723
+ SB_EMIT(SSIZET2NUM((byte_start + wi * 8) * 8 + bit));
581
724
  w &= w - 1;
582
725
  }
583
726
  }
584
- for (ssize_t bi = n_words << 3; bi < len; bi++) {
727
+ for (ssize_t bi = byte_start + (n_words << 3); bi < len; bi++) {
585
728
  unsigned int b = str[bi];
729
+ if (target == 0) b = (~b) & 0xFF;
586
730
  while (b != 0) {
587
731
  int bit = sb_ctz8(b);
588
- VALUE pos = SSIZET2NUM(bi * 8 + bit);
589
- have_block ? rb_yield(pos) : rb_ary_push(ary, pos);
732
+ SB_EMIT(SSIZET2NUM(bi * 8 + bit));
590
733
  b &= b - 1;
591
734
  }
592
735
  }
593
736
  #else
594
- for (ssize_t bi = 0; bi < len; bi++) {
737
+ for (ssize_t bi = byte_start; bi < len; bi++) {
595
738
  unsigned int b = str[bi];
739
+ if (target == 0) b = (~b) & 0xFF;
596
740
  while (b != 0) {
597
741
  int bit = sb_ctz8(b);
598
- VALUE pos = SSIZET2NUM(bi * 8 + bit);
599
- have_block ? rb_yield(pos) : rb_ary_push(ary, pos);
742
+ SB_EMIT(SSIZET2NUM(bi * 8 + bit));
600
743
  b &= b - 1;
601
744
  }
602
745
  }
603
746
  #endif
604
747
  }
605
748
  else {
606
- for (ssize_t bi = 0; bi < len; bi++) {
749
+ /* lsb_first: false => byte order preserved, bits 7..0 map to logical 0..7.
750
+ * In the first (possibly partial) byte, skip the top bit_lo bits. */
751
+ for (ssize_t bi = byte_start; bi < len; bi++) {
607
752
  unsigned int b = str[bi];
753
+ if (target == 0) b = (~b) & 0xFF;
754
+ if (bi == byte_start && bit_lo != 0)
755
+ b &= (1u << (8 - bit_lo)) - 1; /* clear top bit_lo bits */
608
756
  while (b != 0) {
609
757
  int bit = sb_highest_bit8(b);
610
758
  ssize_t physical = bi * 8 + bit;
611
- VALUE pos = SSIZET2NUM(physical_to_count_from(physical, 0));
612
- have_block ? rb_yield(pos) : rb_ary_push(ary, pos);
759
+ SB_EMIT(SSIZET2NUM(logical_to_physical(physical, 0)));
613
760
  b ^= (1u << bit);
614
761
  }
615
762
  }
616
763
  }
617
764
 
618
- return have_block ? self : ary;
765
+ #undef SB_EMIT
766
+ }
767
+
768
+ static VALUE
769
+ rb_str_each_bit_offset(int argc, VALUE *argv, VALUE self)
770
+ {
771
+ RETURN_ENUMERATOR(self, argc, argv);
772
+
773
+ VALUE bit_val, start_offset_v = Qnil, opts = Qnil;
774
+ rb_scan_args(argc, argv, "11:", &bit_val, &start_offset_v, &opts);
775
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
776
+ int lsb_first = parse_lsb_first_opt(opts);
777
+ int target = parse_bit_target(bit_val);
778
+ ssize_t start_offset = parse_start_offset(start_offset_v);
779
+
780
+ emit_bit_offsets((const unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self),
781
+ target, lsb_first, start_offset, Qnil);
782
+ return self;
783
+ }
784
+
785
+ static VALUE
786
+ rb_str_bit_offsets(int argc, VALUE *argv, VALUE self)
787
+ {
788
+ VALUE bit_val, start_offset_v = Qnil, opts = Qnil;
789
+ rb_scan_args(argc, argv, "11:", &bit_val, &start_offset_v, &opts);
790
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
791
+ int lsb_first = parse_lsb_first_opt(opts);
792
+ int target = parse_bit_target(bit_val);
793
+ ssize_t start_offset = parse_start_offset(start_offset_v);
794
+
795
+ ssize_t len = RSTRING_LEN(self);
796
+ const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
797
+
798
+ if (rb_block_given_p()) {
799
+ emit_bit_offsets(str, len, target, lsb_first, start_offset, Qnil);
800
+ return self;
801
+ }
802
+
803
+ /* Pre-size the Array using popcount to avoid repeated reallocation.
804
+ * For target=0 the expected count is (len * 8 - popcount). */
805
+ ssize_t set_count = count_set_bits(str, len);
806
+ ssize_t count = (target == 1) ? set_count : (len * 8 - set_count);
807
+ VALUE ary = rb_ary_new_capa(count);
808
+ emit_bit_offsets(str, len, target, lsb_first, start_offset, ary);
809
+ return ary;
619
810
  }
620
811
 
621
812
  /* multi-bit mutation ------------------------------------------------------ */
@@ -712,40 +903,36 @@ bit_copy_core(unsigned char *dst, ssize_t dst_bit_off,
712
903
  if (tmp != stack_tmp) ruby_xfree(tmp);
713
904
  }
714
905
 
715
- /* String#bit_slice(bit_offset, bit_length) -> String
716
- * String#bit_slice(range) -> String
717
- *
718
- * str = "\xFF\x00" # 11111111 00000000
719
- * str.bit_slice(4, 8) # => "\xF0" (11110000)
720
- */
906
+ /* Extract a sub-sequence of bits into a new String. */
721
907
  static VALUE
722
908
  rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
723
909
  {
724
910
  ssize_t src_len = RSTRING_LEN(self);
725
911
  ssize_t total_bits = src_len * 8;
726
- ssize_t offset, length;
912
+ ssize_t bit_offset, bit_length;
727
913
  VALUE v0, v1, opts;
728
914
  int n_pos = rb_scan_args(argc, argv, "11:", &v0, &v1, &opts);
729
915
  validate_option_hash(opts, SB_KW_LSB_FIRST);
730
916
  int lsb_first = parse_lsb_first_opt(opts);
731
917
 
732
918
  if (n_pos == 1 && rb_obj_is_kind_of(v0, rb_cRange)) {
919
+ sb_range_validate_endpoints(v0);
733
920
  ssize_t beg, len;
734
921
  if (!RTEST(sb_range_beg_len(v0, &beg, &len, total_bits, 0))) {
735
922
  return Qnil;
736
923
  }
737
- offset = beg;
738
- length = len;
924
+ bit_offset = beg;
925
+ bit_length = len;
739
926
  }
740
927
  else if (n_pos == 2) {
741
928
  if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1)) {
742
929
  return Qnil;
743
930
  }
744
931
 
745
- offset = integer_to_bit_idx(v0);
746
- length = integer_to_bit_idx(v1);
932
+ bit_offset = integer_to_bit_idx(v0);
933
+ bit_length = integer_to_bit_idx(v1);
747
934
 
748
- if (offset < 0 || length < 0) return Qnil;
935
+ if (bit_offset < 0 || bit_length < 0) return Qnil;
749
936
  }
750
937
  else if (n_pos == 1) {
751
938
  return Qnil;
@@ -755,13 +942,13 @@ rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
755
942
  "wrong number of arguments (given %d, expected 1 or 2)", n_pos);
756
943
  }
757
944
 
758
- if (offset > total_bits) return Qnil;
759
- ssize_t available = total_bits - offset;
760
- if (length > available) length = available;
945
+ if (bit_offset > total_bits) return Qnil;
946
+ ssize_t available = total_bits - bit_offset;
947
+ if (bit_length > available) bit_length = available;
761
948
 
762
- if (length == 0) return rb_str_new("", 0);
949
+ if (bit_length == 0) return rb_str_new("", 0);
763
950
 
764
- ssize_t out_bytes = (length + 7) / 8;
951
+ ssize_t out_bytes = (bit_length + 7) / 8;
765
952
  VALUE result = rb_str_buf_new(out_bytes);
766
953
  rb_str_resize(result, out_bytes);
767
954
  rb_enc_associate(result, rb_enc_get(self));
@@ -771,17 +958,17 @@ rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
771
958
  memset(dst, 0, out_bytes);
772
959
 
773
960
  if (lsb_first) {
774
- bit_copy_core(dst, 0, src, src_len, offset, length);
961
+ bit_copy_core(dst, 0, src, src_len, bit_offset, bit_length);
775
962
  } else {
776
963
  ssize_t dst_bit = 0;
777
- ssize_t start_byte = offset >> 3;
778
- ssize_t end_byte = (offset + length - 1) >> 3;
964
+ ssize_t start_byte = bit_offset >> 3;
965
+ ssize_t end_byte = (bit_offset + bit_length - 1) >> 3;
779
966
 
780
967
  for (ssize_t b = start_byte; b <= end_byte; b++) {
781
968
  ssize_t b_start_l = b << 3;
782
969
  ssize_t b_end_l = b_start_l + 7;
783
- ssize_t l_min = (offset > b_start_l) ? offset : b_start_l;
784
- ssize_t l_max = ((offset + length - 1) < b_end_l) ? (offset + length - 1) : b_end_l;
970
+ ssize_t l_min = (bit_offset > b_start_l) ? bit_offset : b_start_l;
971
+ ssize_t l_max = ((bit_offset + bit_length - 1) < b_end_l) ? (bit_offset + bit_length - 1) : b_end_l;
785
972
 
786
973
  ssize_t p_min = b_start_l + (7 - (l_max & 7L));
787
974
  ssize_t p_max = b_start_l + (7 - (l_min & 7L));
@@ -805,8 +992,8 @@ enum sb_mutation_op {
805
992
  static VALUE
806
993
  rb_str_mutate_bits(int argc, VALUE *argv, VALUE self, enum sb_mutation_op op)
807
994
  {
808
- VALUE target, opts;
809
- rb_scan_args(argc, argv, "1:", &target, &opts);
995
+ VALUE target, bit_length_v = Qnil, opts = Qnil;
996
+ rb_scan_args(argc, argv, "11:", &target, &bit_length_v, &opts);
810
997
  validate_option_hash(opts, SB_KW_LSB_FIRST);
811
998
  int lsb_first = parse_lsb_first_opt(opts);
812
999
 
@@ -814,17 +1001,47 @@ rb_str_mutate_bits(int argc, VALUE *argv, VALUE self, enum sb_mutation_op op)
814
1001
  unsigned char *ptr = (unsigned char *)RSTRING_PTR(self);
815
1002
 
816
1003
  if (rb_integer_type_p(target)) {
817
- ssize_t idx = check_bit_index(self, target, lsb_first);
818
- unsigned char mask = (unsigned char)(1u << (idx % 8));
819
- switch (op) {
820
- case SB_MUT_SET: ptr[idx / 8] |= mask; break;
821
- case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
822
- case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1004
+ if (NIL_P(bit_length_v)) {
1005
+ /* Single-bit form: bit_set(n) */
1006
+ ssize_t idx = check_bit_index(self, target, lsb_first);
1007
+ unsigned char mask = (unsigned char)(1u << (idx % 8));
1008
+ switch (op) {
1009
+ case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1010
+ case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1011
+ case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1012
+ }
1013
+ return self;
1014
+ }
1015
+ /* 2-arg form: bit_set(bit_offset, bit_length) */
1016
+ if (!rb_integer_type_p(bit_length_v))
1017
+ rb_raise(rb_eTypeError, "bit_length must be an integer");
1018
+ ssize_t bit_offset = integer_to_bit_idx(target);
1019
+ if (bit_offset < 0)
1020
+ rb_raise(rb_eIndexError, "bit_offset must be non-negative");
1021
+ ssize_t bit_length = integer_to_bit_idx(bit_length_v);
1022
+ if (bit_length < 0)
1023
+ rb_raise(rb_eArgError, "bit_length must be non-negative");
1024
+ if (bit_length == 0) return self;
1025
+ ssize_t total_bits = RSTRING_LEN(self) * 8;
1026
+ if (bit_offset >= total_bits || bit_offset + bit_length > total_bits)
1027
+ rb_raise(rb_eIndexError, "bit range out of range");
1028
+ for (ssize_t logical = bit_offset; logical < bit_offset + bit_length; logical++) {
1029
+ ssize_t idx = logical_to_physical(logical, lsb_first);
1030
+ unsigned char mask = (unsigned char)(1u << (idx % 8));
1031
+ switch (op) {
1032
+ case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1033
+ case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1034
+ case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1035
+ }
823
1036
  }
824
1037
  return self;
825
1038
  }
826
1039
 
1040
+ if (!NIL_P(bit_length_v))
1041
+ rb_raise(rb_eArgError, "wrong number of arguments");
1042
+
827
1043
  if (rb_obj_is_kind_of(target, rb_cRange)) {
1044
+ sb_range_validate_endpoints(target);
828
1045
  ssize_t total_bits = RSTRING_LEN(self) * 8;
829
1046
  ssize_t beg, len;
830
1047
 
@@ -836,19 +1053,20 @@ rb_str_mutate_bits(int argc, VALUE *argv, VALUE self, enum sb_mutation_op op)
836
1053
 
837
1054
  /* err=0 silently clamps end > total. Detect that and raise instead,
838
1055
  * to stay consistent with bit_splice and single-bit mutation. */
839
- VALUE rng_end = rb_funcall(target, rb_intern("end"), 0);
840
- if (!NIL_P(rng_end)) {
841
- ssize_t end_val = integer_to_bit_idx(rng_end);
842
- if (end_val < 0) end_val += total_bits;
843
- int exclusive = RTEST(rb_funcall(target, rb_intern("exclude_end?"), 0));
844
- ssize_t end_excl = exclusive ? end_val : end_val + 1;
1056
+ VALUE rng_beg_unused, rng_end_v;
1057
+ int excl;
1058
+ rb_range_values(target, &rng_beg_unused, &rng_end_v, &excl);
1059
+ (void)rng_beg_unused;
1060
+ if (!NIL_P(rng_end_v)) {
1061
+ ssize_t end_val = integer_to_bit_idx(rng_end_v);
1062
+ ssize_t end_excl = excl ? end_val : end_val + 1;
845
1063
  if (end_excl > total_bits) {
846
1064
  rb_raise(rb_eIndexError, "bit range out of range");
847
1065
  }
848
1066
  }
849
1067
 
850
1068
  for (ssize_t logical = beg; logical < beg + len; logical++) {
851
- ssize_t idx = lsb_first ? logical : ((logical & ~7L) | (7 - (logical & 7L)));
1069
+ ssize_t idx = logical_to_physical(logical, lsb_first);
852
1070
  unsigned char mask = (unsigned char)(1u << (idx % 8));
853
1071
  switch (op) {
854
1072
  case SB_MUT_SET: ptr[idx / 8] |= mask; break;
@@ -864,19 +1082,19 @@ rb_str_mutate_bits(int argc, VALUE *argv, VALUE self, enum sb_mutation_op op)
864
1082
  }
865
1083
 
866
1084
  static VALUE
867
- rb_str_set_bit(int argc, VALUE *argv, VALUE self)
1085
+ rb_str_bit_set(int argc, VALUE *argv, VALUE self)
868
1086
  {
869
1087
  return rb_str_mutate_bits(argc, argv, self, SB_MUT_SET);
870
1088
  }
871
1089
 
872
1090
  static VALUE
873
- rb_str_clear_bit(int argc, VALUE *argv, VALUE self)
1091
+ rb_str_bit_clear(int argc, VALUE *argv, VALUE self)
874
1092
  {
875
1093
  return rb_str_mutate_bits(argc, argv, self, SB_MUT_CLEAR);
876
1094
  }
877
1095
 
878
1096
  static VALUE
879
- rb_str_flip_bit(int argc, VALUE *argv, VALUE self)
1097
+ rb_str_bit_flip(int argc, VALUE *argv, VALUE self)
880
1098
  {
881
1099
  return rb_str_mutate_bits(argc, argv, self, SB_MUT_FLIP);
882
1100
  }
@@ -902,101 +1120,141 @@ alloc_result(VALUE self)
902
1120
  return result;
903
1121
  }
904
1122
 
905
- static VALUE
906
- rb_str_bit_not(VALUE self)
907
- {
908
- ssize_t len = RSTRING_LEN(self);
909
- VALUE result = alloc_result(self);
910
- const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
911
- unsigned char *dst = (unsigned char *)RSTRING_PTR(result);
912
- for (ssize_t i = 0; i < len; i++) dst[i] = ~src[i];
913
- return result;
914
- }
915
-
916
- static VALUE
917
- rb_str_bit_not_bang(VALUE self)
918
- {
919
- rb_str_modify(self);
920
- ssize_t len = RSTRING_LEN(self);
921
- unsigned char *ptr = (unsigned char *)RSTRING_PTR(self);
922
- for (ssize_t i = 0; i < len; i++) ptr[i] = ~ptr[i];
923
- return self;
924
- }
925
-
926
- static VALUE
927
- rb_str_bit_and(VALUE self, VALUE other)
928
- {
929
- check_binary_op_lengths(self, other);
930
- ssize_t len = RSTRING_LEN(self);
931
- VALUE result = alloc_result(self);
932
- const unsigned char *a = (const unsigned char *)RSTRING_PTR(self);
933
- const unsigned char *b = (const unsigned char *)RSTRING_PTR(other);
934
- unsigned char *dst = (unsigned char *)RSTRING_PTR(result);
935
- for (ssize_t i = 0; i < len; i++) dst[i] = a[i] & b[i];
936
- return result;
937
- }
938
-
939
- static VALUE
940
- rb_str_bit_and_bang(VALUE self, VALUE other)
941
- {
942
- check_binary_op_lengths(self, other);
943
- rb_str_modify(self);
944
- ssize_t len = RSTRING_LEN(self);
945
- unsigned char *a = (unsigned char *)RSTRING_PTR(self);
946
- const unsigned char *b = (const unsigned char *)RSTRING_PTR(other);
947
- for (ssize_t i = 0; i < len; i++) a[i] &= b[i];
948
- return self;
949
- }
950
-
951
- static VALUE
952
- rb_str_bit_or(VALUE self, VALUE other)
953
- {
954
- check_binary_op_lengths(self, other);
955
- ssize_t len = RSTRING_LEN(self);
956
- VALUE result = alloc_result(self);
957
- const unsigned char *a = (const unsigned char *)RSTRING_PTR(self);
958
- const unsigned char *b = (const unsigned char *)RSTRING_PTR(other);
959
- unsigned char *dst = (unsigned char *)RSTRING_PTR(result);
960
- for (ssize_t i = 0; i < len; i++) dst[i] = a[i] | b[i];
961
- return result;
962
- }
963
-
964
- static VALUE
965
- rb_str_bit_or_bang(VALUE self, VALUE other)
966
- {
967
- check_binary_op_lengths(self, other);
968
- rb_str_modify(self);
969
- ssize_t len = RSTRING_LEN(self);
970
- unsigned char *a = (unsigned char *)RSTRING_PTR(self);
971
- const unsigned char *b = (const unsigned char *)RSTRING_PTR(other);
972
- for (ssize_t i = 0; i < len; i++) a[i] |= b[i];
973
- return self;
974
- }
975
-
976
- static VALUE
977
- rb_str_bit_xor(VALUE self, VALUE other)
978
- {
979
- check_binary_op_lengths(self, other);
980
- ssize_t len = RSTRING_LEN(self);
981
- VALUE result = alloc_result(self);
982
- const unsigned char *a = (const unsigned char *)RSTRING_PTR(self);
983
- const unsigned char *b = (const unsigned char *)RSTRING_PTR(other);
984
- unsigned char *dst = (unsigned char *)RSTRING_PTR(result);
985
- for (ssize_t i = 0; i < len; i++) dst[i] = a[i] ^ b[i];
986
- return result;
987
- }
988
-
989
- static VALUE
990
- rb_str_bit_xor_bang(VALUE self, VALUE other)
991
- {
992
- check_binary_op_lengths(self, other);
993
- rb_str_modify(self);
994
- ssize_t len = RSTRING_LEN(self);
995
- unsigned char *a = (unsigned char *)RSTRING_PTR(self);
996
- const unsigned char *b = (const unsigned char *)RSTRING_PTR(other);
997
- for (ssize_t i = 0; i < len; i++) a[i] ^= b[i];
998
- return self;
999
- }
1123
+ /*
1124
+ * Bitwise op kernels: process 32 bytes (4 x uint64_t) per loop iteration via
1125
+ * memcpy + word-wise op + memcpy, then any 8-byte tail, then byte-by-byte for
1126
+ * the final < 8 bytes. memcpy avoids unaligned-load/store issues on strict-
1127
+ * alignment platforms; modern compilers fold each 8-byte memcpy into a single
1128
+ * load/store. Macro-generated to avoid 8 near-identical functions.
1129
+ *
1130
+ * NOT operands take only `src`; binary AND/OR/XOR take `a` and `b`.
1131
+ */
1132
+ #define SB_DEFINE_UNARY_KERNEL(name, expr_word, expr_byte) \
1133
+ static void \
1134
+ name(unsigned char *dst, const unsigned char *src, ssize_t len) \
1135
+ { \
1136
+ ssize_t off = 0; \
1137
+ ssize_t unrolled_end = len & ~31L; \
1138
+ ssize_t aligned_end = len & ~7L; \
1139
+ for (; off < unrolled_end; off += 32) { \
1140
+ uint64_t s0, s1, s2, s3; \
1141
+ memcpy(&s0, src + off, 8); \
1142
+ memcpy(&s1, src + off + 8, 8); \
1143
+ memcpy(&s2, src + off + 16, 8); \
1144
+ memcpy(&s3, src + off + 24, 8); \
1145
+ uint64_t d0 = (expr_word(s0)); \
1146
+ uint64_t d1 = (expr_word(s1)); \
1147
+ uint64_t d2 = (expr_word(s2)); \
1148
+ uint64_t d3 = (expr_word(s3)); \
1149
+ memcpy(dst + off, &d0, 8); \
1150
+ memcpy(dst + off + 8, &d1, 8); \
1151
+ memcpy(dst + off + 16, &d2, 8); \
1152
+ memcpy(dst + off + 24, &d3, 8); \
1153
+ } \
1154
+ for (; off < aligned_end; off += 8) { \
1155
+ uint64_t s; \
1156
+ memcpy(&s, src + off, 8); \
1157
+ uint64_t d = (expr_word(s)); \
1158
+ memcpy(dst + off, &d, 8); \
1159
+ } \
1160
+ for (; off < len; off++) dst[off] = (expr_byte(src[off])); \
1161
+ }
1162
+
1163
+ #define SB_DEFINE_BINARY_KERNEL(name, expr_word, expr_byte) \
1164
+ static void \
1165
+ name(unsigned char *dst, const unsigned char *a, const unsigned char *b, \
1166
+ ssize_t len) \
1167
+ { \
1168
+ ssize_t off = 0; \
1169
+ ssize_t unrolled_end = len & ~31L; \
1170
+ ssize_t aligned_end = len & ~7L; \
1171
+ for (; off < unrolled_end; off += 32) { \
1172
+ uint64_t a0, a1, a2, a3, b0, b1, b2, b3; \
1173
+ memcpy(&a0, a + off, 8); memcpy(&b0, b + off, 8); \
1174
+ memcpy(&a1, a + off + 8, 8); memcpy(&b1, b + off + 8, 8); \
1175
+ memcpy(&a2, a + off + 16, 8); memcpy(&b2, b + off + 16, 8); \
1176
+ memcpy(&a3, a + off + 24, 8); memcpy(&b3, b + off + 24, 8); \
1177
+ uint64_t d0 = expr_word(a0, b0); \
1178
+ uint64_t d1 = expr_word(a1, b1); \
1179
+ uint64_t d2 = expr_word(a2, b2); \
1180
+ uint64_t d3 = expr_word(a3, b3); \
1181
+ memcpy(dst + off, &d0, 8); \
1182
+ memcpy(dst + off + 8, &d1, 8); \
1183
+ memcpy(dst + off + 16, &d2, 8); \
1184
+ memcpy(dst + off + 24, &d3, 8); \
1185
+ } \
1186
+ for (; off < aligned_end; off += 8) { \
1187
+ uint64_t av, bv; \
1188
+ memcpy(&av, a + off, 8); memcpy(&bv, b + off, 8); \
1189
+ uint64_t d = expr_word(av, bv); \
1190
+ memcpy(dst + off, &d, 8); \
1191
+ } \
1192
+ for (; off < len; off++) dst[off] = expr_byte(a[off], b[off]); \
1193
+ }
1194
+
1195
+ #define SB_NOT_WORD(x) (~(x))
1196
+ #define SB_NOT_BYTE(x) ((unsigned char)~(x))
1197
+ #define SB_AND_WORD(x, y) ((x) & (y))
1198
+ #define SB_AND_BYTE(x, y) ((unsigned char)((x) & (y)))
1199
+ #define SB_OR_WORD(x, y) ((x) | (y))
1200
+ #define SB_OR_BYTE(x, y) ((unsigned char)((x) | (y)))
1201
+ #define SB_XOR_WORD(x, y) ((x) ^ (y))
1202
+ #define SB_XOR_BYTE(x, y) ((unsigned char)((x) ^ (y)))
1203
+
1204
+ SB_DEFINE_UNARY_KERNEL (kern_not, SB_NOT_WORD, SB_NOT_BYTE)
1205
+ SB_DEFINE_BINARY_KERNEL(kern_and, SB_AND_WORD, SB_AND_BYTE)
1206
+ SB_DEFINE_BINARY_KERNEL(kern_or, SB_OR_WORD, SB_OR_BYTE)
1207
+ SB_DEFINE_BINARY_KERNEL(kern_xor, SB_XOR_WORD, SB_XOR_BYTE)
1208
+
1209
+ /* Method wrappers: allocate-and-return form, and the in-place (!) form. */
1210
+ #define SB_DEFINE_UNARY_METHODS(op_name, kernel) \
1211
+ static VALUE \
1212
+ rb_str_bitwise_##op_name(VALUE self) \
1213
+ { \
1214
+ ssize_t len = RSTRING_LEN(self); \
1215
+ VALUE result = alloc_result(self); \
1216
+ kernel((unsigned char *)RSTRING_PTR(result), \
1217
+ (const unsigned char *)RSTRING_PTR(self), len); \
1218
+ return result; \
1219
+ } \
1220
+ static VALUE \
1221
+ rb_str_bitwise_##op_name##_bang(VALUE self) \
1222
+ { \
1223
+ rb_str_modify(self); \
1224
+ ssize_t len = RSTRING_LEN(self); \
1225
+ unsigned char *ptr = (unsigned char *)RSTRING_PTR(self); \
1226
+ kernel(ptr, ptr, len); \
1227
+ return self; \
1228
+ }
1229
+
1230
+ #define SB_DEFINE_BINARY_METHODS(op_name, kernel) \
1231
+ static VALUE \
1232
+ rb_str_bitwise_##op_name(VALUE self, VALUE other) \
1233
+ { \
1234
+ check_binary_op_lengths(self, other); \
1235
+ ssize_t len = RSTRING_LEN(self); \
1236
+ VALUE result = alloc_result(self); \
1237
+ kernel((unsigned char *)RSTRING_PTR(result), \
1238
+ (const unsigned char *)RSTRING_PTR(self), \
1239
+ (const unsigned char *)RSTRING_PTR(other), len); \
1240
+ return result; \
1241
+ } \
1242
+ static VALUE \
1243
+ rb_str_bitwise_##op_name##_bang(VALUE self, VALUE other) \
1244
+ { \
1245
+ check_binary_op_lengths(self, other); \
1246
+ rb_str_modify(self); \
1247
+ ssize_t len = RSTRING_LEN(self); \
1248
+ unsigned char *a = (unsigned char *)RSTRING_PTR(self); \
1249
+ const unsigned char *b = (const unsigned char *)RSTRING_PTR(other); \
1250
+ kernel(a, a, b, len); \
1251
+ return self; \
1252
+ }
1253
+
1254
+ SB_DEFINE_UNARY_METHODS (not, kern_not)
1255
+ SB_DEFINE_BINARY_METHODS(and, kern_and)
1256
+ SB_DEFINE_BINARY_METHODS(or, kern_or)
1257
+ SB_DEFINE_BINARY_METHODS(xor, kern_xor)
1000
1258
 
1001
1259
  /* packed bit-field iteration ---------------------------------------------- */
1002
1260
  /*
@@ -1051,25 +1309,7 @@ extract_uint64(const unsigned char *src, ssize_t src_len,
1051
1309
  return val;
1052
1310
  }
1053
1311
 
1054
- /* String#each_bit_field(*bitlens, lsb_first: true) -> self
1055
- * String#each_bit_field(*bitlens, lsb_first: true) -> Enumerator
1056
- *
1057
- * Iterates over the string as a sequence of packed bit-field records. Each
1058
- * positional argument specifies the width (in bits) of one field in the record.
1059
- * On each iteration, one Integer per field is yielded (LSB-first bit layout).
1060
- * Each bitlen must be in the range 1..64.
1061
- *
1062
- * lsb_first: true (default) -- intra-byte field extraction uses bit 0..7.
1063
- * lsb_first: false -- intra-byte field extraction uses bit 7..0.
1064
- *
1065
- * Incomplete trailing bits (when bytesize*8 is not a multiple of sum(bitlens))
1066
- * are silently dropped, matching the behavior of Enumerable#each_slice.
1067
- *
1068
- * Porting to Ruby Core:
1069
- * 1. Move extract_uint64 and this function into string.c.
1070
- * 2. Register with rb_define_method in Init_String().
1071
- * 3. Replace ALLOCA_N with stack arrays for small field counts and heap otherwise.
1072
- */
1312
+ /* Yield each packed bit-field record as one Integer per field. */
1073
1313
  static VALUE
1074
1314
  rb_str_each_bit_field(int argc, VALUE *argv, VALUE self)
1075
1315
  {
@@ -1125,22 +1365,7 @@ rb_str_each_bit_field(int argc, VALUE *argv, VALUE self)
1125
1365
  return self;
1126
1366
  }
1127
1367
 
1128
- /* String#bit_fields(*bitlens, lsb_first: true) -> Array
1129
- * String#bit_fields(*bitlens, lsb_first: true) { |*fields| } -> self
1130
- *
1131
- * Non-iterator complement of each_bit_field. Without a block, returns an
1132
- * Array of all extracted records. With a single bitlen the array is flat
1133
- * (matching each_bit_field(n).to_a); with multiple bitlens each record is
1134
- * itself an Array (matching each_bit_field(a, b, ...).to_a).
1135
- *
1136
- * With a block, behaves identically to each_bit_field without with: ---
1137
- * yielding one Integer per field and returning self.
1138
- *
1139
- * Porting to Ruby Core:
1140
- * 1. Move alongside each_bit_field in string.c.
1141
- * 2. Share extract_uint64 and the bitlen validation logic.
1142
- * 3. Register with rb_define_method in Init_String().
1143
- */
1368
+ /* Non-iterator form of each_bit_field; collect bit-field records into an Array. */
1144
1369
  static VALUE
1145
1370
  rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
1146
1371
  {
@@ -1207,7 +1432,7 @@ rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
1207
1432
 
1208
1433
  /*
1209
1434
  * count_run_lsb: count consecutive bits equal to `target` starting at flat
1210
- * position `pos` (LSB-first). Uses ctz / ctzll to skip bits in bulk:
1435
+ * position `bit_offset` (LSB-first). Uses ctz / ctzll to skip bits in bulk:
1211
1436
  * - partial first byte: ctz on the inverted masked nibble
1212
1437
  * - full 64-bit words (LE): ctzll on the inverted word (64 bits per step)
1213
1438
  * - remaining bytes: ctz on the inverted byte
@@ -1217,11 +1442,11 @@ rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
1217
1442
  * 2. Share sb_ctz8 / sb_ctzll with the existing set-bit helpers.
1218
1443
  */
1219
1444
  static ssize_t
1220
- count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t pos, int target)
1445
+ count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t bit_offset, int target)
1221
1446
  {
1222
- ssize_t max_run = src_len * 8 - pos;
1223
- ssize_t byte_idx = pos >> 3;
1224
- int bit_off = pos & 7;
1447
+ ssize_t max_run = src_len * 8 - bit_offset;
1448
+ ssize_t byte_idx = bit_offset >> 3;
1449
+ int bit_off = bit_offset & 7;
1225
1450
  ssize_t count = 0;
1226
1451
 
1227
1452
  /* partial first byte: shift pos to bit 0, mask remaining bits */
@@ -1272,196 +1497,123 @@ count_run_lsb(const unsigned char *src, ssize_t src_len, ssize_t pos, int target
1272
1497
  return count < max_run ? count : max_run;
1273
1498
  }
1274
1499
 
1275
- /* String#bit_run_count(pos, bit) -> Integer | nil
1276
- *
1277
- * Returns the length of the consecutive run of `bit` starting at flat
1278
- * position `pos`. Returns nil when `pos` is out of range or the bit at `pos`
1279
- * does not equal `bit`.
1280
- *
1281
- * `bit` accepts 0, 1, false, or true (false/true are aliases for 0/1,
1282
- * matching the values yielded by each_bit_run).
1283
- *
1284
- * Counts forward from `pos` toward higher bit indices.
1285
- *
1286
- * Inspired by Gauche Scheme's (bitvector-count-run bit bvec i).
1287
- *
1288
- * Uses the same flat LSB-first addressing as bit_at: byte[pos/8] bit pos%8.
1289
- *
1290
- * Porting to Ruby Core:
1291
- * 1. Move to string.c; register in Init_String().
1292
- * 2. Reuse integer_to_bit_idx for consistent Bignum handling.
1293
- */
1500
+ /* Return the length of the consecutive run of `bit` starting at pos, or nil. */
1294
1501
  static VALUE
1295
1502
  rb_str_bit_run_count(int argc, VALUE *argv, VALUE self)
1296
1503
  {
1297
- VALUE pos_val, bit_val, opts;
1298
- rb_scan_args(argc, argv, "20:", &pos_val, &bit_val, &opts);
1504
+ VALUE bit_offset_v, bit_val, opts;
1505
+ rb_scan_args(argc, argv, "20:", &bit_val, &bit_offset_v, &opts);
1299
1506
  validate_option_hash(opts, SB_KW_LSB_FIRST);
1300
1507
  int lsb_first = parse_lsb_first_opt(opts);
1301
1508
 
1302
- if (!rb_integer_type_p(pos_val)) {
1509
+ if (!rb_integer_type_p(bit_offset_v)) {
1303
1510
  rb_raise(rb_eTypeError, "position must be an integer");
1304
1511
  }
1305
- int target;
1306
- if (bit_val == Qtrue || bit_val == INT2FIX(1)) {
1307
- target = 1;
1308
- } else if (bit_val == Qfalse || bit_val == INT2FIX(0)) {
1309
- target = 0;
1310
- } else {
1311
- rb_raise(rb_eArgError, "bit must be 0, 1, false, or true");
1312
- }
1313
- ssize_t pos = integer_to_bit_idx(pos_val);
1512
+ int target = parse_bit_target(bit_val);
1513
+ ssize_t bit_offset = integer_to_bit_idx(bit_offset_v);
1314
1514
  ssize_t src_len = RSTRING_LEN(self);
1315
- if (pos < 0 || pos >= src_len * 8) return Qnil;
1515
+ if (bit_offset < 0 || bit_offset >= src_len * 8) return Qnil;
1316
1516
 
1317
1517
  const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
1318
1518
  if (lsb_first) {
1319
- if (((src[pos >> 3] >> (pos & 7)) & 1) != target) return Qnil;
1320
- return SSIZET2NUM(count_run_lsb(src, src_len, pos, target));
1519
+ if (((src[bit_offset >> 3] >> (bit_offset & 7)) & 1) != target) return Qnil;
1520
+ return SSIZET2NUM(count_run_lsb(src, src_len, bit_offset, target));
1321
1521
  }
1322
1522
 
1323
- if (logical_get_bit(src, pos, 0) != target) return Qnil;
1523
+ if (logical_get_bit(src, bit_offset, 0) != target) return Qnil;
1324
1524
 
1325
1525
  ssize_t run = 1;
1326
1526
  ssize_t total_bits = src_len * 8;
1327
- while (pos + run < total_bits && logical_get_bit(src, pos + run, 0) == target) {
1527
+ while (bit_offset + run < total_bits && logical_get_bit(src, bit_offset + run, 0) == target) {
1328
1528
  run++;
1329
1529
  }
1330
1530
  return SSIZET2NUM(run);
1331
1531
  }
1332
1532
 
1333
- /* String#each_bit_run(lsb_first: true) { |bit, len| } -> self
1334
- * String#each_bit_run(lsb_first: true) -> Enumerator
1533
+ /* Yield (bit, offset, run_length) triples for each consecutive run of identical bits. */
1534
+ /* Unified emitter for each_bit_run / bit_runs.
1335
1535
  *
1336
- * Yields (bit, run_length) pairs for each consecutive run of identical bits.
1337
- * Run-length boundary detection and counting happen entirely in C, replacing
1338
- * the Ruby-level current/count state machine required when using each_bit.
1536
+ * Walks the bitmap in (bit, run_length) chunks. Yields each pair (when
1537
+ * ary == Qnil) or pushes (bit, run_length) Arrays to the pre-allocated
1538
+ * result. The LSB-first path uses the fast count_run_lsb (word-at-a-time
1539
+ * via ctzll); the MSB-first path scans bit by bit through logical_get_bit.
1339
1540
  *
1340
- * For random data (~50% density) each_bit_run yields ~half as many times as
1341
- * each_bit. For structured data (sparse validity bitmaps, sensor bursts) the
1342
- * ratio is proportional to the average run length.
1343
- *
1344
- * lsb_first: true (default) iterates bit 0..7 within each byte.
1345
- * lsb_first: false iterates bit 7..0 within each byte.
1346
- *
1347
- * Porting to Ruby Core:
1348
- * 1. Move to string.c; register in Init_String().
1349
- * 2. count_run_lsb / count_run_msb move with it.
1541
+ * self is re-read inside the loop because rb_yield can invoke Ruby code
1542
+ * that mutates the receiver, potentially invalidating RSTRING_PTR.
1350
1543
  */
1351
- static VALUE
1352
- rb_str_each_bit_run(int argc, VALUE *argv, VALUE self)
1544
+ static void
1545
+ emit_bit_runs(VALUE self, int lsb_first, ssize_t start_offset, VALUE ary)
1353
1546
  {
1354
- RETURN_ENUMERATOR(self, argc, argv);
1355
-
1356
- int lsb_first = parse_lsb_first(argc, argv);
1357
- ssize_t src_len = RSTRING_LEN(self);
1358
- if (src_len == 0) return self;
1359
-
1547
+ ssize_t src_len = RSTRING_LEN(self);
1548
+ if (src_len == 0 || start_offset >= src_len * 8) return;
1360
1549
  ssize_t total_bits = src_len * 8;
1550
+ ssize_t offset = start_offset;
1551
+
1552
+ #define SB_EMIT_TRIPLE(bval, oval, lval) \
1553
+ do { if (ary == Qnil) rb_yield_values(3, (bval), (oval), (lval)); \
1554
+ else rb_ary_push(ary, rb_ary_new3(3, (bval), (oval), (lval))); } while (0)
1361
1555
 
1362
1556
  if (lsb_first) {
1363
- ssize_t pos = 0;
1364
- while (pos < total_bits) {
1557
+ while (offset < total_bits) {
1365
1558
  const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
1366
- int bit = (src[pos >> 3] >> (pos & 7)) & 1;
1367
- ssize_t run = count_run_lsb(src, src_len, pos, bit);
1368
- rb_yield_values(2, bit ? Qtrue : Qfalse, SSIZET2NUM(run));
1369
- pos += run;
1559
+ int bit = (src[offset >> 3] >> (offset & 7)) & 1;
1560
+ ssize_t run = count_run_lsb(src, src_len, offset, bit);
1561
+ SB_EMIT_TRIPLE(bit ? Qtrue : Qfalse, SSIZET2NUM(offset), SSIZET2NUM(run));
1562
+ offset += run;
1370
1563
  }
1371
1564
  }
1372
1565
  else {
1373
- ssize_t pos = 0;
1374
- while (pos < total_bits) {
1566
+ while (offset < total_bits) {
1375
1567
  const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
1376
- int bit = logical_get_bit(src, pos, 0);
1568
+ int bit = logical_get_bit(src, offset, 0);
1377
1569
  ssize_t run = 1;
1378
- while (pos + run < total_bits && logical_get_bit(src, pos + run, 0) == bit) {
1570
+ while (offset + run < total_bits && logical_get_bit(src, offset + run, 0) == bit) {
1379
1571
  run++;
1380
1572
  }
1381
- rb_yield_values(2, bit ? Qtrue : Qfalse, SSIZET2NUM(run));
1382
- pos += run;
1573
+ SB_EMIT_TRIPLE(bit ? Qtrue : Qfalse, SSIZET2NUM(offset), SSIZET2NUM(run));
1574
+ offset += run;
1383
1575
  }
1384
1576
  }
1385
1577
 
1386
- return self;
1578
+ #undef SB_EMIT_TRIPLE
1387
1579
  }
1388
1580
 
1389
- /* String#bit_runs(lsb_first: true) -> Array
1390
- * String#bit_runs(lsb_first: true) { |bit, len| } -> self
1391
- *
1392
- * Non-iterator complement of each_bit_run. Without a block, collects all
1393
- * (bit, run_length) pairs into an Array and returns it. With a block,
1394
- * yields each pair and returns self.
1395
- *
1396
- * Follows the same pattern as String#bytes vs String#each_byte.
1397
- *
1398
- * Porting to Ruby Core:
1399
- * 1. Move to string.c alongside each_bit_run; register in Init_String().
1400
- */
1401
1581
  static VALUE
1402
- rb_str_bit_runs(int argc, VALUE *argv, VALUE self)
1582
+ rb_str_each_bit_run(int argc, VALUE *argv, VALUE self)
1403
1583
  {
1404
- int lsb_first = parse_lsb_first(argc, argv);
1405
- ssize_t src_len = RSTRING_LEN(self);
1406
- int have_block = rb_block_given_p();
1584
+ RETURN_ENUMERATOR(self, argc, argv);
1407
1585
 
1408
- if (src_len == 0) return have_block ? self : rb_ary_new();
1586
+ VALUE start_offset_v = Qnil, opts = Qnil;
1587
+ rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
1588
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
1589
+ int lsb_first = parse_lsb_first_opt(opts);
1590
+ ssize_t start_offset = parse_start_offset(start_offset_v);
1409
1591
 
1410
- ssize_t total_bits = src_len * 8;
1411
- VALUE result = have_block ? Qnil : rb_ary_new();
1592
+ emit_bit_runs(self, lsb_first, start_offset, Qnil);
1593
+ return self;
1594
+ }
1412
1595
 
1413
- if (lsb_first) {
1414
- ssize_t pos = 0;
1415
- while (pos < total_bits) {
1416
- const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
1417
- int bit = (src[pos >> 3] >> (pos & 7)) & 1;
1418
- ssize_t run = count_run_lsb(src, src_len, pos, bit);
1419
- VALUE bval = bit ? Qtrue : Qfalse;
1420
- VALUE lval = SSIZET2NUM(run);
1421
- have_block ? rb_yield_values(2, bval, lval)
1422
- : rb_ary_push(result, rb_assoc_new(bval, lval));
1423
- pos += run;
1424
- }
1425
- } else {
1426
- ssize_t pos = 0;
1427
- while (pos < total_bits) {
1428
- const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
1429
- int bit = logical_get_bit(src, pos, 0);
1430
- ssize_t run = 1;
1431
- while (pos + run < total_bits && logical_get_bit(src, pos + run, 0) == bit) {
1432
- run++;
1433
- }
1434
- VALUE bval = bit ? Qtrue : Qfalse;
1435
- VALUE lval = SSIZET2NUM(run);
1436
- have_block ? rb_yield_values(2, bval, lval)
1437
- : rb_ary_push(result, rb_assoc_new(bval, lval));
1438
- pos += run;
1439
- }
1596
+ /* Non-iterator form of each_bit_run; collect run triples into an Array. */
1597
+ static VALUE
1598
+ rb_str_bit_runs(int argc, VALUE *argv, VALUE self)
1599
+ {
1600
+ VALUE start_offset_v = Qnil, opts = Qnil;
1601
+ rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
1602
+ validate_option_hash(opts, SB_KW_LSB_FIRST);
1603
+ int lsb_first = parse_lsb_first_opt(opts);
1604
+ ssize_t start_offset = parse_start_offset(start_offset_v);
1605
+
1606
+ if (rb_block_given_p()) {
1607
+ emit_bit_runs(self, lsb_first, start_offset, Qnil);
1608
+ return self;
1440
1609
  }
1441
1610
 
1442
- return have_block ? self : result;
1611
+ VALUE ary = rb_ary_new();
1612
+ emit_bit_runs(self, lsb_first, start_offset, ary);
1613
+ return ary;
1443
1614
  }
1444
1615
 
1445
- /* String#bit_splice(bit_index, bit_length, str) -> self
1446
- * String#bit_splice(bit_index, bit_length, str, str_bit_index, str_bit_length) -> self
1447
- * String#bit_splice(range, str) -> self
1448
- * String#bit_splice(range, str, str_range) -> self
1449
- *
1450
- * Writes bits from str into self at bit-level granularity. The inverse of
1451
- * bit_slice: where bit_slice reads a sub-sequence of bits, bit_splice writes one.
1452
- *
1453
- * The destination and source bit lengths must be equal; bit_splice does not
1454
- * resize self (sub-byte resize is undefined). This mirrors the constraint that
1455
- * bytesplice imposes when the replacement has the same byte length.
1456
- *
1457
- * Negative indices count backward from the end, exactly as in bytesplice.
1458
- * Returns self.
1459
- *
1460
- * Porting to Ruby Core:
1461
- * 1. Move to string.c; register in Init_String().
1462
- * 2. Use rb_str_modify_expand if resize support is ever added.
1463
- * 3. bit_copy_core moves with it; share ebs_extract with bit_slice.
1464
- */
1616
+ /* Write bits from str into self at bit-level granularity (inverse of bit_slice). */
1465
1617
  static VALUE
1466
1618
  rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1467
1619
  {
@@ -1469,14 +1621,15 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1469
1621
  ssize_t src_bit_off, src_bit_len;
1470
1622
  VALUE str;
1471
1623
  ssize_t dst_total = RSTRING_LEN(self) * 8;
1472
- VALUE v0, v1, v2, v3, v4, opts;
1624
+ VALUE v0, v1, v2, v3, opts;
1473
1625
 
1474
- int n_pos = rb_scan_args(argc, argv, "23:", &v0, &v1, &v2, &v3, &v4, &opts);
1626
+ int n_pos = rb_scan_args(argc, argv, "22:", &v0, &v1, &v2, &v3, &opts);
1475
1627
  validate_option_hash(opts, SB_KW_LSB_FIRST);
1476
1628
  int lsb_first = parse_lsb_first_opt(opts);
1477
1629
 
1478
1630
  if (n_pos == 2 && rb_obj_is_kind_of(v0, rb_cRange)) {
1479
1631
  /* bit_splice(range, str) */
1632
+ sb_range_validate_endpoints(v0);
1480
1633
  ssize_t beg, len;
1481
1634
  sb_range_beg_len(v0, &beg, &len, dst_total, 1);
1482
1635
  dst_bit_off = beg;
@@ -1487,20 +1640,21 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1487
1640
  src_bit_len = dst_bit_len;
1488
1641
  }
1489
1642
  else if (n_pos == 3 && rb_obj_is_kind_of(v0, rb_cRange)) {
1490
- /* bit_splice(range, str, str_range) */
1643
+ /* bit_splice(range, str, str_bit_index) */
1644
+ sb_range_validate_endpoints(v0);
1491
1645
  ssize_t beg, len;
1492
1646
  sb_range_beg_len(v0, &beg, &len, dst_total, 1);
1493
1647
  dst_bit_off = beg;
1494
1648
  dst_bit_len = len;
1495
1649
  str = v1;
1496
1650
  Check_Type(str, T_STRING);
1497
- if (!rb_obj_is_kind_of(v2, rb_cRange)) {
1498
- rb_raise(rb_eTypeError, "third argument must be a Range");
1651
+ if (!rb_integer_type_p(v2)) {
1652
+ rb_raise(rb_eTypeError, "third argument must be an Integer");
1499
1653
  }
1500
1654
  ssize_t src_total = RSTRING_LEN(str) * 8;
1501
- sb_range_beg_len(v2, &beg, &len, src_total, 1);
1502
- src_bit_off = beg;
1503
- src_bit_len = len;
1655
+ src_bit_off = integer_to_bit_idx(v2);
1656
+ if (src_bit_off < 0) src_bit_off += src_total;
1657
+ src_bit_len = dst_bit_len;
1504
1658
  }
1505
1659
  else if (n_pos == 3) {
1506
1660
  /* bit_splice(bit_index, bit_length, str) */
@@ -1526,10 +1680,9 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1526
1680
  src_bit_off = 0;
1527
1681
  src_bit_len = dst_bit_len;
1528
1682
  }
1529
- else if (n_pos == 5) {
1530
- /* bit_splice(bit_index, bit_length, str, str_bit_index, str_bit_length) */
1531
- if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1) ||
1532
- !rb_integer_type_p(v3) || !rb_integer_type_p(v4)) {
1683
+ else if (n_pos == 4) {
1684
+ /* bit_splice(bit_index, bit_length, str, str_bit_index) */
1685
+ if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1) || !rb_integer_type_p(v3)) {
1533
1686
  rb_raise(rb_eTypeError, "bit indices and lengths must be integers");
1534
1687
  }
1535
1688
  dst_bit_off = integer_to_bit_idx(v0);
@@ -1539,12 +1692,12 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1539
1692
  Check_Type(str, T_STRING);
1540
1693
  ssize_t src_total = RSTRING_LEN(str) * 8;
1541
1694
  src_bit_off = integer_to_bit_idx(v3);
1542
- src_bit_len = integer_to_bit_idx(v4);
1543
1695
  if (src_bit_off < 0) src_bit_off += src_total;
1696
+ src_bit_len = dst_bit_len;
1544
1697
  }
1545
1698
  else {
1546
1699
  rb_raise(rb_eArgError,
1547
- "wrong number of arguments (given %d, expected 2, 3, or 5)", n_pos);
1700
+ "wrong number of arguments (given %d, expected 2, 3, or 4)", n_pos);
1548
1701
  }
1549
1702
 
1550
1703
  if (dst_bit_off < 0 || dst_bit_len < 0 || dst_bit_off + dst_bit_len > dst_total) {
@@ -1560,12 +1713,6 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
1560
1713
  src_bit_off, src_bit_len, src_total_bits);
1561
1714
  }
1562
1715
 
1563
- if (dst_bit_len != src_bit_len) {
1564
- rb_raise(rb_eArgError,
1565
- "bit_splice: destination length (%ld) must equal source length (%ld)",
1566
- dst_bit_len, src_bit_len);
1567
- }
1568
-
1569
1716
  if (dst_bit_len == 0) return self;
1570
1717
 
1571
1718
  /* Guard against self-aliasing: duplicate src before modifying self */
@@ -1765,38 +1912,36 @@ rb_ary_mask_bang(int argc, VALUE *argv, VALUE self)
1765
1912
  void
1766
1913
  Init_string_bits(void)
1767
1914
  {
1768
- id_bracket = rb_intern("[]");
1769
- sym_lsb_first = ID2SYM(rb_intern("lsb_first"));
1770
- sym_lsb = ID2SYM(rb_intern("lsb"));
1771
- sym_msb = ID2SYM(rb_intern("msb"));
1772
- sym_invert = ID2SYM(rb_intern("invert"));
1773
-
1774
- rb_define_method(rb_cString, "bit_at", rb_str_bit_at, -1);
1775
- rb_define_method(rb_cString, "bit_count", rb_str_bit_count, 0);
1776
- rb_define_method(rb_cString, "each_bit", rb_str_each_bit, -1);
1777
- rb_define_method(rb_cString, "bits", rb_str_bits, -1);
1778
- rb_define_method(rb_cString, "each_set_bit_offset", rb_str_each_set_bit_offset, -1);
1779
- rb_define_method(rb_cString, "set_bit_offsets", rb_str_set_bit_offsets, -1);
1780
- rb_define_method(rb_cString, "bit_slice", rb_str_bit_slice, -1);
1781
- rb_define_method(rb_cString, "bit_splice", rb_str_bit_splice, -1);
1782
- rb_define_method(rb_cString, "bit_run_count", rb_str_bit_run_count, -1);
1783
- rb_define_method(rb_cString, "each_bit_run", rb_str_each_bit_run, -1);
1784
- rb_define_method(rb_cString, "bit_runs", rb_str_bit_runs, -1);
1785
- rb_define_method(rb_cString, "set_bit", rb_str_set_bit, -1);
1786
- rb_define_method(rb_cString, "clear_bit", rb_str_clear_bit, -1);
1787
- rb_define_method(rb_cString, "flip_bit", rb_str_flip_bit, -1);
1788
- rb_define_method(rb_cString, "bit_not", rb_str_bit_not, 0);
1789
- rb_define_method(rb_cString, "bit_not!", rb_str_bit_not_bang, 0);
1790
- rb_define_method(rb_cString, "bit_and", rb_str_bit_and, 1);
1791
- rb_define_method(rb_cString, "bit_and!", rb_str_bit_and_bang, 1);
1792
- rb_define_method(rb_cString, "bit_or", rb_str_bit_or, 1);
1793
- rb_define_method(rb_cString, "bit_or!", rb_str_bit_or_bang, 1);
1794
- rb_define_method(rb_cString, "bit_xor", rb_str_bit_xor, 1);
1795
- rb_define_method(rb_cString, "bit_xor!", rb_str_bit_xor_bang, 1);
1915
+ id_bracket = rb_intern("[]");
1916
+ sym_lsb_first = ID2SYM(rb_intern("lsb_first"));
1917
+ sym_invert = ID2SYM(rb_intern("invert"));
1918
+
1919
+ rb_define_method(rb_cString, "bit_at", rb_str_bit_at, -1);
1920
+ rb_define_method(rb_cString, "bit_count", rb_str_bit_count, -1);
1921
+ rb_define_method(rb_cString, "each_bit", rb_str_each_bit, -1);
1922
+ rb_define_method(rb_cString, "bits", rb_str_bits, -1);
1923
+ rb_define_method(rb_cString, "each_bit_offset", rb_str_each_bit_offset, -1);
1924
+ rb_define_method(rb_cString, "bit_offsets", rb_str_bit_offsets, -1);
1925
+ rb_define_method(rb_cString, "bit_slice", rb_str_bit_slice, -1);
1926
+ rb_define_method(rb_cString, "bit_splice", rb_str_bit_splice, -1);
1927
+ rb_define_method(rb_cString, "bit_run_count", rb_str_bit_run_count, -1);
1928
+ rb_define_method(rb_cString, "each_bit_run", rb_str_each_bit_run, -1);
1929
+ rb_define_method(rb_cString, "bit_runs", rb_str_bit_runs, -1);
1930
+ rb_define_method(rb_cString, "bit_set", rb_str_bit_set, -1);
1931
+ rb_define_method(rb_cString, "bit_clear", rb_str_bit_clear, -1);
1932
+ rb_define_method(rb_cString, "bit_flip", rb_str_bit_flip, -1);
1933
+ rb_define_method(rb_cString, "bitwise_not", rb_str_bitwise_not, 0);
1934
+ rb_define_method(rb_cString, "bitwise_not!", rb_str_bitwise_not_bang, 0);
1935
+ rb_define_method(rb_cString, "bitwise_and", rb_str_bitwise_and, 1);
1936
+ rb_define_method(rb_cString, "bitwise_and!", rb_str_bitwise_and_bang, 1);
1937
+ rb_define_method(rb_cString, "bitwise_or", rb_str_bitwise_or, 1);
1938
+ rb_define_method(rb_cString, "bitwise_or!", rb_str_bitwise_or_bang, 1);
1939
+ rb_define_method(rb_cString, "bitwise_xor", rb_str_bitwise_xor, 1);
1940
+ rb_define_method(rb_cString, "bitwise_xor!", rb_str_bitwise_xor_bang, 1);
1796
1941
 
1797
1942
  // These methods are defined here to avoid cluttering this file, but they are not part of the current core proposal (see FUTURE_PROPOSAL_PLAN.md).
1798
- rb_define_method(rb_cString, "each_bit_field", rb_str_each_bit_field, -1);
1799
- rb_define_method(rb_cString, "bit_fields", rb_str_bit_fields, -1);
1800
- rb_define_method(rb_cArray, "mask", rb_ary_mask, -1);
1801
- rb_define_method(rb_cArray, "mask!", rb_ary_mask_bang, -1);
1943
+ rb_define_method(rb_cString, "each_bit_field", rb_str_each_bit_field, -1);
1944
+ rb_define_method(rb_cString, "bit_fields", rb_str_bit_fields, -1);
1945
+ rb_define_method(rb_cArray, "mask", rb_ary_mask, -1);
1946
+ rb_define_method(rb_cArray, "mask!", rb_ary_mask_bang, -1);
1802
1947
  }