Bug #21644
closedStack consistency error for the `newrange` INSN peephole optimization with chilled string
Description
PR:https://github.com/ruby/ruby/pull/14879
Confirmed on every version since 3.4.
$ 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 this 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"
).
Also, I'm wondering why chilled strings are not ||-peephole-optimized?
ruby --dump insn -ve '"a" || "b"'
ruby 3.5.0dev (2025-10-19T14:22:25Z master 957c832db1) +PRISM [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 putchilledstring "a" ( 1)[Li]
0002 dup
0003 branchif 8
0005 pop
0006 putchilledstring "b"
0008 leave
ruby --enable-frozen-string-literal --dump insn -ve '"a" || "b"'
ruby 3.5.0dev (2025-10-19T14:22:25Z master 957c832db1) +PRISM [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 putobject "a" ( 1)[Li]
0002 leave
ruby --disable-frozen-string-literal --dump insn -ve '"a" || "b"'
ruby 3.5.0dev (2025-10-19T14:22:25Z master 957c832db1) +PRISM [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 putstring "a" ( 1)[Li]
0002 leave
Updated by nobu (Nobuyoshi Nakada) about 24 hours ago
- Backport changed from 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN to 3.2: DONTNEED, 3.3: DONTNEED, 3.4: REQUIRED
Updated by viralpraxis (Iaroslav Kurbatov) about 23 hours ago
- Status changed from Open to Closed
Applied in changeset git|7587e92910e7604a4c66f2b804bfa2076339c6ff.
[Bug #21644] compile.c: fix newrange
INSN peephole optimization for chilled string
ref: https://bugs.ruby-lang.org/issues/21644
$ 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.