From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS198093 171.25.193.128/25 X-Spam-Status: No, score=-1.2 required=3.0 tests=AWL,BAYES_00,BODY_8BITS, RCVD_IN_XBL,URIBL_BLOCKED shortcircuit=no autolearn=no version=3.3.2 X-Original-To: spew@80x24.org Received: from 80x24.org (tor-exit3-readme.dfri.se [171.25.193.235]) by dcvr.yhbt.net (Postfix) with ESMTP id 19AAB1F89E for ; Fri, 10 Oct 2014 00:46:19 +0000 (UTC) From: Eric Wong To: spew@80x24.org Subject: [PATCH] opt_str_lit: one instruction, many optimizations Date: Fri, 10 Oct 2014 00:46:18 +0000 Message-Id: X-Mailer: git-send-email 2.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit List-Id: This is a squash of the following commits in the "opt_str_lit" branch of git://bogomips.org/ruby.git It optimizes away object allocation for string literals in the following cases: * "lit" % obj * str << "lit" * "lit" + str * str + "lit" * "lit" * num * "lit" === obj * obj === "lit" * "lit" == str * str == "lit" * "lit" != str * str != "lit" Full commit logs are in the git repository above, and also viewable with $BROWSER at: http://bogomips.org/ruby.git/log/?h=opt_str_lit --- benchmark/bm_vm2_hash_aref_lit.rb | 6 ++ benchmark/bm_vm2_hash_aset_lit.rb | 6 ++ benchmark/bm_vm2_strcat.rb | 7 ++ benchmark/bm_vm2_streq1.rb | 6 ++ benchmark/bm_vm2_streq2.rb | 6 ++ benchmark/bm_vm2_streqq1.rb | 6 ++ benchmark/bm_vm2_streqq2.rb | 6 ++ benchmark/bm_vm2_strfmt.rb | 5 + benchmark/bm_vm2_strplus1.rb | 6 ++ benchmark/bm_vm2_strplus2.rb | 6 ++ compile.c | 212 +++++++++++++++++++++++++++++++------- insns.def | 114 ++++++++++---------- test/-ext-/symbol/test_type.rb | 1 + test/objspace/test_objspace.rb | 1 + test/ruby/envutil.rb | 10 ++ test/ruby/test_hash.rb | 2 + test/ruby/test_iseq.rb | 1 + test/ruby/test_string.rb | 159 ++++++++++++++++++++++++++++ 18 files changed, 464 insertions(+), 96 deletions(-) diff --git a/benchmark/bm_vm2_hash_aref_lit.rb b/benchmark/bm_vm2_hash_aref_lit.rb new file mode 100644 index 0000000..a6d4d12 --- /dev/null +++ b/benchmark/bm_vm2_hash_aref_lit.rb @@ -0,0 +1,6 @@ +h = { "foo" => nil } +i = 0 +while i<6_000_000 # while loop 2 + i += 1 + h["foo"] +end diff --git a/benchmark/bm_vm2_hash_aset_lit.rb b/benchmark/bm_vm2_hash_aset_lit.rb new file mode 100644 index 0000000..58339ec --- /dev/null +++ b/benchmark/bm_vm2_hash_aset_lit.rb @@ -0,0 +1,6 @@ +h = {} +i = 0 +while i<6_000_000 # while loop 2 + i += 1 + h["foo"] = nil +end diff --git a/benchmark/bm_vm2_strcat.rb b/benchmark/bm_vm2_strcat.rb new file mode 100644 index 0000000..b25ac6e --- /dev/null +++ b/benchmark/bm_vm2_strcat.rb @@ -0,0 +1,7 @@ +i = 0 +str = "" +while i<6_000_000 # benchmark loop 2 + i += 1 + str << "const" + str.clear +end diff --git a/benchmark/bm_vm2_streq1.rb b/benchmark/bm_vm2_streq1.rb new file mode 100644 index 0000000..2a4b0f8 --- /dev/null +++ b/benchmark/bm_vm2_streq1.rb @@ -0,0 +1,6 @@ +i = 0 +foo = "literal" +while i<6_000_000 # benchmark loop 2 + i += 1 + foo == "literal" +end diff --git a/benchmark/bm_vm2_streq2.rb b/benchmark/bm_vm2_streq2.rb new file mode 100644 index 0000000..986020d --- /dev/null +++ b/benchmark/bm_vm2_streq2.rb @@ -0,0 +1,6 @@ +i = 0 +foo = "literal" +while i<6_000_000 # benchmark loop 2 + i += 1 + "literal" == foo +end diff --git a/benchmark/bm_vm2_streqq1.rb b/benchmark/bm_vm2_streqq1.rb new file mode 100644 index 0000000..9183466 --- /dev/null +++ b/benchmark/bm_vm2_streqq1.rb @@ -0,0 +1,6 @@ +i = 0 +foo = "literal" +while i<6_000_000 # benchmark loop 2 + i += 1 + foo === "literal" +end diff --git a/benchmark/bm_vm2_streqq2.rb b/benchmark/bm_vm2_streqq2.rb new file mode 100644 index 0000000..f48a9cd --- /dev/null +++ b/benchmark/bm_vm2_streqq2.rb @@ -0,0 +1,6 @@ +i = 0 +foo = "literal" +while i<6_000_000 # benchmark loop 2 + i += 1 + "literal" === foo +end diff --git a/benchmark/bm_vm2_strfmt.rb b/benchmark/bm_vm2_strfmt.rb new file mode 100644 index 0000000..efb88b6 --- /dev/null +++ b/benchmark/bm_vm2_strfmt.rb @@ -0,0 +1,5 @@ +i = 0 +while i<6_000_000 # benchmark loop 2 + i += 1 + "%d" % i +end diff --git a/benchmark/bm_vm2_strplus1.rb b/benchmark/bm_vm2_strplus1.rb new file mode 100644 index 0000000..714efb8 --- /dev/null +++ b/benchmark/bm_vm2_strplus1.rb @@ -0,0 +1,6 @@ +i = 0 +foo = "a" +while i<6_000_000 # benchmark loop 2 + i += 1 + foo + "b" +end diff --git a/benchmark/bm_vm2_strplus2.rb b/benchmark/bm_vm2_strplus2.rb new file mode 100644 index 0000000..c7f91ed --- /dev/null +++ b/benchmark/bm_vm2_strplus2.rb @@ -0,0 +1,6 @@ +i = 0 +foo = "a" +while i<6_000_000 # benchmark loop 2 + i += 1 + "b" + foo +end diff --git a/compile.c b/compile.c index dda22b0..54c7b9e 100644 --- a/compile.c +++ b/compile.c @@ -1703,6 +1703,45 @@ get_prev_insn(INSN *iobj) return 0; } +static VALUE +new_recvinfo_for_put(rb_iseq_t *iseq, VALUE str, + enum ruby_basic_operators bop, int redef_flag) +{ + VALUE ri = rb_ary_new_from_args(3, str, INT2FIX(bop), INT2FIX(redef_flag)); + + hide_obj(ri); + iseq_add_mark_object(iseq, ri); + + return ri; +} + +static VALUE +new_recvinfo_for_call(rb_iseq_t *iseq, VALUE str, + enum ruby_basic_operators bop, int redef_flag, ID mid) +{ + VALUE ri = rb_ary_new_from_args(4, str, INT2FIX(bop), INT2FIX(redef_flag), + ID2SYM(mid)); + + hide_obj(ri); + iseq_add_mark_object(iseq, ri); + + return ri; +} + +static VALUE +new_recvinfo_for_arg(rb_iseq_t *iseq, VALUE str, + enum ruby_basic_operators bop, int redef_flag, + VALUE klass, int recv_off) +{ + VALUE ri = rb_ary_new_from_args(5, str, INT2FIX(bop), INT2FIX(redef_flag), + klass, INT2FIX(recv_off)); + + hide_obj(ri); + iseq_add_mark_object(iseq, ri); + + return ri; +} + static int iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt) { @@ -1819,6 +1858,84 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal } } } + + /* string literal optimizations */ + if (iobj->insn_id == BIN(putstring)) { + INSN *niobj = (INSN *)get_next_insn((INSN *)list); + + if (niobj && niobj->insn_id == BIN(send)) { + rb_call_info_t *ci = (rb_call_info_t *)niobj->operands[0]; + + if (!ci->blockiseq && !(ci->flag & ~VM_CALL_ARGS_SKIP_SETUP)) { + VALUE ri = Qfalse; + VALUE str = iobj->operands[0]; + + switch (ci->orig_argc) { + case 0: + /* + * optimize: + * "literal".freeze + * "literal".size + * "literal".length + */ + switch (ci->mid) { + case idFreeze: + ri = new_recvinfo_for_call(iseq, str, BOP_FREEZE, + STRING_REDEFINED_OP_FLAG, ci->mid); + REMOVE_ELEM((LINK_ELEMENT *)niobj); + break; + case idSize: + ri = new_recvinfo_for_put(iseq, str, BOP_SIZE, + STRING_REDEFINED_OP_FLAG); + break; + case idLength: + ri = new_recvinfo_for_put(iseq, str, BOP_LENGTH, + STRING_REDEFINED_OP_FLAG); + break; + } + break; + case 1: + switch (ci->mid) { + case idAREF: + /* optimize allocation: obj["lit"] */ + ri = new_recvinfo_for_arg(iseq, str, BOP_AREF, + HASH_REDEFINED_OP_FLAG, rb_cHash, 0); + break; + case idEq: + /* optimize allocation: obj == "lit" */ + ri = new_recvinfo_for_arg(iseq, str, BOP_EQ, + STRING_REDEFINED_OP_FLAG, rb_cString, 0); + break; + case idNeq: + /* optimize allocation: obj != "lit" */ + ri = new_recvinfo_for_arg(iseq, str, BOP_NEQ, + STRING_REDEFINED_OP_FLAG, rb_cString, 0); + break; + case idLTLT: + /* optimize allocation: obj << "lit" */ + ri = new_recvinfo_for_arg(iseq, str, BOP_LTLT, + STRING_REDEFINED_OP_FLAG, rb_cString, 0); + break; + case idPLUS: + /* optimize allocation: obj + "lit" */ + ri = new_recvinfo_for_arg(iseq, str, BOP_PLUS, + STRING_REDEFINED_OP_FLAG, rb_cString, 0); + break; + case idEqq: + /* optimize allocation: obj === "lit" */ + ri = new_recvinfo_for_arg(iseq, str, BOP_EQQ, + STRING_REDEFINED_OP_FLAG, rb_cString, 0); + break; + } + } + if (ri != Qfalse) { + iobj->insn_id = BIN(opt_str_lit); + iobj->operands[0] = ri; + } + } + } + } + return COMPILE_OK; } @@ -3096,6 +3213,20 @@ build_postexe_iseq(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *body) return Qnil; } +static enum ruby_basic_operators +opt_str_lit_recv_bop(ID mid) +{ + switch (mid) { + case idEq: return BOP_EQ; + case idNeq: return BOP_NEQ; + case idPLUS: return BOP_PLUS; + case idMULT: return BOP_MULT; + case idMOD: return BOP_MOD; + case idEqq: return BOP_EQQ; + } + return BOP_LAST_; +} + /** compile each node @@ -4238,37 +4369,6 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) break; } case NODE_CALL: - /* optimization shortcut - * "literal".freeze -> opt_str_freeze("literal") - */ - if (node->nd_recv && nd_type(node->nd_recv) == NODE_STR && - node->nd_mid == idFreeze && node->nd_args == NULL) - { - VALUE str = rb_fstring(node->nd_recv->nd_lit); - iseq_add_mark_object(iseq, str); - ADD_INSN1(ret, line, opt_str_freeze, str); - if (poped) { - ADD_INSN(ret, line, pop); - } - break; - } - /* optimization shortcut - * obj["literal"] -> opt_aref_with(obj, "literal") - */ - if (node->nd_mid == idAREF && !private_recv_p(node) && node->nd_args && - nd_type(node->nd_args) == NODE_ARRAY && node->nd_args->nd_alen == 1 && - nd_type(node->nd_args->nd_head) == NODE_STR) - { - VALUE str = rb_fstring(node->nd_args->nd_head->nd_lit); - node->nd_args->nd_head->nd_lit = str; - COMPILE(ret, "recv", node->nd_recv); - ADD_INSN2(ret, line, opt_aref_with, - new_callinfo(iseq, idAREF, 1, 0, 0), str); - if (poped) { - ADD_INSN(ret, line, pop); - } - break; - } case NODE_FCALL: case NODE_VCALL:{ /* VCALL: variable or call */ /* @@ -4352,7 +4452,31 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) #endif /* receiver */ if (type == NODE_CALL) { - COMPILE(recv, "recv", node->nd_recv); + enum ruby_basic_operators bop; + /* + * optimize: + * "yoda" == other -> opt_str_lit("yoda").send(:==, other) + * "yoda" != other -> opt_str_lit("yoda").send(:!=, other) + * "str" + other -> opt_str_lit("str").send(:+, other) + * "str" * other -> opt_str_lit("str").send(:*, other) + * "fmt" % args -> opt_str_lit("str").send(:%, other) + */ + if (iseq->compile_data->option->peephole_optimization && + ((bop = opt_str_lit_recv_bop(mid)) != BOP_LAST_) && + !private_recv_p(node) && + node->nd_recv && nd_type(node->nd_recv) == NODE_STR && + node->nd_args && nd_type(node->nd_args) == NODE_ARRAY && + node->nd_args->nd_alen == 1) + { + VALUE yoda = rb_fstring(node->nd_recv->nd_lit); + VALUE recv_info = new_recvinfo_for_put(iseq, yoda, + bop, STRING_REDEFINED_OP_FLAG); + + node->nd_recv->nd_lit = yoda; + ADD_INSN1(recv, line, opt_str_lit, recv_info); + } else { + COMPILE(recv, "recv", node->nd_recv); + } } else if (type == NODE_FCALL || type == NODE_VCALL) { ADD_CALL_RECEIVER(recv, line); @@ -5241,23 +5365,33 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) int asgnflag; /* optimization shortcut - * obj["literal"] = value -> opt_aset_with(obj, "literal", value) + * obj["literal"] = val -> send(obj, :[]=, opt_str_lit("lit"), val) + * TODO: ideally this should be done inside iseq_peephole_optimize, + * but that would require a lot of scanning as the `val' (2nd arg) + * is of variable distance between the :putstring and :send insns */ - if (node->nd_mid == idASET && !private_recv_p(node) && node->nd_args && + if (iseq->compile_data->option->peephole_optimization && + node->nd_mid == idASET && !private_recv_p(node) && node->nd_args && nd_type(node->nd_args) == NODE_ARRAY && node->nd_args->nd_alen == 2 && nd_type(node->nd_args->nd_head) == NODE_STR) { VALUE str = rb_fstring(node->nd_args->nd_head->nd_lit); + VALUE recv_info = new_recvinfo_for_arg(iseq, str, + BOP_ASET, HASH_REDEFINED_OP_FLAG, + rb_cHash, 0); + node->nd_args->nd_head->nd_lit = str; - iseq_add_mark_object(iseq, str); + if (!poped) { + ADD_INSN(ret, line, putnil); + } COMPILE(ret, "recv", node->nd_recv); + ADD_INSN1(ret, line, opt_str_lit, recv_info); COMPILE(ret, "value", node->nd_args->nd_next->nd_head); if (!poped) { - ADD_INSN(ret, line, swap); - ADD_INSN1(ret, line, topn, INT2FIX(1)); + ADD_INSN1(ret, line, setn, INT2FIX(3)); } - ADD_INSN2(ret, line, opt_aset_with, - new_callinfo(iseq, idASET, 2, 0, 0), str); + flag = VM_CALL_ARGS_SKIP_SETUP; + ADD_SEND_R(ret, line, node->nd_mid, 2, 0, INT2FIX(flag)); ADD_INSN(ret, line, pop); break; } diff --git a/insns.def b/insns.def index bfa11a9..f6740f8 100644 --- a/insns.def +++ b/insns.def @@ -356,6 +356,63 @@ putstring /** @c put + @e put string val. string may be created depending on recv_info conditions + */ +DEFINE_INSN +opt_str_lit +(VALUE recv_info) +() +(VALUE val) +{ + /* + * recv_info: + * 0 - str + * 1 - basic operator flag (BOP_*) + * 2 - redefined flag (*_REDEFINED_OP_FLAG) + * optional: + * 3 - Class (optimized receiver class) or Symbol (method name) + * 4 - stack offset (Fixint), only present if [3] is a Class, + * -1 stack offset means receiver is the frozen string literal itself + */ + const VALUE *ri = RARRAY_CONST_PTR(recv_info); + long len = RARRAY_LEN(recv_info); + enum ruby_basic_operators bop = FIX2INT(ri[1]); + int redef_flag = FIX2INT(ri[2]); + + val = ri[0]; /* hopefully, this is the only val assignment we need */ + if (len > 3) { + VALUE msym_or_class = ri[3]; + + /* check if the receiver is an on-stack object: */ + if (!SYMBOL_P(msym_or_class)) { + int n = FIX2INT(ri[4]); + VALUE recv = n < 0 ? val : TOPN(n); + + if (SPECIAL_CONST_P(recv) || + RBASIC_CLASS(recv) != msym_or_class || + !BASIC_OP_UNREDEFINED_P(bop, redef_flag)) { + /* bad, somebody redefined an optimized method, slow path: */ + val = rb_str_resurrect(val); + } + } + else { /* receiver is the string literal itself (e.g. "str".freeze) */ + if (!BASIC_OP_UNREDEFINED_P(bop, redef_flag)) { + /* bad, somebody redefined an optimized method, slow path: */ + val = rb_str_resurrect(val); + val = rb_funcall(val, SYM2ID(msym_or_class), 0); + } + } + } + else { /* string lit is receiver, but there are args */ + if (!BASIC_OP_UNREDEFINED_P(bop, redef_flag)) { + /* bad, somebody redefined an optimized method, slow path: */ + val = rb_str_resurrect(val); + } + } +} + +/** + @c put @e put concatenate strings @j スタックトップの文字列を n 個連結し,結果をスタックにプッシュする。 */ @@ -999,20 +1056,6 @@ send CALL_METHOD(ci); } -DEFINE_INSN -opt_str_freeze -(VALUE str) -() -(VALUE val) -{ - if (BASIC_OP_UNREDEFINED_P(BOP_FREEZE, STRING_REDEFINED_OP_FLAG)) { - val = str; - } - else { - val = rb_funcall(rb_str_resurrect(str), idFreeze, 0); - } -} - /** @c optimize @e Invoke method without block, splat @@ -1903,49 +1946,6 @@ opt_aset /** @c optimize - @e recv[str] = set - @j 最適化された recv[str] = set。 - */ -DEFINE_INSN -opt_aset_with -(CALL_INFO ci, VALUE key) -(VALUE recv, VALUE val) -(VALUE val) -{ - if (!SPECIAL_CONST_P(recv) && RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_ASET, HASH_REDEFINED_OP_FLAG)) { - rb_hash_aset(recv, key, val); - } - else { - PUSH(recv); - PUSH(rb_str_resurrect(key)); - PUSH(val); - CALL_SIMPLE_METHOD(recv); - } -} - -/** - @c optimize - @e recv[str] - @j 最適化された recv[str]。 - */ -DEFINE_INSN -opt_aref_with -(CALL_INFO ci, VALUE key) -(VALUE recv) -(VALUE val) -{ - if (!SPECIAL_CONST_P(recv) && RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_AREF, HASH_REDEFINED_OP_FLAG)) { - val = rb_hash_aref(recv, key); - } - else { - PUSH(recv); - PUSH(rb_str_resurrect(key)); - CALL_SIMPLE_METHOD(recv); - } -} - -/** - @c optimize @e optimized length @j 最適化された recv.length()。 */ diff --git a/test/-ext-/symbol/test_type.rb b/test/-ext-/symbol/test_type.rb index f1749f5..5bd79b8 100644 --- a/test/-ext-/symbol/test_type.rb +++ b/test/-ext-/symbol/test_type.rb @@ -4,6 +4,7 @@ require "-test-/symbol" module Test_Symbol class TestType < Test::Unit::TestCase def test_id2str_fstring_bug9171 + require_compile_option(:peephole_optimization) fstr = eval("# encoding: us-ascii 'foobar'.freeze") assert_same fstr, Bug::Symbol.id2str(:foobar) diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 8a5ed34..faacf48 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -195,6 +195,7 @@ class TestObjSpace < Test::Unit::TestCase end def test_dump_flags + require_compile_option(:peephole_optimization) info = ObjectSpace.dump("foo".freeze) assert_match /"wb_protected":true, "old":true, "long_lived":true, "marked":true/, info assert_match /"fstring":true/, info diff --git a/test/ruby/envutil.rb b/test/ruby/envutil.rb index 81b982c..e844822 100644 --- a/test/ruby/envutil.rb +++ b/test/ruby/envutil.rb @@ -477,6 +477,16 @@ eom AssertFile end + def require_compile_option(opt) + case RubyVM::InstructionSequence.compile_option[opt] + when true + when false + skip(":#{opt} disabled") + else + raise ArgumentError, "unrecognized compile option: #{opt.inspect}" + end + end + class << (AssertFile = Struct.new(:failure_message).new) include Assertions def assert_file_predicate(predicate, *args) diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 4431552..bb7e8b5 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -216,6 +216,7 @@ class TestHash < Test::Unit::TestCase end def test_AREF_fstring_key + require_compile_option(:peephole_optimization) h = {"abc" => 1} before = GC.stat(:total_allocated_objects) 5.times{ h["abc"] } @@ -230,6 +231,7 @@ class TestHash < Test::Unit::TestCase end def test_NEWHASH_fstring_key + require_compile_option(:peephole_optimization) a = {"ABC" => :t} b = {"ABC" => :t} assert_same a.keys[0], b.keys[0] diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 94a814c..ac1c417 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -118,6 +118,7 @@ class TestISeq < Test::Unit::TestCase end def test_label_fstring + require_compile_option(:peephole_optimization) c = Class.new{ def foobar() end } a, b = eval("# encoding: us-ascii\n'foobar'.freeze"), diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index d82d2bc..d3357b0 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1908,6 +1908,13 @@ class TestString < Test::Unit::TestCase } end + def test_literal_freeze + require_compile_option(:peephole_optimization) + before = GC.stat(:total_allocated_objects) + 5.times { "".freeze } + assert_equal before, GC.stat(:total_allocated_objects) + end + class S2 < String end def test_str_new4 @@ -2272,6 +2279,33 @@ class TestString < Test::Unit::TestCase end; end if [0].pack("l!").bytesize < [nil].pack("p").bytesize # enable only when string size range is smaller than memory space + + def test_opt_strcat_with + assert_separately([], <<-RUBY) + class String + undef << + def <<(str) + "overridden" + end + end + assert_equal("overridden", "" << "foo") + foo = "foo" + assert_equal("overridden", foo << "bar") + RUBY + + if @cls == String + nr = 10 + recv = "" + before = GC.stat(:total_allocated_objects) + nr.times { recv << "constant" } + assert_equal before, GC.stat(:total_allocated_objects) + assert_equal "constant" * nr, recv + + before = GC.stat(:total_allocated_objects) + nr.times { "recv" << "constant" } + assert_equal before + nr, GC.stat(:total_allocated_objects) + end + end end class TestString2 < TestString @@ -2279,4 +2313,129 @@ class TestString2 < TestString super @cls = S2 end + + def test_opt_str_lit + assert_separately([], <<-RUBY) + class String + undef == + def ==(str) + :TROO + end + end + foo = "foo" + assert_equal(:TROO, (foo == "foo"), 'string == "peephole 2nd pass"') + assert_equal(:TROO, ("foo" == foo), '"yoda 1st pass" == string') + RUBY + + assert_separately([], <<-RUBY) + class String + undef != + def !=(str) + :NOT + end + end + foo = "" + assert_equal(:NOT, ("foo" != foo), '"yoda 1st pass" != string') + assert_equal(:NOT, (foo != "foo"), 'string != "peephole 2nd pass"') + RUBY + + assert_separately([], <<-RUBY) + class String + undef size + undef length + def size + 42 + end + def length + 42 + end + end + assert_equal(42, "".size, 'lit string size') + assert_equal(42, "".length, 'lit string size') + RUBY + + assert_separately([], <<-RUBY) + class String + undef + + def +(other) + :plus + end + end + foo = "a" + assert_equal(:plus, "" + foo, 'lit plus') + assert_equal(:plus, foo + "", 'plus lit') + RUBY + + assert_separately([], <<-RUBY) + class String + undef * + def *(other) + :mult + end + end + assert_equal(:mult, "x" * 3, 'lit mult') + RUBY + + assert_separately([], <<-RUBY) + class String + undef === + def ===(other) + other + end + end + str = "y" + assert_equal(false, "x" === false, 'lit threequal') + assert_equal("x", str === "x", 'threequal lit') + RUBY + + if @cls == String + nr = 10 + + recv = "something" + res = [] + before = GC.stat(:total_allocated_objects) + nr.times { res << (recv == "constant") } # opt_streq1 + nr.times { res << ("constant" == recv) } # opt_streq2 + nr.times { res << ("something " != recv) } # 1st pass peephole + nr.times { res << ("constant" == recv) } # opt_streq2 + nr.times { res << ("constant" === recv) } # opt_streqq2 + nr.times { res << (recv != "something") } # 2nd pass peephole + assert_equal before, GC.stat(:total_allocated_objects) + assert_equal [ false ], res.uniq! + + res.clear + before = GC.stat(:total_allocated_objects) + nr.times { res << (recv == "something") } # opt_streq1 + nr.times { res << ("something" == recv) } # opt_streq2 + nr.times { res << ("something" === recv) } # opt_streqq2 + nr.times { res << (recv === "something") } # opt_streqq2 + nr.times { res << ("constant" != recv) } # 1st pass peephole + nr.times { res << (recv != "constant") } # 2nd pass peephole + nr.times { res << ("a" != "b") } # 1st pass peephole + nr.times { res << ("a" == "a") } # 1st pass peephole + nr.times { res << ("".size == 0) } # 2nd pass peephole + nr.times { res << ("".length == 0) } # 2nd pass peephole + assert_equal before, GC.stat(:total_allocated_objects) + assert_equal [ true ], res.uniq! + + # :+ optimizations + res.clear + before = GC.stat(:total_allocated_objects) + nr.times { res << ("foo" + recv) } + assert_equal before + nr, GC.stat(:total_allocated_objects) + assert_equal [ "foosomething" ], res.uniq! + + res.clear + before = GC.stat(:total_allocated_objects) + nr.times { res << (recv + "foo") } + assert_equal before + nr, GC.stat(:total_allocated_objects) + assert_equal [ "somethingfoo" ], res.uniq! + + res.clear + before = GC.stat(:total_allocated_objects) + nr.times { res << ('a' * 3) } + assert_equal before + nr, GC.stat(:total_allocated_objects) + assert_equal [ "aaa" ], res.uniq! + end + end end -- EW