Cache template literal callSiteObj
authorcaitpotter88 <caitpotter88@gmail.com>
Thu, 20 Nov 2014 22:37:31 +0000 (14:37 -0800)
committerCommit bot <commit-bot@chromium.org>
Thu, 20 Nov 2014 22:37:55 +0000 (22:37 +0000)
BUG=v8:3230
LOG=y

Review URL: https://codereview.chromium.org/742643003

Cr-Commit-Position: refs/heads/master@{#25450}

src/harmony-templates.js
src/parser.cc
src/parser.h
test/mjsunit/harmony/templates.js

index 712d8d3..41ce838 100644 (file)
@@ -4,13 +4,59 @@
 
 'use strict';
 
-function GetTemplateCallSite(siteObj, rawStrings) {
-  // TODO(caitp): ensure same template callsite is used for subsequent tag calls
+var callSiteCache = new $Map;
+
+function SameCallSiteElements(rawStrings, other) {
+  var length = rawStrings.length;
+  var other = other.raw;
+
+  if (length !== other.length) return false;
+
+  for (var i = 0; i < length; ++i) {
+    if (rawStrings[i] !== other[i]) return false;
+  }
+
+  return true;
+}
+
+
+function GetCachedCallSite(siteObj, hash) {
+  var obj = %MapGet(callSiteCache, hash);
+
+  if (IS_UNDEFINED(obj)) return;
+
+  var length = obj.length;
+  for (var i = 0; i < length; ++i) {
+    if (SameCallSiteElements(siteObj, obj[i])) return obj[i];
+  }
+}
+
+
+function SetCachedCallSite(siteObj, hash) {
+  var obj = %MapGet(callSiteCache, hash);
+  var array;
+
+  if (IS_UNDEFINED(obj)) {
+    array = new InternalArray(1);
+    array[0] = siteObj;
+    %MapSet(callSiteCache, hash, array);
+  } else {
+    obj.push(siteObj);
+  }
+
+  return siteObj;
+}
+
+
+function GetTemplateCallSite(siteObj, rawStrings, hash) {
+  var cached = GetCachedCallSite(rawStrings, hash);
+
+  if (!IS_UNDEFINED(cached)) return cached;
 
   %AddNamedProperty(siteObj, "raw", %ObjectFreeze(rawStrings),
       READ_ONLY | DONT_ENUM | DONT_DELETE);
 
-  return %ObjectFreeze(siteObj);
+  return SetCachedCallSite(%ObjectFreeze(siteObj), hash);
 }
 
 
index f716c41..e6c0e26 100644 (file)
@@ -5240,7 +5240,8 @@ Expression* Parser::CloseTemplateLiteral(TemplateLiteralState* state, int start,
     }
     return expr;
   } else {
-    ZoneList<Expression*>* raw_strings = TemplateRawStrings(lit);
+    uint32_t hash;
+    ZoneList<Expression*>* raw_strings = TemplateRawStrings(lit, &hash);
     Handle<String> source(String::cast(script()->source()));
 
     int cooked_idx = function_state_->NextMaterializedLiteralIndex();
@@ -5256,6 +5257,11 @@ Expression* Parser::CloseTemplateLiteral(TemplateLiteralState* state, int start,
         factory()->NewArrayLiteral(
             const_cast<ZoneList<Expression*>*>(raw_strings), raw_idx, pos),
         zone());
+
+    // Ensure hash is suitable as an Smi value
+    Smi* hash_obj = Smi::cast(Internals::IntToSmi(static_cast<int>(hash)));
+    args->Add(factory()->NewSmiLiteral(hash_obj->value(), pos), zone());
+
     this->CheckPossibleEvalCall(tag, scope_);
     Expression* call_site = factory()->NewCallRuntime(
         ast_value_factory()->get_template_callsite_string(), NULL, args, start);
@@ -5270,7 +5276,8 @@ Expression* Parser::CloseTemplateLiteral(TemplateLiteralState* state, int start,
 }
 
 
-ZoneList<Expression*>* Parser::TemplateRawStrings(const TemplateLiteral* lit) {
+ZoneList<Expression*>* Parser::TemplateRawStrings(const TemplateLiteral* lit,
+                                                  uint32_t* hash) {
   const ZoneList<int>* lengths = lit->lengths();
   const ZoneList<Expression*>* cooked_strings = lit->cooked();
   int total = lengths->length();
@@ -5290,12 +5297,27 @@ ZoneList<Expression*>* Parser::TemplateRawStrings(const TemplateLiteral* lit) {
 
   raw_strings = new (zone()) ZoneList<Expression*>(total, zone());
 
+  int num_hash_chars = (total - 1) * 3;
+  for (int index = 0; index < total; ++index) {
+    // Allow about length * 4 to handle most UTF8 sequences.
+    num_hash_chars += lengths->at(index) * 4;
+  }
+
+  Vector<uint8_t> hash_string = Vector<uint8_t>::New(num_hash_chars);
+  num_hash_chars = 0;
+
   for (int index = 0; index < total; ++index) {
     int span_start = cooked_strings->at(index)->position() + 1;
     int span_end = lengths->at(index) - 1;
     int length;
     int to_index = 0;
 
+    if (index) {
+      hash_string[num_hash_chars++] = '$';
+      hash_string[num_hash_chars++] = '{';
+      hash_string[num_hash_chars++] = '}';
+    }
+
     SmartArrayPointer<char> raw_chars =
         source->ToCString(ALLOW_NULLS, FAST_STRING_TRAVERSAL, span_start,
                           span_end, &length);
@@ -5310,6 +5332,7 @@ ZoneList<Expression*>* Parser::TemplateRawStrings(const TemplateLiteral* lit) {
           ++from_index;
         }
       }
+      hash_string[num_hash_chars++] = ch;
       raw_chars[to_index++] = ch;
     }
 
@@ -5319,6 +5342,12 @@ ZoneList<Expression*>* Parser::TemplateRawStrings(const TemplateLiteral* lit) {
     raw_strings->Add(raw_lit, zone());
   }
 
+  hash_string.Truncate(num_hash_chars);
+  int utf16_length;
+  *hash = StringHasher::ComputeUtf8Hash(Vector<const char>::cast(hash_string),
+      num_hash_chars, &utf16_length);
+  hash_string.Dispose();
+
   return raw_strings;
 }
 } }  // namespace v8::internal
index dba829d..23acab6 100644 (file)
@@ -884,7 +884,8 @@ class Parser : public ParserBase<ParserTraits> {
                              Expression* expression);
   Expression* CloseTemplateLiteral(TemplateLiteralState* state, int start,
                                    Expression* tag);
-  ZoneList<Expression*>* TemplateRawStrings(const TemplateLiteral* lit);
+  ZoneList<Expression*>* TemplateRawStrings(const TemplateLiteral* lit,
+                                            uint32_t* hash);
   Scanner scanner_;
   PreParser* reusable_preparser_;
   Scope* original_scope_;  // for ES5 function declarations in sloppy eval
index 7c63ec9..3baf05e 100644 (file)
@@ -338,6 +338,75 @@ var obj = {
 })();
 
 
+(function testCallSiteCaching() {
+  var callSites = [];
+  function tag(cs) { callSites.push(cs); }
+  var a = 1;
+  var b = 2;
+
+  tag`head${a}tail`;
+  tag`head${b}tail`;
+
+  assertEquals(2, callSites.length);
+  assertSame(callSites[0], callSites[1]);
+
+  eval("tag`head${a}tail`");
+  assertEquals(3, callSites.length);
+  assertSame(callSites[1], callSites[2]);
+
+  eval("tag`head${b}tail`");
+  assertEquals(4, callSites.length);
+  assertSame(callSites[2], callSites[3]);
+
+  (new Function("tag", "a", "b", "return tag`head${a}tail`;"))(tag, 1, 2);
+  assertEquals(5, callSites.length);
+  assertSame(callSites[3], callSites[4]);
+
+  (new Function("tag", "a", "b", "return tag`head${b}tail`;"))(tag, 1, 2);
+  assertEquals(6, callSites.length);
+  assertSame(callSites[4], callSites[5]);
+
+  callSites = [];
+
+  tag`foo${a}bar`;
+  tag`foo\${.}bar`;
+  assertEquals(2, callSites.length);
+  assertEquals(2, callSites[0].length);
+  assertEquals(1, callSites[1].length);
+
+  callSites = [];
+
+  eval("tag`\\\r\n\\\n\\\r`");
+  eval("tag`\\\r\n\\\n\\\r`");
+  assertEquals(2, callSites.length);
+  assertSame(callSites[0], callSites[1]);
+  assertEquals("", callSites[0][0]);
+  assertEquals("\\\n\\\n\\\n", callSites[0].raw[0]);
+
+  callSites = [];
+
+  tag`\uc548\ub155`;
+  tag`\uc548\ub155`;
+  assertEquals(2, callSites.length);
+  assertSame(callSites[0], callSites[1]);
+  assertEquals("안녕", callSites[0][0]);
+  assertEquals("\\uc548\\ub155", callSites[0].raw[0]);
+
+  callSites = [];
+
+  tag`\uc548\ub155`;
+  tag`안녕`;
+  assertEquals(2, callSites.length);
+  assertTrue(callSites[0] !== callSites[1]);
+  assertEquals("안녕", callSites[0][0]);
+  assertEquals("\\uc548\\ub155", callSites[0].raw[0]);
+  assertEquals("안녕", callSites[1][0]);
+  // TODO(caitp, arv): blocked on correctly generating raw strings from
+  // multi-byte UTF8.
+  // assertEquals("안녕", callSites[1].raw[0]);
+})();
+
+
 (function testExtendedArrayPrototype() {
   Object.defineProperty(Array.prototype, 0, {
     set: function() {