Author: [email protected]
Date: Fri Mar 20 06:26:16 2009
New Revision: 1562
Modified:
branches/bleeding_edge/src/jsregexp.cc
branches/bleeding_edge/src/jsregexp.h
branches/bleeding_edge/src/regexp-macro-assembler-ia32.cc
branches/bleeding_edge/src/regexp-macro-assembler-ia32.h
branches/bleeding_edge/test/cctest/test-api.cc
branches/bleeding_edge/test/cctest/test-regexp.cc
Log:
RegExps now restart if their input string changes representation during
preemption.
Cleaned up the handling of strings moving, so strings moved by GC and
strings changing representation are handled equivalently.
Modified: branches/bleeding_edge/src/jsregexp.cc
==============================================================================
--- branches/bleeding_edge/src/jsregexp.cc (original)
+++ branches/bleeding_edge/src/jsregexp.cc Fri Mar 20 06:26:16 2009
@@ -466,15 +466,9 @@
Handle<JSArray> last_match_info) {
ASSERT_EQ(regexp->TypeTag(), JSRegExp::IRREGEXP);
- bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
- if (!EnsureCompiledIrregexp(regexp, is_ascii)) {
- return Handle<Object>::null();
- }
-
// Prepare space for the return values.
- Handle<FixedArray> re_data(FixedArray::cast(regexp->data()));
int number_of_capture_registers =
- (IrregexpNumberOfCaptures(*re_data) + 1) * 2;
+ (IrregexpNumberOfCaptures(FixedArray::cast(regexp->data())) + 1) * 2;
OffsetsVector offsets(number_of_capture_registers);
int previous_index = index;
@@ -493,7 +487,7 @@
last_match_info->EnsureSize(number_of_capture_registers +
kLastMatchOverhead);
- return IrregexpExecOnce(re_data,
+ return IrregexpExecOnce(regexp,
number_of_capture_registers,
last_match_info,
subject,
@@ -507,16 +501,10 @@
Handle<String> subject,
Handle<JSArray>
last_match_info) {
ASSERT_EQ(regexp->TypeTag(), JSRegExp::IRREGEXP);
- Handle<FixedArray> irregexp(FixedArray::cast(regexp->data()));
-
- bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
- if (!EnsureCompiledIrregexp(regexp, is_ascii)) {
- return Handle<Object>::null();
- }
// Prepare space for the return values.
int number_of_capture_registers =
- (IrregexpNumberOfCaptures(*irregexp) + 1) * 2;
+ (IrregexpNumberOfCaptures(FixedArray::cast(regexp->data())) + 1) * 2;
OffsetsVector offsets(number_of_capture_registers);
int previous_index = 0;
@@ -545,7 +533,7 @@
}
#endif
HandleScope scope;
- matches = IrregexpExecOnce(irregexp,
+ matches = IrregexpExecOnce(regexp,
number_of_capture_registers,
last_match_info,
subject,
@@ -587,7 +575,7 @@
}
-Handle<Object> RegExpImpl::IrregexpExecOnce(Handle<FixedArray> regexp,
+Handle<Object> RegExpImpl::IrregexpExecOnce(Handle<JSRegExp> jsregexp,
int
number_of_capture_registers,
Handle<JSArray>
last_match_info,
Handle<String> subject,
@@ -595,22 +583,29 @@
int* offsets_vector,
int offsets_vector_length) {
ASSERT(subject->IsFlat());
- bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
bool rc;
Handle<String> original_subject = subject;
+ Handle<FixedArray> regexp(FixedArray::cast(jsregexp->data()));
if (UseNativeRegexp()) {
#ifdef ARM
UNREACHABLE();
#else
- Handle<Code> code(RegExpImpl::IrregexpNativeCode(*regexp, is_ascii));
- RegExpMacroAssemblerIA32::Result res =
- RegExpMacroAssemblerIA32::Match(code,
- subject,
- offsets_vector,
- offsets_vector_length,
- previous_index);
-
+ RegExpMacroAssemblerIA32::Result res;
+ do {
+ bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
+ if (!EnsureCompiledIrregexp(jsregexp, is_ascii)) {
+ return Handle<Object>::null();
+ }
+ Handle<Code> code(RegExpImpl::IrregexpNativeCode(*regexp, is_ascii));
+ res = RegExpMacroAssemblerIA32::Match(code,
+ subject,
+ offsets_vector,
+ offsets_vector_length,
+ previous_index);
+ // If result is RETRY, the string have changed representation, and we
+ // must restart from scratch.
+ } while (res == RegExpMacroAssemblerIA32::RETRY);
if (res == RegExpMacroAssemblerIA32::EXCEPTION) {
ASSERT(Top::has_pending_exception());
return Handle<Object>::null();
@@ -621,6 +616,7 @@
rc = (res == RegExpMacroAssemblerIA32::SUCCESS);
#endif
} else {
+ bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
for (int i = number_of_capture_registers - 1; i >= 0; i--) {
offsets_vector[i] = -1;
}
Modified: branches/bleeding_edge/src/jsregexp.h
==============================================================================
--- branches/bleeding_edge/src/jsregexp.h (original)
+++ branches/bleeding_edge/src/jsregexp.h Fri Mar 20 06:26:16 2009
@@ -158,7 +158,7 @@
// On a successful match, the result is a JSArray containing
// captured positions. On a failure, the result is the null value.
// Returns an empty handle in case of an exception.
- static Handle<Object> IrregexpExecOnce(Handle<FixedArray> regexp,
+ static Handle<Object> IrregexpExecOnce(Handle<JSRegExp> jsregexp,
int num_captures,
Handle<JSArray> lastMatchInfo,
Handle<String> subject16,
Modified: branches/bleeding_edge/src/regexp-macro-assembler-ia32.cc
==============================================================================
--- branches/bleeding_edge/src/regexp-macro-assembler-ia32.cc (original)
+++ branches/bleeding_edge/src/regexp-macro-assembler-ia32.cc Fri Mar 20
06:26:16 2009
@@ -1,4 +1,4 @@
-// Copyright 2008 the V8 project authors. All rights reserved.
+// Copyright 2008-2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -25,7 +25,6 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#include <string.h>
#include "v8.h"
#include "unicode.h"
#include "log.h"
@@ -45,8 +44,8 @@
* - edi : current position in input, as negative offset from end of
string.
* Please notice that this is the byte offset, not the character
offset!
* - esi : end of input (points to byte after last character in input).
- * - ebp : points to the location above the registers on the stack,
- * as if by the "enter <register_count>" opcode.
+ * - ebp : frame pointer. Used to access arguments, local variables and
+ * RegExp registers.
* - esp : points to tip of C stack.
* - ecx : points to tip of backtrack stack
*
@@ -58,15 +57,17 @@
* backtracking stack)
* - at_start (if 1, start at start of string, if 0, don't)
* - int* capture_array (int[num_saved_registers_], for output).
- * - end of input (index of end of string, relative to
*string_base)
- * - start of input (index of first character in string, relative
- * to *string_base)
- * - void** string_base (location of a handle containing the string)
+ * - end of input (Address of end of string)
+ * - start of input (Address of first character in string)
+ * - void* input_string (location of a handle containing the string)
+ * --- frame alignment (if applicable) ---
* - return address
* ebp-> - old ebp
* - backup of caller esi
* - backup of caller edi
* - backup of caller ebx
+ * - Offset of location before start of input (effectively character
+ * position -1). Used to initialize capture registers to a
non-position.
* - register 0 ebp[-4] (Only positions must be stored in the first
* - register 1 ebp[-8] num_saved_registers_ registers)
* - ...
@@ -76,13 +77,13 @@
* character of the string). The remaining registers starts out as garbage.
*
* The data up to the return address must be placed there by the calling
- * code, e.g., by calling the code as cast to:
- * bool (*match)(String** string_base,
- * int start_offset,
- * int end_offset,
- * int* capture_output_array,
- * bool at_start,
- * byte* stack_area_top)
+ * code, e.g., by calling the code entry as cast to:
+ * int (*match)(String* input_string,
+ * Address start,
+ * Address end,
+ * int* capture_output_array,
+ * bool at_start,
+ * byte* stack_area_top)
*/
#define __ masm_->
@@ -174,16 +175,15 @@
void RegExpMacroAssemblerIA32::CheckAtStart(Label* on_at_start) {
- Label ok;
+ Label not_at_start;
// Did we start the match at the start of the string at all?
__ cmp(Operand(ebp, kAtStart), Immediate(0));
- BranchOrBacktrack(equal, &ok);
+ BranchOrBacktrack(equal, ¬_at_start);
// If we did, are we still at the start of the input?
- __ mov(eax, Operand(ebp, kInputEndOffset));
- __ add(eax, Operand(edi));
- __ cmp(eax, Operand(ebp, kInputStartOffset));
+ __ lea(eax, Operand(esi, edi, times_1, 0));
+ __ cmp(eax, Operand(ebp, kInputStart));
BranchOrBacktrack(equal, on_at_start);
- __ bind(&ok);
+ __ bind(¬_at_start);
}
@@ -192,9 +192,8 @@
__ cmp(Operand(ebp, kAtStart), Immediate(0));
BranchOrBacktrack(equal, on_not_at_start);
// If we did, are we still at the start of the input?
- __ mov(eax, Operand(ebp, kInputEndOffset));
- __ add(eax, Operand(edi));
- __ cmp(eax, Operand(ebp, kInputStartOffset));
+ __ lea(eax, Operand(esi, edi, times_1, 0));
+ __ cmp(eax, Operand(ebp, kInputStart));
BranchOrBacktrack(not_equal, on_not_at_start);
}
@@ -329,33 +328,29 @@
__ push(edi);
__ push(backtrack_stackpointer());
__ push(ebx);
- const int four_arguments = 4;
- FrameAlign(four_arguments, ecx);
+
+ const int argument_count = 3;
+ FrameAlign(argument_count, ecx);
// Put arguments into allocated stack area, last argument highest on
stack.
// Parameters are
- // UC16** buffer - really the String** of the input string
- // int byte_offset1 - byte offset from *buffer of start of capture
- // int byte_offset2 - byte offset from *buffer of current position
+ // Address byte_offset1 - Address captured substring's start.
+ // Address byte_offset2 - Address of current character position.
// size_t byte_length - length of capture in bytes(!)
// Set byte_length.
- __ mov(Operand(esp, 3 * kPointerSize), ebx);
+ __ mov(Operand(esp, 2 * kPointerSize), ebx);
// Set byte_offset2.
// Found by adding negative string-end offset of current position (edi)
- // to String** offset of end of string.
- __ mov(ecx, Operand(ebp, kInputEndOffset));
- __ add(edi, Operand(ecx));
- __ mov(Operand(esp, 2 * kPointerSize), edi);
+ // to end of string.
+ __ add(edi, Operand(esi));
+ __ mov(Operand(esp, 1 * kPointerSize), edi);
// Set byte_offset1.
// Start of capture, where edx already holds string-end negative
offset.
- __ add(edx, Operand(ecx));
- __ mov(Operand(esp, 1 * kPointerSize), edx);
- // Set buffer. Original String** parameter to regexp code.
- __ mov(eax, Operand(ebp, kInputBuffer));
- __ mov(Operand(esp, 0 * kPointerSize), eax);
+ __ add(edx, Operand(esi));
+ __ mov(Operand(esp, 0 * kPointerSize), edx);
Address function_address = FUNCTION_ADDR(&CaseInsensitiveCompareUC16);
- CallCFunction(function_address, four_arguments);
+ CallCFunction(function_address, argument_count);
// Pop original values before reacting on result value.
__ pop(ebx);
__ pop(backtrack_stackpointer());
@@ -630,7 +625,7 @@
// Start new stack frame.
__ push(ebp);
__ mov(ebp, esp);
- // Save callee-save registers. Order here should correspond to order of
+ // Save callee-save registers. Order here should correspond to order of
// kBackup_ebx etc.
__ push(esi);
__ push(edi);
@@ -653,24 +648,18 @@
// the stack limit.
__ cmp(ecx, num_registers_ * kPointerSize);
__ j(above_equal, &stack_ok, taken);
- // Exit with exception.
+ // Exit with OutOfMemory exception. There is not enough space on the
stack
+ // for our working registers.
__ mov(eax, EXCEPTION);
__ jmp(&exit_label_);
__ bind(&stack_limit_hit);
- int num_arguments = 2;
- FrameAlign(num_arguments, ebx);
- __ mov(Operand(esp, 1 * kPointerSize), Immediate(masm_->CodeObject()));
- __ lea(eax, Operand(esp, -kPointerSize));
- __ mov(Operand(esp, 0 * kPointerSize), eax);
- CallCFunction(FUNCTION_ADDR(&CheckStackGuardState), num_arguments);
+ CallCheckStackGuardState(ebx);
__ or_(eax, Operand(eax));
- // If returned value is non-zero, the stack guard reports the actual
- // stack limit being hit and an exception has already been raised.
+ // If returned value is non-zero, we exit with the returned value as
result.
// Otherwise it was a preemption and we just check the limit again.
__ j(equal, &retry_stack_check);
- // Return value was non-zero. Exit with exception.
- __ mov(eax, EXCEPTION);
+ // Return value was non-zero. Exit with exception or retry.
__ jmp(&exit_label_);
__ bind(&stack_ok);
@@ -678,17 +667,11 @@
// Allocate space on stack for registers.
__ sub(Operand(esp), Immediate(num_registers_ * kPointerSize));
// Load string length.
- __ mov(esi, Operand(ebp, kInputEndOffset));
+ __ mov(esi, Operand(ebp, kInputEnd));
// Load input position.
- __ mov(edi, Operand(ebp, kInputStartOffset));
+ __ mov(edi, Operand(ebp, kInputStart));
// Set up edi to be negative offset from string end.
__ sub(edi, Operand(esi));
- // Set up esi to be end of string. First get location.
- __ mov(edx, Operand(ebp, kInputBuffer));
- // Dereference location to get string start.
- __ mov(edx, Operand(edx, 0));
- // Add start to length to complete esi setup.
- __ add(esi, Operand(edx));
if (num_saved_registers_ > 0) {
// Fill saved registers with initial value = start offset - 1
// Fill in stack push order, to avoid accessing across an unwritten
@@ -738,8 +721,8 @@
if (num_saved_registers_ > 0) {
// copy captures to output
__ mov(ebx, Operand(ebp, kRegisterOutput));
- __ mov(ecx, Operand(ebp, kInputEndOffset));
- __ sub(ecx, Operand(ebp, kInputStartOffset));
+ __ mov(ecx, Operand(ebp, kInputEnd));
+ __ sub(ecx, Operand(ebp, kInputStart));
for (int i = 0; i < num_saved_registers_; i++) {
__ mov(eax, register_location(i));
__ add(eax, Operand(ecx)); // Convert to index from start, not
end.
@@ -781,28 +764,21 @@
Label retry;
__ bind(&retry);
- int num_arguments = 2;
- FrameAlign(num_arguments, ebx);
- __ mov(Operand(esp, 1 * kPointerSize), Immediate(masm_->CodeObject()));
- __ lea(eax, Operand(esp, -kPointerSize));
- __ mov(Operand(esp, 0 * kPointerSize), eax);
- CallCFunction(FUNCTION_ADDR(&CheckStackGuardState), num_arguments);
- // Return value must be zero. We cannot have a stack overflow at
- // this point, since we checked the stack on entry and haven't
- // pushed anything since, that we haven't also popped again.
-
+ CallCheckStackGuardState(ebx);
+ __ or_(eax, Operand(eax));
+ // If returning non-zero, we should end execution with the given
+ // result as return value.
+ __ j(not_zero, &exit_label_);
+ // Check if we are still preempted.
ExternalReference stack_guard_limit =
ExternalReference::address_of_stack_guard_limit();
- // Check if we are still preempted.
__ cmp(esp, Operand::StaticVariable(stack_guard_limit));
__ j(below_equal, &retry);
__ pop(edi);
__ pop(backtrack_stackpointer());
- // String might have moved: Recompute esi from scratch.
- __ mov(esi, Operand(ebp, kInputBuffer));
- __ mov(esi, Operand(esi, 0));
- __ add(esi, Operand(ebp, kInputEndOffset));
+ // String might have moved: Reload esi from frame.
+ __ mov(esi, Operand(ebp, kInputEnd));
SafeReturn();
}
@@ -978,65 +954,53 @@
}
-
RegExpMacroAssemblerIA32::Result RegExpMacroAssemblerIA32::Match(
Handle<Code> regexp_code,
Handle<String> subject,
int* offsets_vector,
int offsets_vector_length,
int previous_index) {
+
+ ASSERT(subject->IsFlat());
+
+ // No allocations before calling the regexp, but we can't use
+ // AssertNoAllocation, since regexps might be preempted, and another
thread
+ // might do allocation anyway.
+
+ String* subject_ptr = *subject;
// Character offsets into string.
int start_offset = previous_index;
- int end_offset = subject->length();
+ int end_offset = subject_ptr->length();
- if (StringShape(*subject).IsCons()) {
- subject =
- Handle<String>(String::cast(ConsString::cast(*subject)->first()));
- } else if (StringShape(*subject).IsSliced()) {
- SlicedString* slice = SlicedString::cast(*subject);
+ if (StringShape(subject_ptr).IsCons()) {
+ subject_ptr = ConsString::cast(subject_ptr)->first();
+ } else if (StringShape(subject_ptr).IsSliced()) {
+ SlicedString* slice = SlicedString::cast(subject_ptr);
start_offset += slice->start();
end_offset += slice->start();
- subject = Handle<String>(String::cast(slice->buffer()));
+ subject_ptr = slice->buffer();
}
// String is now either Sequential or External
bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
int char_size_shift = is_ascii ? 0 : 1;
+ int char_length = end_offset - start_offset;
- RegExpMacroAssemblerIA32::Result res;
-
- if (StringShape(*subject).IsExternal()) {
- const byte* address;
- if (is_ascii) {
- ExternalAsciiString* ext = ExternalAsciiString::cast(*subject);
- address = reinterpret_cast<const byte*>(ext->resource()->data());
- } else {
- ExternalTwoByteString* ext = ExternalTwoByteString::cast(*subject);
- address = reinterpret_cast<const byte*>(ext->resource()->data());
- }
-
- res = Execute(*regexp_code,
- const_cast<Address*>(&address),
- start_offset << char_size_shift,
- end_offset << char_size_shift,
- offsets_vector,
- previous_index == 0);
- } else { // Sequential string
- ASSERT(StringShape(*subject).IsSequential());
- Address char_address =
- is_ascii ? SeqAsciiString::cast(*subject)->GetCharsAddress()
- : SeqTwoByteString::cast(*subject)->GetCharsAddress();
- int byte_offset = char_address - reinterpret_cast<Address>(*subject);
- res = Execute(*regexp_code,
- reinterpret_cast<Address*>(subject.location()),
- byte_offset + (start_offset << char_size_shift),
- byte_offset + (end_offset << char_size_shift),
- offsets_vector,
- previous_index == 0);
- }
+ const byte* input_start =
+ StringCharacterPosition(subject_ptr, start_offset);
+ int byte_length = char_length << char_size_shift;
+ const byte* input_end = input_start + byte_length;
+ RegExpMacroAssemblerIA32::Result res = Execute(*regexp_code,
+ subject_ptr,
+ start_offset,
+ input_start,
+ input_end,
+ offsets_vector,
+ previous_index == 0);
- if (res == RegExpMacroAssemblerIA32::SUCCESS) {
+ if (res == SUCCESS) {
// Capture values are relative to start_offset only.
+ // Convert them to be relative to start of string.
for (int i = 0; i < offsets_vector_length; i++) {
if (offsets_vector[i] >= 0) {
offsets_vector[i] += previous_index;
@@ -1047,20 +1011,20 @@
return res;
}
-
// Private methods:
-
static unibrow::Mapping<unibrow::Ecma262Canonicalize> canonicalize;
RegExpMacroAssemblerIA32::Result RegExpMacroAssemblerIA32::Execute(
Code* code,
- Address* input,
+ String* input,
int start_offset,
- int end_offset,
+ const byte* input_start,
+ const byte* input_end,
int* output,
bool at_start) {
- typedef int (*matcher)(Address*, int, int, int*, int, Address);
+ typedef int (*matcher)(String*, int, const byte*,
+ const byte*, int*, int, Address);
matcher matcher_func = FUNCTION_CAST<matcher>(code->entry());
int at_start_val = at_start ? 1 : 0;
@@ -1071,31 +1035,32 @@
int result = matcher_func(input,
start_offset,
- end_offset,
+ input_start,
+ input_end,
output,
at_start_val,
stack_top);
+ ASSERT(result <= SUCCESS);
+ ASSERT(result >= RETRY);
- if (result < 0 && !Top::has_pending_exception()) {
+ if (result == EXCEPTION && !Top::has_pending_exception()) {
// We detected a stack overflow (on the backtrack stack) in RegExp
code,
// but haven't created the exception yet.
Top::StackOverflow();
}
- return (result < 0) ? EXCEPTION : (result ? SUCCESS : FAILURE);
+ return static_cast<Result>(result);
}
-int RegExpMacroAssemblerIA32::CaseInsensitiveCompareUC16(uc16** buffer,
- int byte_offset1,
- int byte_offset2,
+int RegExpMacroAssemblerIA32::CaseInsensitiveCompareUC16(Address
byte_offset1,
+ Address
byte_offset2,
size_t
byte_length) {
// This function is not allowed to cause a garbage collection.
// A GC might move the calling generated code and invalidate the
// return address on the stack.
ASSERT(byte_length % 2 == 0);
- Address buffer_address = reinterpret_cast<Address>(*buffer);
- uc16* substring1 = reinterpret_cast<uc16*>(buffer_address +
byte_offset1);
- uc16* substring2 = reinterpret_cast<uc16*>(buffer_address +
byte_offset2);
+ uc16* substring1 = reinterpret_cast<uc16*>(byte_offset1);
+ uc16* substring2 = reinterpret_cast<uc16*>(byte_offset2);
size_t length = byte_length >> 1;
for (size_t i = 0; i < length; i++) {
@@ -1115,19 +1080,75 @@
}
+void RegExpMacroAssemblerIA32::CallCheckStackGuardState(Register scratch) {
+ int num_arguments = 3;
+ FrameAlign(num_arguments, scratch);
+ // RegExp code frame pointer.
+ __ mov(Operand(esp, 2 * kPointerSize), ebp);
+ // Code* of self.
+ __ mov(Operand(esp, 1 * kPointerSize), Immediate(masm_->CodeObject()));
+ // Next address on the stack (will be address of return address).
+ __ lea(eax, Operand(esp, -kPointerSize));
+ __ mov(Operand(esp, 0 * kPointerSize), eax);
+ CallCFunction(FUNCTION_ADDR(&CheckStackGuardState), num_arguments);
+}
+
+
+// Helper function for reading a value out of a stack frame.
+template <typename T>
+static T& frame_entry(Address re_frame, int frame_offset) {
+ return reinterpret_cast<T&>(Memory::int32_at(re_frame + frame_offset));
+}
+
+
+const byte* RegExpMacroAssemblerIA32::StringCharacterPosition(String*
subject,
+ int
start_index) {
+ // Not just flat, but ultra flat.
+ ASSERT(subject->IsExternalString() || subject->IsSeqString());
+ ASSERT(start_index >= 0);
+ ASSERT(start_index <= subject->length());
+ if (StringShape(subject).IsAsciiRepresentation()) {
+ const byte* address;
+ if (subject->IsExternalAsciiString()) {
+ const char* data =
ExternalAsciiString::cast(subject)->resource()->data();
+ address = reinterpret_cast<const byte*>(data);
+ } else {
+ ASSERT(subject->IsSeqAsciiString());
+ char* data = SeqAsciiString::cast(subject)->GetChars();
+ address = reinterpret_cast<const byte*>(data);
+ }
+ return address + start_index;
+ }
+ const uc16* data;
+ if (subject->IsExternalTwoByteString()) {
+ data = ExternalTwoByteString::cast(subject)->resource()->data();
+ } else {
+ ASSERT(subject->IsSeqTwoByteString());
+ data = SeqTwoByteString::cast(subject)->GetChars();
+ }
+ return reinterpret_cast<const byte*>(data + start_index);
+}
+
+
int RegExpMacroAssemblerIA32::CheckStackGuardState(Address* return_address,
- Code* re_code) {
+ Code* re_code,
+ Address re_frame) {
if (StackGuard::IsStackOverflow()) {
Top::StackOverflow();
- return 1;
+ return EXCEPTION;
}
// If not real stack overflow the stack guard was used to interrupt
// execution for another purpose.
// Prepare for possible GC.
+ HandleScope handles;
Handle<Code> code_handle(re_code);
+ Handle<String> subject(frame_entry<String*>(re_frame, kInputString));
+ // Current string.
+ bool is_ascii = StringShape(*subject).IsAsciiRepresentation();
+
ASSERT(re_code->instruction_start() <= *return_address);
ASSERT(*return_address <=
re_code->instruction_start() + re_code->instruction_size());
@@ -1141,8 +1162,41 @@
}
if (result->IsException()) {
- return 1;
+ return EXCEPTION;
}
+
+ // String might have changed.
+ if (StringShape(*subject).IsAsciiRepresentation() != is_ascii) {
+ // If we changed between an ASCII and an UC16 string, the specialized
+ // code cannot be used, and we need to restart regexp matching from
+ // scratch (including, potentially, compiling a new version of the
code).
+ return RETRY;
+ }
+
+ // Otherwise, the content of the string might have moved. It must still
+ // be a sequential or external string with the same content.
+ // Update the start and end pointers in the stack frame to the current
+ // location (whether it has actually moved or not).
+ ASSERT(StringShape(*subject).IsSequential() ||
+ StringShape(*subject).IsExternal());
+
+ // The original start address of the characters to match.
+ const byte* start_address = frame_entry<const byte*>(re_frame,
kInputStart);
+
+ // Find the current start address of the same character at the current
string
+ // position.
+ int start_index = frame_entry<int>(re_frame, kStartIndex);
+ const byte* new_address = StringCharacterPosition(*subject, start_index);
+
+ if (start_address != new_address) {
+ // If there is a difference, update start and end addresses in the
+ // RegExp stack frame to match the new value.
+ const byte* end_address = frame_entry<const byte* >(re_frame,
kInputEnd);
+ int byte_length = end_address - start_address;
+ frame_entry<const byte*>(re_frame, kInputStart) = new_address;
+ frame_entry<const byte*>(re_frame, kInputEnd) = new_address +
byte_length;
+ }
+
return 0;
}
@@ -1266,9 +1320,9 @@
void RegExpMacroAssemblerIA32::FrameAlign(int num_arguments, Register
scratch) {
- // TODO(lrn): Since we no longer use the system stack arbitrarily, we
- // know the current stack alignment - esp points to the last regexp
register.
- // We can do this simpler then.
+ // TODO(lrn): Since we no longer use the system stack arbitrarily (but
we do
+ // use it, e.g., for SafeCall), we know the number of elements on the
stack
+ // since the last frame alignment. We might be able to do this simpler
then.
int frameAlignment = OS::ActivationFrameAlignment();
if (frameAlignment != 0) {
// Make stack end at alignment and make room for num_arguments words
Modified: branches/bleeding_edge/src/regexp-macro-assembler-ia32.h
==============================================================================
--- branches/bleeding_edge/src/regexp-macro-assembler-ia32.h (original)
+++ branches/bleeding_edge/src/regexp-macro-assembler-ia32.h Fri Mar 20
06:26:16 2009
@@ -1,4 +1,4 @@
-// Copyright 2008 the V8 project authors. All rights reserved.
+// Copyright 2008-2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@@ -34,7 +34,16 @@
public:
// Type of input string to generate code for.
enum Mode { ASCII = 1, UC16 = 2 };
- enum Result { EXCEPTION = -1, FAILURE = 0, SUCCESS = 1 };
+ // Result of calling the generated RegExp code:
+ // RETRY: Something significant changed during execution, and the
matching
+ // should be retried from scratch.
+ // EXCEPTION: Something failed during execution. If no exception has been
+ // thrown, it's an internal out-of-memory, and the caller should
+ // throw the exception.
+ // FAILURE: Matching failed.
+ // SUCCESS: Matching succeeded, and the output array has been filled with
+ // capture positions.
+ enum Result { RETRY = -2, EXCEPTION = -1, FAILURE = 0, SUCCESS = 1 };
RegExpMacroAssemblerIA32(Mode mode, int registers_to_save);
virtual ~RegExpMacroAssemblerIA32();
@@ -120,9 +129,10 @@
int previous_index);
static Result Execute(Code* code,
- Address* input,
+ String* input,
int start_offset,
- int end_offset,
+ const byte* input_start,
+ const byte* input_end,
int* output,
bool at_start);
@@ -131,10 +141,13 @@
static const int kFramePointer = 0;
// Above the frame pointer - function parameters and return address.
static const int kReturn_eip = kFramePointer + kPointerSize;
- static const int kInputBuffer = kReturn_eip + kPointerSize;
- static const int kInputStartOffset = kInputBuffer + kPointerSize;
- static const int kInputEndOffset = kInputStartOffset + kPointerSize;
- static const int kRegisterOutput = kInputEndOffset + kPointerSize;
+ static const int kFrameAlign = kReturn_eip + kPointerSize;
+ // Parameters.
+ static const int kInputString = kFrameAlign;
+ static const int kStartIndex = kInputString + kPointerSize;
+ static const int kInputStart = kStartIndex + kPointerSize;
+ static const int kInputEnd = kInputStart + kPointerSize;
+ static const int kRegisterOutput = kInputEnd + kPointerSize;
static const int kAtStart = kRegisterOutput + kPointerSize;
static const int kStackHighEnd = kAtStart + kPointerSize;
// Below the frame pointer - local stack variables.
@@ -152,11 +165,12 @@
// Initial size of constant buffers allocated during compilation.
static const int kRegExpConstantsSize = 256;
+ static const byte* StringCharacterPosition(String* subject, int
start_index);
+
// Compares two-byte strings case insensitively.
// Called from generated RegExp code.
- static int CaseInsensitiveCompareUC16(uc16** buffer,
- int byte_offset1,
- int byte_offset2,
+ static int CaseInsensitiveCompareUC16(Address byte_offset1,
+ Address byte_offset2,
size_t byte_length);
// Load a number of characters at the given offset from the
@@ -172,7 +186,12 @@
// Called from RegExp if the stack-guard is triggered.
// If the code object is relocated, the return address is fixed before
// returning.
- static int CheckStackGuardState(Address* return_address, Code* re_code);
+ static int CheckStackGuardState(Address* return_address,
+ Code* re_code,
+ Address re_frame);
+
+ // Generate a call to CheckStackGuardState.
+ void CallCheckStackGuardState(Register scratch);
// Called from RegExp if the backtrack stack limit is hit.
// Tries to expand the stack. Returns the new stack-pointer if
Modified: branches/bleeding_edge/test/cctest/test-api.cc
==============================================================================
--- branches/bleeding_edge/test/cctest/test-api.cc (original)
+++ branches/bleeding_edge/test/cctest/test-api.cc Fri Mar 20 06:26:16 2009
@@ -5757,6 +5757,8 @@
class RegExpInterruptTest {
public:
+ RegExpInterruptTest() : block_(NULL) {}
+ ~RegExpInterruptTest() { delete block_; }
void RunTest() {
block_ = i::OS::CreateSemaphore(0);
gc_count_ = 0;
@@ -5776,8 +5778,6 @@
CHECK(regexp_success_);
CHECK(gc_success_);
}
- RegExpInterruptTest() : block_(NULL) {}
- ~RegExpInterruptTest() { delete block_; }
private:
// Number of garbage collections required.
static const int kRequiredGCs = 5;
@@ -5813,7 +5813,7 @@
while (gc_during_regexp_ < kRequiredGCs) {
int gc_before = gc_count_;
{
- // match 15-30 "a"'s against 14 and a "b".
+ // Match 15-30 "a"'s against 14 and a "b".
const char* c_source =
"/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/"
".exec('aaaaaaaaaaaaaaab') === null";
@@ -5826,7 +5826,7 @@
}
}
{
- // match 15-30 "a"'s against 15 and a "b".
+ // Match 15-30 "a"'s against 15 and a "b".
const char* c_source =
"/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/"
".exec('aaaaaaaaaaaaaaaab')[0] === 'aaaaaaaaaaaaaaaa'";
@@ -5908,4 +5908,173 @@
clone->Set(v8_str("beta"), v8::Integer::New(456));
CHECK_EQ(v8::Integer::New(123), obj->Get(v8_str("beta")));
CHECK_EQ(v8::Integer::New(456), clone->Get(v8_str("beta")));
+}
+
+
+class RegExpStringModificationTest {
+ public:
+ RegExpStringModificationTest()
+ : block_(i::OS::CreateSemaphore(0)),
+ morphs_(0),
+ morphs_during_regexp_(0),
+ ascii_resource_(i::Vector<const char>("aaaaaaaaaaaaaab", 15)),
+ uc16_resource_(i::Vector<const uint16_t>(two_byte_content_, 15)) {}
+ ~RegExpStringModificationTest() { delete block_; }
+ void RunTest() {
+ regexp_success_ = false;
+ morph_success_ = false;
+
+ // Initialize the contents of two_byte_content_ to be a uc16
representation
+ // of "aaaaaaaaaaaaaab".
+ for (int i = 0; i < 14; i++) {
+ two_byte_content_[i] = 'a';
+ }
+ two_byte_content_[14] = 'b';
+
+ // Create the input string for the regexp - the one we are going to
change
+ // properties of.
+ input_ = i::Factory::NewExternalStringFromAscii(&ascii_resource_);
+
+ // Inject the input as a global variable.
+ i::Handle<i::String> input_name =
+ i::Factory::NewStringFromAscii(i::Vector<const char>("input", 5));
+ i::Top::global_context()->global()->SetProperty(*input_name, *input_,
NONE);
+
+
+ MorphThread morph_thread(this);
+ morph_thread.Start();
+ v8::Locker::StartPreemption(1);
+ LongRunningRegExp();
+ {
+ v8::Unlocker unlock;
+ morph_thread.Join();
+ }
+ v8::Locker::StopPreemption();
+ CHECK(regexp_success_);
+ CHECK(morph_success_);
+ }
+ private:
+
+ class AsciiVectorResource : public
v8::String::ExternalAsciiStringResource {
+ public:
+ explicit AsciiVectorResource(i::Vector<const char> vector)
+ : data_(vector) {}
+ virtual ~AsciiVectorResource() {}
+ virtual size_t length() const { return data_.length(); }
+ virtual const char* data() const { return data_.start(); }
+ private:
+ i::Vector<const char> data_;
+ };
+ class UC16VectorResource : public v8::String::ExternalStringResource {
+ public:
+ explicit UC16VectorResource(i::Vector<const i::uc16> vector)
+ : data_(vector) {}
+ virtual ~UC16VectorResource() {}
+ virtual size_t length() const { return data_.length(); }
+ virtual const i::uc16* data() const { return data_.start(); }
+ private:
+ i::Vector<const i::uc16> data_;
+ };
+ // Number of string modifications required.
+ static const int kRequiredModifications = 5;
+ static const int kMaxModifications = 100;
+
+ class MorphThread : public i::Thread {
+ public:
+ explicit MorphThread(RegExpStringModificationTest* test)
+ : test_(test) {}
+ virtual void Run() {
+ test_->MorphString();
+ }
+ private:
+ RegExpStringModificationTest* test_;
+ };
+
+ void MorphString() {
+ block_->Wait();
+ while (morphs_during_regexp_ < kRequiredModifications &&
+ morphs_ < kMaxModifications) {
+ {
+ v8::Locker lock;
+ // Swap string between ascii and two-byte representation.
+ i::String* string = *input_;
+ CHECK(i::StringShape(string).IsExternal());
+ if (i::StringShape(string).IsAsciiRepresentation()) {
+ // Morph external string to be TwoByte string.
+ i::ExternalAsciiString* ext_string =
+ i::ExternalAsciiString::cast(string);
+ i::ExternalTwoByteString* morphed =
+ reinterpret_cast<i::ExternalTwoByteString*>(ext_string);
+ morphed->map()->set_instance_type(i::SHORT_EXTERNAL_STRING_TYPE);
+ morphed->set_resource(&uc16_resource_);
+ } else {
+ // Morph external string to be ASCII string.
+ i::ExternalTwoByteString* ext_string =
+ i::ExternalTwoByteString::cast(string);
+ i::ExternalAsciiString* morphed =
+ reinterpret_cast<i::ExternalAsciiString*>(ext_string);
+ morphed->map()->set_instance_type(
+ i::SHORT_EXTERNAL_ASCII_STRING_TYPE);
+ morphed->set_resource(&ascii_resource_);
+ }
+ morphs_++;
+ }
+ i::OS::Sleep(1);
+ }
+ morph_success_ = true;
+ }
+
+ void LongRunningRegExp() {
+ block_->Signal(); // Enable morphing thread on next preemption.
+ while (morphs_during_regexp_ < kRequiredModifications &&
+ morphs_ < kMaxModifications) {
+ int morphs_before = morphs_;
+ {
+ // Match 15-30 "a"'s against 14 and a "b".
+ const char* c_source =
+ "/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/"
+ ".exec(input) === null";
+ Local<String> source = String::New(c_source);
+ Local<Script> script = Script::Compile(source);
+ Local<Value> result = script->Run();
+ CHECK(result->IsTrue());
+ }
+ int morphs_after = morphs_;
+ morphs_during_regexp_ += morphs_after - morphs_before;
+ }
+ regexp_success_ = true;
+ }
+
+ i::uc16 two_byte_content_[15];
+ i::Semaphore* block_;
+ int morphs_;
+ int morphs_during_regexp_;
+ bool regexp_success_;
+ bool morph_success_;
+ i::Handle<i::String> input_;
+ AsciiVectorResource ascii_resource_;
+ UC16VectorResource uc16_resource_;
+};
+
+
+// Test that a regular expression execution can be interrupted and
+// the string changed without failing.
+TEST(RegExpStringModification) {
+ v8::Locker lock;
+ v8::V8::Initialize();
+ v8::HandleScope scope;
+ Local<Context> local_env;
+ {
+ LocalContext env;
+ local_env = env.local();
+ }
+
+ // Local context should still be live.
+ CHECK(!local_env.IsEmpty());
+ local_env->Enter();
+
+ // Should complete without problems.
+ RegExpStringModificationTest().RunTest();
+
+ local_env->Exit();
}
Modified: branches/bleeding_edge/test/cctest/test-regexp.cc
==============================================================================
--- branches/bleeding_edge/test/cctest/test-regexp.cc (original)
+++ branches/bleeding_edge/test/cctest/test-regexp.cc Fri Mar 20 06:26:16
2009
@@ -679,36 +679,20 @@
v8::internal::StackGuard stack_guard_;
};
-// Helper functions for calling the Execute method.
-template <typename T>
-static RegExpMacroAssemblerIA32::Result ExecuteIA32(Code* code,
- const T** input,
- int start_offset,
- int end_offset,
- int* captures,
- bool at_start) {
- return RegExpMacroAssemblerIA32::Execute(
- code,
- reinterpret_cast<Address*>(
- reinterpret_cast<void*>(const_cast<T**>(input))),
- start_offset,
- end_offset,
- captures,
- at_start);
-}
-template <typename T>
static RegExpMacroAssemblerIA32::Result ExecuteIA32(Code* code,
- T** input,
+ String* input,
int start_offset,
- int end_offset,
+ const byte*
input_start,
+ const byte* input_end,
int* captures,
bool at_start) {
return RegExpMacroAssemblerIA32::Execute(
code,
- reinterpret_cast<Address*>(reinterpret_cast<void*>(input)),
+ input,
start_offset,
- end_offset,
+ input_start,
+ input_end,
captures,
at_start);
}
@@ -729,15 +713,15 @@
int captures[4] = {42, 37, 87, 117};
Handle<String> input = Factory::NewStringFromAscii(CStrVector("foofoo"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
- Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
+ const byte* start_adr =
+ reinterpret_cast<const byte*>(seq_input->GetCharsAddress());
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + seq_input->length(),
captures,
true);
@@ -775,14 +759,13 @@
Handle<String> input = Factory::NewStringFromAscii(CStrVector("foofoo"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
captures,
true);
@@ -795,13 +778,12 @@
input = Factory::NewStringFromAscii(CStrVector("barbarbar"));
seq_input = Handle<SeqAsciiString>::cast(input);
start_adr = seq_input->GetCharsAddress();
- start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- end_offset = start_offset + seq_input->length();
result = ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
captures,
true);
@@ -837,14 +819,13 @@
Factory::NewStringFromTwoByte(Vector<const uc16>(input_data, 6));
Handle<SeqTwoByteString> seq_input =
Handle<SeqTwoByteString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length() * sizeof(uc16);
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
captures,
true);
@@ -858,13 +839,12 @@
input = Factory::NewStringFromTwoByte(Vector<const uc16>(input_data2,
9));
seq_input = Handle<SeqTwoByteString>::cast(input);
start_adr = seq_input->GetCharsAddress();
- start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- end_offset = start_offset + seq_input->length() * sizeof(uc16);
result = ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length() * 2,
captures,
true);
@@ -896,14 +876,13 @@
Handle<String> input = Factory::NewStringFromAscii(CStrVector("foofoo"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
NULL,
true);
@@ -939,15 +918,14 @@
Handle<String> input = Factory::NewStringFromAscii(CStrVector("fooofo"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
int output[3];
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
output,
true);
@@ -989,15 +967,13 @@
Handle<SeqTwoByteString> seq_input =
Handle<SeqTwoByteString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length() *
sizeof(input_data[0]);
-
int output[3];
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length() * 2,
output,
true);
@@ -1043,24 +1019,23 @@
Handle<String> input = Factory::NewStringFromAscii(CStrVector("foobar"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
NULL,
true);
CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result);
- start_offset += 3;
result = ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 3,
+ start_adr + 3,
+ start_adr + input->length(),
NULL,
false);
@@ -1105,15 +1080,14 @@
Factory::NewStringFromAscii(CStrVector("aBcAbCABCxYzab"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
int output[4];
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
output,
true);
@@ -1206,15 +1180,14 @@
Factory::NewStringFromAscii(CStrVector("foofoofoofoofoo"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
int output[5];
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
output,
true);
@@ -1248,14 +1221,13 @@
Factory::NewStringFromAscii(CStrVector("dummy"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
NULL,
true);
@@ -1294,15 +1266,14 @@
Factory::NewStringFromAscii(CStrVector("sample text"));
Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input);
Address start_adr = seq_input->GetCharsAddress();
- int start_offset = start_adr - reinterpret_cast<Address>(*seq_input);
- int end_offset = start_offset + seq_input->length();
int captures[2];
RegExpMacroAssemblerIA32::Result result =
ExecuteIA32(*code,
- seq_input.location(),
- start_offset,
- end_offset,
+ *input,
+ 0,
+ start_adr,
+ start_adr + input->length(),
captures,
true);
--~--~---------~--~----~------------~-------~--~----~
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
-~----------~----~----~----~------~----~------~--~---