From b8f9e85b4b97d9ac27c44ffee29b22f10287f519 Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Thu, 23 Mar 2017 08:20:34 +0000
Subject: [PATCH] compile.c: optimize literal String range in case/when
 dispatch

This is similar in spirit to opt_case_dispatch as the literal
Range here is guaranteed to be immutable when used for
checkmatch.

Normal range literals with non-frozen strings are actually
mutable, as Range#begin and Range#end exposes the strings to
modification.  So those Range objects cannot be frozen without
breaking compatibility, but Ranges in case/when dispatch can be
frozen at compile time.

* compile.c (iseq_peephole_optimize): persistent Range creation
  when String literals are used as beginning and end of range
  when used for case/when dispatch.
---
 compile.c                      | 31 +++++++++++++++++++++++++++++++
 test/ruby/test_optimization.rb | 15 +++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/compile.c b/compile.c
index e3d66b6809..496e4d8857 100644
--- a/compile.c
+++ b/compile.c
@@ -2144,6 +2144,37 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
 	}
     }
 
+    /*
+     * putstring "beg"
+     * putstring "end"
+     * newrange excl
+     *
+     * ==>
+     *
+     * putobject "beg".."end"
+     */
+    if (IS_INSN_ID(iobj, checkmatch)) {
+	INSN *range = (INSN *)get_prev_insn(iobj);
+	INSN *beg, *end;
+
+	if (range && IS_INSN_ID(range, newrange) &&
+		(end = (INSN *)get_prev_insn(range)) != 0 &&
+		IS_INSN_ID(end, putstring) &&
+		(beg = (INSN *)get_prev_insn(end)) != 0 &&
+		IS_INSN_ID(beg, putstring)) {
+	    VALUE sbeg = OPERAND_AT(beg, 0);
+	    VALUE send = OPERAND_AT(end, 0);
+	    int excl = FIX2INT(OPERAND_AT(range, 0));
+	    VALUE lit_range = rb_range_new(sbeg, send, excl);
+
+	    iseq_add_mark_object_compile_time(iseq, lit_range);
+	    REMOVE_ELEM(&beg->link);
+	    REMOVE_ELEM(&end->link);
+	    range->insn_id = BIN(putobject);
+	    OPERAND_AT(range, 0) = lit_range;
+	}
+    }
+
     if (IS_INSN_ID(iobj, leave)) {
 	remove_unreachable_chunk(iseq, iobj->link.next);
     }
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index 502d12389e..aa3c82fdfd 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -490,4 +490,19 @@ def test_nil_safe_conditional_assign
     bug11816 = '[ruby-core:74993] [Bug #11816]'
     assert_ruby_status([], 'nil&.foo &&= false', bug11816)
   end
+
+  def test_peephole_string_literal_range
+    code = <<-EOF
+      case ver
+      when "2.0.0".."2.3.2" then :foo
+      when "1.8.0"..."1.8.8" then :bar
+      end
+    EOF
+    iseq = RubyVM::InstructionSequence.compile(code)
+    insn = iseq.disasm
+    assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn
+    assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn
+    assert_no_match /putstring/, insn
+    assert_no_match /newrange/, insn
+  end
 end
-- 
EW

