Revision: 5310
Author: [email protected]
Date: Fri Aug 20 02:37:22 2010
Log: Use Copy-on-write arrays for cached regexp results.

Review URL: http://codereview.chromium.org/3158020
http://code.google.com/p/v8/source/detail?r=5310

Modified:
 /branches/bleeding_edge/src/arm/codegen-arm.cc
 /branches/bleeding_edge/src/arm/codegen-arm.h
 /branches/bleeding_edge/src/arm/full-codegen-arm.cc
 /branches/bleeding_edge/src/arm/simulator-arm.cc
 /branches/bleeding_edge/src/codegen.h
 /branches/bleeding_edge/src/full-codegen.cc
 /branches/bleeding_edge/src/full-codegen.h
 /branches/bleeding_edge/src/ia32/codegen-ia32.cc
 /branches/bleeding_edge/src/ia32/codegen-ia32.h
 /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc
 /branches/bleeding_edge/src/regexp.js
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/runtime.h
 /branches/bleeding_edge/src/x64/codegen-x64.cc
 /branches/bleeding_edge/src/x64/codegen-x64.h
 /branches/bleeding_edge/src/x64/full-codegen-x64.cc
 /branches/bleeding_edge/test/mjsunit/regexp.js

=======================================
--- /branches/bleeding_edge/src/arm/codegen-arm.cc      Fri Aug 20 00:10:18 2010
+++ /branches/bleeding_edge/src/arm/codegen-arm.cc      Fri Aug 20 02:37:22 2010
@@ -5263,6 +5263,67 @@
   frame_->Forget(3);
   frame_->EmitPush(r0);
 }
+
+
+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 {
=======================================
--- /branches/bleeding_edge/src/arm/codegen-arm.h       Mon Aug 16 09:06:46 2010
+++ /branches/bleeding_edge/src/arm/codegen-arm.h       Fri Aug 20 02:37:22 2010
@@ -528,6 +528,8 @@

   void GenerateRegExpConstructResult(ZoneList<Expression*>* args);

+  void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
+
   // Support for fast native caches.
   void GenerateGetFromCache(ZoneList<Expression*>* args);

=======================================
--- /branches/bleeding_edge/src/arm/full-codegen-arm.cc Mon Aug 16 09:06:46 2010 +++ /branches/bleeding_edge/src/arm/full-codegen-arm.cc Fri Aug 20 02:37:22 2010
@@ -2534,6 +2534,14 @@
   __ CallRuntime(Runtime::kRegExpConstructResult, 3);
   Apply(context_, r0);
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/arm/simulator-arm.cc Mon Aug 16 00:52:49 2010 +++ /branches/bleeding_edge/src/arm/simulator-arm.cc Fri Aug 20 02:37:22 2010
@@ -727,6 +727,10 @@
 // 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 @@
     }
     case 3: {
       // Print("ib");
-      UNIMPLEMENTED();
+      start_address = rn_val + 4;
+      end_address = rn_val + (num_regs * 4);
+      rn_val = end_address;
       break;
     }
     default: {
=======================================
--- /branches/bleeding_edge/src/codegen.h       Mon Aug 16 09:06:46 2010
+++ /branches/bleeding_edge/src/codegen.h       Fri Aug 20 02:37:22 2010
@@ -108,6 +108,7 @@
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) \
=======================================
--- /branches/bleeding_edge/src/full-codegen.cc Fri Aug 13 02:07:09 2010
+++ /branches/bleeding_edge/src/full-codegen.cc Fri Aug 20 02:37:22 2010
@@ -851,80 +851,17 @@

 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
 }


=======================================
--- /branches/bleeding_edge/src/full-codegen.h  Fri Aug 13 02:07:09 2010
+++ /branches/bleeding_edge/src/full-codegen.h  Fri Aug 20 02:37:22 2010
@@ -394,42 +394,11 @@

   // 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);
=======================================
--- /branches/bleeding_edge/src/ia32/codegen-ia32.cc Fri Aug 20 00:10:18 2010 +++ /branches/bleeding_edge/src/ia32/codegen-ia32.cc Fri Aug 20 02:37:22 2010
@@ -5522,9 +5522,12 @@

 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 @@
  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);
+    }
+  }
 }


@@ -7362,6 +7378,89 @@
   frame_->Forget(3);
   frame_->Push(eax);
 }
+
+
+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 {
=======================================
--- /branches/bleeding_edge/src/ia32/codegen-ia32.h     Mon Aug 16 00:52:49 2010
+++ /branches/bleeding_edge/src/ia32/codegen-ia32.h     Fri Aug 20 02:37:22 2010
@@ -699,8 +699,14 @@
   // 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);

=======================================
--- /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc Mon Aug 16 09:06:46 2010 +++ /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc Fri Aug 20 02:37:22 2010
@@ -2638,6 +2638,14 @@
   __ CallRuntime(Runtime::kRegExpConstructResult, 3);
   Apply(context_, eax);
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/regexp.js       Fri Aug  6 06:04:27 2010
+++ /branches/bleeding_edge/src/regexp.js       Fri Aug 20 02:37:22 2010
@@ -137,17 +137,6 @@
 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 @@
       %_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 @@
     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';
   }
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Wed Aug 18 01:54:25 2010
+++ /branches/bleeding_edge/src/runtime.cc      Fri Aug 20 02:37:22 2010
@@ -1362,6 +1362,65 @@
   array->InObjectPropertyAtPut(JSRegExpResult::kInputIndex, args[2]);
   return array;
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/runtime.h       Fri Aug  6 06:04:27 2010
+++ /branches/bleeding_edge/src/runtime.h       Fri Aug 20 02:37:22 2010
@@ -162,6 +162,7 @@
   F(RegExpExecMultiple, 4, 1) \
   F(RegExpInitializeObject, 5, 1) \
   F(RegExpConstructResult, 3, 1) \
+  F(RegExpCloneResult, 1, 1) \
   \
   /* Strings */ \
   F(StringCharCodeAt, 2, 1) \
=======================================
--- /branches/bleeding_edge/src/x64/codegen-x64.cc      Fri Aug 20 00:10:18 2010
+++ /branches/bleeding_edge/src/x64/codegen-x64.cc      Fri Aug 20 02:37:22 2010
@@ -4802,8 +4802,10 @@

 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 @@
  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);
+    }
+  }
 }


@@ -6606,6 +6621,79 @@
   frame_->Forget(3);
   frame_->Push(rax);
 }
+
+
+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 {
=======================================
--- /branches/bleeding_edge/src/x64/codegen-x64.h       Mon Aug 16 00:52:49 2010
+++ /branches/bleeding_edge/src/x64/codegen-x64.h       Fri Aug 20 02:37:22 2010
@@ -659,6 +659,8 @@

   void GenerateRegExpConstructResult(ZoneList<Expression*>* args);

+  void GenerateRegExpCloneResult(ZoneList<Expression*>* args);
+
   // Support for fast native caches.
   void GenerateGetFromCache(ZoneList<Expression*>* args);

=======================================
--- /branches/bleeding_edge/src/x64/full-codegen-x64.cc Mon Aug 16 09:06:46 2010 +++ /branches/bleeding_edge/src/x64/full-codegen-x64.cc Fri Aug 20 02:37:22 2010
@@ -2626,6 +2626,14 @@
   __ CallRuntime(Runtime::kRegExpConstructResult, 3);
   Apply(context_, rax);
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/test/mjsunit/regexp.js      Mon Apr 26 08:10:42 2010
+++ /branches/bleeding_edge/test/mjsunit/regexp.js      Fri Aug 20 02:37:22 2010
@@ -484,3 +484,21 @@
 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);
+}

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to