diff options
| author | viralpraxis <viralpraxis@evilmartians.com> | 2025-10-19 22:55:45 +0400 |
|---|---|---|
| committer | Nobuyoshi Nakada <nobu.nakada@gmail.com> | 2025-10-20 14:17:36 +0900 |
| commit | 7587e92910e7604a4c66f2b804bfa2076339c6ff (patch) | |
| tree | 9a615bbc81c58afdd630d0e73e4182ee94d0f4b0 | |
| parent | 4353187d0812e55c5663299eca16b962000fccd9 (diff) | |
[Bug #21644] compile.c: fix `newrange` INSN peephole optimization for chilled string
ref: https://bugs.ruby-lang.org/issues/21644
```shell
$ ruby -v -e '("a" || "b").."c"'
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
-e:1: warning: possibly useless use of .. in void context
-e:1: [BUG] Stack consistency error (sp: 7, bp: 6)
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0002 p:0013 s:0007 e:000005 EVAL -e:1 [FINISH]
c:0001 p:0000 s:0003 E:001920 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
-e:1:in '<main>'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
ruby/3.4.7/lib/libruby.so.3.4(rb_print_backtrace+0x8) [0x78aa9573c882] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_dump.c:823
ruby/3.4.7/lib/libruby.so.3.4(rb_vm_bugreport) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_dump.c:1155
ruby/3.4.7/lib/libruby.so.3.4(rb_bug_without_die_internal+0x6b) [0x78aa9544c62f] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/error.c:1097
ruby/3.4.7/lib/libruby.so.3.4(rb_bug) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/error.c:1115
ruby/3.4.7/lib/libruby.so.3.4(vm_stack_consistency_error+0x1f) [0x78aa9544f091] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:6523
ruby/3.4.7/lib/libruby.so.3.4(vm_get_cref) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/insns.def:1134
ruby/3.4.7/lib/libruby.so.3.4(vm_setclassvariable) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:1630
ruby/3.4.7/lib/libruby.so.3.4(vm_setclassvariable) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:1627
ruby/3.4.7/lib/libruby.so.3.4(vm_exec_core) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/insns.def:253
ruby/3.4.7/lib/libruby.so.3.4(vm_exec_loop+0xa) [0x78aa95724959] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm.c:2622
ruby/3.4.7/lib/libruby.so.3.4(rb_vm_exec) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm.c:2598
ruby/3.4.7/lib/libruby.so.3.4(rb_ec_exec_node+0xa5) [0x78aa95525695] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/eval.c:281
ruby/3.4.7/lib/libruby.so.3.4(ruby_run_node+0x83) [0x78aa95529333] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/eval.c:319
ruby/3.4.7/bin/ruby(rb_main+0x21) [0x59d86f5e0186] ./main.c:43
ruby/3.4.7/bin/ruby(main) ./main.c:68
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_call_main+0x7a) [0x78aa9502a1ca] ../sysdeps/nptl/libc_start_call_main.h:58
/lib/x86_64-linux-gnu/libc.so.6(call_init+0x0) [0x78aa9502a28b] ../csu/libc-start.c:360
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main_impl) ../csu/libc-start.c:347
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main) (null):0
[0x59d86f5e01d5]
```
The optimization in question:
https://github.com/ruby/ruby/blob/957c832db137e67289e93dfd9fd9e915b1f2fc87/compile.c\#L3453-L3480
Before entering the `newrange` optimization, the iseq looks like this:
```
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,17)>
0000 putchilledstring "a" ( 1)[Li]
0002 dup
0003 branchif 8
0005 pop
0006 putchilledstring "b"
0008 putchilledstring "c"
0010 newrange 0
0012 leave
```
So the optimization constructs a new range using the wrong operands (`"b"` and `"c"` instead of `"a"` and `"c"`).
I tried to fix this by checking whether the two previous instructions are labeled.
| -rw-r--r-- | compile.c | 22 | ||||
| -rw-r--r-- | test/ruby/test_range.rb | 1 |
2 files changed, 22 insertions, 1 deletions
@@ -3223,6 +3223,25 @@ is_frozen_putstring(INSN *insn, VALUE *op) } static int +insn_has_label_before(LINK_ELEMENT *elem) +{ + LINK_ELEMENT *prev = elem->prev; + while (prev) { + if (prev->type == ISEQ_ELEMENT_LABEL) { + LABEL *label = (LABEL *)prev; + if (label->refcnt > 0) { + return 1; + } + } + else if (prev->type == ISEQ_ELEMENT_INSN) { + break; + } + prev = prev->prev; + } + return 0; +} + +static int optimize_checktype(rb_iseq_t *iseq, INSN *iobj) { /* @@ -3467,7 +3486,8 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal if ((end = (INSN *)get_prev_insn(range)) != 0 && is_frozen_putstring(end, &str_end) && (beg = (INSN *)get_prev_insn(end)) != 0 && - is_frozen_putstring(beg, &str_beg)) { + is_frozen_putstring(beg, &str_beg) && + !(insn_has_label_before(&beg->link) || insn_has_label_before(&end->link))) { int excl = FIX2INT(OPERAND_AT(range, 0)); VALUE lit_range = rb_range_new(str_beg, str_end, excl); diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index f875c0ab40..cdf6a0cea5 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -36,6 +36,7 @@ class TestRange < Test::Unit::TestCase assert_equal(["a"], ("a" ... "b").to_a) assert_equal(["a", "b"], ("a" .. "b").to_a) assert_equal([*"a".."z", "aa"], ("a"..).take(27)) + assert_equal([*"a".."z"], eval("('a' || 'b')..'z'").to_a) end def test_range_numeric_string |
