// Copyright 2006-2010 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.

#ifndef V8_SPACES_INL_H_
#define V8_SPACES_INL_H_

#include "isolate.h"
#include "spaces.h"
#include "v8memory.h"

namespace v8 {
namespace internal {


// -----------------------------------------------------------------------------
// PageIterator

bool PageIterator::has_next() {
  return prev_page_ != stop_page_;
}


Page* PageIterator::next() {
  ASSERT(has_next());
  prev_page_ = (prev_page_ == NULL)
               ? space_->first_page_
               : prev_page_->next_page();
  return prev_page_;
}


// -----------------------------------------------------------------------------
// Page

Page* Page::next_page() {
  return heap_->isolate()->memory_allocator()->GetNextPage(this);
}


Address Page::AllocationTop() {
  PagedSpace* owner = heap_->isolate()->memory_allocator()->PageOwner(this);
  return owner->PageAllocationTop(this);
}


Address Page::AllocationWatermark() {
  PagedSpace* owner = heap_->isolate()->memory_allocator()->PageOwner(this);
  if (this == owner->AllocationTopPage()) {
    return owner->top();
  }
  return address() + AllocationWatermarkOffset();
}


uint32_t Page::AllocationWatermarkOffset() {
  return static_cast<uint32_t>((flags_ & kAllocationWatermarkOffsetMask) >>
                               kAllocationWatermarkOffsetShift);
}


void Page::SetAllocationWatermark(Address allocation_watermark) {
  if ((heap_->gc_state() == Heap::SCAVENGE) && IsWatermarkValid()) {
    // When iterating intergenerational references during scavenge
    // we might decide to promote an encountered young object.
    // We will allocate a space for such an object and put it
    // into the promotion queue to process it later.
    // If space for object was allocated somewhere beyond allocation
    // watermark this might cause garbage pointers to appear under allocation
    // watermark. To avoid visiting them during dirty regions iteration
    // which might be still in progress we store a valid allocation watermark
    // value and mark this page as having an invalid watermark.
    SetCachedAllocationWatermark(AllocationWatermark());
    InvalidateWatermark(true);
  }

  flags_ = (flags_ & kFlagsMask) |
           Offset(allocation_watermark) << kAllocationWatermarkOffsetShift;
  ASSERT(AllocationWatermarkOffset()
         == static_cast<uint32_t>(Offset(allocation_watermark)));
}


void Page::SetCachedAllocationWatermark(Address allocation_watermark) {
  mc_first_forwarded = allocation_watermark;
}


Address Page::CachedAllocationWatermark() {
  return mc_first_forwarded;
}


uint32_t Page::GetRegionMarks() {
  return dirty_regions_;
}


void Page::SetRegionMarks(uint32_t marks) {
  dirty_regions_ = marks;
}


int Page::GetRegionNumberForAddress(Address addr) {
  // Each page is divided into 256 byte regions. Each region has a corresponding
  // dirty mark bit in the page header. Region can contain intergenerational
  // references iff its dirty mark is set.
  // A normal 8K page contains exactly 32 regions so all region marks fit
  // into 32-bit integer field. To calculate a region number we just divide
  // offset inside page by region size.
  // A large page can contain more then 32 regions. But we want to avoid
  // additional write barrier code for distinguishing between large and normal
  // pages so we just ignore the fact that addr points into a large page and
  // calculate region number as if addr pointed into a normal 8K page. This way
  // we get a region number modulo 32 so for large pages several regions might
  // be mapped to a single dirty mark.
  ASSERT_PAGE_ALIGNED(this->address());
  STATIC_ASSERT((kPageAlignmentMask >> kRegionSizeLog2) < kBitsPerInt);

  // We are using masking with kPageAlignmentMask instead of Page::Offset()
  // to get an offset to the beginning of 8K page containing addr not to the
  // beginning of actual page which can be bigger then 8K.
  intptr_t offset_inside_normal_page = OffsetFrom(addr) & kPageAlignmentMask;
  return static_cast<int>(offset_inside_normal_page >> kRegionSizeLog2);
}


uint32_t Page::GetRegionMaskForAddress(Address addr) {
  return 1 << GetRegionNumberForAddress(addr);
}


uint32_t Page::GetRegionMaskForSpan(Address start, int length_in_bytes) {
  uint32_t result = 0;
  if (length_in_bytes >= kPageSize) {
    result = kAllRegionsDirtyMarks;
  } else if (length_in_bytes > 0) {
    int start_region = GetRegionNumberForAddress(start);
    int end_region =
        GetRegionNumberForAddress(start + length_in_bytes - kPointerSize);
    uint32_t start_mask = (~0) << start_region;
    uint32_t end_mask = ~((~1) << end_region);
    result = start_mask & end_mask;
    // if end_region < start_region, the mask is ored.
    if (result == 0) result = start_mask | end_mask;
  }
#ifdef DEBUG
  if (FLAG_enable_slow_asserts) {
    uint32_t expected = 0;
    for (Address a = start; a < start + length_in_bytes; a += kPointerSize) {
      expected |= GetRegionMaskForAddress(a);
    }
    ASSERT(expected == result);
  }
#endif
  return result;
}


void Page::MarkRegionDirty(Address address) {
  SetRegionMarks(GetRegionMarks() | GetRegionMaskForAddress(address));
}


bool Page::IsRegionDirty(Address address) {
  return GetRegionMarks() & GetRegionMaskForAddress(address);
}


void Page::ClearRegionMarks(Address start, Address end, bool reaches_limit) {
  int rstart = GetRegionNumberForAddress(start);
  int rend = GetRegionNumberForAddress(end);

  if (reaches_limit) {
    end += 1;
  }

  if ((rend - rstart) == 0) {
    return;
  }

  uint32_t bitmask = 0;

  if ((OffsetFrom(start) & kRegionAlignmentMask) == 0
      || (start == ObjectAreaStart())) {
    // First region is fully covered
    bitmask = 1 << rstart;
  }

  while (++rstart < rend) {
    bitmask |= 1 << rstart;
  }

  if (bitmask) {
    SetRegionMarks(GetRegionMarks() & ~bitmask);
  }
}


void Page::FlipMeaningOfInvalidatedWatermarkFlag(Heap* heap) {
  heap->page_watermark_invalidated_mark_ ^= 1 << WATERMARK_INVALIDATED;
}


bool Page::IsWatermarkValid() {
  return (flags_ & (1 << WATERMARK_INVALIDATED)) !=
      heap_->page_watermark_invalidated_mark_;
}


void Page::InvalidateWatermark(bool value) {
  if (value) {
    flags_ = (flags_ & ~(1 << WATERMARK_INVALIDATED)) |
             heap_->page_watermark_invalidated_mark_;
  } else {
    flags_ =
        (flags_ & ~(1 << WATERMARK_INVALIDATED)) |
        (heap_->page_watermark_invalidated_mark_ ^
         (1 << WATERMARK_INVALIDATED));
  }

  ASSERT(IsWatermarkValid() == !value);
}


bool Page::GetPageFlag(PageFlag flag) {
  return (flags_ & static_cast<intptr_t>(1 << flag)) != 0;
}


void Page::SetPageFlag(PageFlag flag, bool value) {
  if (value) {
    flags_ |= static_cast<intptr_t>(1 << flag);
  } else {
    flags_ &= ~static_cast<intptr_t>(1 << flag);
  }
}


void Page::ClearPageFlags() {
  flags_ = 0;
}


void Page::ClearGCFields() {
  InvalidateWatermark(true);
  SetAllocationWatermark(ObjectAreaStart());
  if (heap_->gc_state() == Heap::SCAVENGE) {
    SetCachedAllocationWatermark(ObjectAreaStart());
  }
  SetRegionMarks(kAllRegionsCleanMarks);
}


bool Page::WasInUseBeforeMC() {
  return GetPageFlag(WAS_IN_USE_BEFORE_MC);
}


void Page::SetWasInUseBeforeMC(bool was_in_use) {
  SetPageFlag(WAS_IN_USE_BEFORE_MC, was_in_use);
}


bool Page::IsLargeObjectPage() {
  return !GetPageFlag(IS_NORMAL_PAGE);
}


void Page::SetIsLargeObjectPage(bool is_large_object_page) {
  SetPageFlag(IS_NORMAL_PAGE, !is_large_object_page);
}

bool Page::IsPageExecutable() {
  return GetPageFlag(IS_EXECUTABLE);
}


void Page::SetIsPageExecutable(bool is_page_executable) {
  SetPageFlag(IS_EXECUTABLE, is_page_executable);
}


// -----------------------------------------------------------------------------
// MemoryAllocator

void MemoryAllocator::ChunkInfo::init(Address a, size_t s, PagedSpace* o) {
  address_ = a;
  size_ = s;
  owner_ = o;
  executable_ = (o == NULL) ? NOT_EXECUTABLE : o->executable();
  owner_identity_ = (o == NULL) ? FIRST_SPACE : o->identity();
}


bool MemoryAllocator::IsValidChunk(int chunk_id) {
  if (!IsValidChunkId(chunk_id)) return false;

  ChunkInfo& c = chunks_[chunk_id];
  return (c.address() != NULL) && (c.size() != 0) && (c.owner() != NULL);
}


bool MemoryAllocator::IsValidChunkId(int chunk_id) {
  return (0 <= chunk_id) && (chunk_id < max_nof_chunks_);
}


bool MemoryAllocator::IsPageInSpace(Page* p, PagedSpace* space) {
  ASSERT(p->is_valid());

  int chunk_id = GetChunkId(p);
  if (!IsValidChunkId(chunk_id)) return false;

  ChunkInfo& c = chunks_[chunk_id];
  return (c.address() <= p->address()) &&
         (p->address() < c.address() + c.size()) &&
         (space == c.owner());
}


Page* MemoryAllocator::GetNextPage(Page* p) {
  ASSERT(p->is_valid());
  intptr_t raw_addr = p->opaque_header & ~Page::kPageAlignmentMask;
  return Page::FromAddress(AddressFrom<Address>(raw_addr));
}


int MemoryAllocator::GetChunkId(Page* p) {
  ASSERT(p->is_valid());
  return static_cast<int>(p->opaque_header & Page::kPageAlignmentMask);
}


void MemoryAllocator::SetNextPage(Page* prev, Page* next) {
  ASSERT(prev->is_valid());
  int chunk_id = GetChunkId(prev);
  ASSERT_PAGE_ALIGNED(next->address());
  prev->opaque_header = OffsetFrom(next->address()) | chunk_id;
}


PagedSpace* MemoryAllocator::PageOwner(Page* page) {
  int chunk_id = GetChunkId(page);
  ASSERT(IsValidChunk(chunk_id));
  return chunks_[chunk_id].owner();
}


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());
}


// --------------------------------------------------------------------------
// PagedSpace

bool PagedSpace::Contains(Address addr) {
  Page* p = Page::FromAddress(addr);
  if (!p->is_valid()) return false;
  return heap()->isolate()->memory_allocator()->IsPageInSpace(p, this);
}


// Try linear allocation in the page of alloc_info's allocation top.  Does
// not contain slow case logic (eg, move to the next page or try free list
// allocation) so it can be used by all the allocation functions and for all
// the paged spaces.
HeapObject* PagedSpace::AllocateLinearly(AllocationInfo* alloc_info,
                                         int size_in_bytes) {
  Address current_top = alloc_info->top;
  Address new_top = current_top + size_in_bytes;
  if (new_top > alloc_info->limit) return NULL;

  alloc_info->top = new_top;
  ASSERT(alloc_info->VerifyPagedAllocation());
  accounting_stats_.AllocateBytes(size_in_bytes);
  return HeapObject::FromAddress(current_top);
}


// Raw allocation.
MaybeObject* PagedSpace::AllocateRaw(int size_in_bytes) {
  ASSERT(HasBeenSetup());
  ASSERT_OBJECT_SIZE(size_in_bytes);
  HeapObject* object = AllocateLinearly(&allocation_info_, size_in_bytes);
  if (object != NULL) return object;

  object = SlowAllocateRaw(size_in_bytes);
  if (object != NULL) return object;

  return Failure::RetryAfterGC(identity());
}


// Reallocating (and promoting) objects during a compacting collection.
MaybeObject* PagedSpace::MCAllocateRaw(int size_in_bytes) {
  ASSERT(HasBeenSetup());
  ASSERT_OBJECT_SIZE(size_in_bytes);
  HeapObject* object = AllocateLinearly(&mc_forwarding_info_, size_in_bytes);
  if (object != NULL) return object;

  object = SlowMCAllocateRaw(size_in_bytes);
  if (object != NULL) return object;

  return Failure::RetryAfterGC(identity());
}


// -----------------------------------------------------------------------------
// LargeObjectChunk

Address LargeObjectChunk::GetStartAddress() {
  // Round the chunk address up to the nearest page-aligned address
  // and return the heap object in that page.
  Page* page = Page::FromAddress(RoundUp(address(), Page::kPageSize));
  return page->ObjectAreaStart();
}


void LargeObjectChunk::Free(Executability executable) {
  Isolate* isolate =
      Page::FromAddress(RoundUp(address(), Page::kPageSize))->heap_->isolate();
  isolate->memory_allocator()->FreeRawMemory(address(), size(), executable);
}

// -----------------------------------------------------------------------------
// NewSpace

MaybeObject* NewSpace::AllocateRawInternal(int size_in_bytes,
                                           AllocationInfo* alloc_info) {
  Address new_top = alloc_info->top + size_in_bytes;
  if (new_top > alloc_info->limit) return Failure::RetryAfterGC();

  Object* obj = HeapObject::FromAddress(alloc_info->top);
  alloc_info->top = new_top;
#ifdef DEBUG
  SemiSpace* space =
      (alloc_info == &allocation_info_) ? &to_space_ : &from_space_;
  ASSERT(space->low() <= alloc_info->top
         && alloc_info->top <= space->high()
         && alloc_info->limit == space->high());
#endif
  return obj;
}


intptr_t LargeObjectSpace::Available() {
  return LargeObjectChunk::ObjectSizeFor(
      heap()->isolate()->memory_allocator()->Available());
}


template <typename StringType>
void NewSpace::ShrinkStringAtAllocationBoundary(String* string, int length) {
  ASSERT(length <= string->length());
  ASSERT(string->IsSeqString());
  ASSERT(string->address() + StringType::SizeFor(string->length()) ==
         allocation_info_.top);
  allocation_info_.top =
      string->address() + StringType::SizeFor(length);
  string->set_length(length);
}


bool FreeListNode::IsFreeListNode(HeapObject* object) {
  return object->map() == HEAP->raw_unchecked_byte_array_map()
      || object->map() == HEAP->raw_unchecked_one_pointer_filler_map()
      || object->map() == HEAP->raw_unchecked_two_pointer_filler_map();
}

} }  // namespace v8::internal

#endif  // V8_SPACES_INL_H_
