Add mruby-string-bitops gem - #7011
Conversation
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
📝 WalkthroughWalkthroughThis PR adds the ChangesString bit operations
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
mrbgems/mruby-string-bitops/test/string_bitops.rb (2)
148-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider 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,rhsalso aliasesdst. 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 winConsider 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 handlelen == 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 winDocument the
lsb_firstvalidation rule.The implementation raises
ArgumentErrorwhenlsb_firstis neithertruenorfalse. The test file asserts this behavior. Add that rule here so users know thatnilis 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
📒 Files selected for processing (4)
mrbgems/mruby-string-bitops/README.mdmrbgems/mruby-string-bitops/mrbgem.rakemrbgems/mruby-string-bitops/src/string_bitops.cmrbgems/mruby-string-bitops/test/string_bitops.rb
There was a problem hiding this comment.
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_strraises an exception, this helper continues and then raises a new TypeError, which hides the original exception. Re-raise immediately whenmrb_funcall_argvsetsmrb->excso exceptions from user-definedto_strpropagate 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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
I don't think we need to call mrb_exc_raise. mrb_funcall_argv should propagate an exception by longjmp, right?
| 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))); | ||
| } |
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.
There was a problem hiding this comment.
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%Yso the message includes the actual offending value (seemrb_ensure_string_typein src/object.c). Switching to%Yimproves 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%Yto include the offending value (e.g.mrb_ensure_integer_typein src/object.c). Using%Ymakes 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.
There was a problem hiding this comment.
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_intraises insidemrb_funcall_argv,mrb->excwill be set and this helper currently continues, potentially overwriting the original exception with the later TypeError and/or inspecting an invalid return value. Checkmrb->excimmediately 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_strraises insidemrb_funcall_argv,mrb->excwill be set and this helper currently continues, potentially overwriting the original exception with the later TypeError and/or inspecting an invalid return value. Checkmrb->excimmediately 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)) {
There was a problem hiding this comment.
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 winClarify 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_countuses auint64_taccumulator 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
📒 Files selected for processing (2)
mrbgems/mruby-string-bitops/README.mdmrbgems/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
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, andbitwise_not/and/or/xorwith 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_intwith 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
mruby-string-bitopswith String bit access, mutation, counting, inversion, and binary bitwise operations.Documentation
Tests