From e5a1ac645099eccb3357408f04a12574719cc94a Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Sun, 2 Nov 2014 20:13:41 +0000
Subject: [PATCH v5] opt_str_lit*: avoid string literal allocations

Changes since -v4:

* rebase ontop of r48239 ("Optimize keyword and splat argument")
  [Feature #10440]

* fix 32-bit compile [ruby-core:65902]
  and test for Time#strftime

* .gitignore: add /opt_method.h

Changes since -v3

* cleanup tests to be more DRY

* optimize allocations for Hash#fetch, String#r?partition

* split opt_str_lit into 3 instructions to reduce insn complexity
  (opt_str_freeze, opt_aset_with, opt_aref_with) are still gone.

  While having one instruction is appealing in some ways,
  hiding compile-time-resolvable branches behind it is
  misleading and it should be easier-to-follow when divided
  into three methods.

  1. opt_str_lit_recv: for "literal string" receivers
     This no longer optimizes the method dispatch away
     for String#freeze, the method call now always happens
     (but allocation is avoided)

  2. opt_str_lit_tmask: the most heavily-used
     This optimizes allocations away for "literal string" arguments

  3. opt_str_lit_data: currently only used for Time#strftime
     We may remove this (and the strftime optimization)
     if it is too rare to be useful.

Benchmarks:

  "make rdoc" performance improved from 66.3 => 63.3 sec

vm2_* benchmarks:

We take a speed hit from losing opt_aref_with/opt_aset_with
but I think the flexibility of the new instructions is worth it.

Speedup ratio: compare with the result of `trunk' (greater is better)

name	built
loop_whileloop2	1.000
vm2_array*	0.997
vm2_array_delete_lit*	2.204
vm2_array_include_lit*	2.372
vm2_bigarray*	1.002
vm2_bighash*	1.004
vm2_case*	1.030
vm2_defined_method*	1.013
vm2_dstr*	0.977
vm2_eval*	1.019
vm2_hash_aref_lit*	0.767
vm2_hash_aset_lit*	0.828
vm2_hash_delete_lit*	2.254
vm2_method*	1.010
vm2_method_missing*	1.010
vm2_method_with_block*	0.996
vm2_mutex*	0.954
vm2_newlambda*	1.010
vm2_poly_method*	0.974
vm2_poly_method_ov*	0.990
vm2_proc*	1.003
vm2_raise1*	0.992
vm2_raise2*	0.993
vm2_regexp*	1.053
vm2_send*	1.000
vm2_set_include_lit*	1.051
vm2_str_delete*	1.365
vm2_str_eq1*	2.282
vm2_str_eq2*	2.700
vm2_str_eqq1*	2.062
vm2_str_eqq2*	2.421
vm2_str_fmt*	1.271
vm2_str_gsub_bang_lit*	1.650
vm2_str_gsub_bang_re*	1.153
vm2_str_gsub_re*	1.110
vm2_str_plus1*	1.496
vm2_str_plus2*	1.618
vm2_str_tr_bang*	1.527
vm2_strcat*	1.406
vm2_super*	1.005
vm2_unif1*	0.985
vm2_zsuper*	0.992

-----------------------------------------------------------
raw data:

[["loop_whileloop2",
  [[0.09168246667832136,
    0.09153273981064558,
    0.09135112632066011,
    0.09144351910799742,
    0.0913569862022996],
   [0.09150420501828194,
    0.09135987050831318,
    0.09148290101438761,
    0.09135456290096045,
    0.09141282178461552]]],
 ["vm2_array",
  [[0.6448190519586205,
    0.6377620408311486,
    0.6456073289737105,
    0.636782786808908,
    0.64611473120749],
   [0.6434593833982944,
    0.6479324344545603,
    0.6385641889646649,
    0.6491997549310327,
    0.6610294701531529]]],
 ["vm2_array_delete_lit",
  [[0.44069126434624195,
    0.43327025789767504,
    0.44154794327914715,
    0.43351241294294596,
    0.4343419009819627],
   [0.24649471696466208,
    0.24813515041023493,
    0.2801106022670865,
    0.24796569626778364,
    0.25444996636360884]]],
 ["vm2_array_include_lit",
  [[0.42663843277841806,
    0.42771619465202093,
    0.4342082254588604,
    0.4192065382376313,
    0.4339462127536535],
   [0.24434014037251472,
    0.23243559524416924,
    0.23569373413920403,
    0.2295856410637498,
    0.23521045502275229]]],
 ["vm2_bigarray",
  [[5.9676302736625075,
    5.921381871215999,
    5.893002421595156,
    5.898605763912201,
    5.92879247572273],
   [5.918988090008497,
    5.953728860244155,
    5.910121874883771,
    5.894348439760506,
    5.882020344957709]]],
 ["vm2_bighash",
  [[3.5214368999004364,
    3.5914944410324097,
    3.5528544737026095,
    3.6002828497439623,
    3.595806434750557],
   [3.6454197568818927,
    3.5987599324434996,
    3.631806828081608,
    3.5076274275779724,
    3.5060981484130025]]],
 ["vm2_case",
  [[0.16305183991789818,
    0.16213963273912668,
    0.1626761993393302,
    0.1703133536502719,
    0.16371900774538517],
   [0.1630518063902855,
    0.16097174491733313,
    0.16129930969327688,
    0.16106242593377829,
    0.16007240489125252]]],
 ["vm2_defined_method",
  [[2.4309032559394836,
    2.4551019677892327,
    2.4333217879757285,
    2.4307468980550766,
    2.4270543046295643],
   [2.3963797464966774,
    2.4110122229903936,
    2.592901307158172,
    2.7106671230867505,
    2.6869526272639632]]],
 ["vm2_dstr",
  [[1.206045768223703,
    1.1181026576086879,
    1.29782950039953,
    1.074333599768579,
    1.060404953546822],
   [1.0911998134106398,
    1.3560991054400802,
    1.096764899790287,
    1.0906088771298528,
    1.0831655990332365]]],
 ["vm2_eval",
  [[12.45692038256675,
    12.536506623961031,
    12.868694677948952,
    12.425183075480163,
    12.422813648357987],
   [12.490455374121666,
    12.190652490593493,
    12.194032781757414,
    12.581901006400585,
    13.230092607438564]]],
 ["vm2_hash_aref_lit",
  [[0.2547442754730582,
    0.25785687286406755,
    0.2552897445857525,
    0.2547551356256008,
    0.2546374099329114],
   [0.3131725639104843,
    0.3109410647302866,
    0.30428329203277826,
    0.30984809901565313,
    0.30932857654988766]]],
 ["vm2_hash_aset_lit",
  [[0.3067243071272969,
    0.307466727681458,
    0.3059097733348608,
    0.32101333420723677,
    0.3114894386380911],
   [0.3522613048553467,
    0.3512485157698393,
    0.3521018158644438,
    0.35033643897622824,
    0.35163887683302164]]],
 ["vm2_hash_delete_lit",
  [[0.4271302009001374,
    0.5321928421035409,
    0.42596039827913046,
    0.4215333154425025,
    0.4221466425806284],
   [0.2427450241521001,
    0.23863658774644136,
    0.23870530910789967,
    0.23786015156656504,
    0.24112990126013756]]],
 ["vm2_method",
  [[1.2295677298679948,
    1.2423510188236833,
    1.2246184339746833,
    1.213058383204043,
    1.3788639837875962],
   [1.2468778286129236,
    1.2022472834214568,
    1.319407593458891,
    1.3409598469734192,
    1.246872222982347]]],
 ["vm2_method_missing",
  [[1.8783203130587935,
    2.0230442117899656,
    1.7176201958209276,
    1.790073310956359,
    1.7432779222726822],
   [1.710430753417313,
    1.8266426706686616,
    1.7299889298155904,
    1.7529844669625163,
    1.7007915144786239]]],
 ["vm2_method_with_block",
  [[1.3849838990718126,
    1.345238379202783,
    1.3416069420054555,
    1.3473590090870857,
    1.3418097402900457],
   [1.3468122174963355,
    2.0074896160513163,
    1.3560442496091127,
    1.353834005072713,
    1.3525155214592814]]],
 ["vm2_mutex",
  [[0.6452304208651185,
    0.7461022492498159,
    0.7083938084542751,
    0.7519227871671319,
    0.7953951777890325],
   [0.6722144978120923,
    0.6991892093792558,
    0.699041954241693,
    0.8469044668599963,
    0.7956293905153871]]],
 ["vm2_newlambda",
  [[0.7562470361590385,
    0.7670294912531972,
    0.7572819162160158,
    0.8686427380889654,
    0.772026221267879],
   [0.7586023258045316,
    0.7559101320803165,
    0.7497995179146528,
    0.7534453617408872,
    0.7510695895180106]]],
 ["vm2_poly_method",
  [[2.1563803972676396,
    1.9838355090469122,
    2.0223182002082467,
    1.9927900172770023,
    2.055130822584033],
   [2.03470276389271,
    2.187690651975572,
    2.162980039604008,
    2.143593772314489,
    2.04829403758049]]],
 ["vm2_poly_method_ov",
  [[0.22944753244519234,
    0.2304667690768838,
    0.22847569175064564,
    0.22843772917985916,
    0.22901444043964148],
   [0.2298243623226881,
    0.2334523554891348,
    0.23789969086647034,
    0.23442872054874897,
    0.23049725405871868]]],
 ["vm2_proc",
  [[0.46109406277537346,
    0.4476210633292794,
    0.44980251509696245,
    0.4451689962297678,
    0.4452860997989774],
   [0.4545932151377201,
    0.4440324120223522,
    0.44979235529899597,
    0.4695265833288431,
    0.4635683596134186]]],
 ["vm2_raise1",
  [[4.989030849188566,
    4.951415436342359,
    4.755166156217456,
    4.824490828439593,
    4.795467809773982],
   [4.797340838238597,
    4.799810292199254,
    4.792353850789368,
    4.966345896013081,
    5.099304006434977]]],
 ["vm2_raise2",
  [[7.062344457022846,
    7.118377918377519,
    7.290325260721147,
    7.1321266973391175,
    7.341989707201719],
   [7.456813280470669,
    7.130581725388765,
    7.1143358228728175,
    7.1227226899936795,
    7.2687620194628835]]],
 ["vm2_regexp",
  [[1.0923050865530968,
    1.0876065697520971,
    1.0792832653969526,
    1.0728685557842255,
    1.098634515888989],
   [1.172722346149385,
    1.0436540711671114,
    1.0638451064005494,
    1.0391646642237902,
    1.023901374079287]]],
 ["vm2_send",
  [[0.3253856906667352,
    0.31978365778923035,
    0.32439478673040867,
    0.3290995704010129,
    0.32464261166751385],
   [0.31982277520000935,
    0.32479251362383366,
    0.31968420650810003,
    0.3205655198544264,
    0.32221281714737415]]],
 ["vm2_set_include_lit",
  [[0.7017893251031637,
    0.7203339897096157,
    0.714060970582068,
    0.7558876499533653,
    0.7028816910460591],
   [0.6870902189984918,
    0.6755420127883554,
    0.6719080805778503,
    0.6900417571887374,
    1.1408466212451458]]],
 ["vm2_str_delete",
  [[0.671600878238678,
    0.6570039531216025,
    0.6776775699108839,
    0.6644531870260835,
    0.97628088388592],
   [0.508894819766283,
    0.5154125597327948,
    0.5064034061506391,
    0.5130562232807279,
    0.5056716529652476]]],
 ["vm2_str_eq1",
  [[0.42520802468061447,
    0.42302330397069454,
    0.42416366562247276,
    0.4216942973434925,
    0.4239108320325613],
   [0.23870286531746387,
    0.23699089046567678,
    0.2361262608319521,
    0.31979569140821695,
    0.26054865028709173]]],
 ["vm2_str_eq2",
  [[0.42534991446882486,
    0.4238837053999305,
    0.4327298905700445,
    0.42395070381462574,
    0.42501260805875063],
   [0.21500772424042225,
    0.21449210867285728,
    0.2152256891131401,
    0.21711387671530247,
    0.2152888299897313]]],
 ["vm2_str_eqq1",
  [[0.45151620265096426,
    0.4597599729895592,
    0.45280721783638,
    0.4582480965182185,
    0.45380780193954706],
   [0.26729187835007906,
    0.26626693457365036,
    0.2660442851483822,
    0.26674115750938654,
    0.2721241358667612]]],
 ["vm2_str_eqq2",
  [[0.45260279811918736,
    0.44771482422947884,
    0.4570333072915673,
    0.5335573730990291,
    0.4592890217900276],
   [0.2385527715086937,
    0.24223692156374454,
    0.24510123394429684,
    0.2744274791330099,
    0.2447566892951727]]],
 ["vm2_str_fmt",
  [[2.614012835547328,
    2.6062369514256716,
    2.5333361653611064,
    2.50343619287014,
    2.641179056838155],
   [1.9900542199611664,
    2.0983974151313305,
    1.9898552373051643,
    2.001521130092442,
    1.9963106652721763]]],
 ["vm2_str_gsub_bang_lit",
  [[1.1346126468852162,
    1.134159336797893,
    1.1467241132631898,
    1.170719631947577,
    1.149439207278192],
   [0.737271074205637,
    0.7308303005993366,
    0.730314377695322,
    0.7260715514421463,
    0.7231733025982976]]],
 ["vm2_str_gsub_bang_re",
  [[1.4583028750494123,
    1.4592840988188982,
    1.4590299287810922,
    1.4751051738858223,
    1.492074583657086],
   [1.3006651625037193,
    1.3510901927947998,
    1.2767879888415337,
    1.3884455161169171,
    1.277703725732863]]],
 ["vm2_str_gsub_re",
  [[1.7273316802456975,
    1.7103399503976107,
    1.7015334982424974,
    1.690703290514648,
    1.694624281488359],
   [1.5322920577600598,
    1.5391994584351778,
    1.5388264516368508,
    1.5372542077675462,
    1.5352015374228358]]],
 ["vm2_str_plus1",
  [[0.653569158166647,
    0.6917095249518752,
    0.6521412674337626,
    0.6697740163654089,
    0.7296328162774444],
   [0.46872936747968197,
    0.48235206957906485,
    0.46762560587376356,
    0.4766495209187269,
    0.4661587802693248]]],
 ["vm2_str_plus2",
  [[0.6671840893104672,
    0.6488652648404241,
    0.6509246686473489,
    0.6638444662094116,
    0.6557880509644747],
   [0.43602617271244526,
    0.4365514535456896,
    0.45153723005205393,
    0.437131704762578,
    0.44073084741830826]]],
 ["vm2_str_tr_bang",
  [[2.6861952347680926,
    2.816385295242071,
    2.919709806330502,
    2.712329135276377,
    2.6914397440850735],
   [1.79120559617877,
    1.8011440439149737,
    1.7935738116502762,
    1.7961191991344094,
    1.794896787032485]]],
 ["vm2_strcat",
  [[0.7171547841280699,
    0.7207952421158552,
    0.7209635498002172,
    0.7520132083445787,
    0.7179927034303546],
   [0.5424934774637222,
    0.5365820089355111,
    0.5409346511587501,
    0.5369464093819261,
    0.5396620389074087]]],
 ["vm2_super",
  [[0.43017072789371014,
    0.4671447016298771,
    0.43235956504940987,
    0.43128165043890476,
    0.4320727000012994],
   [0.42843823600560427,
    0.4359660716727376,
    0.46027328819036484,
    0.43320534005761147,
    0.4283680962398648]]],
 ["vm2_unif1",
  [[0.22890623100101948,
    0.23932419251650572,
    0.2282293662428856,
    0.2281231889501214,
    0.22829711250960827],
   [0.23160281032323837,
    0.23210297524929047,
    0.23051423579454422,
    0.23075581435114145,
    0.2301806192845106]]],
 ["vm2_zsuper",
  [[0.44561540707945824,
    0.4586751004680991,
    0.468279717490077,
    0.44244454242289066,
    0.4582256320863962],
   [0.5099742524325848,
    0.44541092310100794,
    0.4480971107259393,
    0.4709290647879243,
    0.5650665555149317]]]]

Elapsed time: 646.078768307 (sec)
-----------------------------------------------------------
benchmark results:
minimum results in each 5 measurements.
Execution time (sec)
name	trunk	built
loop_whileloop2	0.091	0.091
vm2_array*	0.545	0.547
vm2_array_delete_lit*	0.342	0.155
vm2_array_include_lit*	0.328	0.138
vm2_bigarray*	5.802	5.791
vm2_bighash*	3.430	3.415
vm2_case*	0.071	0.069
vm2_defined_method*	2.336	2.305
vm2_dstr*	0.969	0.992
vm2_eval*	12.331	12.099
vm2_hash_aref_lit*	0.163	0.213
vm2_hash_aset_lit*	0.215	0.259
vm2_hash_delete_lit*	0.330	0.147
vm2_method*	1.122	1.111
vm2_method_missing*	1.626	1.609
vm2_method_with_block*	1.250	1.255
vm2_mutex*	0.554	0.581
vm2_newlambda*	0.665	0.658
vm2_poly_method*	1.892	1.943
vm2_poly_method_ov*	0.137	0.138
vm2_proc*	0.354	0.353
vm2_raise1*	4.664	4.701
vm2_raise2*	6.971	7.023
vm2_regexp*	0.982	0.933
vm2_send*	0.228	0.228
vm2_set_include_lit*	0.610	0.581
vm2_str_delete*	0.566	0.414
vm2_str_eq1*	0.330	0.145
vm2_str_eq2*	0.333	0.123
vm2_str_eqq1*	0.360	0.175
vm2_str_eqq2*	0.356	0.147
vm2_str_fmt*	2.412	1.899
vm2_str_gsub_bang_lit*	1.043	0.632
vm2_str_gsub_bang_re*	1.367	1.185
vm2_str_gsub_re*	1.599	1.441
vm2_str_plus1*	0.561	0.375
vm2_str_plus2*	0.558	0.345
vm2_str_tr_bang*	2.595	1.700
vm2_strcat*	0.626	0.445
vm2_super*	0.339	0.337
vm2_unif1*	0.137	0.139
vm2_zsuper*	0.351	0.354
---
 .gitignore                            |   1 +
 benchmark/bm_vm2_array_delete_lit.rb  |   6 +
 benchmark/bm_vm2_array_include_lit.rb |   6 +
 benchmark/bm_vm2_hash_aref_lit.rb     |   6 +
 benchmark/bm_vm2_hash_aset_lit.rb     |   6 +
 benchmark/bm_vm2_hash_delete_lit.rb   |   6 +
 benchmark/bm_vm2_set_include_lit.rb   |   7 +
 benchmark/bm_vm2_str_delete.rb        |   6 +
 benchmark/bm_vm2_str_eq1.rb           |   6 +
 benchmark/bm_vm2_str_eq2.rb           |   6 +
 benchmark/bm_vm2_str_eqq1.rb          |   6 +
 benchmark/bm_vm2_str_eqq2.rb          |   6 +
 benchmark/bm_vm2_str_fmt.rb           |   5 +
 benchmark/bm_vm2_str_gsub_bang_lit.rb |   6 +
 benchmark/bm_vm2_str_gsub_bang_re.rb  |   6 +
 benchmark/bm_vm2_str_gsub_re.rb       |   6 +
 benchmark/bm_vm2_str_plus1.rb         |   6 +
 benchmark/bm_vm2_str_plus2.rb         |   6 +
 benchmark/bm_vm2_str_tr_bang.rb       |   7 +
 benchmark/bm_vm2_strcat.rb            |   7 +
 common.mk                             |  18 +-
 compile.c                             | 331 ++++++++++++++++++++++++++++++----
 defs/id.def                           |  38 ++++
 defs/opt_method.def                   |  90 +++++++++
 insns.def                             | 275 ++++++++++++++++------------
 template/opt_method.h.tmpl            | 111 ++++++++++++
 template/opt_method.inc.tmpl          |  42 +++++
 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_optimization.rb        | 171 +++++++++++++++++-
 vm.c                                  |  67 +------
 vm_core.h                             |  44 +----
 vm_insnhelper.c                       |   8 +-
 vm_insnhelper.h                       |  27 +++
 37 files changed, 1090 insertions(+), 264 deletions(-)
 create mode 100644 benchmark/bm_vm2_array_delete_lit.rb
 create mode 100644 benchmark/bm_vm2_array_include_lit.rb
 create mode 100644 benchmark/bm_vm2_hash_aref_lit.rb
 create mode 100644 benchmark/bm_vm2_hash_aset_lit.rb
 create mode 100644 benchmark/bm_vm2_hash_delete_lit.rb
 create mode 100644 benchmark/bm_vm2_set_include_lit.rb
 create mode 100644 benchmark/bm_vm2_str_delete.rb
 create mode 100644 benchmark/bm_vm2_str_eq1.rb
 create mode 100644 benchmark/bm_vm2_str_eq2.rb
 create mode 100644 benchmark/bm_vm2_str_eqq1.rb
 create mode 100644 benchmark/bm_vm2_str_eqq2.rb
 create mode 100644 benchmark/bm_vm2_str_fmt.rb
 create mode 100644 benchmark/bm_vm2_str_gsub_bang_lit.rb
 create mode 100644 benchmark/bm_vm2_str_gsub_bang_re.rb
 create mode 100644 benchmark/bm_vm2_str_gsub_re.rb
 create mode 100644 benchmark/bm_vm2_str_plus1.rb
 create mode 100644 benchmark/bm_vm2_str_plus2.rb
 create mode 100644 benchmark/bm_vm2_str_tr_bang.rb
 create mode 100644 benchmark/bm_vm2_strcat.rb
 create mode 100644 defs/opt_method.def
 create mode 100644 template/opt_method.h.tmpl
 create mode 100644 template/opt_method.inc.tmpl

diff --git a/.gitignore b/.gitignore
index d895fe1..66e6f98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,6 +74,7 @@ y.tab.c
 /newdate.rb
 /newline.c
 /newver.rb
+/opt_method.h
 /parse.c
 /parse.h
 /patches
diff --git a/benchmark/bm_vm2_array_delete_lit.rb b/benchmark/bm_vm2_array_delete_lit.rb
new file mode 100644
index 0000000..60d599a
--- /dev/null
+++ b/benchmark/bm_vm2_array_delete_lit.rb
@@ -0,0 +1,6 @@
+ary = []
+i = 0
+while i<6_000_000 # while loop 2
+  i += 1
+  ary.delete("foo")
+end
diff --git a/benchmark/bm_vm2_array_include_lit.rb b/benchmark/bm_vm2_array_include_lit.rb
new file mode 100644
index 0000000..c81e230
--- /dev/null
+++ b/benchmark/bm_vm2_array_include_lit.rb
@@ -0,0 +1,6 @@
+ary = []
+i = 0
+while i<6_000_000 # while loop 2
+  i += 1
+  ary.include?("foo")
+end
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_hash_delete_lit.rb b/benchmark/bm_vm2_hash_delete_lit.rb
new file mode 100644
index 0000000..22dd95f
--- /dev/null
+++ b/benchmark/bm_vm2_hash_delete_lit.rb
@@ -0,0 +1,6 @@
+h = {}
+i = 0
+while i<6_000_000 # while loop 2
+  i += 1
+  h.delete("foo")
+end
diff --git a/benchmark/bm_vm2_set_include_lit.rb b/benchmark/bm_vm2_set_include_lit.rb
new file mode 100644
index 0000000..25d8b89
--- /dev/null
+++ b/benchmark/bm_vm2_set_include_lit.rb
@@ -0,0 +1,7 @@
+require 'set'
+set = Set.new
+i = 0
+while i<6_000_000 # while loop 2
+  i += 1
+  set.include?("foo")
+end
diff --git a/benchmark/bm_vm2_str_delete.rb b/benchmark/bm_vm2_str_delete.rb
new file mode 100644
index 0000000..c242f29
--- /dev/null
+++ b/benchmark/bm_vm2_str_delete.rb
@@ -0,0 +1,6 @@
+str = ''
+i = 0
+while i<6_000_000 # while loop 2
+  i += 1
+  str.delete("foo")
+end
diff --git a/benchmark/bm_vm2_str_eq1.rb b/benchmark/bm_vm2_str_eq1.rb
new file mode 100644
index 0000000..2a4b0f8
--- /dev/null
+++ b/benchmark/bm_vm2_str_eq1.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_str_eq2.rb b/benchmark/bm_vm2_str_eq2.rb
new file mode 100644
index 0000000..986020d
--- /dev/null
+++ b/benchmark/bm_vm2_str_eq2.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_str_eqq1.rb b/benchmark/bm_vm2_str_eqq1.rb
new file mode 100644
index 0000000..9183466
--- /dev/null
+++ b/benchmark/bm_vm2_str_eqq1.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_str_eqq2.rb b/benchmark/bm_vm2_str_eqq2.rb
new file mode 100644
index 0000000..f48a9cd
--- /dev/null
+++ b/benchmark/bm_vm2_str_eqq2.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_str_fmt.rb b/benchmark/bm_vm2_str_fmt.rb
new file mode 100644
index 0000000..efb88b6
--- /dev/null
+++ b/benchmark/bm_vm2_str_fmt.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_str_gsub_bang_lit.rb b/benchmark/bm_vm2_str_gsub_bang_lit.rb
new file mode 100644
index 0000000..9251fb1
--- /dev/null
+++ b/benchmark/bm_vm2_str_gsub_bang_lit.rb
@@ -0,0 +1,6 @@
+i = 0
+str = ""
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str.gsub!("nomatch", "")
+end
diff --git a/benchmark/bm_vm2_str_gsub_bang_re.rb b/benchmark/bm_vm2_str_gsub_bang_re.rb
new file mode 100644
index 0000000..e5fc9ea
--- /dev/null
+++ b/benchmark/bm_vm2_str_gsub_bang_re.rb
@@ -0,0 +1,6 @@
+i = 0
+str = ""
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str.gsub!(/a/, "")
+end
diff --git a/benchmark/bm_vm2_str_gsub_re.rb b/benchmark/bm_vm2_str_gsub_re.rb
new file mode 100644
index 0000000..606f247
--- /dev/null
+++ b/benchmark/bm_vm2_str_gsub_re.rb
@@ -0,0 +1,6 @@
+i = 0
+str = ""
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str.gsub(/a/, "")
+end
diff --git a/benchmark/bm_vm2_str_plus1.rb b/benchmark/bm_vm2_str_plus1.rb
new file mode 100644
index 0000000..714efb8
--- /dev/null
+++ b/benchmark/bm_vm2_str_plus1.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_str_plus2.rb b/benchmark/bm_vm2_str_plus2.rb
new file mode 100644
index 0000000..c7f91ed
--- /dev/null
+++ b/benchmark/bm_vm2_str_plus2.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/benchmark/bm_vm2_str_tr_bang.rb b/benchmark/bm_vm2_str_tr_bang.rb
new file mode 100644
index 0000000..8065a65
--- /dev/null
+++ b/benchmark/bm_vm2_str_tr_bang.rb
@@ -0,0 +1,7 @@
+i = 0
+str = "a"
+while i<6_000_000 # benchmark loop 2
+  i += 1
+  str.tr!("a", "A")
+  str.tr!("A", "a")
+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/common.mk b/common.mk
index 75c6a7a..b6859e6 100644
--- a/common.mk
+++ b/common.mk
@@ -642,7 +642,7 @@ PROBES_H_INCLUDES  = {$(VPATH)}probes.h
 VM_CORE_H_INCLUDES = {$(VPATH)}vm_core.h {$(VPATH)}thread_$(THREAD_MODEL).h \
 		     {$(VPATH)}node.h {$(VPATH)}method.h {$(VPATH)}ruby_atomic.h \
 	             {$(VPATH)}vm_debug.h {$(VPATH)}id.h {$(VPATH)}thread_native.h \
-	             $(CCAN_LIST_INCLUDES)
+	             $(CCAN_LIST_INCLUDES) {$(VPATH)}opt_method.h
 
 ###
 
@@ -829,7 +829,7 @@ vm.$(OBJEXT): {$(VPATH)}vm.c {$(VPATH)}gc.h {$(VPATH)}iseq.h {$(VPATH)}vm_args.c
   $(VM_CORE_H_INCLUDES) {$(VPATH)}vm_method.c {$(VPATH)}vm_eval.c \
   {$(VPATH)}vm_insnhelper.c {$(VPATH)}vm_insnhelper.h {$(VPATH)}vm_exec.c \
   {$(VPATH)}vm_exec.h {$(VPATH)}insns.def {$(VPATH)}vmtc.inc \
-  {$(VPATH)}vm.inc {$(VPATH)}insns.inc \
+  {$(VPATH)}vm.inc {$(VPATH)}insns.inc {$(VPATH)}opt_method.inc \
   {$(VPATH)}internal.h {$(VPATH)}vm.h {$(VPATH)}constant.h \
   $(PROBES_H_INCLUDES) {$(VPATH)}probes_helper.h {$(VPATH)}vm_opts.h
 vm_dump.$(OBJEXT): {$(VPATH)}vm_dump.c $(RUBY_H_INCLUDES) \
@@ -941,6 +941,20 @@ incs: $(INSNS) {$(VPATH)}node_name.inc {$(VPATH)}encdb.h {$(VPATH)}transdb.h {$(
 
 insns: $(INSNS)
 
+opt_method.h: $(srcdir)/tool/generic_erb.rb \
+		$(srcdir)/template/opt_method.h.tmpl \
+		$(srcdir)/defs/opt_method.def
+	$(ECHO) generating $@
+	$(Q) $(BASERUBY) $(srcdir)/tool/generic_erb.rb --output=$@ \
+		$(srcdir)/template/opt_method.h.tmpl
+
+opt_method.inc: $(srcdir)/tool/generic_erb.rb \
+		$(srcdir)/template/opt_method.inc.tmpl \
+		$(srcdir)/defs/opt_method.def
+	$(ECHO) generating $@
+	$(Q) $(BASERUBY) $(srcdir)/tool/generic_erb.rb --output=$@ \
+		$(srcdir)/template/opt_method.inc.tmpl
+
 id.h: $(srcdir)/tool/generic_erb.rb $(srcdir)/template/id.h.tmpl $(srcdir)/defs/id.def
 	$(ECHO) generating $@
 	$(Q) $(BASERUBY) $(srcdir)/tool/generic_erb.rb --output=$@ \
diff --git a/compile.c b/compile.c
index 860fe13..fa5ca8d 100644
--- a/compile.c
+++ b/compile.c
@@ -1744,6 +1744,153 @@ get_prev_insn(INSN *iobj)
     return 0;
 }
 
+#define new_recvinfo_for_recv(iseq,str,mid,klass) \
+    new_recvinfo_for_recv_(iseq,str,OM_##mid##__##klass)
+static VALUE
+new_recvinfo_for_recv_(rb_iseq_t *iseq, VALUE str,
+			enum ruby_optimized_method om)
+{
+    VALUE ri = rb_ary_new_from_args(2, str, INT2FIX(om));
+
+    hide_obj(ri);
+    iseq_add_mark_object(iseq, ri);
+
+    return ri;
+}
+
+#define new_recvinfo_for_arg(iseq,str,mid,klass,off) \
+    new_recvinfo_for_arg_((iseq),(str),(OM_##mid##__##klass),\
+                          (OM_TMASK_##klass),(off))
+static VALUE
+new_recvinfo_for_arg_(rb_iseq_t *iseq, VALUE str,
+		enum ruby_optimized_method om,
+		VALUE tmask, int recv_off)
+{
+    VALUE ri = rb_ary_new_from_args(4, str, INT2FIX(om),
+				    tmask, INT2FIX(recv_off));
+
+    hide_obj(ri);
+    iseq_add_mark_object(iseq, ri);
+
+    return ri;
+}
+
+/* optimize allocation for: obj.method("literal string") */
+static void
+opt_str_lit_1(rb_iseq_t *iseq, VALUE str, rb_call_info_t *ci, INSN *list)
+{
+    enum ruby_optimized_method om;
+    VALUE tmask;
+    VALUE ri;
+    int data_p = 0;
+
+    switch (ci->mid) {
+#define C(mid,klass) \
+  case mid: \
+    om = OM_##mid##__##klass; \
+    tmask = OM_TMASK_##klass; \
+    break
+      C(idAREF, Hash);
+      C(idEq, String);
+      C(idNeq, String);
+      C(idLTLT, String);
+      C(idPLUS, String);
+      C(idEqq, String);
+      C(idDelete, Array_Hash_String);
+      C(idIncludeP, Array_Hash_String);
+      C(idMemberP, Hash);
+      C(idHas_keyP, Hash);
+      C(idKeyP, Hash);
+      C(idFetch, Hash); /* TODO: hash.fetch("lit") { ... } block */
+      C(idPack, Array);
+      C(idUnpack, String);
+      C(idSplit, String); /* TODO: str.split("lit", num) */
+      C(idJoin, Array);
+      C(idCount, String);
+      C(idChomp, String);
+      C(idChomp_bang, String);
+      C(idSqueeze, String);
+      C(idSqueeze_bang, String);
+      C(idDelete_bang, String);
+      C(idEncode, String);
+      C(idEncode_bang, String);
+      C(idForce_encoding, String);
+      C(idIndex, String); /* TODO: str.index("lit", num) */
+      C(idRindex, String);
+      C(idMatch, String);
+      C(idCasecmp, String);
+      C(idStart_withP, String);
+      C(idEnd_withP, String);
+      C(idPartition, String);
+      C(idRpartition, String);
+#undef C
+#define C(mid,klass) \
+  case mid: \
+    om = OM_##mid##__##klass; \
+    tmask = OM_TMASK_##klass; \
+    data_p = 1; \
+    break
+      /* opt_str_lit_data oddities, maybe this is not worth supporting */
+      C(idStrftime, Time);
+#undef C
+      default: return;
+    }
+
+    list->insn_id = data_p ? BIN(opt_str_lit_data) : BIN(opt_str_lit_tmask);
+    ri = new_recvinfo_for_arg_(iseq, str, om, tmask, 0);
+    list->operands[0] = ri;
+}
+
+/*
+ * optimize common string calls which take one or two string literals:
+ *   obj.method("lit 1", "lit 2")
+ *   obj.method(any, "lit 2") # any may be regexp
+ */
+static void
+opt_str_lit_2(rb_iseq_t *iseq, VALUE str, rb_call_info_t *ci, INSN *list)
+{
+    INSN *piobj;
+    enum ruby_optimized_method om;
+    VALUE ri;
+
+    switch (ci->mid) {
+#define C(mid) case mid: om = OM_##mid##__String; break
+      C(idSub);
+      C(idSub_bang);
+      C(idGsub);
+      C(idGsub_bang);
+      C(idTr);
+      C(idTr_bang);
+      C(idTr_s);
+      C(idTr_s_bang);
+      C(idInsert); /* String#insert(num, "lit") */
+
+      /* String#encode("dst", "src") */
+      C(idEncode);
+      C(idEncode_bang);
+#undef C
+      default: return;
+    }
+
+    /*
+     * previous arg may be a string literal, too:
+     *   foo.gsub!("from", "to")
+     *   foo.tr!("from", "to")
+     *   ..
+     */
+    piobj = (INSN *)get_prev_insn(list);
+    if (piobj && piobj->insn_id == BIN(putstring)) {
+	VALUE pstr = piobj->operands[0];
+	VALUE pri = new_recvinfo_for_arg_(iseq, pstr, om, OM_TMASK_String, 0);
+	piobj->operands[0] = pri;
+	piobj->insn_id = BIN(opt_str_lit_tmask);
+    }
+
+    list->insn_id = BIN(opt_str_lit_tmask);
+    ri = new_recvinfo_for_arg_(iseq, str, om, OM_TMASK_String, 1);
+    list->operands[0] = ri;
+}
+
 static int
 iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt)
 {
@@ -1860,6 +2007,53 @@ 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_SIMPLE)) {
+		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_recv(iseq, str, idFreeze, String);
+			break;
+		      case idSize:
+			ri = new_recvinfo_for_recv(iseq, str, idSize, String);
+			break;
+		      case idLength:
+			ri = new_recvinfo_for_recv(iseq, str, idLength, String);
+			break;
+		    }
+		    if (ri != Qfalse) {
+			iobj->insn_id = BIN(opt_str_lit_recv);
+			iobj->operands[0] = ri;
+		    }
+		    break;
+		  case 1:
+		    opt_str_lit_1(iseq, str, ci, (INSN *)list);
+		    break;
+		  case 2:
+		    opt_str_lit_2(iseq, str, ci, (INSN *)list);
+		    break;
+		}
+	    }
+	}
+    }
+
     return COMPILE_OK;
 }
 
@@ -3186,6 +3380,20 @@ build_postexe_iseq(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *body)
     return Qnil;
 }
 
+static enum ruby_optimized_method
+opt_str_lit_recv_om(ID mid)
+{
+    switch (mid) {
+      case idEq: return OM_idEq__String;
+      case idNeq: return OM_idNeq__String;
+      case idPLUS: return OM_idPLUS__String;
+      case idMULT: return OM_idMULT__String;
+      case idMOD: return OM_idMOD__String;
+      case idEqq: return OM_idEqq__String;
+    }
+    return OM_LAST_;
+}
+
 /**
   compile each node
 
@@ -4320,37 +4528,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, NULL), str);
-	    if (poped) {
-		ADD_INSN(ret, line, pop);
-	    }
-	    break;
-	}
       case NODE_FCALL:
       case NODE_VCALL:{		/* VCALL: variable or call */
 	/*
@@ -4435,7 +4612,32 @@ 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_optimized_method om;
+	    /*
+	     * optimize:
+	     *   "string literal".method(...)
+	     *   "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 &&
+		((om = opt_str_lit_recv_om(mid)) != OM_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_recv_(iseq, yoda, om);
+
+		node->nd_recv->nd_lit = yoda;
+		ADD_INSN1(recv, line, opt_str_lit_recv, recv_info);
+	    } else {
+		COMPILE(recv, "recv", node->nd_recv);
+	    }
 	}
 	else if (type == NODE_FCALL || type == NODE_VCALL) {
 	    ADD_CALL_RECEIVER(recv, line);
@@ -5332,23 +5534,32 @@ 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, idASET, Hash, 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_tmask, 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, NULL), str);
+	    flag = VM_CALL_ARGS_SIMPLE;
+	    ADD_SEND_WITH_FLAG(ret, line, node->nd_mid, INT2FIX(2),
+				INT2FIX(flag));
 	    ADD_INSN(ret, line, pop);
 	    break;
 	}
@@ -5997,3 +6208,45 @@ rb_parse_in_main(void)
 {
     return GET_THREAD()->parse_in_eval < 0;
 }
+
+/*
+ * Live bytecode patch:
+ *   - opt_str_lit(recv_info)
+ *   + putstring(str) # str is recv_info[0]
+ *
+ * If allocation optimization fails at this call site once, assume it
+ * will fail in the future.  This prevents performance regressions for
+ * things like #include? calls which may be used with unoptimized
+ * classes (Set,*DBM and many others) as well as optimized core classes
+ * (Array/Hash/String).  Call sites which only use optimized core
+ * classes will never get here.
+ */
+void
+rb_undo_opt_str_lit(rb_control_frame_t *cfp)
+{
+    VALUE *insn = cfp->pc - insn_len(BIN(opt_str_lit_tmask));
+
+#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
+    const void * const *table = rb_vm_get_insns_address_table();
+
+    assert(((VALUE)table[BIN(opt_str_lit_tmask)] == insn[0] ||
+            (VALUE)table[BIN(opt_str_lit_data)] == insn[0] ||
+            (VALUE)table[BIN(opt_str_lit_recv)] == insn[0]) &&
+	    "mismatch");
+    insn[0] = (VALUE)table[BIN(putstring)];
+#else
+    assert(((VALUE)BIN(opt_str_lit_tmask) == insn[0] ||
+            (VALUE)BIN(opt_str_lit_data) == insn[0] ||
+            (VALUE)BIN(opt_str_lit_recv) == insn[0]) &&
+	    "mismatch");
+    insn[0] = (VALUE)BIN(putstring);
+#endif
+    assert(insn_len(BIN(putstring)) == insn_len(BIN(opt_str_lit_tmask)));
+    assert(insn_len(BIN(putstring)) == insn_len(BIN(opt_str_lit_data)));
+    assert(insn_len(BIN(putstring)) == insn_len(BIN(opt_str_lit_recv)));
+
+    assert(T_ARRAY == BUILTIN_TYPE(insn[1]));
+
+    /* n.b.: recv_info remains marked */
+    insn[1] = RARRAY_AREF(insn[1], 0); /* recv_info[0] == str */
+}
diff --git a/defs/id.def b/defs/id.def
index 224ed70..7332f29 100644
--- a/defs/id.def
+++ b/defs/id.def
@@ -58,6 +58,43 @@ firstline, predefined = __LINE__+1, %[\
   core#hash_merge_ary
   core#hash_merge_ptr
   core#hash_merge_kwd
+  gsub
+  gsub!
+  sub
+  sub!
+  tr
+  tr!
+  tr_s
+  tr_s!
+  delete
+  delete!
+  include?
+  member?
+  has_key?
+  key?
+  fetch
+  count
+  chomp
+  chomp!
+  squeeze
+  squeeze!
+  strftime
+  pack
+  unpack
+  split
+  join
+  encode
+  encode!
+  force_encoding
+  index
+  rindex
+  match
+  casecmp
+  insert
+  start_with?
+  end_with?
+  partition
+  rpartition
 ]
 
 class KeywordError < RuntimeError
@@ -84,6 +121,7 @@ predefined.split(/^/).each_with_index do |line, num|
     token = "_#{token.gsub(/\W+/, '_')}"
   else
     token = token.sub(/\?/, 'P').sub(/\A[a-z]/) {$&.upcase}
+    token.sub!(/!\z/, "_bang")
     token.sub!(/\A\$/, "_G_")
     token.sub!(/\A@@/, "_C_")
     token.sub!(/\A@/, "_I_")
diff --git a/defs/opt_method.def b/defs/opt_method.def
new file mode 100644
index 0000000..1d1fb21
--- /dev/null
+++ b/defs/opt_method.def
@@ -0,0 +1,90 @@
+# byte align the bitmap for now, maybe some arches do better with long or int
+# we may also use a larger size (in the unlikely case) we need more than
+# 7 optimized classes per mid.   Currently this caps us to 256 optimized
+# (mid, klass) combinations (tested with OM_SHIFT=4, giving us 64K)
+OM_SHIFT = 3
+OM_ALIGN = 1 << OM_SHIFT
+OM_ALIGN_MASK = ~(OM_ALIGN - 1)
+OPT_METHODS = [
+  %w(idPLUS Fixnum Float String Array),
+  %w(idMINUS Fixnum Float),
+  %w(idMULT Fixnum Float String),
+  %w(idDIV Fixnum Float),
+  %w(idMOD Fixnum Float String),
+  %w(idEq Fixnum Float String),
+  %w(idNeq Fixnum Float String),
+  # id, mask classes
+  [ 'idEqq', %w(Bignum Fixnum Float Symbol), *%w(String) ],
+  %w(idLT Fixnum Float),
+  %w(idLE Fixnum Float),
+  %w(idGT Fixnum Float),
+  %w(idGE Fixnum Float),
+  %w(idLTLT Array String),
+  %w(idAREF Array Hash),
+  %w(idASET Array Hash),
+  %w(idLength Array Hash String),
+  %w(idSize Array Hash String),
+  %w(idEmptyP Array Hash String),
+  %w(idSucc Fixnum String Time),
+  %w(idEqTilde Regexp String),
+  %w(idFreeze String),
+  %w(idGsub String),
+  %w(idGsub_bang String),
+  %w(idSub String),
+  %w(idSub_bang String),
+  %w(idTr String),
+  %w(idTr_bang String),
+  %w(idTr_s String),
+  %w(idTr_s_bang String),
+  [ "idDelete", %w(Array Hash String) ],
+  [ "idIncludeP", %w(Array Hash String) ],
+  %w(idMemberP Hash),
+  %w(idHas_keyP Hash),
+  %w(idKeyP Hash),
+  %w(idFetch Hash),
+  %w(idStrftime Time),
+  %w(idUnpack String),
+  %w(idPack Array),
+  %w(idSplit String),
+  %w(idJoin Array),
+  %w(idCount String),
+  %w(idChomp String),
+  %w(idChomp_bang String),
+  %w(idSqueeze String),
+  %w(idSqueeze_bang String),
+  %w(idDelete_bang String),
+  %w(idEncode String),
+  %w(idEncode_bang String),
+  %w(idForce_encoding String),
+  %w(idIndex String),
+  %w(idRindex String),
+  %w(idMatch String),
+  %w(idCasecmp String),
+  %w(idInsert String),
+  %w(idStart_withP String),
+  %w(idEnd_withP String),
+  %w(idPartition String),
+  %w(idRpartition String),
+]
+
+# for checking optimized classes,
+# speeds up method definitions of non-core classes
+def opt_classes
+  rv = {}
+  OPT_METHODS.each do |(_, *classes)|
+    classes.flatten.each { |c| rv[c] = true }
+  end
+  rv
+end
+
+def om(mid, klass)
+  if Array === klass
+    "OM_#{mid}__#{klass.join('_')}"
+  else
+    "OM_#{mid}__#{klass}"
+  end
+end
+
+IS_T_DATA = {
+  "Time" => true
+}
diff --git a/insns.def b/insns.def
index 9e1c16b..326115d 100644
--- a/insns.def
+++ b/insns.def
@@ -357,6 +357,117 @@ putstring
 }
 
 /**
+  @c optimize
+  @e put string val. string may be created depending on recv_info conditions
+ */
+DEFINE_INSN
+opt_str_lit_tmask
+(VALUE recv_info)
+()
+(VALUE val)
+{
+    /*
+     * recv_info:
+     * 0 - str
+     * 1 - optimized method flag (OM_*)
+     * 2 - tmask (optimized receiver classes)
+     * 3 - stack offset (Fixint),
+     *     -1 stack offset means receiver is the frozen string literal itself
+     */
+    const VALUE *ri = RARRAY_CONST_PTR(recv_info);
+    enum ruby_optimized_method om = FIX2UINT(ri[1]);
+    int tmask = FIX2INT(ri[2]);
+    int n = FIX2INT(ri[3]);
+    VALUE recv;
+
+    val = ri[0]; /* hopefully, this is the only val assignment we need */
+    recv = n < 0 ? val : TOPN(n);
+
+    if (!SPECIAL_CONST_P(recv)) {
+	enum ruby_value_type btype = BUILTIN_TYPE(recv);
+	int rmask = 1 << btype;
+
+	if ((rmask & tmask) &&
+		(rb_opt_method_class(btype) == RBASIC_CLASS(recv))) {
+	    if (rb_opt_method_is_mask(om)) {
+		if (rb_basic_mask_unredefined_p(om)) {
+		    goto out_tmask; /* fast path */
+		}
+	    }
+	    else if (rb_basic_op_unredefined_p(om)) {
+		goto out_tmask; /* fast path */
+	    }
+	}
+    }
+    /* slow path */
+    val = rb_str_resurrect(val);
+    rb_undo_opt_str_lit(GET_CFP());
+out_tmask:
+}
+
+/**
+  @c optimize
+  @e put string val. string may be created depending on recv_info conditions
+ */
+DEFINE_INSN
+opt_str_lit_data
+(VALUE recv_info)
+()
+(VALUE val)
+{
+    /*
+     * recv_info:
+     * 0 - str
+     * 1 - optimized method flag (OM_*)
+     * 2 - class
+     * 3 - stack offset (Fixint),
+     *     -1 stack offset means receiver is the frozen string literal itself
+     */
+    const VALUE *ri = RARRAY_CONST_PTR(recv_info);
+    enum ruby_optimized_method om = FIX2UINT(ri[1]);
+    VALUE klass = ri[2];
+    int n = FIX2INT(ri[3]);
+    VALUE recv;
+
+    val = ri[0]; /* hopefully, this is the only val assignment we need */
+    recv = n < 0 ? val : TOPN(n);
+
+    if (klass != RBASIC_CLASS(recv) || !rb_basic_op_unredefined_p(om)) {
+	val = rb_str_resurrect(val);
+	rb_undo_opt_str_lit(GET_CFP());
+    }
+}
+
+/**
+  @c optimize
+  @e put string val. string may be created depending on recv_info conditions
+ */
+DEFINE_INSN
+opt_str_lit_recv
+(VALUE recv_info)
+()
+(VALUE val)
+{
+    /*
+     * recv_info:
+     * 0 - str
+     * 1 - optimized method flag (OM_*)
+     */
+    const VALUE *ri = RARRAY_CONST_PTR(recv_info);
+    enum ruby_optimized_method om = FIX2UINT(ri[1]);
+
+    val = ri[0]; /* hopefully, this is the only val assignment we need */
+
+    if (!rb_basic_op_unredefined_p(om)) {
+	val = rb_str_resurrect(val);
+	/*
+	 * do not bother with: rb_undo_opt_str_lit(GET_CFP());
+	 * here, it is crazy to redefine core String methods :P
+	 */
+    }
+}
+
+/**
   @c put
   @e put concatenate strings
   @j スタックトップの文字列を n 個連結し，結果をスタックにプッシュする。
@@ -1024,20 +1135,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
@@ -1307,11 +1404,7 @@ opt_case_dispatch
       case T_FIXNUM:
       case T_BIGNUM:
       case T_STRING:
-	if (BASIC_OP_UNREDEFINED_P(BOP_EQQ,
-				   SYMBOL_REDEFINED_OP_FLAG |
-				   FIXNUM_REDEFINED_OP_FLAG |
-				   BIGNUM_REDEFINED_OP_FLAG |
-				   STRING_REDEFINED_OP_FLAG)) {
+	if (rb_basic_mask_unredefined_p(OM_idEqq__Bignum_Fixnum_Float_Symbol)) {
 	    st_data_t val;
 	    if (st_lookup(RHASH_TBL_RAW(hash), key, &val)) {
 		JUMP(FIX2INT((VALUE)val));
@@ -1339,8 +1432,7 @@ opt_plus
 (VALUE recv, VALUE obj)
 (VALUE val)
 {
-    if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_PLUS,FIXNUM_REDEFINED_OP_FLAG)) {
+    if (FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idPLUS, Fixnum)) {
 	/* fixnum + fixnum */
 #ifndef LONG_LONG_VALUE
 	val = (recv + (obj & (~1)));
@@ -1363,20 +1455,20 @@ opt_plus
 #endif
     }
     else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_PLUS, FLOAT_REDEFINED_OP_FLAG)) {
+	     BASIC_OP_UNREDEFINED_P(idPLUS, Float)) {
 	val = DBL2NUM(RFLOAT_VALUE(recv) + RFLOAT_VALUE(obj));
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat &&
-	    BASIC_OP_UNREDEFINED_P(BOP_PLUS, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idPLUS, Float)) {
 	    val = DBL2NUM(RFLOAT_VALUE(recv) + RFLOAT_VALUE(obj));
 	}
 	else if (RBASIC_CLASS(recv) == rb_cString && RBASIC_CLASS(obj) == rb_cString &&
-		 BASIC_OP_UNREDEFINED_P(BOP_PLUS, STRING_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idPLUS, String)) {
 	    val = rb_str_plus(recv, obj);
 	}
 	else if (RBASIC_CLASS(recv) == rb_cArray &&
-		 BASIC_OP_UNREDEFINED_P(BOP_PLUS, ARRAY_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idPLUS, Array)) {
 	    val = rb_ary_plus(recv, obj);
 	}
 	else {
@@ -1403,7 +1495,7 @@ opt_minus
 (VALUE val)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_MINUS, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idMINUS, Fixnum)) {
 	long a, b, c;
 
 	a = FIX2LONG(recv);
@@ -1418,12 +1510,12 @@ opt_minus
 	}
     }
     else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_MINUS, FLOAT_REDEFINED_OP_FLAG)) {
+	     BASIC_OP_UNREDEFINED_P(idMINUS, Float)) {
 	val = DBL2NUM(RFLOAT_VALUE(recv) - RFLOAT_VALUE(obj));
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat  &&
-	    BASIC_OP_UNREDEFINED_P(BOP_MINUS, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idMINUS, Float)) {
 	    val = DBL2NUM(RFLOAT_VALUE(recv) - RFLOAT_VALUE(obj));
 	}
 	else {
@@ -1451,7 +1543,7 @@ opt_mult
 (VALUE val)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_MULT, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idMULT, Fixnum)) {
 	long a, b;
 
 	a = FIX2LONG(recv);
@@ -1468,13 +1560,12 @@ opt_mult
             }
 	}
     }
-    else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_MULT, FLOAT_REDEFINED_OP_FLAG)) {
+    else if (FLONUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idMULT, Float)) {
 	val = DBL2NUM(RFLOAT_VALUE(recv) * RFLOAT_VALUE(obj));
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat  &&
-	    BASIC_OP_UNREDEFINED_P(BOP_MULT, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idMULT, Float)) {
 	    val = DBL2NUM(RFLOAT_VALUE(recv) * RFLOAT_VALUE(obj));
 	}
 	else {
@@ -1500,8 +1591,7 @@ opt_div
 (VALUE recv, VALUE obj)
 (VALUE val)
 {
-    if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_DIV, FIXNUM_REDEFINED_OP_FLAG)) {
+    if (FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idDIV, Fixnum)) {
 	long x, y, div;
 
 	x = FIX2LONG(recv);
@@ -1531,13 +1621,12 @@ opt_div
 	}
 	val = LONG2NUM(div);
     }
-    else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_DIV, FLOAT_REDEFINED_OP_FLAG)) {
+    else if (FLONUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idDIV, Float)) {
 	val = DBL2NUM(RFLOAT_VALUE(recv) / RFLOAT_VALUE(obj));
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat  &&
-	    BASIC_OP_UNREDEFINED_P(BOP_DIV, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idDIV, Float)) {
 	    val = DBL2NUM(RFLOAT_VALUE(recv) / RFLOAT_VALUE(obj));
 	}
 	else {
@@ -1563,8 +1652,7 @@ opt_mod
 (VALUE recv, VALUE obj)
 (VALUE val)
 {
-    if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_MOD, FIXNUM_REDEFINED_OP_FLAG )) {
+    if (FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idMOD, Fixnum )) {
 	long x, y;
 
 	x = FIX2LONG(recv);
@@ -1598,13 +1686,12 @@ opt_mod
 	    val = LONG2FIX(mod);
 	}
     }
-    else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_MOD, FLOAT_REDEFINED_OP_FLAG)) {
+    else if (FLONUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idMOD, Float)) {
 	val = DBL2NUM(ruby_float_mod(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj)));
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat &&
-	    BASIC_OP_UNREDEFINED_P(BOP_MOD, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idMOD, Float)) {
 	    val = DBL2NUM(ruby_float_mod(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj)));
 	}
 	else {
@@ -1683,7 +1770,7 @@ opt_lt
 (VALUE val)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_LT, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idLT, Fixnum)) {
 	SIGNED_VALUE a = recv, b = obj;
 
 	if (a < b) {
@@ -1694,13 +1781,13 @@ opt_lt
 	}
     }
     else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_LT, FLOAT_REDEFINED_OP_FLAG)) {
+	     BASIC_OP_UNREDEFINED_P(idLT, Float)) {
 	/* flonum is not NaN */
 	val = RFLOAT_VALUE(recv) < RFLOAT_VALUE(obj) ? Qtrue : Qfalse;
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat  &&
-	    BASIC_OP_UNREDEFINED_P(BOP_LT, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idLT, Float)) {
 	    val = double_cmp_lt(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj));
 	}
 	else {
@@ -1727,7 +1814,7 @@ opt_le
 (VALUE val)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_LE, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idLE, Fixnum)) {
 	SIGNED_VALUE a = recv, b = obj;
 
 	if (a <= b) {
@@ -1738,7 +1825,7 @@ opt_le
 	}
     }
     else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_LE, FLOAT_REDEFINED_OP_FLAG)) {
+	     BASIC_OP_UNREDEFINED_P(idLE, Float)) {
 	/* flonum is not NaN */
 	val = RFLOAT_VALUE(recv) <= RFLOAT_VALUE(obj) ? Qtrue : Qfalse;
     }
@@ -1762,7 +1849,7 @@ opt_gt
 (VALUE val)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_GT, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idGT, Fixnum)) {
 	SIGNED_VALUE a = recv, b = obj;
 
 	if (a > b) {
@@ -1773,13 +1860,13 @@ opt_gt
 	}
     }
     else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_GT, FLOAT_REDEFINED_OP_FLAG)) {
+	     BASIC_OP_UNREDEFINED_P(idGT, Float)) {
 	/* flonum is not NaN */
 	val = RFLOAT_VALUE(recv) > RFLOAT_VALUE(obj) ? Qtrue : Qfalse;
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat && RBASIC_CLASS(obj) == rb_cFloat  &&
-	    BASIC_OP_UNREDEFINED_P(BOP_GT, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idGT, Float)) {
 	    val = double_cmp_gt(RFLOAT_VALUE(recv), RFLOAT_VALUE(obj));
 	}
 	else {
@@ -1806,7 +1893,7 @@ opt_ge
 (VALUE val)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_GE, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idGE, Fixnum)) {
 	SIGNED_VALUE a = recv, b = obj;
 
 	if (a >= b) {
@@ -1816,8 +1903,7 @@ opt_ge
 	    val = Qfalse;
 	}
     }
-    else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_GE, FLOAT_REDEFINED_OP_FLAG)) {
+    else if (FLONUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED_P(idGE, Float)) {
 	/* flonum is not NaN */
 	val = RFLOAT_VALUE(recv) >= RFLOAT_VALUE(obj) ? Qtrue : Qfalse;
     }
@@ -1841,11 +1927,11 @@ opt_ltlt
 {
     if (!SPECIAL_CONST_P(recv)) {
 	if (RBASIC_CLASS(recv) == rb_cString &&
-	    BASIC_OP_UNREDEFINED_P(BOP_LTLT, STRING_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idLTLT, String)) {
 	    val = rb_str_concat(recv, obj);
 	}
 	else if (RBASIC_CLASS(recv) == rb_cArray &&
-		 BASIC_OP_UNREDEFINED_P(BOP_LTLT, ARRAY_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idLTLT, Array)) {
 	    val = rb_ary_push(recv, obj);
 	}
 	else {
@@ -1872,10 +1958,10 @@ opt_aref
 (VALUE val)
 {
     if (!SPECIAL_CONST_P(recv)) {
-	if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) && FIXNUM_P(obj)) {
+	if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(idAREF, Array) && FIXNUM_P(obj)) {
 	    val = rb_ary_entry(recv, FIX2LONG(obj));
 	}
-	else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_AREF, HASH_REDEFINED_OP_FLAG)) {
+	else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(idAREF, Hash)) {
 	    val = rb_hash_aref(recv, obj);
 	}
 	else {
@@ -1902,11 +1988,11 @@ opt_aset
 (VALUE val)
 {
     if (!SPECIAL_CONST_P(recv)) {
-	if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_ASET, ARRAY_REDEFINED_OP_FLAG) && FIXNUM_P(obj)) {
+	if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(idASET, Array) && FIXNUM_P(obj)) {
 	    rb_ary_store(recv, FIX2LONG(obj), set);
 	    val = set;
 	}
-	else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_ASET, HASH_REDEFINED_OP_FLAG)) {
+	else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(idASET, Hash)) {
 	    rb_hash_aset(recv, obj, set);
 	    val = set;
 	}
@@ -1925,49 +2011,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()。
  */
@@ -1979,15 +2022,15 @@ opt_length
 {
     if (!SPECIAL_CONST_P(recv)) {
 	if (RBASIC_CLASS(recv) == rb_cString &&
-	    BASIC_OP_UNREDEFINED_P(BOP_LENGTH, STRING_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idLength, String)) {
 	    val = rb_str_length(recv);
 	}
 	else if (RBASIC_CLASS(recv) == rb_cArray &&
-		 BASIC_OP_UNREDEFINED_P(BOP_LENGTH, ARRAY_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idLength, Array)) {
 	    val = LONG2NUM(RARRAY_LEN(recv));
 	}
 	else if (RBASIC_CLASS(recv) == rb_cHash &&
-		 BASIC_OP_UNREDEFINED_P(BOP_LENGTH, HASH_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idLength, Hash)) {
 	    val = INT2FIX(RHASH_SIZE(recv));
 	}
 	else {
@@ -2014,15 +2057,15 @@ opt_size
 {
     if (!SPECIAL_CONST_P(recv)) {
 	if (RBASIC_CLASS(recv) == rb_cString &&
-	    BASIC_OP_UNREDEFINED_P(BOP_SIZE, STRING_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idSize, String)) {
 	    val = rb_str_length(recv);
 	}
 	else if (RBASIC_CLASS(recv) == rb_cArray &&
-		 BASIC_OP_UNREDEFINED_P(BOP_SIZE, ARRAY_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idSize, Array)) {
 	    val = LONG2NUM(RARRAY_LEN(recv));
 	}
 	else if (RBASIC_CLASS(recv) == rb_cHash &&
-		 BASIC_OP_UNREDEFINED_P(BOP_SIZE, HASH_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idSize, Hash)) {
 	    val = INT2FIX(RHASH_SIZE(recv));
 	}
 	else {
@@ -2049,17 +2092,17 @@ opt_empty_p
 {
     if (!SPECIAL_CONST_P(recv)) {
 	if (RBASIC_CLASS(recv) == rb_cString &&
-	    BASIC_OP_UNREDEFINED_P(BOP_EMPTY_P, STRING_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idEmptyP, String)) {
 	    if (RSTRING_LEN(recv) == 0) val = Qtrue;
 	    else val = Qfalse;
 	}
 	else if (RBASIC_CLASS(recv) == rb_cArray &&
-		 BASIC_OP_UNREDEFINED_P(BOP_EMPTY_P, ARRAY_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idEmptyP, Array)) {
 	    if (RARRAY_LEN(recv) == 0) val = Qtrue;
 	    else val = Qfalse;
 	}
 	else if (RBASIC_CLASS(recv) == rb_cHash &&
-		 BASIC_OP_UNREDEFINED_P(BOP_EMPTY_P, HASH_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idEmptyP, Hash)) {
 	    if (RHASH_EMPTY_P(recv)) val = Qtrue;
 	    else val = Qfalse;
 	}
@@ -2087,7 +2130,7 @@ opt_succ
 {
     if (SPECIAL_CONST_P(recv)) {
 	if (FIXNUM_P(recv) &&
-	    BASIC_OP_UNREDEFINED_P(BOP_SUCC, FIXNUM_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idSucc, Fixnum)) {
 	    const VALUE obj = INT2FIX(1);
 	    /* fixnum + INT2FIX(1) */
 	    val = (recv + (obj & (~1)));
@@ -2102,11 +2145,11 @@ opt_succ
     }
     else {
 	if (RBASIC_CLASS(recv) == rb_cString &&
-	    BASIC_OP_UNREDEFINED_P(BOP_SUCC, STRING_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idSucc, String)) {
 	    val = rb_str_succ(recv);
 	}
 	else if (RBASIC_CLASS(recv) == rb_cTime &&
-		 BASIC_OP_UNREDEFINED_P(BOP_SUCC, TIME_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idSucc, Time)) {
 	    val = rb_time_succ(recv);
 	}
 	else
@@ -2156,7 +2199,7 @@ opt_regexpmatch1
 (VALUE obj)
 (VALUE val)
 {
-    if (BASIC_OP_UNREDEFINED_P(BOP_MATCH, REGEXP_REDEFINED_OP_FLAG)) {
+    if (BASIC_OP_UNREDEFINED_P(idEqTilde, Regexp)) {
 	val = rb_reg_match(r, obj);
     }
     else {
@@ -2176,7 +2219,7 @@ opt_regexpmatch2
 (VALUE val)
 {
     if (CLASS_OF(obj2) == rb_cString &&
-	BASIC_OP_UNREDEFINED_P(BOP_MATCH, STRING_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idEqTilde, String)) {
 	val = rb_reg_match(obj1, obj2);
     }
     else {
diff --git a/template/opt_method.h.tmpl b/template/opt_method.h.tmpl
new file mode 100644
index 0000000..ccff31d
--- /dev/null
+++ b/template/opt_method.h.tmpl
@@ -0,0 +1,111 @@
+/* DO NOT EDIT THIS FILE DIRECTLY: edit template/opt_method.h.tmpl instead */
+#ifndef RUBY_OPT_METHOD_H
+#define RUBY_OPT_METHOD_H
+<%
+defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/opt_method.def")
+eval(File.read(defs), binding, defs)
+tmasks = []
+%>
+typedef uint<%= OM_ALIGN %>_t rb_om_bitmap_t;
+
+enum ruby_optimized_method {
+<%
+opt_masks = {}
+mask_classes = {}
+n = 0
+OPT_METHODS.each do |(mid, *classes)|
+  classes.each do |klass|
+    if Array === klass
+      opt_masks[mid] = klass.dup
+      # we will align these in the second loop, below
+      klass.each { |k| mask_classes[k] = true }
+      next
+    end %>
+    <%= om(mid, klass) %> = <%= n += 1 %>,
+<%
+  end # classes.each
+end # OPT_METHODS.each
+
+# align multi-class bits so a single AND operation may
+# be byte-aligned and used to check an mid for up to 7 classes at once:
+opt_masks.each do |mid, classes|
+  # round up n to the next aligned byte slot
+  n = (n + OM_ALIGN) & OM_ALIGN_MASK
+
+  classes.each do |k|
+%>
+    <%= om(mid, k) %> = <%= n += 1 %>,
+<%=
+# we need this macro to generate shifts for the masks enums below:
+"#define #{om(mid, k)} (#{n})"
+%>
+<%
+  end # classes.each
+end # opt_masks.each
+if n >= ((1 << OM_ALIGN) - 1)
+  raise "OM_ALIGN needs to be raised to support more optimized methods"
+end
+%>
+    OM_LAST_ = <%= om_last = (n += 1) %>, /* for bitmap sizing */
+    /* special mask values below */
+<%
+# generate mask enums
+opt_masks.each do |mid, c|
+  # n.b.: negate masks to simplify the rb_opt_method_is_mask check:
+%>
+    <%= om(mid, c) %> = -(<%=
+      # pack into 16 bits so it may be a negative Fixnum
+      # 1) 8 byte offset
+      # 2) OM_ALIGN bytes mask (8 or 16)
+      sep = "|\n    "
+      "/* offset: */ ((#{om(mid, c[0])} / #{OM_ALIGN}) << #{OM_ALIGN}) " \
+      "#{sep} /* mask: */ (" +
+      c.map { |k| "(1U << (#{om(mid, k)} % #{OM_ALIGN}))" }.join(sep) + # mask
+      ')'
+  %>),
+<%
+  # mask for type checking in insns.def, we name this like the OM_*
+  # enum so it is easy to get this name using CPP macros
+  tmasks << [
+    "OM_TMASK_#{c.join('_')}",
+    'INT2FIX(' +
+      c.map {|k| "(1U << RUBY_T_#{k.upcase})" }.join("|\\\n\t") +
+      ')'
+  ]
+end # opt_masks.each
+opt_classes.each_key do |k|
+  if IS_T_DATA[k]
+    tmasks << [ "OM_TMASK_#{k}", "rb_c#{k}" ]
+  else
+    tmasks << [ "OM_TMASK_#{k}", "INT2FIX(1U << RUBY_T_#{k.upcase})" ]
+  end
+end # opt_classes.each_key
+%>
+    OM_ALIGN_ = <%= OM_ALIGN %>,
+    OM_SIZE_ = <%= ((om_last + OM_ALIGN) & OM_ALIGN_MASK) / OM_ALIGN %>,
+    OM_GETMASK_ = (1 << OM_ALIGN_) - 1
+};
+
+/* macros */
+<% tmasks.each do |(k,v)| %>
+#define <%= k %> (<%= v %>)
+<% end %>
+
+/* map a raw type to the preferred (optimized) class */
+static inline VALUE
+rb_opt_method_class(enum ruby_value_type type)
+{
+    switch (type) {
+<%
+opt_classes.each_key do |k|
+  next if IS_T_DATA[k]
+%>
+      case RUBY_T_<%= k.upcase %>: return rb_c<%= k %>;
+<%
+end
+%>
+      default: return Qfalse;
+    }
+}
+
+#endif /* RUBY_OPT_METHOD_H */
diff --git a/template/opt_method.inc.tmpl b/template/opt_method.inc.tmpl
new file mode 100644
index 0000000..acbdc1a
--- /dev/null
+++ b/template/opt_method.inc.tmpl
@@ -0,0 +1,42 @@
+/* DO NOT EDIT THIS FILE DIRECTLY: edit template/opt_method.inc.tmpl instead */
+<%
+defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/opt_method.def")
+eval(File.read(defs), binding, defs)
+%>
+
+static void
+add_opt_method(st_table *tbl, VALUE klass, ID mid,
+		enum ruby_optimized_method om)
+{
+    rb_method_entry_t *me = rb_method_entry_at(klass, mid);
+
+    if (me && me->def && me->def->type == VM_METHOD_TYPE_CFUNC) {
+	st_insert(tbl, (st_data_t)me, (st_data_t)om);
+    }
+    else if (mid != idNeq) {
+	rb_bug("undefined optimized method: %s", rb_id2name(mid));
+    }
+}
+
+static void
+vm_init_redefined_flags(void *tbl)
+{
+<%
+OPT_METHODS.each do |(mid, *classes)|
+  classes.flatten.each do |klass|
+%>
+    add_opt_method(tbl, rb_c<%= klass %>, <%= mid %>, <%= om(mid, klass) %>);
+<%
+  end # classes.each
+end # OPT_METHODS.each
+%>
+}
+
+static int
+vm_redefinition_check_flag(VALUE klass)
+{
+<% opt_classes.each_key do |klass| %>
+    if (klass == rb_c<%= klass %>) return 1;
+<% end %>
+    return 0;
+}
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 6b6d918..55f73c1 100644
--- a/test/ruby/envutil.rb
+++ b/test/ruby/envutil.rb
@@ -552,6 +552,16 @@ eom
         values
       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_optimization.rb b/test/ruby/test_optimization.rb
index 129f62a..63ed22b 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -28,6 +28,13 @@ class TestRubyOptimization < Test::Unit::TestCase
     end;
   end
 
+  def assert_no_allocation(mesg = "", adjust = 0)
+    before = GC.stat(:total_allocated_objects)
+    yield
+    after = GC.stat(:total_allocated_objects)
+    assert_equal before, after - adjust, mesg
+  end
+
   def test_fixnum_plus
     a, b = 1, 2
     assert_equal 3, a + b
@@ -104,6 +111,11 @@ class TestRubyOptimization < Test::Unit::TestCase
     assert_equal "x", "" + "x"
     assert_equal "ab", "a" + "b"
     assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"')
+    require_compile_option(:peephole_optimization)
+    assert_no_allocation("String#+", 1) { "a" + "b" }
+    s = ""
+    assert_no_allocation("String#+", 1) { "b" + s }
+    assert_no_allocation("String#+", 1) { s + "b" }
   end
 
   def test_string_succ
@@ -114,15 +126,19 @@ class TestRubyOptimization < Test::Unit::TestCase
   def test_string_format
     assert_equal '2', '%d' % 2
     assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2')
+    require_compile_option(:peephole_optimization)
+    assert_no_allocation("String#%", 1) { '%d' % 2 }
   end
 
   def test_string_freeze
-    assert_equal "foo", "foo".freeze
     assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze')
+    require_compile_option(:peephole_optimization)
+    assert_no_allocation { 5.times { "".freeze } }
+    assert_equal "foo", "foo".freeze
   end
 
-  def test_string_eq_neq
-    %w(== !=).each do |m|
+  def test_string_eq_neq_eqq
+    %w(== != ==).each do |m|
       assert_redefine_method('String', m, <<-end)
         assert_equal :b, ("a" #{m} "b").to_sym
         b = 'b'
@@ -138,6 +154,11 @@ class TestRubyOptimization < Test::Unit::TestCase
     assert_equal "x", "" << "x"
     assert_equal "ab", "a" << "b"
     assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"')
+    require_compile_option(:peephole_optimization)
+    assert_no_allocation("String#<<", 1) { "a" << "b" }
+    s = ""
+    assert_no_allocation("String#<<", 1) { "a" << s }
+    assert_no_allocation("String#<<") { s << "b" }
   end
 
   def test_array_plus
@@ -253,4 +274,148 @@ class TestRubyOptimization < Test::Unit::TestCase
     EOF
     assert_equal(123, delay { 123 }.call, bug6901)
   end
+
+  def test_string_opt_str_lit_1_syntax
+    require_compile_option(:peephole_optimization)
+    s = "a"
+    res = true
+    assert_no_allocation do
+      res &&= s == "a"
+      res &&= "a" == s
+      res &&= !(s == "b")
+      res &&= !("b" == s)
+      res &&= s != "b"
+      res &&= "b" != s
+      res &&= "a" === s
+      res &&= s === "a"
+      res &&= !("b" === s)
+      res &&= !(s === "b")
+    end
+    assert_equal true, res, res.inspect
+  end
+
+  def test_string_opt_str_lit_methods
+    require_compile_option(:peephole_optimization)
+    {
+      split: 3,
+      count: 0,
+      include?: 0,
+      chomp: 1,
+      chomp!: 0,
+      delete!: 0,
+      squeeze: 1,
+      squeeze!: 0,
+      index: 0,
+      rindex: 0,
+      start_with?: 0,
+      end_with?: 0,
+      partition: 3,
+      rpartition: 3,
+      casecmp: 0,
+      match: 7, # opt_str_lit only saved one allocation here...
+    }.each do |k,v|
+      eval <<-EOF
+      s = 'a=b'
+      assert_no_allocation('String##{k}', #{v}) { s.#{k}("=") }
+      assert_redefine_method('String', '#{k}', 'assert_nil "".#{k}(nil)')
+      EOF
+    end
+
+    {
+      tr!: 0,
+      tr_s!: 0,
+      tr: 1,
+      tr_s: 1,
+    }.each do |k,v|
+      eval <<-EOF
+      s = 'a=b'
+      assert_no_allocation('String##{k}', #{v}) { s.#{k}("=", "!") }
+      assert_redefine_method('String', '#{k}', 'assert_nil "".#{k}(nil)')
+      EOF
+    end
+
+    # String#encode(dst)
+    s = ''
+    assert_no_allocation('String#encode', 1) { s.encode("utf-8") }
+    assert_no_allocation('String#encode!') { s.encode!("utf-8") }
+    assert_no_allocation('String#force_encoding') { s.force_encoding("utf-8") }
+
+    # String#encode{,!}(dst, src)
+    s = ''.b
+    assert_no_allocation('String#encode', 1) { s.encode("utf-8", "binary") }
+    assert_no_allocation('String#encode!') { s.encode!("utf-8", "binary") }
+
+    assert_no_allocation('String#insert') { s.insert(0, "a") }
+    %w(encode encode! force_encoding insert).each do |m|
+      assert_redefine_method('String', m, "assert_nil ''.#{m}")
+    end
+
+
+  end
+
+  def test_array_opt_str_lit_methods
+    require_compile_option(:peephole_optimization)
+    {
+      join: 1,
+      delete: 0,
+      include?: 0,
+    }.each do |k,v|
+      eval <<-EOF
+      x = %w(a b)
+      assert_no_allocation('Array##{k}', #{v}) { x.#{k}("m") }
+      assert_redefine_method('Array', '#{k}', 'assert_nil [].#{k}(nil)')
+      EOF
+    end
+
+    x = %w(a b)
+    assert_no_allocation('Array#pack', 1) { x.pack('m') }
+    # assert_separately uses Array#pack, so we must use assert_normal_exit:
+    assert_ruby_status(%w(--disable-gems), <<-EOF, "Array#pack redefine")
+      class Array
+        undef pack
+        def pack(*args); true; end
+      end
+      exit(%w(a b).pack(false))
+    EOF
+  end
+
+  def test_hash_opt_str_lit_methods
+    require_compile_option(:peephole_optimization)
+    {
+      delete: 0,
+      include?: 0,
+      member?: 0,
+      has_key?: 0,
+      key?: 0,
+      fetch: 0,
+      # TODO: more more methods, maybe assoc/rassoc
+    }.each do |k,v|
+      eval <<-EOF
+      x = {"x" => 1}
+      assert_no_allocation('Hash##{k}') { x.#{k}("x") }
+      assert_redefine_method('Hash', '#{k}', 'assert_nil({}.#{k}(nil))')
+      EOF
+    end
+  end
+
+  def test_time_opt_str_lit
+    require_compile_option(:peephole_optimization)
+    t = Time.now
+    t.strftime("%Y") # initialize
+    n = 5
+
+    case 1.size
+    when 4
+      adjust = 2
+    when 8
+      adjust = 3
+    else
+      skip "unsupported word size"
+    end
+    assert_no_allocation("Time#strftime", n * adjust) {
+      n.times { t.strftime("%Y") }
+    }
+    assert_redefine_method('Time', 'strftime',
+                           'assert_equal "%Y", Time.now.strftime("%Y")')
+  end
 end
diff --git a/vm.c b/vm.c
index f478d26..b20f13c 100644
--- a/vm.c
+++ b/vm.c
@@ -20,6 +20,7 @@
 #include "eval_intern.h"
 #include "probes.h"
 #include "probes_helper.h"
+#include "opt_method.inc"
 
 static inline VALUE *
 VM_EP_LEP(VALUE *ep)
@@ -1169,30 +1170,16 @@ rb_iter_break_value(VALUE val)
 
 static st_table *vm_opt_method_table = 0;
 
-static int
-vm_redefinition_check_flag(VALUE klass)
-{
-    if (klass == rb_cFixnum) return FIXNUM_REDEFINED_OP_FLAG;
-    if (klass == rb_cFloat)  return FLOAT_REDEFINED_OP_FLAG;
-    if (klass == rb_cString) return STRING_REDEFINED_OP_FLAG;
-    if (klass == rb_cArray)  return ARRAY_REDEFINED_OP_FLAG;
-    if (klass == rb_cHash)   return HASH_REDEFINED_OP_FLAG;
-    if (klass == rb_cBignum) return BIGNUM_REDEFINED_OP_FLAG;
-    if (klass == rb_cSymbol) return SYMBOL_REDEFINED_OP_FLAG;
-    if (klass == rb_cTime)   return TIME_REDEFINED_OP_FLAG;
-    if (klass == rb_cRegexp) return REGEXP_REDEFINED_OP_FLAG;
-    return 0;
-}
-
 static void
 rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass)
 {
-    st_data_t bop;
+    st_data_t om;
     if (!me->def || me->def->type == VM_METHOD_TYPE_CFUNC) {
-	if (st_lookup(vm_opt_method_table, (st_data_t)me, &bop)) {
-	    int flag = vm_redefinition_check_flag(klass);
+	if (st_lookup(vm_opt_method_table, (st_data_t)me, &om)) {
+	    unsigned int i = om / OM_ALIGN_;
+	    rb_om_bitmap_t mask = (rb_om_bitmap_t)(1U << (om % OM_ALIGN_));
 
-	    ruby_vm_redefined_flag[bop] |= flag;
+	    ruby_vm_redefined_flag[i] |= mask;
 	}
     }
 }
@@ -1219,51 +1206,11 @@ rb_vm_check_redefinition_by_prepend(VALUE klass)
 }
 
 static void
-add_opt_method(VALUE klass, ID mid, VALUE bop)
-{
-    rb_method_entry_t *me = rb_method_entry_at(klass, mid);
-
-    if (me && me->def &&
-	me->def->type == VM_METHOD_TYPE_CFUNC) {
-	st_insert(vm_opt_method_table, (st_data_t)me, (st_data_t)bop);
-    }
-    else {
-	rb_bug("undefined optimized method: %s", rb_id2name(mid));
-    }
-}
-
-static void
 vm_init_redefined_flag(void)
 {
-    ID mid;
-    VALUE bop;
-
     vm_opt_method_table = st_init_numtable();
 
-#define OP(mid_, bop_) (mid = id##mid_, bop = BOP_##bop_, ruby_vm_redefined_flag[bop] = 0)
-#define C(k) add_opt_method(rb_c##k, mid, bop)
-    OP(PLUS, PLUS), (C(Fixnum), C(Float), C(String), C(Array));
-    OP(MINUS, MINUS), (C(Fixnum), C(Float));
-    OP(MULT, MULT), (C(Fixnum), C(Float));
-    OP(DIV, DIV), (C(Fixnum), C(Float));
-    OP(MOD, MOD), (C(Fixnum), C(Float));
-    OP(Eq, EQ), (C(Fixnum), C(Float), C(String));
-    OP(Eqq, EQQ), (C(Fixnum), C(Bignum), C(Float), C(Symbol), C(String));
-    OP(LT, LT), (C(Fixnum), C(Float));
-    OP(LE, LE), (C(Fixnum), C(Float));
-    OP(GT, GT), (C(Fixnum), C(Float));
-    OP(GE, GE), (C(Fixnum), C(Float));
-    OP(LTLT, LTLT), (C(String), C(Array));
-    OP(AREF, AREF), (C(Array), C(Hash));
-    OP(ASET, ASET), (C(Array), C(Hash));
-    OP(Length, LENGTH), (C(Array), C(String), C(Hash));
-    OP(Size, SIZE), (C(Array), C(String), C(Hash));
-    OP(EmptyP, EMPTY_P), (C(Array), C(String), C(Hash));
-    OP(Succ, SUCC), (C(Fixnum), C(String), C(Time));
-    OP(EqTilde, MATCH), (C(Regexp), C(String));
-    OP(Freeze, FREEZE), (C(String));
-#undef C
-#undef OP
+    vm_init_redefined_flags(vm_opt_method_table); /* opt_method.h.tmpl */
 }
 
 /* for vm development */
diff --git a/vm_core.h b/vm_core.h
index f8494d0..1382d3c 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -24,6 +24,7 @@
 #include "method.h"
 #include "ruby_atomic.h"
 #include "ccan/list/list.h"
+#include "opt_method.h"
 
 #include "ruby/thread_native.h"
 #if   defined(_WIN32)
@@ -328,33 +329,6 @@ enum ruby_special_exceptions {
     ruby_special_error_count
 };
 
-enum ruby_basic_operators {
-    BOP_PLUS,
-    BOP_MINUS,
-    BOP_MULT,
-    BOP_DIV,
-    BOP_MOD,
-    BOP_EQ,
-    BOP_EQQ,
-    BOP_LT,
-    BOP_LE,
-    BOP_LTLT,
-    BOP_AREF,
-    BOP_ASET,
-    BOP_LENGTH,
-    BOP_SIZE,
-    BOP_EMPTY_P,
-    BOP_SUCC,
-    BOP_GT,
-    BOP_GE,
-    BOP_NOT,
-    BOP_NEQ,
-    BOP_MATCH,
-    BOP_FREEZE,
-
-    BOP_LAST_
-};
-
 #define GetVMPtr(obj, ptr) \
   GetCoreDataFromValue((obj), rb_vm_t, (ptr))
 
@@ -449,7 +423,7 @@ typedef struct rb_vm_struct {
 	size_t fiber_machine_stack_size;
     } default_params;
 
-    short redefined_flag[BOP_LAST_];
+    rb_om_bitmap_t redefined_flag[OM_SIZE_];
 } rb_vm_t;
 
 /* default values */
@@ -466,18 +440,8 @@ typedef struct rb_vm_struct {
 #define RUBY_VM_FIBER_MACHINE_STACK_SIZE      (  64 * 1024 * sizeof(VALUE)) /*  256 KB or  512 KB */
 #define RUBY_VM_FIBER_MACHINE_STACK_SIZE_MIN  (  16 * 1024 * sizeof(VALUE)) /*   64 KB or  128 KB */
 
-/* optimize insn */
-#define FIXNUM_REDEFINED_OP_FLAG (1 << 0)
-#define FLOAT_REDEFINED_OP_FLAG  (1 << 1)
-#define STRING_REDEFINED_OP_FLAG (1 << 2)
-#define ARRAY_REDEFINED_OP_FLAG  (1 << 3)
-#define HASH_REDEFINED_OP_FLAG   (1 << 4)
-#define BIGNUM_REDEFINED_OP_FLAG (1 << 5)
-#define SYMBOL_REDEFINED_OP_FLAG (1 << 6)
-#define TIME_REDEFINED_OP_FLAG   (1 << 7)
-#define REGEXP_REDEFINED_OP_FLAG (1 << 8)
-
-#define BASIC_OP_UNREDEFINED_P(op, klass) (LIKELY((GET_VM()->redefined_flag[(op)]&(klass)) == 0))
+#define BASIC_OP_UNREDEFINED_P(mid, klass) \
+	rb_basic_op_unredefined_p(OM_##mid##__##klass)
 
 #ifndef VM_DEBUG_BP_CHECK
 #define VM_DEBUG_BP_CHECK 0
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 95d232e..6fce0d3 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -847,17 +847,17 @@ VALUE
 opt_eq_func(VALUE recv, VALUE obj, CALL_INFO ci)
 {
     if (FIXNUM_2_P(recv, obj) &&
-	BASIC_OP_UNREDEFINED_P(BOP_EQ, FIXNUM_REDEFINED_OP_FLAG)) {
+	BASIC_OP_UNREDEFINED_P(idEq, Fixnum)) {
 	return (recv == obj) ? Qtrue : Qfalse;
     }
     else if (FLONUM_2_P(recv, obj) &&
-	     BASIC_OP_UNREDEFINED_P(BOP_EQ, FLOAT_REDEFINED_OP_FLAG)) {
+	     BASIC_OP_UNREDEFINED_P(idEq, Float)) {
 	return (recv == obj) ? Qtrue : Qfalse;
     }
     else if (!SPECIAL_CONST_P(recv) && !SPECIAL_CONST_P(obj)) {
 	if (RBASIC_CLASS(recv) == rb_cFloat &&
 	    RBASIC_CLASS(obj) == rb_cFloat &&
-	    BASIC_OP_UNREDEFINED_P(BOP_EQ, FLOAT_REDEFINED_OP_FLAG)) {
+	    BASIC_OP_UNREDEFINED_P(idEq, Float)) {
 	    double a = RFLOAT_VALUE(recv);
 	    double b = RFLOAT_VALUE(obj);
 
@@ -868,7 +868,7 @@ opt_eq_func(VALUE recv, VALUE obj, CALL_INFO ci)
 	}
 	else if (RBASIC_CLASS(recv) == rb_cString &&
 		 RBASIC_CLASS(obj) == rb_cString &&
-		 BASIC_OP_UNREDEFINED_P(BOP_EQ, STRING_REDEFINED_OP_FLAG)) {
+		 BASIC_OP_UNREDEFINED_P(idEq, String)) {
 	    return rb_str_equal(recv, obj);
 	}
     }
diff --git a/vm_insnhelper.h b/vm_insnhelper.h
index 31f8ffc..51dd658 100644
--- a/vm_insnhelper.h
+++ b/vm_insnhelper.h
@@ -229,5 +229,32 @@ enum vm_regan_acttype {
 static VALUE make_no_method_exception(VALUE exc, const char *format,
 				      VALUE obj, int argc, const VALUE *argv);
 
+static inline int
+rb_basic_op_unredefined_p(enum ruby_optimized_method om)
+{
+    unsigned int i = om / OM_ALIGN_;
+    rb_om_bitmap_t mask = (rb_om_bitmap_t)(1U << (om % OM_ALIGN_));
+
+    return LIKELY((GET_VM()->redefined_flag[i] & mask) == 0);
+}
+
+static inline int
+rb_basic_mask_unredefined_p(enum ruby_optimized_method om)
+{
+    unsigned int uom = (unsigned int)-om;
+    unsigned int offset = 0xffU & (uom >> OM_ALIGN_);
+    rb_om_bitmap_t mask = (rb_om_bitmap_t)(OM_GETMASK_ & uom);
+
+    return LIKELY((GET_VM()->redefined_flag[offset] & mask) == 0);
+}
+
+static inline int
+rb_opt_method_is_mask(enum ruby_optimized_method om)
+{
+    return !!((int)om < 0);
+}
+
+/* compile.c */
+void rb_undo_opt_str_lit(rb_control_frame_t *cfp);
 
 #endif /* RUBY_INSNHELPER_H */
-- 
EW

