Use Copy-on-write arrays for cached regexp results.
authorlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 20 Aug 2010 09:37:22 +0000 (09:37 +0000)
committerlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 20 Aug 2010 09:37:22 +0000 (09:37 +0000)
Review URL: http://codereview.chromium.org/3158020

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5310 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

17 files changed:
src/arm/codegen-arm.cc
src/arm/codegen-arm.h
src/arm/full-codegen-arm.cc
src/arm/simulator-arm.cc
src/codegen.h
src/full-codegen.cc
src/full-codegen.h
src/ia32/codegen-ia32.cc
src/ia32/codegen-ia32.h
src/ia32/full-codegen-ia32.cc
src/regexp.js
src/runtime.cc
src/runtime.h
src/x64/codegen-x64.cc
src/x64/codegen-x64.h
src/x64/full-codegen-x64.cc
test/mjsunit/regexp.js

index 675c5867b85069782474b2b9f0d761d11547f885..aca6b4f3cdeefb1f64668628125c7e3d6080c805 100644 (file)
@@ -5265,6 +5265,67 @@ void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) {
 }
 
 
+void CodeGenerator::GenerateRegExpCloneResult(ZoneList<Expression*>* args) {
+  ASSERT_EQ(1, args->length());
+
+  Load(args->at(0));
+  frame_->PopToR0();
+  {
+    VirtualFrame::SpilledScope spilled_scope(frame_);
+
+    Label done;
+    Label call_runtime;
+    __ BranchOnSmi(r0, &done);
+
+    // Load JSRegExp map into r1. Check that argument object has this map.
+    // Arguments to this function should be results of calling RegExp exec,
+    // which is either an unmodified JSRegExpResult or null. Anything not having
+    // the unmodified JSRegExpResult map is returned unmodified.
+    // This also ensures that elements are fast.
+
+    __ ldr(r1, ContextOperand(cp, Context::GLOBAL_INDEX));
+    __ ldr(r1, FieldMemOperand(r1, GlobalObject::kGlobalContextOffset));
+    __ ldr(r1, ContextOperand(r1, Context::REGEXP_RESULT_MAP_INDEX));
+    __ ldr(ip, FieldMemOperand(r0, HeapObject::kMapOffset));
+    __ cmp(r1, Operand(ip));
+    __ b(ne, &done);
+
+    // All set, copy the contents to a new object.
+    __ AllocateInNewSpace(JSRegExpResult::kSize,
+                          r2,
+                          r3,
+                          r4,
+                          &call_runtime,
+                          NO_ALLOCATION_FLAGS);
+    // Store RegExpResult map as map of allocated object.
+    ASSERT(JSRegExpResult::kSize == 6 * kPointerSize);
+    // Copy all fields (map is already in r1) from (untagged) r0 to r2.
+    // Change map of elements array (ends up in r4) to be a FixedCOWArray.
+    __ bic(r0, r0, Operand(kHeapObjectTagMask));
+    __ ldm(ib, r0, r3.bit() | r4.bit() | r5.bit() | r6.bit() | r7.bit());
+    __ stm(ia, r2,
+           r1.bit() | r3.bit() | r4.bit() | r5.bit() | r6.bit() | r7.bit());
+    ASSERT(!Heap::InNewSpace(Heap::fixed_cow_array_map()));
+    ASSERT(JSRegExp::kElementsOffset == 2 * kPointerSize);
+    // Check whether elements array is empty fixed array, and otherwise make
+    // it copy-on-write (it never should be empty unless someone is messing
+    // with the arguments to the runtime function).
+    __ LoadRoot(ip, Heap::kEmptyFixedArrayRootIndex);
+    __ add(r0, r2, Operand(kHeapObjectTag));  // Tag result and move it to r0.
+    __ cmp(r4, ip);
+    __ b(eq, &done);
+    __ LoadRoot(ip, Heap::kFixedCOWArrayMapRootIndex);
+    __ str(ip, FieldMemOperand(r4, HeapObject::kMapOffset));
+    __ b(&done);
+    __ bind(&call_runtime);
+    __ push(r0);
+    __ CallRuntime(Runtime::kRegExpCloneResult, 1);
+    __ bind(&done);
+  }
+  frame_->EmitPush(r0);
+}
+
+
 class DeferredSearchCache: public DeferredCode {
  public:
   DeferredSearchCache(Register dst, Register cache, Register key)
index 8a632a9c78cecd9d3efddaedb5d7ba74622004f2..e550a62c33e26a7d2e22f245b6030979dd98f4eb 100644 (file)
@@ -528,6 +528,8 @@ class CodeGenerator: public AstVisitor {
 
   void GenerateRegExpConstructResult(ZoneList<Expression*>* args);
 
+  void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
+
   // Support for fast native caches.
   void GenerateGetFromCache(ZoneList<Expression*>* args);
 
index f3adab2814eb76e20d50b894b414665d2ccb64aa..218132418288dccc986ff3037427bed6c727d972 100644 (file)
@@ -2536,6 +2536,14 @@ void FullCodeGenerator::EmitRegExpConstructResult(ZoneList<Expression*>* args) {
 }
 
 
+void FullCodeGenerator::EmitRegExpCloneResult(ZoneList<Expression*>* args) {
+  ASSERT(args->length() == 1);
+  VisitForValue(args->at(0), kStack);
+  __ CallRuntime(Runtime::kRegExpConstructResult, 1);
+  Apply(context_, r0);
+}
+
+
 void FullCodeGenerator::EmitSwapElements(ZoneList<Expression*>* args) {
   ASSERT(args->length() == 3);
   VisitForValue(args->at(0), kStack);
index c4cc8d46cb9cbada94e58c4577513e140bbb8811..c7fc13f8132ab44c1ba14abeac5d9e2887a9f4a4 100644 (file)
@@ -727,6 +727,10 @@ void Simulator::set_register(int reg, int32_t value) {
 // the special case of accessing the PC register.
 int32_t Simulator::get_register(int reg) const {
   ASSERT((reg >= 0) && (reg < num_registers));
+  // Stupid code added to avoid bug in GCC.
+  // See: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43949
+  if (reg >= num_registers) return 0;
+  // End stupid code.
   return registers_[reg] + ((reg == pc) ? Instr::kPCReadOffset : 0);
 }
 
@@ -1378,7 +1382,9 @@ void Simulator::HandleRList(Instr* instr, bool load) {
     }
     case 3: {
       // Print("ib");
-      UNIMPLEMENTED();
+      start_address = rn_val + 4;
+      end_address = rn_val + (num_regs * 4);
+      rn_val = end_address;
       break;
     }
     default: {
index 353e18656a249fb4d6dde4eebbdf4894ecdc5cfa..56c175e43a55d03bfc6caae0e5a455b7a0435618 100644 (file)
@@ -108,6 +108,7 @@ enum UncatchableExceptionType { OUT_OF_MEMORY, TERMINATION };
   F(StringCompare, 2, 1)                                                     \
   F(RegExpExec, 4, 1)                                                        \
   F(RegExpConstructResult, 3, 1)                                             \
+  F(RegExpCloneResult, 1, 1)                                                 \
   F(GetFromCache, 2, 1)                                                      \
   F(NumberToString, 1, 1)                                                    \
   F(SwapElements, 3, 1)                                                      \
index e97ed76072042647def2fbb181764bca30d4ac9f..cd5db80a98e3e0122a3dcc67af4c8dcb79c831ee 100644 (file)
@@ -851,80 +851,17 @@ void FullCodeGenerator::SetSourcePosition(int pos) {
 
 void FullCodeGenerator::EmitInlineRuntimeCall(CallRuntime* expr) {
   Handle<String> name = expr->name();
-  if (strcmp("_IsSmi", *name->ToCString()) == 0) {
-    EmitIsSmi(expr->arguments());
-  } else if (strcmp("_IsNonNegativeSmi", *name->ToCString()) == 0) {
-    EmitIsNonNegativeSmi(expr->arguments());
-  } else if (strcmp("_IsObject", *name->ToCString()) == 0) {
-    EmitIsObject(expr->arguments());
-  } else if (strcmp("_IsSpecObject", *name->ToCString()) == 0) {
-    EmitIsSpecObject(expr->arguments());
-  } else if (strcmp("_IsUndetectableObject", *name->ToCString()) == 0) {
-    EmitIsUndetectableObject(expr->arguments());
-  } else if (strcmp("_IsFunction", *name->ToCString()) == 0) {
-    EmitIsFunction(expr->arguments());
-  } else if (strcmp("_IsArray", *name->ToCString()) == 0) {
-    EmitIsArray(expr->arguments());
-  } else if (strcmp("_IsRegExp", *name->ToCString()) == 0) {
-    EmitIsRegExp(expr->arguments());
-  } else if (strcmp("_IsConstructCall", *name->ToCString()) == 0) {
-    EmitIsConstructCall(expr->arguments());
-  } else if (strcmp("_ObjectEquals", *name->ToCString()) == 0) {
-    EmitObjectEquals(expr->arguments());
-  } else if (strcmp("_Arguments", *name->ToCString()) == 0) {
-    EmitArguments(expr->arguments());
-  } else if (strcmp("_ArgumentsLength", *name->ToCString()) == 0) {
-    EmitArgumentsLength(expr->arguments());
-  } else if (strcmp("_ClassOf", *name->ToCString()) == 0) {
-    EmitClassOf(expr->arguments());
-  } else if (strcmp("_Log", *name->ToCString()) == 0) {
-    EmitLog(expr->arguments());
-  } else if (strcmp("_RandomHeapNumber", *name->ToCString()) == 0) {
-    EmitRandomHeapNumber(expr->arguments());
-  } else if (strcmp("_SubString", *name->ToCString()) == 0) {
-    EmitSubString(expr->arguments());
-  } else if (strcmp("_RegExpExec", *name->ToCString()) == 0) {
-    EmitRegExpExec(expr->arguments());
-  } else if (strcmp("_ValueOf", *name->ToCString()) == 0) {
-    EmitValueOf(expr->arguments());
-  } else if (strcmp("_SetValueOf", *name->ToCString()) == 0) {
-    EmitSetValueOf(expr->arguments());
-  } else if (strcmp("_NumberToString", *name->ToCString()) == 0) {
-    EmitNumberToString(expr->arguments());
-  } else if (strcmp("_StringCharFromCode", *name->ToCString()) == 0) {
-    EmitStringCharFromCode(expr->arguments());
-  } else if (strcmp("_StringCharCodeAt", *name->ToCString()) == 0) {
-    EmitStringCharCodeAt(expr->arguments());
-  } else if (strcmp("_StringCharAt", *name->ToCString()) == 0) {
-    EmitStringCharAt(expr->arguments());
-  } else if (strcmp("_StringAdd", *name->ToCString()) == 0) {
-    EmitStringAdd(expr->arguments());
-  } else if (strcmp("_StringCompare", *name->ToCString()) == 0) {
-    EmitStringCompare(expr->arguments());
-  } else if (strcmp("_MathPow", *name->ToCString()) == 0) {
-    EmitMathPow(expr->arguments());
-  } else if (strcmp("_MathSin", *name->ToCString()) == 0) {
-    EmitMathSin(expr->arguments());
-  } else if (strcmp("_MathCos", *name->ToCString()) == 0) {
-    EmitMathCos(expr->arguments());
-  } else if (strcmp("_MathSqrt", *name->ToCString()) == 0) {
-    EmitMathSqrt(expr->arguments());
-  } else if (strcmp("_CallFunction", *name->ToCString()) == 0) {
-    EmitCallFunction(expr->arguments());
-  } else if (strcmp("_RegExpConstructResult", *name->ToCString()) == 0) {
-    EmitRegExpConstructResult(expr->arguments());
-  } else if (strcmp("_SwapElements", *name->ToCString()) == 0) {
-    EmitSwapElements(expr->arguments());
-  } else if (strcmp("_GetFromCache", *name->ToCString()) == 0) {
-    EmitGetFromCache(expr->arguments());
-  } else if (strcmp("_IsRegExpEquivalent", *name->ToCString()) == 0) {
-    EmitIsRegExpEquivalent(expr->arguments());
-  } else if (strcmp("_IsStringWrapperSafeForDefaultValueOf",
-                    *name->ToCString()) == 0) {
-    EmitIsStringWrapperSafeForDefaultValueOf(expr->arguments());
-  } else {
-    UNREACHABLE();
+  SmartPointer<char> cstring = name->ToCString();
+
+#define CHECK_EMIT_INLINE_CALL(name, x, y) \
+  if (strcmp("_"#name, *cstring) == 0) {   \
+    Emit##name(expr->arguments());         \
+    return;                                \
   }
+
+  INLINE_RUNTIME_FUNCTION_LIST(CHECK_EMIT_INLINE_CALL)
+  UNREACHABLE();
+#undef CHECK_EMIT_INLINE_CALL
 }
 
 
index 00f4c06e29156d76522ab90a434edf5d559aae28..9aab3d56bafddffd37d2a5043185ca5cce01d526 100644 (file)
@@ -394,42 +394,11 @@ class FullCodeGenerator: public AstVisitor {
 
   // Platform-specific code for inline runtime calls.
   void EmitInlineRuntimeCall(CallRuntime* expr);
-  void EmitIsSmi(ZoneList<Expression*>* arguments);
-  void EmitIsNonNegativeSmi(ZoneList<Expression*>* arguments);
-  void EmitIsObject(ZoneList<Expression*>* arguments);
-  void EmitIsSpecObject(ZoneList<Expression*>* arguments);
-  void EmitIsUndetectableObject(ZoneList<Expression*>* arguments);
-  void EmitIsFunction(ZoneList<Expression*>* arguments);
-  void EmitIsArray(ZoneList<Expression*>* arguments);
-  void EmitIsRegExp(ZoneList<Expression*>* arguments);
-  void EmitIsConstructCall(ZoneList<Expression*>* arguments);
-  void EmitIsStringWrapperSafeForDefaultValueOf(
-      ZoneList<Expression*>* arguments);
-  void EmitObjectEquals(ZoneList<Expression*>* arguments);
-  void EmitArguments(ZoneList<Expression*>* arguments);
-  void EmitArgumentsLength(ZoneList<Expression*>* arguments);
-  void EmitClassOf(ZoneList<Expression*>* arguments);
-  void EmitValueOf(ZoneList<Expression*>* arguments);
-  void EmitSetValueOf(ZoneList<Expression*>* arguments);
-  void EmitNumberToString(ZoneList<Expression*>* arguments);
-  void EmitStringCharFromCode(ZoneList<Expression*>* arguments);
-  void EmitStringCharCodeAt(ZoneList<Expression*>* arguments);
-  void EmitStringCharAt(ZoneList<Expression*>* arguments);
-  void EmitStringCompare(ZoneList<Expression*>* arguments);
-  void EmitStringAdd(ZoneList<Expression*>* arguments);
-  void EmitLog(ZoneList<Expression*>* arguments);
-  void EmitRandomHeapNumber(ZoneList<Expression*>* arguments);
-  void EmitSubString(ZoneList<Expression*>* arguments);
-  void EmitRegExpExec(ZoneList<Expression*>* arguments);
-  void EmitMathPow(ZoneList<Expression*>* arguments);
-  void EmitMathSin(ZoneList<Expression*>* arguments);
-  void EmitMathCos(ZoneList<Expression*>* arguments);
-  void EmitMathSqrt(ZoneList<Expression*>* arguments);
-  void EmitCallFunction(ZoneList<Expression*>* arguments);
-  void EmitRegExpConstructResult(ZoneList<Expression*>* arguments);
-  void EmitSwapElements(ZoneList<Expression*>* arguments);
-  void EmitGetFromCache(ZoneList<Expression*>* arguments);
-  void EmitIsRegExpEquivalent(ZoneList<Expression*>* arguments);
+
+#define EMIT_INLINE_RUNTIME_CALL(name, x, y) \
+  void Emit##name(ZoneList<Expression*>* arguments);
+  INLINE_RUNTIME_FUNCTION_LIST(EMIT_INLINE_RUNTIME_CALL)
+#undef EMIT_INLINE_RUNTIME_CALL
 
   // Platform-specific code for loading variables.
   void EmitVariableLoad(Variable* expr, Expression::Context context);
index 78d83ddfd880bd5910e1e84838bee43b11f15118..a48c74e994e0b69617b880b0eb0967c5fd909974 100644 (file)
@@ -5522,9 +5522,12 @@ void DeferredRegExpLiteral::Generate() {
 
 class DeferredAllocateInNewSpace: public DeferredCode {
  public:
-  DeferredAllocateInNewSpace(int size, Register target)
-    : size_(size), target_(target) {
+  DeferredAllocateInNewSpace(int size,
+                             Register target,
+                             int registers_to_save = 0)
+    : size_(size), target_(target), registers_to_save_(registers_to_save) {
     ASSERT(size >= kPointerSize && size <= Heap::MaxObjectSizeInNewSpace());
+    ASSERT_EQ(0, registers_to_save & target.bit());
     set_comment("[ DeferredAllocateInNewSpace");
   }
   void Generate();
@@ -5532,15 +5535,28 @@ class DeferredAllocateInNewSpace: public DeferredCode {
  private:
   int size_;
   Register target_;
+  int registers_to_save_;
 };
 
 
 void DeferredAllocateInNewSpace::Generate() {
+  for (int i = 0; i < kNumRegs; i++) {
+    if (registers_to_save_ & (1 << i)) {
+      Register save_register = { i };
+      __ push(save_register);
+    }
+  }
   __ push(Immediate(Smi::FromInt(size_)));
   __ CallRuntime(Runtime::kAllocateInNewSpace, 1);
   if (!target_.is(eax)) {
     __ mov(target_, eax);
   }
+  for (int i = kNumRegs - 1; i >= 0; i--) {
+    if (registers_to_save_ & (1 << i)) {
+      Register save_register = { i };
+      __ pop(save_register);
+    }
+  }
 }
 
 
@@ -7364,6 +7380,89 @@ void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) {
 }
 
 
+void CodeGenerator::GenerateRegExpCloneResult(ZoneList<Expression*>* args) {
+  ASSERT_EQ(1, args->length());
+
+  Load(args->at(0));
+  Result object_result = frame_->Pop();
+  object_result.ToRegister(eax);
+  object_result.Unuse();
+  {
+    VirtualFrame::SpilledScope spilled_scope;
+
+    Label done;
+
+    __ test(eax, Immediate(kSmiTagMask));
+    __ j(zero, &done);
+
+    // Load JSRegExpResult map into edx.
+    // Arguments to this function should be results of calling RegExp exec,
+    // which is either an unmodified JSRegExpResult or null. Anything not having
+    // the unmodified JSRegExpResult map is returned unmodified.
+    // This also ensures that elements are fast.
+    __ mov(edx, ContextOperand(esi, Context::GLOBAL_INDEX));
+    __ mov(edx, FieldOperand(edx, GlobalObject::kGlobalContextOffset));
+    __ mov(edx, ContextOperand(edx, Context::REGEXP_RESULT_MAP_INDEX));
+    __ cmp(edx, FieldOperand(eax, HeapObject::kMapOffset));
+    __ j(not_equal, &done);
+
+    if (FLAG_debug_code) {
+      // Check that object really has empty properties array, as the map
+      // should guarantee.
+      __ cmp(FieldOperand(eax, JSObject::kPropertiesOffset),
+             Immediate(Factory::empty_fixed_array()));
+      __ Check(equal, "JSRegExpResult: default map but non-empty properties.");
+    }
+
+    DeferredAllocateInNewSpace* allocate_fallback =
+        new DeferredAllocateInNewSpace(JSRegExpResult::kSize,
+                                       ebx,
+                                       edx.bit() | eax.bit());
+
+    // All set, copy the contents to a new object.
+    __ AllocateInNewSpace(JSRegExpResult::kSize,
+                          ebx,
+                          ecx,
+                          no_reg,
+                          allocate_fallback->entry_label(),
+                          TAG_OBJECT);
+    __ bind(allocate_fallback->exit_label());
+
+    // Copy all fields from eax to ebx.
+    STATIC_ASSERT(JSRegExpResult::kSize % (2 * kPointerSize) == 0);
+    // There is an even number of fields, so unroll the loop once
+    // for efficiency.
+    for (int i = 0; i < JSRegExpResult::kSize; i += 2 * kPointerSize) {
+      STATIC_ASSERT(JSObject::kMapOffset % (2 * kPointerSize) == 0);
+      if (i != JSObject::kMapOffset) {
+        // The map was already loaded into edx.
+        __ mov(edx, FieldOperand(eax, i));
+      }
+      __ mov(ecx, FieldOperand(eax, i + kPointerSize));
+
+      STATIC_ASSERT(JSObject::kElementsOffset % (2 * kPointerSize) == 0);
+      if (i == JSObject::kElementsOffset) {
+        // If the elements array isn't empty, make it copy-on-write
+        // before copying it.
+        Label empty;
+        __ cmp(Operand(edx), Immediate(Factory::empty_fixed_array()));
+        __ j(equal, &empty);
+        ASSERT(!Heap::InNewSpace(Heap::fixed_cow_array_map()));
+        __ mov(FieldOperand(edx, HeapObject::kMapOffset),
+               Immediate(Factory::fixed_cow_array_map()));
+        __ bind(&empty);
+      }
+      __ mov(FieldOperand(ebx, i), edx);
+      __ mov(FieldOperand(ebx, i + kPointerSize), ecx);
+    }
+    __ mov(eax, ebx);
+
+    __ bind(&done);
+  }
+  frame_->Push(eax);
+}
+
+
 class DeferredSearchCache: public DeferredCode {
  public:
   DeferredSearchCache(Register dst, Register cache, Register key)
index 37b70110ccdee83439d74183958afccd9090328c..ce1bcf6a09fba36fdbf82e764259f4377b1d2e09 100644 (file)
@@ -699,8 +699,14 @@ class CodeGenerator: public AstVisitor {
   // Support for direct calls from JavaScript to native RegExp code.
   void GenerateRegExpExec(ZoneList<Expression*>* args);
 
+  // Construct a RegExp exec result with two in-object properties.
   void GenerateRegExpConstructResult(ZoneList<Expression*>* args);
 
+  // Clone the result of a regexp function.
+  // Must be an object created by GenerateRegExpConstructResult with
+  // no extra properties.
+  void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
+
   // Support for fast native caches.
   void GenerateGetFromCache(ZoneList<Expression*>* args);
 
index 68a0a960f9e92266a1b1f3512805bfb682fd7901..684ee14dd1a45d94574fd7633aa8bc458175380f 100644 (file)
@@ -2640,6 +2640,14 @@ void FullCodeGenerator::EmitRegExpConstructResult(ZoneList<Expression*>* args) {
 }
 
 
+void FullCodeGenerator::EmitRegExpCloneResult(ZoneList<Expression*>* args) {
+  ASSERT(args->length() == 1);
+  VisitForValue(args->at(0), kStack);
+  __ CallRuntime(Runtime::kRegExpCloneResult, 1);
+  Apply(context_, eax);
+}
+
+
 void FullCodeGenerator::EmitSwapElements(ZoneList<Expression*>* args) {
   ASSERT(args->length() == 3);
   VisitForValue(args->at(0), kStack);
index fa702b2a8276b078a44f52082b874596763050ce..566a96c3489ed6c525ac836338ae99c3dae73c74 100644 (file)
@@ -137,17 +137,6 @@ function RegExpCache() {
 var regExpCache = new RegExpCache();
 
 
-function CloneRegExpResult(array) {
-  if (array == null) return null;
-  var length = array.length;
-  var answer = %_RegExpConstructResult(length, array.index, array.input);
-  for (var i = 0; i < length; i++) {
-    answer[i] = array[i];
-  }
-  return answer;
-}
-
-
 function BuildResultFromMatchInfo(lastMatchInfo, s) {
   var numResults = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1;
   var result = %_RegExpConstructResult(numResults, lastMatchInfo[CAPTURE0], s);
@@ -197,7 +186,7 @@ function RegExpExec(string) {
       %_IsRegExpEquivalent(cache.regExp, this) &&
       %_ObjectEquals(cache.subject, string)) {
     if (cache.answerSaved) {
-      return CloneRegExpResult(cache.answer);
+      return %_RegExpCloneResult(cache.answer);
     } else {
       saveAnswer = true;
     }
@@ -251,7 +240,7 @@ function RegExpExec(string) {
     cache.regExp = this;
     cache.subject = s;
     cache.lastIndex = lastIndex;
-    if (saveAnswer) cache.answer = CloneRegExpResult(result);
+    if (saveAnswer) cache.answer = %_RegExpCloneResult(result);
     cache.answerSaved = saveAnswer;
     cache.type = 'exec';
   }
index 288b2260215c281f5957f3afe470a67117ec3263..afb0df0f4774c8acecc3e59c2bff4a830f942cd1 100644 (file)
@@ -1364,6 +1364,65 @@ static Object* Runtime_RegExpConstructResult(Arguments args) {
 }
 
 
+static Object* Runtime_RegExpCloneResult(Arguments args) {
+  ASSERT(args.length() == 1);
+  Map* regexp_result_map;
+  {
+    AssertNoAllocation no_gc;
+    HandleScope handles;
+    regexp_result_map = Top::global_context()->regexp_result_map();
+  }
+  if (!args[0]->IsJSArray()) return args[0];
+
+  JSArray* result = JSArray::cast(args[0]);
+  // Arguments to RegExpCloneResult should always be fresh RegExp exec call
+  // results (either a fresh JSRegExpResult or null).
+  // If the argument is not a JSRegExpResult, or isn't unmodified, just return
+  // the argument uncloned.
+  if (result->map() != regexp_result_map) return result;
+
+  // Having the original JSRegExpResult map guarantees that we have
+  // fast elements and no properties except the two in-object properties.
+  ASSERT(result->HasFastElements());
+  ASSERT(result->properties() == Heap::empty_fixed_array());
+  ASSERT_EQ(2, regexp_result_map->inobject_properties());
+
+  Object* new_array_alloc = Heap::AllocateRaw(JSRegExpResult::kSize,
+                                              NEW_SPACE,
+                                              OLD_POINTER_SPACE);
+  if (new_array_alloc->IsFailure()) return new_array_alloc;
+
+  // Set HeapObject map to JSRegExpResult map.
+  reinterpret_cast<HeapObject*>(new_array_alloc)->set_map(regexp_result_map);
+
+  JSArray* new_array = JSArray::cast(new_array_alloc);
+
+  // Copy JSObject properties.
+  new_array->set_properties(result->properties());  // Empty FixedArray.
+
+  // Copy JSObject elements as copy-on-write.
+  FixedArray* elements = FixedArray::cast(result->elements());
+  if (elements != Heap::empty_fixed_array()) {
+    ASSERT(!Heap::InNewSpace(Heap::fixed_cow_array_map()));
+    // No write barrier is necessary when writing old-space pointer.
+    elements->set_map(Heap::fixed_cow_array_map());
+  }
+  new_array->set_elements(elements);
+
+  // Copy JSArray length.
+  new_array->set_length(result->length());
+
+  // Copy JSRegExpResult in-object property fields input and index.
+  new_array->FastPropertyAtPut(JSRegExpResult::kIndexIndex,
+                               result->FastPropertyAt(
+                                   JSRegExpResult::kIndexIndex));
+  new_array->FastPropertyAtPut(JSRegExpResult::kInputIndex,
+                               result->FastPropertyAt(
+                                   JSRegExpResult::kInputIndex));
+  return new_array;
+}
+
+
 static Object* Runtime_RegExpInitializeObject(Arguments args) {
   AssertNoAllocation no_alloc;
   ASSERT(args.length() == 5);
index 26a2b9df2428d855a72c2d85b561e83e69b3f116..001e05fc3bc93220e63f4d88a713b0aece02e7ef 100644 (file)
@@ -162,6 +162,7 @@ namespace internal {
   F(RegExpExecMultiple, 4, 1) \
   F(RegExpInitializeObject, 5, 1) \
   F(RegExpConstructResult, 3, 1) \
+  F(RegExpCloneResult, 1, 1) \
   \
   /* Strings */ \
   F(StringCharCodeAt, 2, 1) \
index 9f075af1120814df201e4bb352d0776f130fe92a..77828d63a53591d8bad692494b9e4cf4f9867dc6 100644 (file)
@@ -4802,8 +4802,10 @@ void DeferredRegExpLiteral::Generate() {
 
 class DeferredAllocateInNewSpace: public DeferredCode {
  public:
-  DeferredAllocateInNewSpace(int size, Register target)
-    : size_(size), target_(target) {
+  DeferredAllocateInNewSpace(int size,
+                             Register target,
+                             int registers_to_save = 0)
+    : size_(size), target_(target), registers_to_save_(registers_to_save) {
     ASSERT(size >= kPointerSize && size <= Heap::MaxObjectSizeInNewSpace());
     set_comment("[ DeferredAllocateInNewSpace");
   }
@@ -4812,15 +4814,28 @@ class DeferredAllocateInNewSpace: public DeferredCode {
  private:
   int size_;
   Register target_;
+  int registers_to_save_;
 };
 
 
 void DeferredAllocateInNewSpace::Generate() {
+  for (int i = 0; i < kNumRegs; i++) {
+    if (registers_to_save_ & (1 << i)) {
+      Register save_register = { i };
+      __ push(save_register);
+    }
+  }
   __ Push(Smi::FromInt(size_));
   __ CallRuntime(Runtime::kAllocateInNewSpace, 1);
   if (!target_.is(rax)) {
     __ movq(target_, rax);
   }
+  for (int i = kNumRegs - 1; i >= 0; i--) {
+    if (registers_to_save_ & (1 << i)) {
+      Register save_register = { i };
+      __ push(save_register);
+    }
+  }
 }
 
 
@@ -6608,6 +6623,79 @@ void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) {
 }
 
 
+void CodeGenerator::GenerateRegExpCloneResult(ZoneList<Expression*>* args) {
+  ASSERT_EQ(1, args->length());
+
+  Load(args->at(0));
+  Result object_result = frame_->Pop();
+  object_result.ToRegister(rax);
+  object_result.Unuse();
+  {
+    VirtualFrame::SpilledScope spilled_scope;
+
+    Label done;
+    __ JumpIfSmi(rax, &done);
+
+    // Load JSRegExpResult map into rdx.
+    // Arguments to this function should be results of calling RegExp exec,
+    // which is either an unmodified JSRegExpResult or null. Anything not having
+    // the unmodified JSRegExpResult map is returned unmodified.
+    // This also ensures that elements are fast.
+
+    __ movq(rdx, ContextOperand(rsi, Context::GLOBAL_INDEX));
+    __ movq(rdx, FieldOperand(rdx, GlobalObject::kGlobalContextOffset));
+    __ movq(rdx, ContextOperand(rdx, Context::REGEXP_RESULT_MAP_INDEX));
+    __ cmpq(rdx, FieldOperand(rax, HeapObject::kMapOffset));
+    __ j(not_equal, &done);
+
+    DeferredAllocateInNewSpace* allocate_fallback =
+        new DeferredAllocateInNewSpace(JSRegExpResult::kSize,
+                                       rbx,
+                                       rdx.bit() | rax.bit());
+
+    // All set, copy the contents to a new object.
+    __ AllocateInNewSpace(JSRegExpResult::kSize,
+                          rbx,
+                          no_reg,
+                          no_reg,
+                          allocate_fallback->entry_label(),
+                          TAG_OBJECT);
+    __ bind(allocate_fallback->exit_label());
+
+    STATIC_ASSERT(JSRegExpResult::kSize % (2 * kPointerSize) == 0);
+    // There is an even number of fields, so unroll the loop once
+    // for efficiency.
+    for (int i = 0; i < JSRegExpResult::kSize; i += 2 * kPointerSize) {
+      STATIC_ASSERT(JSObject::kMapOffset % (2 * kPointerSize) == 0);
+      if (i != JSObject::kMapOffset) {
+        // The map was already loaded into edx.
+        __ movq(rdx, FieldOperand(rax, i));
+      }
+      __ movq(rcx, FieldOperand(rax, i + kPointerSize));
+
+      STATIC_ASSERT(JSObject::kElementsOffset % (2 * kPointerSize) == 0);
+      if (i == JSObject::kElementsOffset) {
+        // If the elements array isn't empty, make it copy-on-write
+        // before copying it.
+        Label empty;
+        __ CompareRoot(rdx, Heap::kEmptyFixedArrayRootIndex);
+        __ j(equal, &empty);
+        ASSERT(!Heap::InNewSpace(Heap::fixed_cow_array_map()));
+        __ LoadRoot(kScratchRegister, Heap::kFixedCOWArrayMapRootIndex);
+        __ movq(FieldOperand(rdx, HeapObject::kMapOffset), kScratchRegister);
+        __ bind(&empty);
+      }
+      __ movq(FieldOperand(rbx, i), rdx);
+      __ movq(FieldOperand(rbx, i + kPointerSize), rcx);
+    }
+    __ movq(rax, rbx);
+
+    __ bind(&done);
+  }
+  frame_->Push(rax);
+}
+
+
 class DeferredSearchCache: public DeferredCode {
  public:
   DeferredSearchCache(Register dst,
index 14f690eb81a243d9df2ab7d54262574b48e0c532..31f229deb3139e08b03e8dd4fdf953db4e5a8814 100644 (file)
@@ -659,6 +659,8 @@ class CodeGenerator: public AstVisitor {
 
   void GenerateRegExpConstructResult(ZoneList<Expression*>* args);
 
+  void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
+
   // Support for fast native caches.
   void GenerateGetFromCache(ZoneList<Expression*>* args);
 
index a5ccaf509ca49a317df21b349987b08f479f6056..470b5bf72e66da68ab7b4f7a383ec5d37378ffd7 100644 (file)
@@ -2628,6 +2628,14 @@ void FullCodeGenerator::EmitRegExpConstructResult(ZoneList<Expression*>* args) {
 }
 
 
+void FullCodeGenerator::EmitRegExpCloneResult(ZoneList<Expression*>* args) {
+  ASSERT(args->length() == 1);
+  VisitForValue(args->at(0), kStack);
+  __ CallRuntime(Runtime::kRegExpCloneResult, 1);
+  Apply(context_, rax);
+}
+
+
 void FullCodeGenerator::EmitSwapElements(ZoneList<Expression*>* args) {
   ASSERT(args->length() == 3);
   VisitForValue(args->at(0), kStack);
index a8891969f4839db012459d9d273d384cce3ce201..db8b13388e4b9e75e13052546bcbe1821c305cbd 100644 (file)
@@ -484,3 +484,21 @@ assertRegExpTest(/[,b]\b[,b]/, ",b", true);
 assertRegExpTest(/[,b]\B[,b]/, ",b", false);
 assertRegExpTest(/[,b]\b[,b]/, "b,", true);
 assertRegExpTest(/[,b]\B[,b]/, "b,", false);
+
+// Test that caching of result doesn't share result objects.
+// More iterations increases the chance of hitting a GC.
+for (var i = 0; i < 100; i++) {
+  var re = /x(y)z/;
+  var res = re.exec("axyzb");
+  assertTrue(!!res);
+  assertEquals(2, res.length);
+  assertEquals("xyz", res[0]);
+  assertEquals("y", res[1]);
+  assertEquals(1, res.index);
+  assertEquals("axyzb", res.input);
+  assertEquals(undefined, res.foobar);
+  
+  res.foobar = "Arglebargle";
+  res[3] = "Glopglyf";
+  assertEquals("Arglebargle", res.foobar);
+}