Author: [email protected]
Date: Tue Mar 24 07:47:14 2009
New Revision: 1603
Modified:
trunk/ChangeLog
trunk/SConstruct
trunk/src/api.cc
trunk/src/ast.h
trunk/src/codegen-ia32.cc
trunk/src/d8.cc
trunk/src/d8.h
trunk/src/flag-definitions.h
trunk/src/globals.h
trunk/src/heap.cc
trunk/src/heap.h
trunk/src/jump-target.cc
trunk/src/log.cc
trunk/src/platform-freebsd.cc
trunk/src/platform-linux.cc
trunk/src/platform-macos.cc
trunk/src/platform-nullos.cc
trunk/src/platform-win32.cc
trunk/src/platform.h
trunk/src/regexp-macro-assembler-irregexp-inl.h (props changed)
trunk/src/rewriter.cc
trunk/src/serialize.cc
trunk/src/spaces-inl.h
trunk/src/spaces.cc
trunk/src/spaces.h
trunk/src/virtual-frame-arm.cc
trunk/src/virtual-frame-arm.h
trunk/src/virtual-frame-ia32.cc
trunk/src/virtual-frame-ia32.h
trunk/src/virtual-frame.cc
trunk/test/mjsunit/global-load-from-eval-in-with.js (props changed)
trunk/test/mjsunit/local-load-from-eval.js (props changed)
trunk/test/mjsunit/property-load-across-eval.js (props changed)
trunk/tools/test.py
Log:
Fixed assertion failures in compilation of loop conditions.
Removed STL dependency from developer shell (d8).
Added infrastructure for protecting the V8 heap from corruption caused by
memory modifications from the outside.
Modified: trunk/ChangeLog
==============================================================================
--- trunk/ChangeLog (original)
+++ trunk/ChangeLog Tue Mar 24 07:47:14 2009
@@ -1,3 +1,13 @@
+2009-03-24: Version 1.1.3
+
+ Fixed assertion failures in compilation of loop conditions.
+
+ Removed STL dependency from developer shell (d8).
+
+ Added infrastructure for protecting the V8 heap from corruption
+ caused by memory modifications from the outside.
+
+
2009-03-24: Version 1.1.2
Improved frame merge code generated by the code generator.
Modified: trunk/SConstruct
==============================================================================
--- trunk/SConstruct (original)
+++ trunk/SConstruct Tue Mar 24 07:47:14 2009
@@ -70,6 +70,17 @@
ANDROID_TOP + '/bionic/libm/include/arch/arm',
ANDROID_TOP + '/bionic/libthread_db/include']
+ANDROID_LINKFLAGS = ['-nostdlib',
+ '-Bdynamic',
+ '-Wl,-T,' + ANDROID_TOP + '/build/core/armelf.x',
+ '-Wl,-dynamic-linker,/system/bin/linker',
+ '-Wl,--gc-sections',
+ '-Wl,-z,nocopyreloc',
+ '-Wl,-rpath-link=' + ANDROID_TOP
+ '/out/target/product/generic/obj/lib',
+ ANDROID_TOP
+ '/out/target/product/generic/obj/lib/crtbegin_dynamic.o',
+ ANDROID_TOP
+
'/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a',
+ ANDROID_TOP
+ '/out/target/product/generic/obj/lib/crtend_android.o'];
+
LIBRARY_FLAGS = {
'all': {
'CPPDEFINES': ['ENABLE_LOGGING_AND_PROFILING']
@@ -306,16 +317,7 @@
'CCFLAGS': ANDROID_FLAGS,
'CPPPATH': ANDROID_INCLUDES,
'LIBPATH': [ANDROID_TOP + '/out/target/product/generic/obj/lib'],
- 'LINKFLAGS': ['-nostdlib',
- '-Bdynamic',
- '-Wl,-T,' + ANDROID_TOP + '/build/core/armelf.x',
- '-Wl,-dynamic-linker,/system/bin/linker',
- '-Wl,--gc-sections',
- '-Wl,-z,nocopyreloc',
- '-Wl,-rpath-link=' + ANDROID_TOP
+ '/out/target/product/generic/obj/lib',
- ANDROID_TOP
+ '/out/target/product/generic/obj/lib/crtbegin_dynamic.o',
- ANDROID_TOP
+
'/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a',
- ANDROID_TOP
+ '/out/target/product/generic/obj/lib/crtend_android.o'],
+ 'LINKFLAGS': ANDROID_LINKFLAGS,
'LIBS': ['c', 'stdc++', 'm'],
'mode:release': {
'CPPDEFINES': ['SK_RELEASE', 'NDEBUG']
@@ -385,6 +387,11 @@
},
'os:freebsd': {
'LIBS': ['pthread'],
+ },
+ 'os:android': {
+ 'LIBPATH': [ANDROID_TOP + '/out/target/product/generic/obj/lib'],
+ 'LINKFLAGS': ANDROID_LINKFLAGS,
+ 'LIBS': ['c', 'stdc++', 'm'],
},
'os:win32': {
'LIBS': ['winmm', 'ws2_32'],
Modified: trunk/src/api.cc
==============================================================================
--- trunk/src/api.cc (original)
+++ trunk/src/api.cc Tue Mar 24 07:47:14 2009
@@ -2273,7 +2273,7 @@
const char* v8::V8::GetVersion() {
- return "1.1.2";
+ return "1.1.3";
}
Modified: trunk/src/ast.h
==============================================================================
--- trunk/src/ast.h (original)
+++ trunk/src/ast.h Tue Mar 24 07:47:14 2009
@@ -296,7 +296,7 @@
init_(NULL),
cond_(NULL),
next_(NULL),
- has_function_literal_(false) {
+ may_have_function_literal_(true) {
}
void Initialize(Statement* init,
@@ -317,7 +317,9 @@
Statement* init() const { return init_; }
Expression* cond() const { return cond_; }
Statement* next() const { return next_; }
- bool has_function_literal() const { return has_function_literal_; }
+ bool may_have_function_literal() const {
+ return may_have_function_literal_;
+ }
#ifdef DEBUG
const char* OperatorString() const;
@@ -329,7 +331,7 @@
Expression* cond_;
Statement* next_;
// True if there is a function literal subexpression in the condition.
- bool has_function_literal_;
+ bool may_have_function_literal_;
friend class AstOptimizer;
};
Modified: trunk/src/codegen-ia32.cc
==============================================================================
--- trunk/src/codegen-ia32.cc (original)
+++ trunk/src/codegen-ia32.cc Tue Mar 24 07:47:14 2009
@@ -2261,11 +2261,10 @@
}
case LoopStatement::WHILE_LOOP: {
- // Do not duplicate conditions with function literal
+ // Do not duplicate conditions that may have function literal
// subexpressions. This can cause us to compile the function
// literal twice.
- bool test_at_bottom =
- !scope_->is_global_scope() && !node->has_function_literal();
+ bool test_at_bottom = !node->may_have_function_literal();
IncrementLoopNesting();
@@ -2360,11 +2359,10 @@
}
case LoopStatement::FOR_LOOP: {
- // Do not duplicate conditions with function literal
+ // Do not duplicate conditions that may have function literal
// subexpressions. This can cause us to compile the function
// literal twice.
- bool test_at_bottom =
- !scope_->is_global_scope() && !node->has_function_literal();
+ bool test_at_bottom = !node->may_have_function_literal();
// Compile the init expression if present.
if (node->init() != NULL) {
Modified: trunk/src/d8.cc
==============================================================================
--- trunk/src/d8.cc (original)
+++ trunk/src/d8.cc Tue Mar 24 07:47:14 2009
@@ -86,7 +86,7 @@
}
-Shell::CounterMap Shell::counter_map_;
+CounterMap* Shell::counter_map_;
i::OS::MemoryMappedFile* Shell::counters_file_ = NULL;
CounterCollection Shell::local_counters_;
CounterCollection* Shell::counters_ = &local_counters_;
@@ -94,6 +94,13 @@
Persistent<Context> Shell::evaluation_context_;
+bool CounterMap::Match(void* key1, void* key2) {
+ const char* name1 = reinterpret_cast<const char*>(key1);
+ const char* name2 = reinterpret_cast<const char*>(key2);
+ return strcmp(name1, name2) == 0;
+}
+
+
// Converts a V8 value to a C string.
const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
@@ -298,20 +305,31 @@
}
+int CounterMap::Hash(const char* name) {
+ int h = 0;
+ int c;
+ while ((c = *name++) != 0) {
+ h += h << 5;
+ h += c;
+ }
+ return h;
+}
+
+
int* Shell::LookupCounter(const char* name) {
- CounterMap::iterator item = counter_map_.find(name);
- if (item != counter_map_.end()) {
- Counter* result = (*item).second;
- return result->ptr();
+ Counter* counter = counter_map_->Lookup(name);
+ if (counter != NULL) {
+ return counter->ptr();
}
Counter* result = counters_->GetNextCounter();
if (result == NULL) return NULL;
- counter_map_[name] = result;
+ counter_map_->Set(name, result);
return result->Bind(name);
}
void Shell::Initialize() {
+ Shell::counter_map_ = new CounterMap();
// Set up counters
if (i::FLAG_map_counters != NULL)
MapCounters(i::FLAG_map_counters);
@@ -382,11 +400,9 @@
::printf("+----------------------------------------+-------------+\n");
::printf("| Name | Value |\n");
::printf("+----------------------------------------+-------------+\n");
- for (CounterMap::iterator i = counter_map_.begin();
- i != counter_map_.end();
- i++) {
- Counter* counter = (*i).second;
- ::printf("| %-38s | %11i |\n", (*i).first, counter->value());
+ for (CounterMap::Iterator i(counter_map_); i.More(); i.Next()) {
+ Counter* counter = i.CurrentValue();
+ ::printf("| %-38s | %11i |\n", i.CurrentKey(), counter->value());
}
::printf("+----------------------------------------+-------------+\n");
}
Modified: trunk/src/d8.h
==============================================================================
--- trunk/src/d8.h (original)
+++ trunk/src/d8.h Tue Mar 24 07:47:14 2009
@@ -28,12 +28,8 @@
#ifndef V8_D8_H_
#define V8_D8_H_
-
-// Disable exceptions on windows to not generate warnings from <map>.
-#define _HAS_EXCEPTIONS 0
-#include <map>
-
#include "v8.h"
+#include "hashmap.h"
namespace v8 {
@@ -72,6 +68,44 @@
};
+class CounterMap {
+ public:
+ CounterMap(): hash_map_(Match) { }
+ Counter* Lookup(const char* name) {
+ i::HashMap::Entry* answer = hash_map_.Lookup(
+ const_cast<char*>(name),
+ Hash(name),
+ false);
+ if (!answer) return NULL;
+ return reinterpret_cast<Counter*>(answer->value);
+ }
+ void Set(const char* name, Counter* value) {
+ i::HashMap::Entry* answer = hash_map_.Lookup(
+ const_cast<char*>(name),
+ Hash(name),
+ true);
+ ASSERT(answer != NULL);
+ answer->value = value;
+ }
+ class Iterator {
+ public:
+ explicit Iterator(CounterMap* map)
+ : map_(&map->hash_map_), entry_(map_->Start()) { }
+ void Next() { entry_ = map_->Next(entry_); }
+ bool More() { return entry_ != NULL; }
+ const char* CurrentKey() { return static_cast<const
char*>(entry_->key); }
+ Counter* CurrentValue() { return static_cast<Counter*>(entry_->value);
}
+ private:
+ i::HashMap* map_;
+ i::HashMap::Entry* entry_;
+ };
+ private:
+ static int Hash(const char* name);
+ static bool Match(void* key1, void* key2);
+ i::HashMap hash_map_;
+};
+
+
class Shell: public i::AllStatic {
public:
static bool ExecuteString(Handle<String> source,
@@ -104,8 +138,7 @@
private:
static Persistent<Context> utility_context_;
static Persistent<Context> evaluation_context_;
- typedef std::map<const char*, Counter*> CounterMap;
- static CounterMap counter_map_;
+ static CounterMap* counter_map_;
// We statically allocate a set of local counters to be used if we
// don't want to store the stats in a memory-mapped file
static CounterCollection local_counters_;
Modified: trunk/src/flag-definitions.h
==============================================================================
--- trunk/src/flag-definitions.h (original)
+++ trunk/src/flag-definitions.h Tue Mar 24 07:47:14 2009
@@ -337,8 +337,20 @@
DEFINE_bool(sliding_state_window, false,
"Update sliding state window counters.")
DEFINE_string(logfile, "v8.log", "Specify the name of the log file.")
-DEFINE_bool(oprofile, false,
- "Enable JIT agent for OProfile.")
+DEFINE_bool(oprofile, false, "Enable JIT agent for OProfile.")
+
+//
+// Heap protection flags
+// Using heap protection requires ENABLE_LOGGING_AND_PROFILING as well.
+//
+#ifdef ENABLE_HEAP_PROTECTION
+#undef FLAG
+#define FLAG FLAG_FULL
+
+DEFINE_bool(protect_heap, false,
+ "Protect/unprotect V8's heap when leaving/entring the VM.")
+
+#endif
//
// Disassembler only flags
Modified: trunk/src/globals.h
==============================================================================
--- trunk/src/globals.h (original)
+++ trunk/src/globals.h Tue Mar 24 07:47:14 2009
@@ -359,7 +359,8 @@
V(JS) \
V(GC) \
V(COMPILER) \
- V(OTHER)
+ V(OTHER) \
+ V(EXTERNAL)
enum StateTag {
#define DEF_STATE_TAG(name) name,
Modified: trunk/src/heap.cc
==============================================================================
--- trunk/src/heap.cc (original)
+++ trunk/src/heap.cc Tue Mar 24 07:47:14 2009
@@ -2861,6 +2861,30 @@
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void Heap::Protect() {
+ new_space_.Protect();
+ map_space_->Protect();
+ old_pointer_space_->Protect();
+ old_data_space_->Protect();
+ code_space_->Protect();
+ lo_space_->Protect();
+}
+
+
+void Heap::Unprotect() {
+ new_space_.Unprotect();
+ map_space_->Unprotect();
+ old_pointer_space_->Unprotect();
+ old_data_space_->Unprotect();
+ code_space_->Unprotect();
+ lo_space_->Unprotect();
+}
+
+#endif
+
+
#ifdef DEBUG
class PrintHandleVisitor: public ObjectVisitor {
Modified: trunk/src/heap.h
==============================================================================
--- trunk/src/heap.h (original)
+++ trunk/src/heap.h Tue Mar 24 07:47:14 2009
@@ -273,6 +273,12 @@
return new_space_.allocation_limit_address();
}
+#ifdef ENABLE_HEAP_PROTECTION
+ // Protect/unprotect the heap by marking all spaces read-only/writable.
+ static void Protect();
+ static void Unprotect();
+#endif
+
// Allocates and initializes a new JavaScript object based on a
// constructor.
// Returns Failure::RetryAfterGC(requested_bytes, space) if the
allocation
Modified: trunk/src/jump-target.cc
==============================================================================
--- trunk/src/jump-target.cc (original)
+++ trunk/src/jump-target.cc Tue Mar 24 07:47:14 2009
@@ -272,11 +272,15 @@
// Set the copied flags in the frame to be exact. This assumes that
// the backing store of copies is always lower in the frame.
+ // Set the register counts and indices.
for (int i = 0; i < length; i++) {
+ FrameElement current = entry_frame_->elements_[i];
entry_frame_->elements_[i].clear_copied();
- if (entry_frame_->elements_[i].is_copy()) {
- int index = entry_frame_->elements_[i].index();
- entry_frame_->elements_[index].set_copied();
+ if (current.is_copy()) {
+ entry_frame_->elements_[current.index()].set_copied();
+ } else if (current.is_register()) {
+ entry_frame_->frame_registers_.Use(current.reg());
+ entry_frame_->register_locations_[current.reg().code()] = i;
}
}
@@ -292,11 +296,6 @@
stack_pointer--;
}
entry_frame_->stack_pointer_ = stack_pointer;
-
- // Unuse the reserved registers---they do not actually appear in
- // the entry frame.
- RegisterAllocator::UnuseReserved(&frame_registers);
- entry_frame_->frame_registers_ = frame_registers;
}
Modified: trunk/src/log.cc
==============================================================================
--- trunk/src/log.cc (original)
+++ trunk/src/log.cc Tue Mar 24 07:47:14 2009
@@ -29,10 +29,12 @@
#include "v8.h"
+#include "bootstrapper.h"
#include "log.h"
+#include "macro-assembler.h"
#include "platform.h"
+#include "serialize.h"
#include "string-stream.h"
-#include "macro-assembler.h"
namespace v8 { namespace internal {
@@ -1115,10 +1117,23 @@
if (FLAG_log_state_changes) {
LOG(UncheckedStringEvent("Entering", StateToString(state_)));
- if (previous_) {
+ if (previous_ != NULL) {
LOG(UncheckedStringEvent("From", StateToString(previous_->state_)));
}
}
+
+#ifdef ENABLE_HEAP_PROTECTION
+ if (FLAG_protect_heap && previous_ != NULL) {
+ if (state_ == EXTERNAL) {
+ // We are leaving V8.
+ ASSERT(previous_ == NULL || previous_->state_ != EXTERNAL);
+ Heap::Protect();
+ } else {
+ // Are we entering V8?
+ if (previous_->state_ == EXTERNAL) Heap::Unprotect();
+ }
+ }
+#endif
}
@@ -1127,10 +1142,22 @@
if (FLAG_log_state_changes) {
LOG(UncheckedStringEvent("Leaving", StateToString(state_)));
- if (previous_) {
+ if (previous_ != NULL) {
LOG(UncheckedStringEvent("To", StateToString(previous_->state_)));
}
}
+
+#ifdef ENABLE_HEAP_PROTECTION
+ if (FLAG_protect_heap && previous_ != NULL) {
+ if (state_ == EXTERNAL) {
+ // Are we (re)entering V8?
+ if (previous_->state_ != EXTERNAL) Heap::Unprotect();
+ } else {
+ // Are we leaving V8?
+ if (previous_->state_ == EXTERNAL) Heap::Protect();
+ }
+ }
+#endif
}
#endif
Modified: trunk/src/platform-freebsd.cc
==============================================================================
--- trunk/src/platform-freebsd.cc (original)
+++ trunk/src/platform-freebsd.cc Tue Mar 24 07:47:14 2009
@@ -257,6 +257,20 @@
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void OS::Protect(void* address, size_t size) {
+ UNIMPLEMENTED();
+}
+
+
+void OS::Unprotect(void* address, size_t size, bool is_executable) {
+ UNIMPLEMENTED();
+}
+
+#endif
+
+
void OS::Sleep(int milliseconds) {
unsigned int ms = static_cast<unsigned int>(milliseconds);
usleep(1000 * ms);
Modified: trunk/src/platform-linux.cc
==============================================================================
--- trunk/src/platform-linux.cc (original)
+++ trunk/src/platform-linux.cc Tue Mar 24 07:47:14 2009
@@ -234,9 +234,9 @@
void* OS::Allocate(const size_t requested,
size_t* allocated,
- bool executable) {
+ bool is_executable) {
const size_t msize = RoundUp(requested, sysconf(_SC_PAGESIZE));
- int prot = PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0);
+ int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
void* mbase = mmap(NULL, msize, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1,
0);
if (mbase == MAP_FAILED) {
LOG(StringEvent("OS::Allocate", "mmap failed"));
@@ -248,12 +248,29 @@
}
-void OS::Free(void* buf, const size_t length) {
+void OS::Free(void* address, const size_t size) {
// TODO(1240712): munmap has a return value which is ignored here.
- munmap(buf, length);
+ munmap(address, size);
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void OS::Protect(void* address, size_t size) {
+ // TODO(1240712): mprotect has a return value which is ignored here.
+ mprotect(address, size, PROT_READ);
+}
+
+
+void OS::Unprotect(void* address, size_t size, bool is_executable) {
+ // TODO(1240712): mprotect has a return value which is ignored here.
+ int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
+ mprotect(address, size, prot);
+}
+
+#endif
+
+
void OS::Sleep(int milliseconds) {
unsigned int ms = static_cast<unsigned int>(milliseconds);
usleep(1000 * ms);
@@ -267,7 +284,7 @@
void OS::DebugBreak() {
-#if defined (__arm__) || defined(__thumb__)
+#if defined(__arm__) || defined(__thumb__)
asm("bkpt 0");
#else
asm("int $3");
@@ -418,8 +435,8 @@
}
-bool VirtualMemory::Commit(void* address, size_t size, bool executable) {
- int prot = PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0);
+bool VirtualMemory::Commit(void* address, size_t size, bool is_executable)
{
+ int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
if (MAP_FAILED == mmap(address, size, prot,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
kMmapFd, kMmapFdOffset)) {
Modified: trunk/src/platform-macos.cc
==============================================================================
--- trunk/src/platform-macos.cc (original)
+++ trunk/src/platform-macos.cc Tue Mar 24 07:47:14 2009
@@ -228,9 +228,9 @@
void* OS::Allocate(const size_t requested,
size_t* allocated,
- bool executable) {
+ bool is_executable) {
const size_t msize = RoundUp(requested, getpagesize());
- int prot = PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0);
+ int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
void* mbase = mmap(NULL, msize, prot, MAP_PRIVATE | MAP_ANON, -1, 0);
if (mbase == MAP_FAILED) {
LOG(StringEvent("OS::Allocate", "mmap failed"));
@@ -242,12 +242,26 @@
}
-void OS::Free(void* buf, const size_t length) {
+void OS::Free(void* address, const size_t size) {
// TODO(1240712): munmap has a return value which is ignored here.
- munmap(buf, length);
+ munmap(address, size);
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void OS::Protect(void* address, size_t size) {
+ UNIMPLEMENTED();
+}
+
+
+void OS::Unprotect(void* address, size_t size, bool is_executable) {
+ UNIMPLEMENTED();
+}
+
+#endif
+
+
void OS::Sleep(int milliseconds) {
usleep(1000 * milliseconds);
}
@@ -370,8 +384,8 @@
}
-bool VirtualMemory::Commit(void* address, size_t size, bool executable) {
- int prot = PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0);
+bool VirtualMemory::Commit(void* address, size_t size, bool is_executable)
{
+ int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
if (MAP_FAILED == mmap(address, size, prot,
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
kMmapFd, kMmapFdOffset)) {
@@ -388,6 +402,7 @@
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE,
kMmapFd, kMmapFdOffset) != MAP_FAILED;
}
+
class ThreadHandle::PlatformData : public Malloced {
public:
Modified: trunk/src/platform-nullos.cc
==============================================================================
--- trunk/src/platform-nullos.cc (original)
+++ trunk/src/platform-nullos.cc Tue Mar 24 07:47:14 2009
@@ -173,6 +173,20 @@
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void OS::Protect(void* address, size_t size) {
+ UNIMPLEMENTED();
+}
+
+
+void OS::Unprotect(void* address, size_t size, bool is_executable) {
+ UNIMPLEMENTED();
+}
+
+#endif
+
+
void OS::Sleep(int milliseconds) {
UNIMPLEMENTED();
}
Modified: trunk/src/platform-win32.cc
==============================================================================
--- trunk/src/platform-win32.cc (original)
+++ trunk/src/platform-win32.cc Tue Mar 24 07:47:14 2009
@@ -801,12 +801,12 @@
void* OS::Allocate(const size_t requested,
size_t* allocated,
- bool executable) {
+ bool is_executable) {
// VirtualAlloc rounds allocated size to page size automatically.
size_t msize = RoundUp(requested, GetPageSize());
// Windows XP SP2 allows Data Excution Prevention (DEP).
- int prot = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
+ int prot = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
LPVOID mbase = VirtualAlloc(NULL, msize, MEM_COMMIT | MEM_RESERVE, prot);
if (mbase == NULL) {
LOG(StringEvent("OS::Allocate", "VirtualAlloc failed"));
@@ -821,13 +821,32 @@
}
-void OS::Free(void* buf, const size_t length) {
+void OS::Free(void* address, const size_t size) {
// TODO(1240712): VirtualFree has a return value which is ignored here.
- VirtualFree(buf, 0, MEM_RELEASE);
- USE(length);
+ VirtualFree(address, 0, MEM_RELEASE);
+ USE(size);
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void OS::Protect(void* address, size_t size) {
+ // TODO(1240712): VirtualProtect has a return value which is ignored
here.
+ DWORD old_protect;
+ VirtualProtect(address, size, PAGE_READONLY, &old_protect);
+}
+
+
+void OS::Unprotect(void* address, size_t size, bool is_executable) {
+ // TODO(1240712): VirtualProtect has a return value which is ignored
here.
+ DWORD new_protect = is_executable ? PAGE_EXECUTE_READWRITE :
PAGE_READWRITE;
+ DWORD old_protect;
+ VirtualProtect(address, size, new_protect, &old_protect);
+}
+
+#endif
+
+
void OS::Sleep(int milliseconds) {
::Sleep(milliseconds);
}
@@ -1299,8 +1318,8 @@
}
-bool VirtualMemory::Commit(void* address, size_t size, bool executable) {
- int prot = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
+bool VirtualMemory::Commit(void* address, size_t size, bool is_executable)
{
+ int prot = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
if (NULL == VirtualAlloc(address, size, MEM_COMMIT, prot)) {
return false;
}
Modified: trunk/src/platform.h
==============================================================================
--- trunk/src/platform.h (original)
+++ trunk/src/platform.h Tue Mar 24 07:47:14 2009
@@ -168,11 +168,17 @@
// Returns the address of allocated memory, or NULL if failed.
static void* Allocate(const size_t requested,
size_t* allocated,
- bool executable);
- static void Free(void* buf, const size_t length);
+ bool is_executable);
+ static void Free(void* address, const size_t size);
// Get the Alignment guaranteed by Allocate().
static size_t AllocateAlignment();
+#ifdef ENABLE_HEAP_PROTECTION
+ // Protect/unprotect a block of memory by marking it read-only/writable.
+ static void Protect(void* address, size_t size);
+ static void Unprotect(void* address, size_t size, bool is_executable);
+#endif
+
// Returns an indication of whether a pointer is in a space that
// has been allocated by Allocate(). This method may conservatively
// always return false, but giving more accurate information may
@@ -267,7 +273,7 @@
size_t size() { return size_; }
// Commits real memory. Returns whether the operation succeeded.
- bool Commit(void* address, size_t size, bool executable);
+ bool Commit(void* address, size_t size, bool is_executable);
// Uncommit real memory. Returns whether the operation succeeded.
bool Uncommit(void* address, size_t size);
Modified: trunk/src/rewriter.cc
==============================================================================
--- trunk/src/rewriter.cc (original)
+++ trunk/src/rewriter.cc Tue Mar 24 07:47:14 2009
@@ -100,7 +100,7 @@
if (node->cond() != NULL) {
has_function_literal_ = false;
Visit(node->cond());
- node->has_function_literal_ = has_function_literal_;
+ node->may_have_function_literal_ = has_function_literal_;
}
if (node->body() != NULL) {
Visit(node->body());
Modified: trunk/src/serialize.cc
==============================================================================
--- trunk/src/serialize.cc (original)
+++ trunk/src/serialize.cc Tue Mar 24 07:47:14 2009
@@ -963,8 +963,8 @@
Address Serializer::GetSavedAddress(HeapObject* obj) {
- HashMap::Entry* entry
- = saved_addresses_.Lookup(obj, HeapObjectHash(obj), false);
+ HashMap::Entry* entry =
+ saved_addresses_.Lookup(obj, HeapObjectHash(obj), false);
ASSERT(entry != NULL);
return reinterpret_cast<Address>(entry->value);
}
Modified: trunk/src/spaces-inl.h
==============================================================================
--- trunk/src/spaces-inl.h (original)
+++ trunk/src/spaces-inl.h Tue Mar 24 07:47:14 2009
@@ -219,6 +219,43 @@
}
+bool MemoryAllocator::InInitialChunk(Address address) {
+ if (initial_chunk_ == NULL) return false;
+
+ Address start = static_cast<Address>(initial_chunk_->address());
+ return (start <= address) && (address < start + initial_chunk_->size());
+}
+
+
+#ifdef ENABLE_HEAP_PROTECTION
+
+void MemoryAllocator::Protect(Address start, size_t size) {
+ OS::Protect(start, size);
+}
+
+
+void MemoryAllocator::Unprotect(Address start,
+ size_t size,
+ Executability executable) {
+ OS::Unprotect(start, size, executable);
+}
+
+
+void MemoryAllocator::ProtectChunkFromPage(Page* page) {
+ int id = GetChunkId(page);
+ OS::Protect(chunks_[id].address(), chunks_[id].size());
+}
+
+
+void MemoryAllocator::UnprotectChunkFromPage(Page* page) {
+ int id = GetChunkId(page);
+ OS::Unprotect(chunks_[id].address(), chunks_[id].size(),
+ chunks_[id].owner()->executable() == EXECUTABLE);
+}
+
+#endif
+
+
//
--------------------------------------------------------------------------
// PagedSpace
Modified: trunk/src/spaces.cc
==============================================================================
--- trunk/src/spaces.cc (original)
+++ trunk/src/spaces.cc Tue Mar 24 07:47:14 2009
@@ -302,9 +302,8 @@
*num_pages = PagesInChunk(start, size);
ASSERT(*num_pages > 0);
ASSERT(initial_chunk_ != NULL);
- ASSERT(initial_chunk_->address() <= start);
- ASSERT(start + size <=
reinterpret_cast<Address>(initial_chunk_->address())
- + initial_chunk_->size());
+ ASSERT(InInitialChunk(start));
+ ASSERT(InInitialChunk(start + size - 1));
if (!initial_chunk_->Commit(start, size, owner->executable() ==
EXECUTABLE)) {
return Page::FromAddress(NULL);
}
@@ -325,9 +324,8 @@
ASSERT(start != NULL);
ASSERT(size > 0);
ASSERT(initial_chunk_ != NULL);
- ASSERT(initial_chunk_->address() <= start);
- ASSERT(start + size <=
reinterpret_cast<Address>(initial_chunk_->address())
- + initial_chunk_->size());
+ ASSERT(InInitialChunk(start));
+ ASSERT(InInitialChunk(start + size - 1));
if (!initial_chunk_->Commit(start, size, executable)) return false;
Counters::memory_allocated.Increment(size);
@@ -407,14 +405,7 @@
// We cannot free a chunk contained in the initial chunk because it was
not
// allocated with AllocateRawMemory. Instead we uncommit the virtual
// memory.
- bool in_initial_chunk = false;
- if (initial_chunk_ != NULL) {
- Address start = static_cast<Address>(initial_chunk_->address());
- Address end = start + initial_chunk_->size();
- in_initial_chunk = (start <= c.address()) && (c.address() < end);
- }
-
- if (in_initial_chunk) {
+ if (InInitialChunk(c.address())) {
// TODO(1240712): VirtualMemory::Uncommit has a return value which
// is ignored here.
initial_chunk_->Uncommit(c.address(), c.size());
@@ -529,6 +520,28 @@
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void PagedSpace::Protect() {
+ Page* page = first_page_;
+ while (page->is_valid()) {
+ MemoryAllocator::ProtectChunkFromPage(page);
+ page = MemoryAllocator::FindLastPageInSameChunk(page)->next_page();
+ }
+}
+
+
+void PagedSpace::Unprotect() {
+ Page* page = first_page_;
+ while (page->is_valid()) {
+ MemoryAllocator::UnprotectChunkFromPage(page);
+ page = MemoryAllocator::FindLastPageInSameChunk(page)->next_page();
+ }
+}
+
+#endif
+
+
void PagedSpace::ClearRSet() {
PageIterator it(this, PageIterator::ALL_PAGES);
while (it.has_next()) {
@@ -834,6 +847,24 @@
}
+#ifdef ENABLE_HEAP_PROTECTION
+
+void NewSpace::Protect() {
+ MemoryAllocator::Protect(ToSpaceLow(), Capacity());
+ MemoryAllocator::Protect(FromSpaceLow(), Capacity());
+}
+
+
+void NewSpace::Unprotect() {
+ MemoryAllocator::Unprotect(ToSpaceLow(), Capacity(),
+ to_space_.executable());
+ MemoryAllocator::Unprotect(FromSpaceLow(), Capacity(),
+ from_space_.executable());
+}
+
+#endif
+
+
void NewSpace::Flip() {
SemiSpace tmp = from_space_;
from_space_ = to_space_;
@@ -2240,6 +2271,30 @@
size_ = 0;
page_count_ = 0;
}
+
+
+#ifdef ENABLE_HEAP_PROTECTION
+
+void LargeObjectSpace::Protect() {
+ LargeObjectChunk* chunk = first_chunk_;
+ while (chunk != NULL) {
+ MemoryAllocator::Protect(chunk->address(), chunk->size());
+ chunk = chunk->next();
+ }
+}
+
+
+void LargeObjectSpace::Unprotect() {
+ LargeObjectChunk* chunk = first_chunk_;
+ while (chunk != NULL) {
+ bool is_code = chunk->GetObject()->IsCode();
+ MemoryAllocator::Unprotect(chunk->address(), chunk->size(),
+ is_code ? EXECUTABLE : NOT_EXECUTABLE);
+ chunk = chunk->next();
+ }
+}
+
+#endif
Object* LargeObjectSpace::AllocateRawInternal(int requested_size,
Modified: trunk/src/spaces.h
==============================================================================
--- trunk/src/spaces.h (original)
+++ trunk/src/spaces.h Tue Mar 24 07:47:14 2009
@@ -277,16 +277,22 @@
public:
Space(AllocationSpace id, Executability executable)
: id_(id), executable_(executable) {}
+
virtual ~Space() {}
+
// Does the space need executable memory?
Executability executable() { return executable_; }
+
// Identity used in error reporting.
AllocationSpace identity() { return id_; }
+
virtual int Size() = 0;
+
#ifdef DEBUG
virtual void Verify() = 0;
virtual void Print() = 0;
#endif
+
private:
AllocationSpace id_;
Executability executable_;
@@ -396,6 +402,17 @@
static Page* FindFirstPageInSameChunk(Page* p);
static Page* FindLastPageInSameChunk(Page* p);
+#ifdef ENABLE_HEAP_PROTECTION
+ // Protect/unprotect a block of memory by marking it read-only/writable.
+ static inline void Protect(Address start, size_t size);
+ static inline void Unprotect(Address start, size_t size,
+ Executability executable);
+
+ // Protect/unprotect a chunk given a page in the chunk.
+ static inline void ProtectChunkFromPage(Page* page);
+ static inline void UnprotectChunkFromPage(Page* page);
+#endif
+
#ifdef DEBUG
// Reports statistic info of the space.
static void ReportStatistics();
@@ -460,6 +477,9 @@
// Returns the chunk id that a page belongs to.
static inline int GetChunkId(Page* p);
+ // True if the address lies in the initial chunk.
+ static inline bool InInitialChunk(Address address);
+
// Initializes pages in a chunk. Returns the first page address.
// This function and GetChunkId() are provided for the mark-compact
// collector to rebuild page headers in the from space, which is
@@ -669,7 +689,6 @@
class PagedSpace : public Space {
- friend class PageIterator;
public:
// Creates a space with a maximum capacity, and an id.
PagedSpace(int max_capacity, AllocationSpace id, Executability
executable);
@@ -764,6 +783,12 @@
// Ensures that the capacity is at least 'capacity'. Returns false on
failure.
bool EnsureCapacity(int capacity);
+#ifdef ENABLE_HEAP_PROTECTION
+ // Protect/unprotect the space by marking it read-only/writable.
+ void Protect();
+ void Unprotect();
+#endif
+
#ifdef DEBUG
// Print meta info and objects in this space.
virtual void Print();
@@ -834,6 +859,8 @@
// Returns the number of total pages in this space.
int CountTotalPages();
#endif
+
+ friend class PageIterator;
};
@@ -1117,6 +1144,12 @@
bool ToSpaceContains(Address a) { return to_space_.Contains(a); }
bool FromSpaceContains(Address a) { return from_space_.Contains(a); }
+#ifdef ENABLE_HEAP_PROTECTION
+ // Protect/unprotect the space by marking it read-only/writable.
+ virtual void Protect();
+ virtual void Unprotect();
+#endif
+
#ifdef DEBUG
// Verify the active semispace.
virtual void Verify();
@@ -1554,7 +1587,6 @@
class LargeObjectSpace : public Space {
- friend class LargeObjectIterator;
public:
explicit LargeObjectSpace(AllocationSpace id);
virtual ~LargeObjectSpace() {}
@@ -1606,6 +1638,12 @@
// Checks whether the space is empty.
bool IsEmpty() { return first_chunk_ == NULL; }
+#ifdef ENABLE_HEAP_PROTECTION
+ // Protect/unprotect the space by marking it read-only/writable.
+ void Protect();
+ void Unprotect();
+#endif
+
#ifdef DEBUG
virtual void Verify();
virtual void Print();
@@ -1634,6 +1672,8 @@
// Returns the number of extra bytes (rounded up to the nearest full
word)
// required for extra_object_bytes of extra pointers (in bytes).
static inline int ExtraRSetBytesFor(int extra_object_bytes);
+
+ friend class LargeObjectIterator;
public:
TRACK_MEMORY("LargeObjectSpace")
Modified: trunk/src/virtual-frame-arm.cc
==============================================================================
--- trunk/src/virtual-frame-arm.cc (original)
+++ trunk/src/virtual-frame-arm.cc Tue Mar 24 07:47:14 2009
@@ -52,6 +52,9 @@
for (int i = 0; i < parameter_count_ + 1; i++) {
elements_.Add(FrameElement::MemoryElement());
}
+ for (int i = 0; i < kNumRegisters; i++) {
+ register_locations_[i] = kIllegalIndex;
+ }
}
Modified: trunk/src/virtual-frame-arm.h
==============================================================================
--- trunk/src/virtual-frame-arm.h (original)
+++ trunk/src/virtual-frame-arm.h Tue Mar 24 07:47:14 2009
@@ -338,6 +338,10 @@
// (the fp register).
int frame_pointer_;
+ // The index of the register frame element using each register, or
+ // kIllegalIndex if a register is not on the frame.
+ int register_locations_[kNumRegisters];
+
// The frame has an embedded register file that it uses to track
registers
// used in the frame.
RegisterFile frame_registers_;
@@ -377,12 +381,13 @@
}
// Record an occurrence of a register in the virtual frame. This has the
- // effect of incrementing both the register's frame-internal reference
- // count and its external reference count.
- void Use(Register reg);
+ // effect of incrementing the register's external reference count and
+ // of updating the index of the register's location in the frame.
+ void Use(Register reg, int index);
// Record that a register reference has been dropped from the frame.
This
- // decrements both the register's internal and external reference counts.
+ // decrements the register's external reference count and invalidates the
+ // index of the register's location in the frame.
void Unuse(Register reg);
// Spill the element at a particular index---write it to memory if
Modified: trunk/src/virtual-frame-ia32.cc
==============================================================================
--- trunk/src/virtual-frame-ia32.cc (original)
+++ trunk/src/virtual-frame-ia32.cc Tue Mar 24 07:47:14 2009
@@ -51,6 +51,9 @@
for (int i = 0; i < parameter_count_ + 2; i++) {
elements_.Add(FrameElement::MemoryElement());
}
+ for (int i = 0; i < kNumRegisters; i++) {
+ register_locations_[i] = kIllegalIndex;
+ }
}
@@ -325,7 +328,7 @@
if (target.is_synced() && !source.is_synced()) {
SyncElementAt(i);
}
- Use(target.reg());
+ Use(target.reg(), i);
Unuse(source.reg());
elements_[i] = target;
__ mov(target.reg(), source.reg());
@@ -388,7 +391,7 @@
if (target.is_synced() && !source.is_memory()) {
SyncElementAt(i);
}
- Use(target.reg());
+ Use(target.reg(), i);
elements_[i] = target;
}
}
@@ -527,12 +530,13 @@
if (original.is_memory()) {
Result fresh = cgen_->allocator()->Allocate();
ASSERT(fresh.is_valid());
- Use(fresh.reg());
+ Use(fresh.reg(), new_backing_index);
backing_reg = fresh.reg();
__ mov(backing_reg, Operand(ebp, fp_relative(index)));
} else {
// The original was in a register.
backing_reg = original.reg();
+ register_locations_[backing_reg.code()] = new_backing_index;
}
// Invalidate the element at index.
elements_[index] = FrameElement::InvalidElement();
@@ -574,13 +578,13 @@
FrameElement new_element =
FrameElement::RegisterElement(fresh.reg(),
FrameElement::NOT_SYNCED);
- Use(fresh.reg());
+ Use(fresh.reg(), elements_.length());
elements_.Add(new_element);
__ mov(fresh.reg(), Operand(ebp, fp_relative(index)));
break;
}
case FrameElement::REGISTER:
- Use(original.reg());
+ Use(original.reg(), elements_.length());
// Fall through.
case FrameElement::CONSTANT:
case FrameElement::COPY:
@@ -641,11 +645,14 @@
ASSERT(temp.is_valid());
__ mov(temp.reg(), Operand(ebp, fp_relative(backing_index)));
__ mov(Operand(ebp, fp_relative(index)), temp.reg());
- } else if (backing_element.is_synced()) {
- // If the element is a register, we will not actually move
- // anything on the stack but only update the virtual frame
- // element.
- backing_element.clear_sync();
+ } else {
+ register_locations_[backing_element.reg().code()] = index;
+ if (backing_element.is_synced()) {
+ // If the element is a register, we will not actually move
+ // anything on the stack but only update the virtual frame
+ // element.
+ backing_element.clear_sync();
+ }
}
elements_[index] = backing_element;
@@ -686,6 +693,7 @@
__ mov(temp.reg(), Operand(esp, 0));
__ mov(Operand(ebp, fp_relative(index)), temp.reg());
} else if (top.is_register()) {
+ register_locations_[top.reg().code()] = index;
// The stored-to slot has the (unsynced) register reference and
// the top element becomes a copy. The sync state of the top is
// preserved.
@@ -892,7 +900,7 @@
ASSERT(index <= stack_pointer_);
Result temp = cgen_->allocator()->Allocate();
ASSERT(temp.is_valid());
- Use(temp.reg());
+ Use(temp.reg(), index);
FrameElement new_element =
FrameElement::RegisterElement(temp.reg(), FrameElement::SYNCED);
elements_[index] = new_element;
Modified: trunk/src/virtual-frame-ia32.h
==============================================================================
--- trunk/src/virtual-frame-ia32.h (original)
+++ trunk/src/virtual-frame-ia32.h Tue Mar 24 07:47:14 2009
@@ -337,6 +337,10 @@
// used in the frame.
RegisterFile frame_registers_;
+ // The index of the register frame element using each register, or
+ // kIllegalIndex if a register is not on the frame.
+ int register_locations_[kNumRegisters];
+
// The index of the first parameter. The receiver lies below the first
// parameter.
int param0_index() const { return 1; }
@@ -372,12 +376,13 @@
}
// Record an occurrence of a register in the virtual frame. This has the
- // effect of incrementing both the register's frame-internal reference
- // count and its external reference count.
- void Use(Register reg);
+ // effect of incrementing the register's external reference count and
+ // of updating the index of the register's location in the frame.
+ void Use(Register reg, int index);
// Record that a register reference has been dropped from the frame.
This
- // decrements both the register's internal and external reference counts.
+ // decrements the register's external reference count and invalidates the
+ // index of the register's location in the frame.
void Unuse(Register reg);
// Spill the element at a particular index---write it to memory if
Modified: trunk/src/virtual-frame.cc
==============================================================================
--- trunk/src/virtual-frame.cc (original)
+++ trunk/src/virtual-frame.cc Tue Mar 24 07:47:14 2009
@@ -63,6 +63,9 @@
for (int i = 0; i < original->elements_.length(); i++) {
elements_.Add(original->elements_[i]);
}
+ for (int i = 0; i < kNumRegisters; i++) {
+ register_locations_[i] = original->register_locations_[i];
+ }
}
@@ -150,19 +153,26 @@
Unuse(last.reg());
} else {
frame_registers_.Unuse(last.reg());
+ register_locations_[last.reg().code()] = kIllegalIndex;
}
}
}
}
-void VirtualFrame::Use(Register reg) {
+void VirtualFrame::Use(Register reg, int index) {
+ ASSERT(frame_registers_.count(reg) == 0);
+ ASSERT(register_locations_[reg.code()] == kIllegalIndex);
+ register_locations_[reg.code()] = index;
frame_registers_.Use(reg);
cgen_->allocator()->Use(reg);
}
void VirtualFrame::Unuse(Register reg) {
+ ASSERT(frame_registers_.count(reg) == 1);
+ ASSERT(register_locations_[reg.code()] != kIllegalIndex);
+ register_locations_[reg.code()] = kIllegalIndex;
frame_registers_.Unuse(reg);
cgen_->allocator()->Unuse(reg);
}
@@ -270,6 +280,7 @@
Unuse(source.reg());
} else {
frame_registers_.Unuse(source.reg());
+ register_locations_[source.reg().code()] = kIllegalIndex;
}
}
elements_[i] = target;
@@ -382,7 +393,7 @@
// There are two cases depending no whether the register already
// occurs in the frame or not.
if (register_count(value->reg()) == 0) {
- Use(value->reg());
+ Use(value->reg(), frame_index);
elements_[frame_index] =
FrameElement::RegisterElement(value->reg(),
FrameElement::NOT_SYNCED);
@@ -408,6 +419,7 @@
elements_[i].set_sync();
}
elements_[frame_index].clear_sync();
+ register_locations_[value->reg().code()] = frame_index;
for (int j = i + 1; j < elements_.length(); j++) {
if (elements_[j].is_copy() && elements_[j].index() == i) {
elements_[j].set_index(frame_index);
@@ -487,7 +499,7 @@
void VirtualFrame::Push(Register reg) {
FrameElement new_element;
if (register_count(reg) == 0) {
- Use(reg);
+ Use(reg, elements_.length());
new_element =
FrameElement::RegisterElement(reg, FrameElement::NOT_SYNCED);
} else {
@@ -558,6 +570,9 @@
for (int i = 0; i < kNumRegisters; i++) {
if (frame_registers_.count(i) != other->frame_registers_.count(i)) {
+ return false;
+ }
+ if (register_locations_[i] != other->register_locations_[i]) {
return false;
}
}
Modified: trunk/tools/test.py
==============================================================================
--- trunk/tools/test.py (original)
+++ trunk/tools/test.py Tue Mar 24 07:47:14 2009
@@ -163,7 +163,7 @@
print failed.output.stdout.strip()
print "Command: %s" % EscapeCommand(failed.command)
if failed.HasCrashed():
- print "--- CRASHED ---"
+ print "--- CRASHED ---"
if len(self.failed) == 0:
print "==="
print "=== All tests succeeded"
@@ -244,7 +244,7 @@
print self.templates['stderr'] % stderr
print "Command: %s" % EscapeCommand(output.command)
if output.HasCrashed():
- print "--- CRASHED ---"
+ print "--- CRASHED ---"
def Truncate(self, str, length):
if length and (len(str) > (length - 3)):
@@ -345,7 +345,7 @@
def GetSource(self):
return "(no source available)"
-
+
def RunCommand(self, command):
full_command = self.context.processor(command)
output = Execute(full_command, self.context, self.context.timeout)
@@ -411,7 +411,7 @@
except ImportError:
pass
return prev_error_mode
-
+
def RunProcess(context, timeout, args, **rest):
if context.verbose: print "#", " ".join(args)
popen_args = args
--~--~---------~--~----~------------~-------~--~----~
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
-~----------~----~----~----~------~----~------~--~---