string.c: write MRB_STR_SINGLE_BYTE through the accessors it has - #7156
Conversation
`include/mruby/string.h` names a way in and out of each string flag it
defines, and `MRB_STR_SINGLE_BYTE` has all four: a predicate, a set, an
unset and a copy. Three writes reach past them and name the bit:
mrb_str_ptr(str)->flags |= MRB_STR_SINGLE_BYTE; /* str_ascii_only_p */
mrb_str_ptr(str)->flags |= src_sb_flag; /* str_escape */
mrb_str_ptr(result)->flags |= sb_flag; /* str_escape */
The first is in `mruby-string-ext`, outside the `MRB_UTF8_STRING` guard
the flag's meaning sits behind, so it runs in a build without that
define as well, and there it sets a bit nothing on a string reads:
`RSTR_SINGLE_BYTE_P()` is `TRUE` for every string in that build, and
the one other flag at bit 5, `MRB_FL_OBJ_SHAPED`, is read through a
`struct RObject*` and never on a string. `RSTR_SET_ASCII_FLAG()` is
what the same file calls a few hundred lines above once it knows the
bytes in hand are ASCII, and it leaves the `MRB_UTF8_STRING` build
writing what it wrote before while dropping the write from the build
that has no reader for it. The `struct RString*` is already in hand as
`s`, so the accessor takes that instead of reaching through `str` a
second time.
The other two are in `str_escape()`, which carries the bit in
`uint32_t` locals so that the write at the end can be one `|=` of a
value that is either `MRB_STR_SINGLE_BYTE` or zero. The locals become
`mrb_bool` and the accessor does the writing. Both stay under the
`#ifdef` they are already in, since only a build that reads whole
characters out of `str` has an answer to record.
No answer any of the three gives changes, so no test comes with this.
`gcc -S -O3` over both files, with `MRB_UTF8_STRING` and without, moves
in two of the four combinations, and in no function other than the two
above:
* `str_escape()` with the define: the flags word takes a constant
under a branch instead of a runtime value inserted into the
bitfield, and `.text` for `src/string.c` loses 144 bytes.
* `str_ascii_only_p()` without it: the insert goes, and `.text` for
the gem loses 32 bytes.
The other two combinations are identical, byte for byte, and nothing
grows.
|
Warning Review limit reached
Next review available in: 43 seconds You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
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 |
#7150 gave
MRB_STR_BINARYthe two accessors it was missing and listed what it deliberately left behind. The first item on that list:This PR answers that question, and picks up two more writes the grep behind that list could not see.
The three sites
The grep in #7150 was for
flags |= MRB_STR_SINGLE_BYTE, so it found the first and not the other two: those spell the value as a local holdingMRB_STR_SINGLE_BYTEor zero, which is the same write with the constant moved a few lines up.MRB_STR_SINGLE_BYTEis not short of accessors the wayMRB_STR_BINARYwas. It already has all four, so nothing is added toinclude/mruby/string.hhere; the three sites simply start using what is there.str_ascii_only_p(): what the behavior question comes toThe site sits outside the
#ifdef MRB_UTF8_STRINGblock that gives the flag its meaning, so the raw write also runs in a build without that define. What it writes there is a bit nothing on a string reads:RSTR_SINGLE_BYTE_P()isTRUEfor every string in that build, so no reader of the flag consults the bit.MRB_FL_OBJ_SHAPED, is only ever read or cleared through astruct RObject*(src/variable.c), never on a string.So the behavior question comes to this: the
MRB_UTF8_STRINGbuild keeps writing exactly what it wrote before, and the build without it stops making a write no one can observe. That is the whole of the difference.The accessor used is
RSTR_SET_ASCII_FLAG(), notRSTR_SET_SINGLE_BYTE_FLAG()directly. They are the same macro, but the first is the name this codebase uses at the moment it knows the bytes in hand are ASCII, including twice in this very file (int_chr_binary(),int_chr_utf8()) and insrc/object.c,src/symbol.candmruby-time.str_ascii_only_p()has just walked the bytes and proved precisely that. It also passes thestruct RString*it already holds ass, matching the two reads above it, rather than callingmrb_str_ptr(str)a second time.str_escape(): two locals that carry a bitThe two locals exist so that the walk can record what it saw and the write can happen once at the end. Carrying the flag bit itself is what let that write be an unconditional
|=; with the accessor, a plain truth value does the same job:Both stay under the
#ifdefthey are already in. Only a build that reads whole characters out ofstrhas an answer to record at all.Generated code
gcc -S -O3over both changed files, withMRB_UTF8_STRINGand without, so all four combinations:.textsrc/string.cMRB_UTF8_STRINGstr_escape()onlysrc/string.cmruby-string-extMRB_UTF8_STRINGmruby-string-extstr_ascii_only_p()onlyBoth moves shrink. In
str_escape()the flags word now takes a constant under a branch instead of having a runtime value inserted into the bitfield; instr_ascii_only_p()the insert disappears with the write. Every differing line falls inside those two functions, and no other function in either file changes.Testing
MRUBY_CONFIG=ci/gcc-clang rake -m test, all builds green, 0 KO, 0 crash, 0 warnings:MRB_UTF8_STRING: 2286 tests, 2276 OK, 10 skip.mruby-encodingis absent and strings index by byte: 2070 tests, 2042 OK, 28 skip.No test accompanies the change. Neither
String#ascii_only?norString#inspectgives a different answer anywhere, so there is nothing new to pin.Still left out
The other item on #7150's list,
RSTR_SET_EMBED_LEN()written out by hand inmrbgems/mruby-sprintf/src/sprintf.c, is untouched. It is a different flag family, and folding it intoRSTR_SET_LEN()next to a branch that sets the heap length is not a rename.After this change,
grep -rn "MRB_STR_SINGLE_BYTE" --include='*.c' src mrbgemsfinds one line, and it is a comment.