Skip to content

Add mruby-string-bitops gem - #7011

Merged
matz merged 3 commits into
mruby:masterfrom
hasumikin:string-bitops
Aug 7, 2026
Merged

Add mruby-string-bitops gem#7011
matz merged 3 commits into
mruby:masterfrom
hasumikin:string-bitops

Conversation

@hasumikin

@hasumikin hasumikin commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Implement String bit operations from CRuby
(spec in https://bugs.ruby-lang.org/issues/22118): bit_get, bit_set?, bit_set, bit_clear, bit_flip, bit_count, and bitwise_not/and/or/xor with their bang! variants.

The port minds mruby-specific concerns: the bulk kernels use pointer-width words so 32-bit targets avoid emulated 64-bit arithmetic, word-aligned buffers get true word loads even on cores without unaligned access support (e.g. Cortex-M0+), and bit offsets are mrb_int with no Bignum path. See the gem's README.md for details and the intentional differences from CRuby.

I didn't add this gem to any gembox. @matz I leave this matter to you

Summary by CodeRabbit

  • New Features

    • Added mruby-string-bitops with String bit access, mutation, counting, inversion, and binary bitwise operations.
    • Supports configurable bit order, operand conversion, large strings, and both in-place and non-mutating variants.
    • Added validation for invalid offsets, mismatched lengths, frozen strings, and unsupported inputs.
  • Documentation

    • Added comprehensive usage guidance, examples, return values, errors, encoding behavior, and compatibility notes.
  • Tests

    • Added coverage for bit operations, coercion, edge cases, encodings, and error handling.

Implement String bit operations from CRuby
(spec in https://bugs.ruby-lang.org/issues/22118): `bit_get`, `bit_set?`,
`bit_set`, `bit_clear`, `bit_flip`, `bit_count`, and `bitwise_not/and/or/xor`
with their bang`!` variants.

The port minds mruby-specific concerns: the bulk kernels use
pointer-width words so 32-bit targets avoid emulated 64-bit
arithmetic, word-aligned buffers get true word loads even on cores
without unaligned access support (e.g. Cortex-M0+), and bit offsets
are `mrb_int` with no Bignum path. See the gem's README.md for details
and the intentional differences from CRuby.

I didn't add this gem to any gembox. @matz I leave this matter to you
@hasumikin
hasumikin requested a review from matz as a code owner August 7, 2026 04:18
Copilot AI lite review requested due to automatic review settings August 7, 2026 04:18
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds the mruby-string-bitops gem. It implements String bit access, mutation, counting, and bitwise operations with optimized C kernels. It also adds gem metadata, documentation, and comprehensive tests.

Changes

String bit operations

Layer / File(s) Summary
Bitwise kernels and counting
mrbgems/mruby-string-bitops/src/string_bitops.c
Adds architecture-aware word processing, alignment fallbacks, byte-tail handling, and popcount logic.
String API and gem registration
mrbgems/mruby-string-bitops/src/string_bitops.c, mrbgems/mruby-string-bitops/mrbgem.rake, mrbgems/mruby-string-bitops/README.md
Adds bit offset parsing, String methods, bitwise operations, gem registration, metadata, and API documentation.
API and kernel validation
mrbgems/mruby-string-bitops/test/string_bitops.rb
Tests bit access, mutation, counting, bitwise operations, coercion, encoding, errors, frozen strings, long inputs, alignment, and tails.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StringMethod as String#bit_xor
  participant Dispatch as Bitwise operation dispatch
  participant Kernel as Word-at-a-time kernel
  StringMethod->>Dispatch: Convert operand with to_str and validate length
  Dispatch->>Kernel: Process aligned words and byte tails
  Kernel-->>Dispatch: Return transformed bytes
  Dispatch-->>StringMethod: Return binary String
Loading

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding the mruby-string-bitops gem.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
mrbgems/mruby-string-bitops/test/string_bitops.rb (2)

148-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a self-aliasing case for the bang operations.

The bang kernels are called with dst == lhs. When the caller passes the receiver as the operand, rhs also aliases dst. The kernels read each word into locals before the store, so the result should be correct. A test would lock that behavior in.

💚 Proposed test addition
   assert_equal s.object_id, s.bitwise_xor!("\xFF").object_id
   assert_equal "\x33", s
+
+  # Self-aliasing: receiver and operand are the same object.
+  u = "\xA5" * 20
+  u.bitwise_xor!(u)
+  assert_equal "\x00" * 20, u
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mrbgems/mruby-string-bitops/test/string_bitops.rb` around lines 148 - 154,
Add self-aliasing coverage for the bang operations in the existing string bitops
test, invoking bitwise_and!, bitwise_or!, and bitwise_xor! with the receiver
itself as the operand. Assert each operation preserves correct in-place results
and returns the same receiver object.

171-179: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering zero-length operands on both sides.

The length-mismatch cases are covered. The valid zero-length case is not. Add "".bitwise_and(""), "".bitwise_not, and "".bit_get(0) to confirm that the kernels and the bounds checks handle len == 0.

💚 Proposed test addition
+  assert_equal "", "".bitwise_and("")
+  assert_equal "", "".bitwise_or("")
+  assert_equal "", "".bitwise_xor("")
+  assert_equal "", "".bitwise_not
+  assert_nil "".bit_get(0)
   # Length mismatch: other longer, shorter, and empty.
   assert_raise(ArgumentError) { "\xF0".bitwise_and("\x00\x00") }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mrbgems/mruby-string-bitops/test/string_bitops.rb` around lines 171 - 179,
Add zero-length operand coverage to the string bitops tests by asserting valid
results for "".bitwise_and(""), "".bitwise_not, and "".bit_get(0), alongside the
existing mismatch cases. Ensure these assertions verify the kernels and bounds
behavior for len == 0.
mrbgems/mruby-string-bitops/README.md (1)

22-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the lsb_first validation rule.

The implementation raises ArgumentError when lsb_first is neither true nor false. The test file asserts this behavior. Add that rule here so users know that nil is rejected instead of being treated as false.

📝 Proposed documentation addition
 `IndexError` is raised when `offset` is negative, or (for the mutating
 methods) when it is beyond the end of the string.
+
+`lsb_first` accepts only `true` or `false`. Any other value, including
+`nil`, raises `ArgumentError`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mrbgems/mruby-string-bitops/README.md` around lines 22 - 28, Update the
argument-validation documentation in the mruby-string-bitops README to state
that lsb_first must be exactly true or false; any other value, including nil,
raises ArgumentError. Keep the existing offset and IndexError behavior
documentation unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mrbgems/mruby-string-bitops/test/string_bitops.rb`:
- Around line 39-47: Update the oversized value returned by big.to_int in the
bit_get test so it remains an accepted integer type and triggers RangeError with
or without mruby-bigint; avoid relying on 2 ** 100, which becomes Float without
bigint support, or adjust the assertion to match the configuration-independent
behavior.

---

Nitpick comments:
In `@mrbgems/mruby-string-bitops/README.md`:
- Around line 22-28: Update the argument-validation documentation in the
mruby-string-bitops README to state that lsb_first must be exactly true or
false; any other value, including nil, raises ArgumentError. Keep the existing
offset and IndexError behavior documentation unchanged.

In `@mrbgems/mruby-string-bitops/test/string_bitops.rb`:
- Around line 148-154: Add self-aliasing coverage for the bang operations in the
existing string bitops test, invoking bitwise_and!, bitwise_or!, and
bitwise_xor! with the receiver itself as the operand. Assert each operation
preserves correct in-place results and returns the same receiver object.
- Around line 171-179: Add zero-length operand coverage to the string bitops
tests by asserting valid results for "".bitwise_and(""), "".bitwise_not, and
"".bit_get(0), alongside the existing mismatch cases. Ensure these assertions
verify the kernels and bounds behavior for len == 0.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cf3c2a1b-510e-4c1c-b682-b3510834fd30

📥 Commits

Reviewing files that changed from the base of the PR and between 32f94bc and 3067378.

📒 Files selected for processing (4)
  • mrbgems/mruby-string-bitops/README.md
  • mrbgems/mruby-string-bitops/mrbgem.rake
  • mrbgems/mruby-string-bitops/src/string_bitops.c
  • mrbgems/mruby-string-bitops/test/string_bitops.rb

Comment thread mrbgems/mruby-string-bitops/test/string_bitops.rb

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new mruby-string-bitops gem that adds CRuby-inspired bit-level operations on String (single-bit access/mutation, population count, and whole-string bitwise ops), with attention to mruby portability/performance concerns.

Changes:

  • Adds the C implementation of String#bit_get, #bit_set?, #bit_set, #bit_clear, #bit_flip, #bit_count, and #bitwise_{not,and,or,xor} (+ ! variants).
  • Adds a comprehensive test suite ported/adapted from CRuby coverage for the new APIs.
  • Adds gem metadata and README documenting behavior and intentional differences from CRuby.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
mrbgems/mruby-string-bitops/src/string_bitops.c Implements the new String bit operations and registers the methods on String.
mrbgems/mruby-string-bitops/test/string_bitops.rb Adds behavioral tests for offsets, keyword handling, mutation, frozen strings, and long-string paths.
mrbgems/mruby-string-bitops/README.md Documents API surface, usage examples, implementation notes, and differences from CRuby.
mrbgems/mruby-string-bitops/mrbgem.rake Adds gem specification metadata (name/license/author/summary).
Suppressed comments (1)

mrbgems/mruby-string-bitops/src/string_bitops.c:478

  • If to_str raises an exception, this helper continues and then raises a new TypeError, which hides the original exception. Re-raise immediately when mrb_funcall_argv sets mrb->exc so exceptions from user-defined to_str propagate correctly.
  if (mrb_respond_to(mrb, other, MRB_SYM(to_str))) {
    mrb_value converted = mrb_funcall_argv(mrb, other, MRB_SYM(to_str), 0, NULL);
    if (mrb_string_p(converted)) {
      return converted;
    }
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +277 to +283
if (mrb_respond_to(mrb, index, MRB_SYM(to_int))) {
mrb_value converted = mrb_funcall_argv(mrb, index, MRB_SYM(to_int), 0, NULL);
if (mrb_type(converted) == MRB_TT_INTEGER || mrb_type(converted) == MRB_TT_BIGINT) {
index = converted;
break;
}
}

@hasumikin hasumikin Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to call mrb_exc_raise. mrb_funcall_argv should propagate an exception by longjmp, right?

Comment on lines +414 to +419
static mrb_value
mrb_str_bit_count(mrb_state *mrb, mrb_value str)
{
mrb_get_args(mrb, "");
return mrb_int_value(mrb, bitop_count_bits((const unsigned char*)RSTRING_PTR(str), RSTRING_LEN(str)));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3433ef

@hasumikin
hasumikin marked this pull request as draft August 7, 2026 05:18
On 32-bit mrb_int builds the count of a string over 256MiB exceeds
MRB_INT_MAX.  Count in uint64_t like CRuby does, and when the total
does not fit in mrb_int, return a Bignum when mruby-bigint is present
or raise RangeError otherwise.
Copilot AI review requested due to automatic review settings August 7, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

mrbgems/mruby-string-bitops/src/string_bitops.c:496

  • This conversion error uses %t (type name). Elsewhere in mruby, conversion errors typically use %Y so the message includes the actual offending value (see mrb_ensure_string_type in src/object.c). Switching to %Y improves diagnostics and keeps messages consistent.
  mrb_raisef(mrb, E_TYPE_ERROR, "%t cannot be converted to String", other);

mrbgems/mruby-string-bitops/src/string_bitops.c:291

  • Conversion error messages here use %t (type name) but mruby’s core conversion helpers use %Y to include the offending value (e.g. mrb_ensure_integer_type in src/object.c). Using %Y makes the error more informative and consistent across the codebase.

This issue also appears on line 496 of the same file.

    mrb_raisef(mrb, E_TYPE_ERROR, "%t cannot be converted to Integer", index);

Match core conversion helpers such as mrb_ensure_string_type, which
report the offending value with %Y instead of only its type.
Copilot AI review requested due to automatic review settings August 7, 2026 05:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

mrbgems/mruby-string-bitops/src/string_bitops.c:286

  • If to_int raises inside mrb_funcall_argv, mrb->exc will be set and this helper currently continues, potentially overwriting the original exception with the later TypeError and/or inspecting an invalid return value. Check mrb->exc immediately after the call and re-raise to preserve the original exception semantics.
      mrb_value converted = mrb_funcall_argv(mrb, index, MRB_SYM(to_int), 0, NULL);
      if (mrb_type(converted) == MRB_TT_INTEGER || mrb_type(converted) == MRB_TT_BIGINT) {

mrbgems/mruby-string-bitops/src/string_bitops.c:492

  • If to_str raises inside mrb_funcall_argv, mrb->exc will be set and this helper currently continues, potentially overwriting the original exception with the later TypeError and/or inspecting an invalid return value. Check mrb->exc immediately after the call and re-raise to preserve the original exception semantics.
    mrb_value converted = mrb_funcall_argv(mrb, other, MRB_SYM(to_str), 0, NULL);
    if (mrb_string_p(converted)) {

@hasumikin
hasumikin marked this pull request as ready for review August 7, 2026 05:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mrbgems/mruby-string-bitops/README.md (1)

57-60: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the bulk-kernel performance description.

Line 57 combines “one machine word per iteration” with “4x unrolling.” If the loop is unrolled four times, describe it as processing four words per unrolled iteration.

Lines 59-60 also imply that 32-bit targets avoid all emulated 64-bit arithmetic. Scope this statement to word processing because bit_count uses a uint64_t accumulator on 32-bit builds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mrbgems/mruby-string-bitops/README.md` around lines 57 - 60, Update the
bulk-kernel performance description in the README to state that each
four-times-unrolled iteration processes four machine words. Qualify the 32-bit
target statement so it only claims that word processing avoids emulated 64-bit
arithmetic, while preserving that bit_count uses a uint64_t accumulator on
32-bit builds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@mrbgems/mruby-string-bitops/README.md`:
- Around line 57-60: Update the bulk-kernel performance description in the
README to state that each four-times-unrolled iteration processes four machine
words. Qualify the 32-bit target statement so it only claims that word
processing avoids emulated 64-bit arithmetic, while preserving that bit_count
uses a uint64_t accumulator on 32-bit builds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ce46654b-1c32-438a-806f-2aca6384f654

📥 Commits

Reviewing files that changed from the base of the PR and between 3067378 and 2c2f79c.

📒 Files selected for processing (2)
  • mrbgems/mruby-string-bitops/README.md
  • mrbgems/mruby-string-bitops/src/string_bitops.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • mrbgems/mruby-string-bitops/src/string_bitops.c

@matz
matz merged commit 8b7b712 into mruby:master Aug 7, 2026
21 checks passed
@hasumikin
hasumikin deleted the string-bitops branch August 7, 2026 06:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants