blob: 359d627262703be997ec712009aca0fe696f98ba [file] [log] [blame]
// Copyright 2006-2008 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:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (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 "v8.h"
#include "accessors.h"
#include "api.h"
#include "execution.h"
#include "global-handles.h"
#include "ic-inl.h"
#include "natives.h"
#include "platform.h"
#include "runtime.h"
#include "serialize.h"
#include "stub-cache.h"
#include "v8threads.h"
namespace v8 { namespace internal {
// Encoding: a RelativeAddress must be able to fit in a pointer:
// it is encoded as an Address with (from MS to LS bits):
// 27 bits identifying a word in the space, in one of three formats:
// - MAP and OLD spaces: 16 bits of page number, 11 bits of word offset in page
// - NEW space: 27 bits of word offset
// - LO space: 27 bits of page number
// 3 bits to encode the AllocationSpace (special values for code in LO space)
// 2 bits identifying this as a HeapObject
const int kSpaceShift = kHeapObjectTagSize;
const int kSpaceBits = kSpaceTagSize;
const int kSpaceMask = kSpaceTagMask;
// These value are used instead of space numbers when serializing/
// deserializing. They indicate an object that is in large object space, but
// should be treated specially.
// Make the pages executable on platforms that support it:
const int kLOSpaceExecutable = LAST_SPACE + 1;
// Reserve space for write barrier bits (for objects that can contain
// references to new space):
const int kLOSpacePointer = LAST_SPACE + 2;
const int kOffsetShift = kSpaceShift + kSpaceBits;
const int kOffsetBits = 11;
const int kOffsetMask = (1 << kOffsetBits) - 1;
const int kPageBits = 32 - (kOffsetBits + kSpaceBits + kHeapObjectTagSize);
const int kPageShift = kOffsetShift + kOffsetBits;
const int kPageMask = (1 << kPageBits) - 1;
const int kPageAndOffsetShift = kOffsetShift;
const int kPageAndOffsetBits = kPageBits + kOffsetBits;
const int kPageAndOffsetMask = (1 << kPageAndOffsetBits) - 1;
static inline AllocationSpace GetSpace(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
int space_number = ((encoded >> kSpaceShift) & kSpaceMask);
if (space_number == kLOSpaceExecutable) space_number = LO_SPACE;
else if (space_number == kLOSpacePointer) space_number = LO_SPACE;
return static_cast<AllocationSpace>(space_number);
}
static inline bool IsLargeExecutableObject(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
const int space_number = ((encoded >> kSpaceShift) & kSpaceMask);
if (space_number == kLOSpaceExecutable) return true;
return false;
}
static inline bool IsLargeFixedArray(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
const int space_number = ((encoded >> kSpaceShift) & kSpaceMask);
if (space_number == kLOSpacePointer) return true;
return false;
}
static inline int PageIndex(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
return (encoded >> kPageShift) & kPageMask;
}
static inline int PageOffset(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
return ((encoded >> kOffsetShift) & kOffsetMask) << kObjectAlignmentBits;
}
static inline int NewSpaceOffset(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
return ((encoded >> kPageAndOffsetShift) & kPageAndOffsetMask) <<
kObjectAlignmentBits;
}
static inline int LargeObjectIndex(Address addr) {
const int encoded = reinterpret_cast<int>(addr);
return (encoded >> kPageAndOffsetShift) & kPageAndOffsetMask;
}
// A RelativeAddress encodes a heap address that is independent of
// the actual memory addresses in real heap. The general case (for the
// OLD, CODE and MAP spaces) is as a (space id, page number, page offset)
// triple. The NEW space has page number == 0, because there are no
// pages. The LARGE_OBJECT space has page offset = 0, since there is
// exactly one object per page. RelativeAddresses are encodable as
// Addresses, so that they can replace the map() pointers of
// HeapObjects. The encoded Addresses are also encoded as HeapObjects
// and allow for marking (is_marked() see mark(), clear_mark()...) as
// used by the Mark-Compact collector.
class RelativeAddress {
public:
RelativeAddress(AllocationSpace space,
int page_index,
int page_offset)
: space_(space), page_index_(page_index), page_offset_(page_offset) {
ASSERT(space <= LAST_SPACE && space >= 0);
}
// Return the encoding of 'this' as an Address. Decode with constructor.
Address Encode() const;
AllocationSpace space() const {
if (space_ == kLOSpaceExecutable) return LO_SPACE;
if (space_ == kLOSpacePointer) return LO_SPACE;
return static_cast<AllocationSpace>(space_);
}
int page_index() const { return page_index_; }
int page_offset() const { return page_offset_; }
bool in_paged_space() const {
return space_ == CODE_SPACE ||
space_ == OLD_POINTER_SPACE ||
space_ == OLD_DATA_SPACE ||
space_ == MAP_SPACE;
}
void next_address(int offset) { page_offset_ += offset; }
void next_page(int init_offset = 0) {
page_index_++;
page_offset_ = init_offset;
}
#ifdef DEBUG
void Verify();
#endif
void set_to_large_code_object() {
ASSERT(space_ == LO_SPACE);
space_ = kLOSpaceExecutable;
}
void set_to_large_fixed_array() {
ASSERT(space_ == LO_SPACE);
space_ = kLOSpacePointer;
}
private:
int space_;
int page_index_;
int page_offset_;
};
Address RelativeAddress::Encode() const {
ASSERT(page_index_ >= 0);
int word_offset = 0;
int result = 0;
switch (space_) {
case MAP_SPACE:
case OLD_POINTER_SPACE:
case OLD_DATA_SPACE:
case CODE_SPACE:
ASSERT_EQ(0, page_index_ & ~kPageMask);
word_offset = page_offset_ >> kObjectAlignmentBits;
ASSERT_EQ(0, word_offset & ~kOffsetMask);
result = (page_index_ << kPageShift) | (word_offset << kOffsetShift);
break;
case NEW_SPACE:
ASSERT_EQ(0, page_index_);
word_offset = page_offset_ >> kObjectAlignmentBits;
ASSERT_EQ(0, word_offset & ~kPageAndOffsetMask);
result = word_offset << kPageAndOffsetShift;
break;
case LO_SPACE:
case kLOSpaceExecutable:
case kLOSpacePointer:
ASSERT_EQ(0, page_offset_);
ASSERT_EQ(0, page_index_ & ~kPageAndOffsetMask);
result = page_index_ << kPageAndOffsetShift;
break;
}
// OR in AllocationSpace and kHeapObjectTag
ASSERT_EQ(0, space_ & ~kSpaceMask);
result |= (space_ << kSpaceShift) | kHeapObjectTag;
return reinterpret_cast<Address>(result);
}
#ifdef DEBUG
void RelativeAddress::Verify() {
ASSERT(page_offset_ >= 0 && page_index_ >= 0);
switch (space_) {
case MAP_SPACE:
case OLD_POINTER_SPACE:
case OLD_DATA_SPACE:
case CODE_SPACE:
ASSERT(Page::kObjectStartOffset <= page_offset_ &&
page_offset_ <= Page::kPageSize);
break;
case NEW_SPACE:
ASSERT(page_index_ == 0);
break;
case LO_SPACE:
case kLOSpaceExecutable:
case kLOSpacePointer:
ASSERT(page_offset_ == 0);
break;
}
}
#endif
enum GCTreatment {
DataObject, // Object that cannot contain a reference to new space.
PointerObject, // Object that can contain a reference to new space.
CodeObject // Object that contains executable code.
};
// A SimulatedHeapSpace simulates the allocation of objects in a page in
// the heap. It uses linear allocation - that is, it doesn't simulate the
// use of a free list. This simulated
// allocation must exactly match that done by Heap.
class SimulatedHeapSpace {
public:
// The default constructor initializes to an invalid state.
SimulatedHeapSpace(): current_(LAST_SPACE, -1, -1) {}
// Sets 'this' to the first address in 'space' that would be
// returned by allocation in an empty heap.
void InitEmptyHeap(AllocationSpace space);
// Sets 'this' to the next address in 'space' that would be returned
// by allocation in the current heap. Intended only for testing
// serialization and deserialization in the current address space.
void InitCurrentHeap(AllocationSpace space);
// Returns the RelativeAddress where the next
// object of 'size' bytes will be allocated, and updates 'this' to
// point to the next free address beyond that object.
RelativeAddress Allocate(int size, GCTreatment special_gc_treatment);
private:
RelativeAddress current_;
};
void SimulatedHeapSpace::InitEmptyHeap(AllocationSpace space) {
switch (space) {
case MAP_SPACE:
case OLD_POINTER_SPACE:
case OLD_DATA_SPACE:
case CODE_SPACE:
current_ = RelativeAddress(space, 0, Page::kObjectStartOffset);
break;
case NEW_SPACE:
case LO_SPACE:
current_ = RelativeAddress(space, 0, 0);
break;
}
}
void SimulatedHeapSpace::InitCurrentHeap(AllocationSpace space) {
switch (space) {
case MAP_SPACE:
case OLD_POINTER_SPACE:
case OLD_DATA_SPACE:
case CODE_SPACE: {
PagedSpace* ps;
if (space == MAP_SPACE) {
ps = Heap::map_space();
} else if (space == OLD_POINTER_SPACE) {
ps = Heap::old_pointer_space();
} else if (space == OLD_DATA_SPACE) {
ps = Heap::old_data_space();
} else {
ASSERT(space == CODE_SPACE);
ps = Heap::code_space();
}
Address top = ps->top();
Page* top_page = Page::FromAllocationTop(top);
int page_index = 0;
PageIterator it(ps, PageIterator::PAGES_IN_USE);
while (it.has_next()) {
if (it.next() == top_page) break;
page_index++;
}
current_ = RelativeAddress(space,
page_index,
top_page->Offset(top));
break;
}
case NEW_SPACE:
current_ = RelativeAddress(space,
0,
Heap::NewSpaceTop() - Heap::NewSpaceStart());
break;
case LO_SPACE:
int page_index = 0;
for (LargeObjectIterator it(Heap::lo_space()); it.has_next(); it.next()) {
page_index++;
}
current_ = RelativeAddress(space, page_index, 0);
break;
}
}
RelativeAddress SimulatedHeapSpace::Allocate(int size,
GCTreatment special_gc_treatment) {
#ifdef DEBUG
current_.Verify();
#endif
int alloc_size = OBJECT_SIZE_ALIGN(size);
if (current_.in_paged_space() &&
current_.page_offset() + alloc_size > Page::kPageSize) {
ASSERT(alloc_size <= Page::kMaxHeapObjectSize);
current_.next_page(Page::kObjectStartOffset);
}
RelativeAddress result = current_;
if (current_.space() == LO_SPACE) {
current_.next_page();
if (special_gc_treatment == CodeObject) {
result.set_to_large_code_object();
} else if (special_gc_treatment == PointerObject) {
result.set_to_large_fixed_array();
}
} else {
current_.next_address(alloc_size);
}
#ifdef DEBUG
current_.Verify();
result.Verify();
#endif
return result;
}
// -----------------------------------------------------------------------------
// Coding of external references.
// The encoding of an external reference. The type is in the high word.
// The id is in the low word.
static uint32_t EncodeExternal(TypeCode type, uint16_t id) {
return static_cast<uint32_t>(type) << 16 | id;
}
static int* GetInternalPointer(StatsCounter* counter) {
// All counters refer to dummy_counter, if deserializing happens without
// setting up counters.
static int dummy_counter = 0;
return counter->Enabled() ? counter->GetInternalPointer() : &dummy_counter;
}
// ExternalReferenceTable is a helper class that defines the relationship
// between external references and their encodings. It is used to build
// hashmaps in ExternalReferenceEncoder and ExternalReferenceDecoder.
class ExternalReferenceTable {
public:
static ExternalReferenceTable* instance() {
if (!instance_) instance_ = new ExternalReferenceTable();
return instance_;
}
int size() const { return refs_.length(); }
Address address(int i) { return refs_[i].address; }
uint32_t code(int i) { return refs_[i].code; }
const char* name(int i) { return refs_[i].name; }
int max_id(int code) { return max_id_[code]; }
private:
static ExternalReferenceTable* instance_;
ExternalReferenceTable();
struct ExternalReferenceEntry {
Address address;
uint32_t code;
const char* name;
};
void Add(Address address, TypeCode type, uint16_t id, const char* name) {
CHECK_NE(NULL, address);
ExternalReferenceEntry entry;
entry.address = address;
entry.code = EncodeExternal(type, id);
entry.name = name;
CHECK_NE(0, entry.code);
refs_.Add(entry);
if (id > max_id_[type]) max_id_[type] = id;
}
List<ExternalReferenceEntry> refs_;
int max_id_[kTypeCodeCount];
};
ExternalReferenceTable* ExternalReferenceTable::instance_ = NULL;
ExternalReferenceTable::ExternalReferenceTable() : refs_(64) {
for (int type_code = 0; type_code < kTypeCodeCount; type_code++) {
max_id_[type_code] = 0;
}
// Define all entries in the table.
// Builtins
#define DEF_ENTRY_C(name) \
Add(Builtins::c_function_address(Builtins::c_##name), \
C_BUILTIN, \
Builtins::c_##name, \
"Builtins::" #name);
BUILTIN_LIST_C(DEF_ENTRY_C)
#undef DEF_ENTRY_C
#define DEF_ENTRY_C(name) \
Add(Builtins::builtin_address(Builtins::name), \
BUILTIN, \
Builtins::name, \
"Builtins::" #name);
#define DEF_ENTRY_A(name, kind, state) DEF_ENTRY_C(name)
BUILTIN_LIST_C(DEF_ENTRY_C)
BUILTIN_LIST_A(DEF_ENTRY_A)
#undef DEF_ENTRY_C
#undef DEF_ENTRY_A
// Runtime functions
#define RUNTIME_ENTRY(name, nargs) \
Add(Runtime::FunctionForId(Runtime::k##name)->entry, \
RUNTIME_FUNCTION, \
Runtime::k##name, \
"Runtime::" #name);
RUNTIME_FUNCTION_LIST(RUNTIME_ENTRY)
#undef RUNTIME_ENTRY
// IC utilities
#define IC_ENTRY(name) \
Add(IC::AddressFromUtilityId(IC::k##name), \
IC_UTILITY, \
IC::k##name, \
"IC::" #name);
IC_UTIL_LIST(IC_ENTRY)
#undef IC_ENTRY
// Debug addresses
Add(Debug_Address(Debug::k_after_break_target_address).address(),
DEBUG_ADDRESS,
Debug::k_after_break_target_address << kDebugIdShift,
"Debug::after_break_target_address()");
Add(Debug_Address(Debug::k_debug_break_return_address).address(),
DEBUG_ADDRESS,
Debug::k_debug_break_return_address << kDebugIdShift,
"Debug::debug_break_return_address()");
const char* debug_register_format = "Debug::register_address(%i)";
size_t dr_format_length = strlen(debug_register_format);
for (int i = 0; i < kNumJSCallerSaved; ++i) {
Vector<char> name = Vector<char>::New(dr_format_length + 1);
OS::SNPrintF(name, debug_register_format, i);
Add(Debug_Address(Debug::k_register_address, i).address(),
DEBUG_ADDRESS,
Debug::k_register_address << kDebugIdShift | i,
name.start());
}
// Stat counters
#define COUNTER_ENTRY(name, caption) \
Add(reinterpret_cast<Address>(GetInternalPointer(&Counters::name)), \
STATS_COUNTER, \
Counters::k_##name, \
"Counters::" #name);
STATS_COUNTER_LIST_1(COUNTER_ENTRY)
STATS_COUNTER_LIST_2(COUNTER_ENTRY)
#undef COUNTER_ENTRY
// Top addresses
const char* top_address_format = "Top::get_address_from_id(%i)";
size_t top_format_length = strlen(top_address_format);
for (uint16_t i = 0; i < Top::k_top_address_count; ++i) {
Vector<char> name = Vector<char>::New(top_format_length + 1);
const char* chars = name.start();
OS::SNPrintF(name, top_address_format, i);
Add(Top::get_address_from_id((Top::AddressId)i), TOP_ADDRESS, i, chars);
}
// Extensions
Add(FUNCTION_ADDR(GCExtension::GC), EXTENSION, 1,
"GCExtension::GC");
// Accessors
#define ACCESSOR_DESCRIPTOR_DECLARATION(name) \
Add((Address)&Accessors::name, \
ACCESSOR, \
Accessors::k##name, \
"Accessors::" #name);
ACCESSOR_DESCRIPTOR_LIST(ACCESSOR_DESCRIPTOR_DECLARATION)
#undef ACCESSOR_DESCRIPTOR_DECLARATION
// Stub cache tables
Add(SCTableReference::keyReference(StubCache::kPrimary).address(),
STUB_CACHE_TABLE,
1,
"StubCache::primary_->key");
Add(SCTableReference::valueReference(StubCache::kPrimary).address(),
STUB_CACHE_TABLE,
2,
"StubCache::primary_->value");
Add(SCTableReference::keyReference(StubCache::kSecondary).address(),
STUB_CACHE_TABLE,
3,
"StubCache::secondary_->key");
Add(SCTableReference::valueReference(StubCache::kSecondary).address(),
STUB_CACHE_TABLE,
4,
"StubCache::secondary_->value");
// Runtime entries
Add(FUNCTION_ADDR(Runtime::PerformGC),
RUNTIME_ENTRY,
1,
"Runtime::PerformGC");
// Miscellaneous
Add(ExternalReference::builtin_passed_function().address(),
UNCLASSIFIED,
1,
"Builtins::builtin_passed_function");
Add(ExternalReference::the_hole_value_location().address(),
UNCLASSIFIED,
2,
"Factory::the_hole_value().location()");
Add(ExternalReference::address_of_stack_guard_limit().address(),
UNCLASSIFIED,
3,
"StackGuard::address_of_limit()");
Add(ExternalReference::debug_break().address(),
UNCLASSIFIED,
4,
"Debug::Break()");
Add(ExternalReference::new_space_start().address(),
UNCLASSIFIED,
5,
"Heap::NewSpaceStart()");
Add(ExternalReference::heap_always_allocate_scope_depth().address(),
UNCLASSIFIED,
6,
"Heap::always_allocate_scope_depth()");
Add(ExternalReference::new_space_allocation_limit_address().address(),
UNCLASSIFIED,
7,
"Heap::NewSpaceAllocationLimitAddress()");
Add(ExternalReference::new_space_allocation_top_address().address(),
UNCLASSIFIED,
8,
"Heap::NewSpaceAllocationTopAddress()");
Add(ExternalReference::debug_step_in_fp_address().address(),
UNCLASSIFIED,
9,
"Debug::step_in_fp_addr()");
}
ExternalReferenceEncoder::ExternalReferenceEncoder()
: encodings_(Match) {
ExternalReferenceTable* external_references =
ExternalReferenceTable::instance();
for (int i = 0; i < external_references->size(); ++i) {
Put(external_references->address(i), i);
}
}
uint32_t ExternalReferenceEncoder::Encode(Address key) const {
int index = IndexOf(key);
return index >=0 ? ExternalReferenceTable::instance()->code(index) : 0;
}
const char* ExternalReferenceEncoder::NameOfAddress(Address key) const {
int index = IndexOf(key);
return index >=0 ? ExternalReferenceTable::instance()->name(index) : NULL;
}
int ExternalReferenceEncoder::IndexOf(Address key) const {
if (key == NULL) return -1;
HashMap::Entry* entry =
const_cast<HashMap &>(encodings_).Lookup(key, Hash(key), false);
return entry == NULL ? -1 : reinterpret_cast<int>(entry->value);
}
void ExternalReferenceEncoder::Put(Address key, int index) {
HashMap::Entry* entry = encodings_.Lookup(key, Hash(key), true);
entry->value = reinterpret_cast<void *>(index);
}
ExternalReferenceDecoder::ExternalReferenceDecoder()
: encodings_(NewArray<Address*>(kTypeCodeCount)) {
ExternalReferenceTable* external_references =
ExternalReferenceTable::instance();
for (int type = kFirstTypeCode; type < kTypeCodeCount; ++type) {
int max = external_references->max_id(type) + 1;
encodings_[type] = NewArray<Address>(max + 1);
}
for (int i = 0; i < external_references->size(); ++i) {
Put(external_references->code(i), external_references->address(i));
}
}
ExternalReferenceDecoder::~ExternalReferenceDecoder() {
for (int type = kFirstTypeCode; type < kTypeCodeCount; ++type) {
DeleteArray(encodings_[type]);
}
DeleteArray(encodings_);
}
//------------------------------------------------------------------------------
// Implementation of Serializer
// Helper class to write the bytes of the serialized heap.
class SnapshotWriter {
public:
SnapshotWriter() {
len_ = 0;
max_ = 8 << 10; // 8K initial size
str_ = NewArray<char>(max_);
}
~SnapshotWriter() {
DeleteArray(str_);
}
void GetString(char** str, int* len) {
*str = NewArray<char>(len_);
memcpy(*str, str_, len_);
*len = len_;
}
void Reserve(int bytes, int pos);
void PutC(char c) {
InsertC(c, len_);
}
void PutInt(int i) {
InsertInt(i, len_);
}
void PutBytes(const byte* a, int size) {
InsertBytes(a, len_, size);
}
void PutString(const char* s) {
InsertString(s, len_);
}
int InsertC(char c, int pos) {
Reserve(1, pos);
str_[pos] = c;
len_++;
return pos + 1;
}
int InsertInt(int i, int pos) {
return InsertBytes(reinterpret_cast<byte*>(&i), pos, sizeof(i));
}
int InsertBytes(const byte* a, int pos, int size) {
Reserve(size, pos);
memcpy(&str_[pos], a, size);
len_ += size;
return pos + size;
}
int InsertString(const char* s, int pos);
int length() { return len_; }
Address position() { return reinterpret_cast<Address>(&str_[len_]); }
private:
char* str_; // the snapshot
int len_; // the curent length of str_
int max_; // the allocated size of str_
};
void SnapshotWriter::Reserve(int bytes, int pos) {
CHECK(0 <= pos && pos <= len_);
while (len_ + bytes >= max_) {
max_ *= 2;
char* old = str_;
str_ = NewArray<char>(max_);
memcpy(str_, old, len_);
DeleteArray(old);
}
if (pos < len_) {
char* old = str_;
str_ = NewArray<char>(max_);
memcpy(str_, old, pos);
memcpy(str_ + pos + bytes, old + pos, len_ - pos);
DeleteArray(old);
}
}
int SnapshotWriter::InsertString(const char* s, int pos) {
int size = strlen(s);
pos = InsertC('[', pos);
pos = InsertInt(size, pos);
pos = InsertC(']', pos);
return InsertBytes(reinterpret_cast<const byte*>(s), pos, size);
}
class ReferenceUpdater: public ObjectVisitor {
public:
ReferenceUpdater(HeapObject* obj, Serializer* serializer)
: obj_address_(obj->address()),
serializer_(serializer),
reference_encoder_(serializer->reference_encoder_),
offsets_(8),
addresses_(8) {
}
virtual void VisitPointers(Object** start, Object** end) {
for (Object** p = start; p < end; ++p) {
if ((*p)->IsHeapObject()) {
offsets_.Add(reinterpret_cast<Address>(p) - obj_address_);
Address a = serializer_->GetSavedAddress(HeapObject::cast(*p));
addresses_.Add(a);
}
}
}
virtual void VisitExternalReferences(Address* start, Address* end) {
for (Address* p = start; p < end; ++p) {
uint32_t code = reference_encoder_->Encode(*p);
CHECK(*p == NULL ? code == 0 : code != 0);
offsets_.Add(reinterpret_cast<Address>(p) - obj_address_);
addresses_.Add(reinterpret_cast<Address>(code));
}
}
virtual void VisitRuntimeEntry(RelocInfo* rinfo) {
Address target = rinfo->target_address();
uint32_t encoding = reference_encoder_->Encode(target);
CHECK(target == NULL ? encoding == 0 : encoding != 0);
offsets_.Add(reinterpret_cast<Address>(rinfo->pc()) - obj_address_);
addresses_.Add(reinterpret_cast<Address>(encoding));
}
void Update(Address start_address) {
for (int i = 0; i < offsets_.length(); i++) {
Address* p = reinterpret_cast<Address*>(start_address + offsets_[i]);
*p = addresses_[i];
}
}
private:
Address obj_address_;
Serializer* serializer_;
ExternalReferenceEncoder* reference_encoder_;
List<int> offsets_;
List<Address> addresses_;
};
// Helper functions for a map of encoded heap object addresses.
static uint32_t HeapObjectHash(HeapObject* key) {
return reinterpret_cast<uint32_t>(key) >> 2;
}
static bool MatchHeapObject(void* key1, void* key2) {
return key1 == key2;
}
Serializer::Serializer()
: global_handles_(4),
saved_addresses_(MatchHeapObject) {
root_ = true;
roots_ = 0;
objects_ = 0;
reference_encoder_ = NULL;
writer_ = new SnapshotWriter();
for (int i = 0; i <= LAST_SPACE; i++) {
allocator_[i] = new SimulatedHeapSpace();
}
}
Serializer::~Serializer() {
for (int i = 0; i <= LAST_SPACE; i++) {
delete allocator_[i];
}
if (reference_encoder_) delete reference_encoder_;
delete writer_;
}
bool Serializer::serialization_enabled_ = true;
#ifdef DEBUG
static const int kMaxTagLength = 32;
void Serializer::Synchronize(const char* tag) {
if (FLAG_debug_serialization) {
int length = strlen(tag);
ASSERT(length <= kMaxTagLength);
writer_->PutC('S');
writer_->PutInt(length);
writer_->PutBytes(reinterpret_cast<const byte*>(tag), length);
}
}
#endif
void Serializer::InitializeAllocators() {
for (int i = 0; i <= LAST_SPACE; i++) {
allocator_[i]->InitEmptyHeap(static_cast<AllocationSpace>(i));
}
}
bool Serializer::IsVisited(HeapObject* obj) {
HashMap::Entry* entry =
saved_addresses_.Lookup(obj, HeapObjectHash(obj), false);
return entry != NULL;
}
Address Serializer::GetSavedAddress(HeapObject* obj) {
HashMap::Entry* entry
= saved_addresses_.Lookup(obj, HeapObjectHash(obj), false);
ASSERT(entry != NULL);
return reinterpret_cast<Address>(entry->value);
}
void Serializer::SaveAddress(HeapObject* obj, Address addr) {
HashMap::Entry* entry =
saved_addresses_.Lookup(obj, HeapObjectHash(obj), true);
entry->value = addr;
}
void Serializer::Serialize() {
// No active threads.
CHECK_EQ(NULL, ThreadState::FirstInUse());
// No active or weak handles.
CHECK(HandleScopeImplementer::instance()->Blocks()->is_empty());
CHECK_EQ(0, GlobalHandles::NumberOfWeakHandles());
// We need a counter function during serialization to resolve the
// references to counters in the code on the heap.
CHECK(StatsTable::HasCounterFunction());
CHECK(enabled());
InitializeAllocators();
reference_encoder_ = new ExternalReferenceEncoder();
PutHeader();
Heap::IterateRoots(this);
PutLog();
PutContextStack();
disable();
}
void Serializer::Finalize(char** str, int* len) {
writer_->GetString(str, len);
}
// Serialize objects by writing them into the stream.
void Serializer::VisitPointers(Object** start, Object** end) {
bool root = root_;
root_ = false;
for (Object** p = start; p < end; ++p) {
bool serialized;
Address a = Encode(*p, &serialized);
if (root) {
roots_++;
// If the object was not just serialized,
// write its encoded address instead.
if (!serialized) PutEncodedAddress(a);
}
}
root_ = root;
}
class GlobalHandlesRetriever: public ObjectVisitor {
public:
explicit GlobalHandlesRetriever(List<Object**>* handles)
: global_handles_(handles) {}
virtual void VisitPointers(Object** start, Object** end) {
for (; start != end; ++start) {
global_handles_->Add(start);
}
}
private:
List<Object**>* global_handles_;
};
void Serializer::PutFlags() {
writer_->PutC('F');
List<const char*>* argv = FlagList::argv();
writer_->PutInt(argv->length());
writer_->PutC('[');
for (int i = 0; i < argv->length(); i++) {
if (i > 0) writer_->PutC('|');
writer_->PutString((*argv)[i]);
DeleteArray((*argv)[i]);
}
writer_->PutC(']');
flags_end_ = writer_->length();
delete argv;
}
void Serializer::PutHeader() {
PutFlags();
writer_->PutC('D');
#ifdef DEBUG
writer_->PutC(FLAG_debug_serialization ? '1' : '0');
#else
writer_->PutC('0');
#endif
// Write sizes of paged memory spaces. Allocate extra space for the old
// and code spaces, because objects in new space will be promoted to them.
writer_->PutC('S');
writer_->PutC('[');
writer_->PutInt(Heap::old_pointer_space()->Size() +
Heap::new_space()->Size());
writer_->PutC('|');
writer_->PutInt(Heap::old_data_space()->Size() + Heap::new_space()->Size());
writer_->PutC('|');
writer_->PutInt(Heap::code_space()->Size() + Heap::new_space()->Size());
writer_->PutC('|');
writer_->PutInt(Heap::map_space()->Size());
writer_->PutC(']');
// Write global handles.
writer_->PutC('G');
writer_->PutC('[');
GlobalHandlesRetriever ghr(&global_handles_);
GlobalHandles::IterateRoots(&ghr);
for (int i = 0; i < global_handles_.length(); i++) {
writer_->PutC('N');
}
writer_->PutC(']');
}
void Serializer::PutLog() {
#ifdef ENABLE_LOGGING_AND_PROFILING
if (FLAG_log_code) {
Logger::TearDown();
int pos = writer_->InsertC('L', flags_end_);
bool exists;
Vector<const char> log = ReadFile(FLAG_logfile, &exists);
writer_->InsertString(log.start(), pos);
log.Dispose();
}
#endif
}
static int IndexOf(const List<Object**>& list, Object** element) {
for (int i = 0; i < list.length(); i++) {
if (list[i] == element) return i;
}
return -1;
}
void Serializer::PutGlobalHandleStack(const List<Handle<Object> >& stack) {
writer_->PutC('[');
writer_->PutInt(stack.length());
for (int i = stack.length() - 1; i >= 0; i--) {
writer_->PutC('|');
int gh_index = IndexOf(global_handles_, stack[i].location());
CHECK_GE(gh_index, 0);
writer_->PutInt(gh_index);
}
writer_->PutC(']');
}
void Serializer::PutContextStack() {
List<Handle<Object> > contexts(2);
while (HandleScopeImplementer::instance()->HasSavedContexts()) {
Handle<Object> context =
HandleScopeImplementer::instance()->RestoreContext();
contexts.Add(context);
}
for (int i = contexts.length() - 1; i >= 0; i--) {
HandleScopeImplementer::instance()->SaveContext(contexts[i]);
}
PutGlobalHandleStack(contexts);
}
void Serializer::PutEncodedAddress(Address addr) {
writer_->PutC('P');
writer_->PutInt(reinterpret_cast<int>(addr));
}
Address Serializer::Encode(Object* o, bool* serialized) {
*serialized = false;
if (o->IsSmi()) {
return reinterpret_cast<Address>(o);
} else {
HeapObject* obj = HeapObject::cast(o);
if (IsVisited(obj)) {
return GetSavedAddress(obj);
} else {
// First visit: serialize the object.
*serialized = true;
return PutObject(obj);
}
}
}
Address Serializer::PutObject(HeapObject* obj) {
Map* map = obj->map();
InstanceType type = map->instance_type();
int size = obj->SizeFromMap(map);
// Simulate the allocation of obj to predict where it will be
// allocated during deserialization.
Address addr = Allocate(obj).Encode();
SaveAddress(obj, addr);
if (type == CODE_TYPE) {
Code* code = Code::cast(obj);
// Ensure Code objects contain Object pointers, not Addresses.
code->ConvertICTargetsFromAddressToObject();
LOG(CodeMoveEvent(code->address(), addr));
}
// Write out the object prologue: type, size, and simulated address of obj.
writer_->PutC('[');
CHECK_EQ(0, size & kObjectAlignmentMask);
writer_->PutInt(type);
writer_->PutInt(size >> kObjectAlignmentBits);
PutEncodedAddress(addr); // encodes AllocationSpace
// Visit all the pointers in the object other than the map. This
// will recursively serialize any as-yet-unvisited objects.
obj->Iterate(this);
// Mark end of recursively embedded objects, start of object body.
writer_->PutC('|');
// Write out the raw contents of the object. No compression, but
// fast to deserialize.
writer_->PutBytes(obj->address(), size);
// Update pointers and external references in the written object.
ReferenceUpdater updater(obj, this);
obj->Iterate(&updater);
updater.Update(writer_->position() - size);
#ifdef DEBUG
if (FLAG_debug_serialization) {
// Write out the object epilogue to catch synchronization errors.
PutEncodedAddress(addr);
writer_->PutC(']');
}
#endif
if (type == CODE_TYPE) {
Code* code = Code::cast(obj);
// Convert relocations from Object* to Address in Code objects
code->ConvertICTargetsFromObjectToAddress();
}
objects_++;
return addr;
}
RelativeAddress Serializer::Allocate(HeapObject* obj) {
// Find out which AllocationSpace 'obj' is in.
AllocationSpace s;
bool found = false;
for (int i = FIRST_SPACE; !found && i <= LAST_SPACE; i++) {
s = static_cast<AllocationSpace>(i);
found = Heap::InSpace(obj, s);
}
CHECK(found);
if (s == NEW_SPACE) {
Space* space = Heap::TargetSpace(obj);
ASSERT(space == Heap::old_pointer_space() ||
space == Heap::old_data_space());
s = (space == Heap::old_pointer_space()) ?
OLD_POINTER_SPACE :
OLD_DATA_SPACE;
}
int size = obj->Size();
GCTreatment gc_treatment = DataObject;
if (obj->IsFixedArray()) gc_treatment = PointerObject;
else if (obj->IsCode()) gc_treatment = CodeObject;
return allocator_[s]->Allocate(size, gc_treatment);
}
//------------------------------------------------------------------------------
// Implementation of Deserializer
static const int kInitArraySize = 32;
Deserializer::Deserializer(const char* str, int len)
: reader_(str, len),
map_pages_(kInitArraySize),
old_pointer_pages_(kInitArraySize),
old_data_pages_(kInitArraySize),
code_pages_(kInitArraySize),
large_objects_(kInitArraySize),
global_handles_(4) {
root_ = true;
roots_ = 0;
objects_ = 0;
reference_decoder_ = NULL;
#ifdef DEBUG
expect_debug_information_ = false;
#endif
}
Deserializer::~Deserializer() {
if (reference_decoder_) delete reference_decoder_;
}
void Deserializer::ExpectEncodedAddress(Address expected) {
Address a = GetEncodedAddress();
USE(a);
ASSERT(a == expected);
}
#ifdef DEBUG
void Deserializer::Synchronize(const char* tag) {
if (expect_debug_information_) {
char buf[kMaxTagLength];
reader_.ExpectC('S');
int length = reader_.GetInt();
ASSERT(length <= kMaxTagLength);
reader_.GetBytes(reinterpret_cast<Address>(buf), length);
ASSERT_EQ(strlen(tag), length);
ASSERT(strncmp(tag, buf, length) == 0);
}
}
#endif
void Deserializer::Deserialize() {
// No active threads.
ASSERT_EQ(NULL, ThreadState::FirstInUse());
// No active handles.
ASSERT(HandleScopeImplementer::instance()->Blocks()->is_empty());
reference_decoder_ = new ExternalReferenceDecoder();
// By setting linear allocation only, we forbid the use of free list
// allocation which is not predicted by SimulatedAddress.
GetHeader();
Heap::IterateRoots(this);
GetContextStack();
}
void Deserializer::VisitPointers(Object** start, Object** end) {
bool root = root_;
root_ = false;
for (Object** p = start; p < end; ++p) {
if (root) {
roots_++;
// Read the next object or pointer from the stream
// pointer in the stream.
int c = reader_.GetC();
if (c == '[') {
*p = GetObject(); // embedded object
} else {
ASSERT(c == 'P'); // pointer to previously serialized object
*p = Resolve(reinterpret_cast<Address>(reader_.GetInt()));
}
} else {
// A pointer internal to a HeapObject that we've already
// read: resolve it to a true address (or Smi)
*p = Resolve(reinterpret_cast<Address>(*p));
}
}
root_ = root;
}
void Deserializer::VisitExternalReferences(Address* start, Address* end) {
for (Address* p = start; p < end; ++p) {
uint32_t code = reinterpret_cast<uint32_t>(*p);
*p = reference_decoder_->Decode(code);
}
}
void Deserializer::VisitRuntimeEntry(RelocInfo* rinfo) {
uint32_t* pc = reinterpret_cast<uint32_t*>(rinfo->pc());
uint32_t encoding = *pc;
Address target = reference_decoder_->Decode(encoding);
rinfo->set_target_address(target);
}
void Deserializer::GetFlags() {
reader_.ExpectC('F');
int argc = reader_.GetInt() + 1;
char** argv = NewArray<char*>(argc);
reader_.ExpectC('[');
for (int i = 1; i < argc; i++) {
if (i > 1) reader_.ExpectC('|');
argv[i] = reader_.GetString();
}
reader_.ExpectC(']');
has_log_ = false;
for (int i = 1; i < argc; i++) {
if (strcmp("--log_code", argv[i]) == 0) {
has_log_ = true;
} else if (strcmp("--nouse_ic", argv[i]) == 0) {
FLAG_use_ic = false;
} else if (strcmp("--debug_code", argv[i]) == 0) {
FLAG_debug_code = true;
} else if (strcmp("--nolazy", argv[i]) == 0) {
FLAG_lazy = false;
}
DeleteArray(argv[i]);
}
DeleteArray(argv);
}
void Deserializer::GetLog() {
if (has_log_) {
reader_.ExpectC('L');
char* snapshot_log = reader_.GetString();
#ifdef ENABLE_LOGGING_AND_PROFILING
if (FLAG_log_code) {
LOG(Preamble(snapshot_log));
}
#endif
DeleteArray(snapshot_log);
}
}
static void InitPagedSpace(PagedSpace* space,
int capacity,
List<Page*>* page_list) {
space->EnsureCapacity(capacity);
// TODO(1240712): PagedSpace::EnsureCapacity can return false due to
// a failure to allocate from the OS to expand the space.
PageIterator it(space, PageIterator::ALL_PAGES);
while (it.has_next()) page_list->Add(it.next());
}
void Deserializer::GetHeader() {
reader_.ExpectC('D');
#ifdef DEBUG
expect_debug_information_ = reader_.GetC() == '1';
#else
// In release mode, don't attempt to read a snapshot containing
// synchronization tags.
if (reader_.GetC() != '0') FATAL("Snapshot contains synchronization tags.");
#endif
// Ensure sufficient capacity in paged memory spaces to avoid growth
// during deserialization.
reader_.ExpectC('S');
reader_.ExpectC('[');
InitPagedSpace(Heap::old_pointer_space(),
reader_.GetInt(),
&old_pointer_pages_);
reader_.ExpectC('|');
InitPagedSpace(Heap::old_data_space(), reader_.GetInt(), &old_data_pages_);
reader_.ExpectC('|');
InitPagedSpace(Heap::code_space(), reader_.GetInt(), &code_pages_);
reader_.ExpectC('|');
InitPagedSpace(Heap::map_space(), reader_.GetInt(), &map_pages_);
reader_.ExpectC(']');
// Create placeholders for global handles later to be fill during
// IterateRoots.
reader_.ExpectC('G');
reader_.ExpectC('[');
int c = reader_.GetC();
while (c != ']') {
ASSERT(c == 'N');
global_handles_.Add(GlobalHandles::Create(NULL).location());
c = reader_.GetC();
}
}
void Deserializer::GetGlobalHandleStack(List<Handle<Object> >* stack) {
reader_.ExpectC('[');
int length = reader_.GetInt();
for (int i = 0; i < length; i++) {
reader_.ExpectC('|');
int gh_index = reader_.GetInt();
stack->Add(global_handles_[gh_index]);
}
reader_.ExpectC(']');
}
void Deserializer::GetContextStack() {
List<Handle<Object> > entered_contexts(2);
GetGlobalHandleStack(&entered_contexts);
for (int i = 0; i < entered_contexts.length(); i++) {
HandleScopeImplementer::instance()->SaveContext(entered_contexts[i]);
}
}
Address Deserializer::GetEncodedAddress() {
reader_.ExpectC('P');
return reinterpret_cast<Address>(reader_.GetInt());
}
Object* Deserializer::GetObject() {
// Read the prologue: type, size and encoded address.
InstanceType type = static_cast<InstanceType>(reader_.GetInt());
int size = reader_.GetInt() << kObjectAlignmentBits;
Address a = GetEncodedAddress();
// Get a raw object of the right size in the right space.
AllocationSpace space = GetSpace(a);
Object* o;
if (IsLargeExecutableObject(a)) {
o = Heap::lo_space()->AllocateRawCode(size);
} else if (IsLargeFixedArray(a)) {
o = Heap::lo_space()->AllocateRawFixedArray(size);
} else {
AllocationSpace retry_space = (space == NEW_SPACE)
? Heap::TargetSpaceId(type)
: space;
o = Heap::AllocateRaw(size, space, retry_space);
}
ASSERT(!o->IsFailure());
// Check that the simulation of heap allocation was correct.
ASSERT(o == Resolve(a));
// Read any recursively embedded objects.
int c = reader_.GetC();
while (c == '[') {
GetObject();
c = reader_.GetC();
}
ASSERT(c == '|');
HeapObject* obj = reinterpret_cast<HeapObject*>(o);
// Read the uninterpreted contents of the object after the map
reader_.GetBytes(obj->address(), size);
#ifdef DEBUG
if (expect_debug_information_) {
// Read in the epilogue to check that we're still synchronized
ExpectEncodedAddress(a);
reader_.ExpectC(']');
}
#endif
// Resolve the encoded pointers we just read in.
// Same as obj->Iterate(this), but doesn't rely on the map pointer being set.
VisitPointer(reinterpret_cast<Object**>(obj->address()));
obj->IterateBody(type, size, this);
if (type == CODE_TYPE) {
Code* code = Code::cast(obj);
// Convert relocations from Object* to Address in Code objects
code->ConvertICTargetsFromObjectToAddress();
LOG(CodeMoveEvent(a, code->address()));
}
objects_++;
return o;
}
static inline Object* ResolvePaged(int page_index,
int page_offset,
PagedSpace* space,
List<Page*>* page_list) {
ASSERT(page_index < page_list->length());
Address address = (*page_list)[page_index]->OffsetToAddress(page_offset);
return HeapObject::FromAddress(address);
}
template<typename T>
void ConcatReversed(List<T>* target, const List<T>& source) {
for (int i = source.length() - 1; i >= 0; i--) {
target->Add(source[i]);
}
}
Object* Deserializer::Resolve(Address encoded) {
Object* o = reinterpret_cast<Object*>(encoded);
if (o->IsSmi()) return o;
// Encoded addresses of HeapObjects always have 'HeapObject' tags.
ASSERT(o->IsHeapObject());
switch (GetSpace(encoded)) {
// For Map space and Old space, we cache the known Pages in map_pages,
// old_pointer_pages and old_data_pages. Even though MapSpace keeps a list
// of page addresses, we don't rely on it since GetObject uses AllocateRaw,
// and that appears not to update the page list.
case MAP_SPACE:
return ResolvePaged(PageIndex(encoded), PageOffset(encoded),
Heap::map_space(), &map_pages_);
case OLD_POINTER_SPACE:
return ResolvePaged(PageIndex(encoded), PageOffset(encoded),
Heap::old_pointer_space(), &old_pointer_pages_);
case OLD_DATA_SPACE:
return ResolvePaged(PageIndex(encoded), PageOffset(encoded),
Heap::old_data_space(), &old_data_pages_);
case CODE_SPACE:
return ResolvePaged(PageIndex(encoded), PageOffset(encoded),
Heap::code_space(), &code_pages_);
case NEW_SPACE:
return HeapObject::FromAddress(Heap::NewSpaceStart() +
NewSpaceOffset(encoded));
case LO_SPACE:
// Cache the known large_objects, allocated one per 'page'
int index = LargeObjectIndex(encoded);
if (index >= large_objects_.length()) {
int new_object_count =
Heap::lo_space()->PageCount() - large_objects_.length();
List<Object*> new_objects(new_object_count);
LargeObjectIterator it(Heap::lo_space());
for (int i = 0; i < new_object_count; i++) {
new_objects.Add(it.next());
}
#ifdef DEBUG
for (int i = large_objects_.length() - 1; i >= 0; i--) {
ASSERT(it.next() == large_objects_[i]);
}
#endif
ConcatReversed(&large_objects_, new_objects);
ASSERT(index < large_objects_.length());
}
return large_objects_[index]; // s.page_offset() is ignored.
}
UNREACHABLE();
return NULL;
}
} } // namespace v8::internal