Files
dmcallis 65a0592776 Initial source code drop
Source code drop for the Actionscript virtual machine in the Adobe
Flash Player for December 2013
2013-12-02 16:04:04 -08:00

605 lines
19 KiB
C++

/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
/* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "avmplus.h"
namespace avmplus
{
MostlyNonBlockingChannel::MostlyNonBlockingChannel(MMgc::GC* gc)
: eof(false), get_tries(0), put_tries(0), head(0), tail(0), fallback(NO_FB)
{
bufferData = new BufferData(gc, this, INIT_LENGTH);
overflowBufferData = NULL;
}
MostlyNonBlockingChannel::~MostlyNonBlockingChannel()
{
// The following will deregister bufferData as GCRoot. The
// GCRoot list is protected by a spinlock, so it should be OK
// to call from the main thread of another GC.
delete bufferData;
}
void MostlyNonBlockingChannel::close()
{
SCOPE_LOCK_NAMED(lk, m_monitor) {
eof = true;
lk.notifyAll();
}
}
void MostlyNonBlockingChannel::unregisterRoot()
{
bufferData->Destroy();
SCOPE_LOCK_NAMED(lk, m_monitor) {
if (overflowBufferData != NULL) {
overflowBufferData->Destroy();
}
}
}
bool MostlyNonBlockingChannel::isClosed()
{
VMPI_memoryBarrier();
return eof;
}
bool MostlyNonBlockingChannel::isEmpty()
{
return (head == tail);
}
bool MostlyNonBlockingChannel::isFull()
{
return ((head + 1) % bufferData->length) == tail;
}
bool MostlyNonBlockingChannel::put(const Atom &in)
{
AvmAssert(put_tries == 0);
do {
do {
if (eof) {
put_tries = 0;
return false;
}
if (put_tries++ == TRIES ||
overflowBufferData != NULL) // putting data into overflow buffer
break;
int32_t old_h = head;
if (((old_h + 1) % bufferData->length) == tail) {
// buffer full
if (fallback != NO_FB) {
// fake "full"
break;
}
else {
// wait for get() to retrieve an item
VMPI_threadSleep(0);
continue;
}
}
else {
// buffer is not full
bufferData->m_items[old_h] = in;
int32_t h = (old_h + 1) % bufferData->length;
if (VMPI_compareAndSwap32WithBarrier(old_h, h, &head)) {
put_tries = 0;
return true;
}
// if CAS failed, it means that get() intervened
// and set state to both full and fallback
AvmAssert(isFull() && fallback != NO_FB);
break;
}
} while (true);
// really full or "fake" full
SCOPE_LOCK_NAMED(lk, m_monitor) {
if (overflowBufferData != NULL) {
AvmAssert(fallback != NO_FB);
AvmAssert(isEmpty()); // original buffer is "fake empty"
// resize when head reached length-1 to leave 1
// empty element in case overflow buffer will be
// turned into regular circular buffer right after
// completely filling up
if (overflowHead == overflowBufferData->length - 1) {
resizeOverflowBuffer();
}
overflowBufferData->m_items[overflowHead] = in;
overflowHead++;
// if get() is in progress then let it clean up
// the overflow while this thread sleeps; if we
// tried resizing on the fly, we would risk
// exposing get() to inconsistent state
if (get_tries > 0) {
lk.wait();
}
// one way or the other, the item has been
// inserted into the channel successfully - return
break;
}
int32_t fb = fallback;
if (fb != NO_FB) {
// get() went to sleep (should be full - marked by
// get() as such)
AvmAssert(isFull());
// fb contains value of head store by get()
head = fb;
AvmAssert(!isFull());
bufferData->m_items[head] = in;
head = (head + 1) % bufferData->length;
lk.notify();
fallback = NO_FB;
break;
}
int32_t t = tail;
if (!(((head + 1) % bufferData->length) == t)) {
// no longer full (get() can only change head
// within lock-based critical section so we can
// use it directly) - retry
continue;
}
else {
// make sure that non-blocking get is not going to
// happen between now and when we go to sleep
int32_t empty_t = head;
// remember the actual value of tail
fallback = t;
if (VMPI_compareAndSwap32WithBarrier(t, empty_t, &tail)) {
// get() hasn't happened in the meantime
if (eof) {
put_tries = 0;
return false;
}
if (overflowBufferData == NULL) {
createOverflowBuffer();
}
overflowBufferData->m_items[overflowHead] = in;
overflowHead++;
// see comments in the first conditional of
// this critical section
if (get_tries > 0) {
lk.wait();
}
break;
}
else {
// get() did happen - retry
fallback = NO_FB;
continue;
}
}
}
} while (true);
put_tries = 0;
return true;
}
bool MostlyNonBlockingChannel::get(Atom *outp)
{
AvmAssert(get_tries == 0);
do {
do {
if (eof) {
get_tries = 0;
return false;
}
if (get_tries++ == TRIES)
break;
int32_t old_t = tail;
if (head == old_t) {
// buffer empty
if (fallback != NO_FB) {
break;
}
else {
// wait for put() to insert an item
VMPI_threadSleep(0);
continue;
}
}
else {
// buffer is not empty
*outp = bufferData->m_items[old_t];
int32_t t = (old_t + 1) % bufferData->length;
if (VMPI_compareAndSwap32WithBarrier(old_t, t, &tail)) {
get_tries = 0;
return true;
}
// if CAS failed, it means that put() intervened
// and set state to both empty and fallback
AvmAssert(isEmpty() && fallback != NO_FB);
}
} while (true);
// really empty or "fake" empty
SCOPE_LOCK_NAMED(lk, m_monitor) {
int32_t fb = fallback;
if (fb != NO_FB) {
// fb contains value of tail store by put()
AvmAssert(overflowBufferData != NULL);
*outp = overflowBufferData->m_items[0];
tail = 1; // just removed the first element
head = overflowHead;
bufferData->Destroy();
delete bufferData;
bufferData = overflowBufferData;
overflowBufferData = NULL;
lk.notify();
fallback = NO_FB;
break;
}
int32_t h = head;
if (h != tail) {
// no longer empty (put() can only change tail
// within lock-based critical section so we can
// use it directly) - retry
continue;
}
else {
// make sure that non-blocking put is not going to
// happen between now and when we go to sleep
int32_t full_h = (tail == 0 ? bufferData->length - 1 : tail - 1);
// remember the actual value of head
fallback = h;
if (VMPI_compareAndSwap32WithBarrier(h, full_h, &head)) {
// put() hasn't happened in the meantime
// since the channel is indeed empty, reset
// all stale references to objects that still
// reside in it
bufferData->cleanup();
if (eof) {
get_tries = 0;
return false;
}
// cannot assert the following because the
// channel is "fake full" at this point
// (though it is in fact empty)
// AvmAssert(!available());
lk.wait();
}
else {
// put() did happen - retry
fallback = NO_FB;
continue;
}
}
}
} while (true);
get_tries = 0;
return true;
}
/*
* Must not be used concurrently with get(),including inside of
* the implementation of get().
*/
bool MostlyNonBlockingChannel::available()
{
VMPI_memoryBarrier();
bool empty = isEmpty();
if (empty) {
// may be a "fake" empty as a result of put()
SCOPE_LOCK_NAMED(lk, m_monitor) {
empty = isEmpty();
if (empty && (fallback != NO_FB)) return head != fallback;
else return !empty;
}
}
return !empty;
}
void MostlyNonBlockingChannel::registerWithGC(MMgc::GC* gc)
{
bufferData->Register(gc);
}
MMgc::GC* MostlyNonBlockingChannel::gc()
{
return bufferData->GetGC();
}
MostlyNonBlockingChannel::BufferData::BufferData(MMgc::GC* gc,
MostlyNonBlockingChannel* ch,
int32_t size) :
GCRoot(gc, MMgc::kExact), length(size), channel(ch)
{
m_items = mmfx_new_array(Atom, length);
cleanup();
}
MostlyNonBlockingChannel::BufferData::~BufferData()
{
mmfx_delete_array(m_items);
}
/*virtual*/ bool MostlyNonBlockingChannel::BufferData::gcTrace(MMgc::GC* gc, size_t cursor)
{
(void)cursor;
for (int i = 0; i < length; i++) {
if (m_items[i] != unreachableAtom) {
// Although TraceAtom() should be able to handle this.
gc->TraceAtom(&m_items[i]);
}
}
return false;
}
void MostlyNonBlockingChannel::BufferData::cleanup()
{
VMPI_memset(m_items, unreachableAtom, length*sizeof(Atom));
}
void MostlyNonBlockingChannel::createOverflowBuffer()
{
int newLength = bufferData->length + INIT_LENGTH;
AvmAssert(bufferData->GetGC() != NULL);
overflowBufferData = new BufferData(bufferData->GetGC(), this, newLength);
// one last element is unused when the buffer is full
for(int i=0; i < bufferData->length-1; i++) {
overflowBufferData->m_items[i] = bufferData->m_items[(fallback + i) % bufferData->length];
}
for(int i=bufferData->length-1; i<newLength; i++) {
overflowBufferData->m_items[i] = unreachableAtom;
}
overflowHead = bufferData->length-1;
}
void MostlyNonBlockingChannel::resizeOverflowBuffer()
{
BufferData* oldBufferData = overflowBufferData;
int oldLength = overflowBufferData->length;
int newLength = oldLength + INIT_LENGTH;
AvmAssert(oldBufferData->GetGC() != NULL);
overflowBufferData = new BufferData(oldBufferData->GetGC(), this, newLength);
for(int i=0; i < oldLength - 1; i++) {
overflowBufferData->m_items[i] = oldBufferData->m_items[i];
}
for(int i=oldLength - 1; i<newLength; i++) {
overflowBufferData->m_items[i] = unreachableAtom;
}
oldBufferData->Destroy();
delete oldBufferData;
}
NoSyncSingleItemBuffer::NoSyncSingleItemBuffer(MMgc::GC* gc) : m_hasData(false)
{
bufferData = new BufferData(gc, this);
}
NoSyncSingleItemBuffer::~NoSyncSingleItemBuffer()
{
delete bufferData;
}
bool NoSyncSingleItemBuffer::isEmpty()
{
return !m_hasData;
}
bool NoSyncSingleItemBuffer::isFull()
{
return m_hasData;
}
bool NoSyncSingleItemBuffer::put(const Atom &in)
{
if (isFull())
return false;
bufferData->m_item = in;
m_hasData = true;
return true;
}
bool NoSyncSingleItemBuffer::get(Atom *outp)
{
if (isEmpty())
return false;
m_hasData = false;
*outp = bufferData->m_item;
return true;
}
void NoSyncSingleItemBuffer::registerWithGC(MMgc::GC* gc)
{
bufferData->Register(gc);
}
MMgc::GC* NoSyncSingleItemBuffer::gc() {
return bufferData->GetGC();
}
void NoSyncSingleItemBuffer::unregisterRoot() {
bufferData->Destroy();
}
NoSyncSingleItemBuffer::BufferData::BufferData(MMgc::GC* gc, NoSyncSingleItemBuffer* buf)
: GCRoot(gc, MMgc::kExact), m_item(0), buffer(buf)
{
}
/*virtual*/ bool NoSyncSingleItemBuffer::BufferData::gcTrace(MMgc::GC* gc, size_t cursor)
{
(void)cursor;
gc->TraceAtom(&m_item);
return false;
}
NoSyncMultiItemBuffer::NoSyncMultiItemBuffer()
: m_hasData(false), head(0), tail(0), bufLength(BUF_LENGTH)
{
m_items = mmfx_new_array_opt(const ChannelItem*, bufLength, MMgc::kZero);
}
NoSyncMultiItemBuffer::~NoSyncMultiItemBuffer()
{
const ChannelItem* p = NULL;
while(get(&p))
{
mmfx_delete((ChannelItem*)p);
}
mmfx_delete_array(m_items);
}
bool NoSyncMultiItemBuffer::isEmpty()
{
return (head == tail) && !m_hasData;
}
bool NoSyncMultiItemBuffer::isFull()
{
return (head == tail) && m_hasData;
}
uint32_t NoSyncMultiItemBuffer::numItemsInBuffer()
{
uint32_t retVal = 0;
if (isEmpty())
{
retVal = 0;
}
else if (isFull())
{
retVal = bufLength;
}
else if (head > tail)
{
retVal = head - tail;
}
// The circular buffer may have a tail greater than head
else
{
retVal = head + bufLength - tail;
}
return retVal;
}
bool NoSyncMultiItemBuffer::put(const ChannelItem* in)
{
if (isFull())
{
// Increase by BUF_LENGTH
uint32_t newLength = bufLength + BUF_LENGTH;
const ChannelItem** newBuf = mmfx_new_array_opt(const ChannelItem*, newLength, MMgc::kZero);
uint32_t it = 0;
// Copy the old buffer into this one
while (get(&newBuf[it]))
{
it++;
}
tail = 0;
head = it;
mmfx_delete_array(m_items);
m_items = newBuf;
bufLength = newLength;
}
m_items[head] = in;
head = (head + 1) % bufLength;
m_hasData = true; // just do it every time instead of checking
return true;
}
bool NoSyncMultiItemBuffer::get(const ChannelItem** outp)
{
if (isEmpty())
return false;
*outp = m_items[tail];
m_items[tail] = NULL;
tail = (tail + 1) % bufLength;
if (tail == head)
m_hasData = false;
return true;
}
NonBlockingMultiItemBuffer::NonBlockingMultiItemBuffer(MMgc::GC* gc)
: head(0), tail(0)
{
bufferData = new BufferData(gc, this);
}
NonBlockingMultiItemBuffer::~NonBlockingMultiItemBuffer()
{
delete bufferData;
}
bool NonBlockingMultiItemBuffer::isEmpty()
{
return (head == tail);
}
bool NonBlockingMultiItemBuffer::isFull()
{
return ((head + 1) % BUF_LENGTH) == tail;
}
bool NonBlockingMultiItemBuffer::put(const Atom &in)
{
if (isFull()) return false;
bufferData->m_items[head] = in;
head = (head + 1) % BUF_LENGTH;
VMPI_memoryBarrier();
return true;
}
bool NonBlockingMultiItemBuffer::get(Atom *outp)
{
if (isEmpty()) return false;
*outp = bufferData->m_items[tail];
tail = (tail + 1) % BUF_LENGTH;
VMPI_memoryBarrier();
return true;
}
void NonBlockingMultiItemBuffer::registerWithGC(MMgc::GC* gc)
{
bufferData->Register(gc);
}
MMgc::GC* NonBlockingMultiItemBuffer::gc() {
return bufferData->GetGC();
}
void NonBlockingMultiItemBuffer::unregisterRoot() {
bufferData->Destroy();
}
NonBlockingMultiItemBuffer::BufferData::BufferData(MMgc::GC* gc, NonBlockingMultiItemBuffer* buf)
: GCRoot(gc, MMgc::kExact), buffer(buf)
{
for (int i = 0; i < BUF_LENGTH; i++) {
m_items[i] = unreachableAtom;
}
}
/*virtual*/ bool NonBlockingMultiItemBuffer::BufferData::gcTrace(MMgc::GC* gc, size_t cursor)
{
(void)cursor;
for (int i = 0; i < BUF_LENGTH; i++) {
gc->TraceAtom(&m_items[i]);
}
return false;
}
}