Source code drop for the Actionscript virtual machine in the Adobe Flash Player for December 2013
519 lines
17 KiB
C++
519 lines
17 KiB
C++
/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
|
|
/* 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/. */
|
|
|
|
#ifndef __avmplus_Isolate__
|
|
#define __avmplus_Isolate__
|
|
|
|
#include "FixedHeapUtils.h"
|
|
#include "Channels.h"
|
|
|
|
#ifdef DEBUG
|
|
#define DEBUG_CONDITION_MUTEX
|
|
#define DEBUG_INTERRUPTIBLE_STATE
|
|
#endif // DEBUG
|
|
|
|
|
|
namespace avmplus
|
|
{
|
|
/*
|
|
* Represents an isolate in various stages of its lifecycle. There's only one Isolate
|
|
* object per isolate.
|
|
*/
|
|
class Isolate : public vmbase::Runnable, public FixedHeapRCObject
|
|
{
|
|
friend class Aggregate;
|
|
friend class WorkerObject;
|
|
|
|
public:
|
|
typedef int32_t descriptor_t;
|
|
|
|
static const descriptor_t INVALID_DESC = 0;
|
|
static const descriptor_t POISON_DESC = 0x7FFFFFFF; // Max int32
|
|
// Isolate objects are created by client code (e.g., the shell), before their AvmCores are instantiated.
|
|
// If the isolate is successfully started, the Isolate object will delete itself upon isolate termination.
|
|
// Otherwise, the AS glue object (IsolateObject) destructor will delete Isolate objects that have not been
|
|
// started or have failed.
|
|
Isolate(descriptor_t desc, descriptor_t parentDesc, Aggregate* aggregate);
|
|
|
|
static Isolate* newIsolate(descriptor_t desc, descriptor_t parentDesc, Aggregate* aggregate);
|
|
|
|
void initialize(AvmCore* core);
|
|
|
|
virtual void run(); // Inherited from Runnable.
|
|
|
|
virtual void destroy();
|
|
virtual ~Isolate();
|
|
|
|
AvmCore* getAvmCore () const
|
|
{
|
|
return m_core;
|
|
}
|
|
|
|
Aggregate* getAggregate() const
|
|
{
|
|
return m_aggregate;
|
|
}
|
|
|
|
bool isParentOf(const Isolate* other) const
|
|
{
|
|
AvmAssert(other != NULL);
|
|
return other->m_parentDesc == m_desc;
|
|
}
|
|
|
|
virtual void copyByteCode(ByteArrayObject* array);
|
|
|
|
// WorkerObject type depends on the overiding class.
|
|
virtual ScriptObject* newWorkerObject(Toplevel* toplevel) = 0;
|
|
|
|
bool interrupt(); // false if already interrupted
|
|
bool isInterrupted() const;
|
|
bool hasFailed() const;
|
|
|
|
// Last phase of termination.
|
|
virtual bool isMemoryManagementShutDown() const;
|
|
|
|
bool isPrimordial() const;
|
|
|
|
enum State {
|
|
NONE = 0, // sentinel
|
|
NEW = 1,
|
|
CANSTART = 2,
|
|
STARTING = 3,
|
|
RUNNING = 4,
|
|
FINISHING = 5,
|
|
TERMINATED = 6,
|
|
FAILED = 7,
|
|
ABORTED = 8,
|
|
EXCEPTION = 9
|
|
};
|
|
|
|
protected:
|
|
FixedHeapArray<uint8_t> getByteCode() const
|
|
{
|
|
return m_code.values[0];
|
|
}
|
|
|
|
virtual void stopRunLoop()
|
|
{
|
|
// This is the default impl for AvmShellIsolate and any other Isolate derived classes that
|
|
// do not have a run loop.
|
|
}
|
|
|
|
virtual void doRun() = 0;
|
|
|
|
public:
|
|
descriptor_t getDesc() const { return m_desc; }
|
|
descriptor_t getParentDesc() const { return m_parentDesc; }
|
|
void resetParent() { m_parentDesc = INVALID_DESC; }
|
|
|
|
protected:
|
|
typedef FixedHeapArray<char>* SharedPropertyNamep;
|
|
|
|
class SharedPropertyMap: public FixedHeapHashTable<SharedPropertyNamep, ChannelItem*>
|
|
{
|
|
public:
|
|
SharedPropertyMap();
|
|
virtual ~SharedPropertyMap();
|
|
protected:
|
|
virtual uintptr_t HashKey(SharedPropertyNamep key) const;
|
|
virtual bool KeysEqual(SharedPropertyNamep key1, const SharedPropertyNamep key2) const;
|
|
virtual void DestroyItem(SharedPropertyNamep key, ChannelItem* value);
|
|
};
|
|
|
|
FixedHeapArray< FixedHeapArray<uint8_t> > m_code;
|
|
|
|
private:
|
|
virtual void stateChanged(Isolate::State from) { (void)from; }
|
|
descriptor_t m_desc;
|
|
descriptor_t m_parentDesc;
|
|
|
|
virtual void releaseActiveResources();
|
|
vmbase::RecursiveMutex m_sharedPropertyLock;
|
|
SharedPropertyMap m_sharedProperties;
|
|
|
|
public:
|
|
void clearThread();
|
|
void setSharedProperty(const StUTF8String& key, ChannelItem* item);
|
|
Atom getSharedProperty(const StUTF8String& key, Toplevel* toplevel);
|
|
virtual ChannelItem* makeChannelItem(Toplevel* toplevel, Atom atom);
|
|
void signalInterruptibleState();
|
|
virtual bool retryInterruptibleState();
|
|
|
|
|
|
/*
|
|
* InterruptibleState provides basic management
|
|
* for any objects running within an isolate that
|
|
* need to enter a blocking state and be
|
|
* interruptable to support termination, debugging,
|
|
* and script-timeouts
|
|
*/
|
|
class InterruptibleState
|
|
{
|
|
public:
|
|
/*
|
|
* the wait record holds a reference to
|
|
* a thread that is waiting on this state's
|
|
* condition variable.
|
|
* it is used to manage fairness for locks
|
|
* and conditions as well as mark individual
|
|
* threads to be signaled in a special way
|
|
* by the runtime (see signal for more details)
|
|
*/
|
|
struct WaitRecord
|
|
{
|
|
WaitRecord()
|
|
: next(NULL)
|
|
, signaled(false)
|
|
#ifdef DEBUG_CONDITION_MUTEX
|
|
, threadID(VMPI_currentThread())
|
|
#endif // DEBUG_CONDITION_MUTEX
|
|
{}
|
|
|
|
WaitRecord* next;
|
|
bool signaled;
|
|
#ifdef DEBUG_CONDITION_MUTEX
|
|
vmpi_thread_t threadID;
|
|
#endif // DEBUG_CONDITION_MUTEX
|
|
};
|
|
|
|
InterruptibleState();
|
|
bool hasWaiters();
|
|
void notify();
|
|
void notifyAll();
|
|
// signal is a specialized case of notifyAll.
|
|
// it makes the distinction between user code
|
|
// waking up blocked threads with notify/notifyAll
|
|
// and the runtime waking up threads due to a
|
|
// termination request, script timeout, etc.
|
|
// it is used to wake up all threads and have
|
|
// them check if they should exit their waiting
|
|
// state or return to a blocked state.
|
|
void signal();
|
|
|
|
//
|
|
// This is intended as a stack based RAII helper for
|
|
// locking and waiting on an InterruptibleState, as
|
|
// well as associating that waiting state with a
|
|
//
|
|
class Enter
|
|
{
|
|
public:
|
|
Enter(WaitRecord& record, InterruptibleState* state, Isolate* isolate);
|
|
~Enter();
|
|
void wait(int32_t timeout=-1);
|
|
WaitRecord* waitListHead() const
|
|
{
|
|
return m_state->m_head;
|
|
}
|
|
void notify();
|
|
bool interrupted;
|
|
bool result;
|
|
|
|
// Force stack allocation
|
|
void* operator new(size_t);
|
|
|
|
private:
|
|
friend class Isolate;
|
|
// RAII for setting and removing the active intteruptible
|
|
// state object for the associated isolate
|
|
class ActiveInterruptibleStateHelper
|
|
{
|
|
public:
|
|
ActiveInterruptibleStateHelper(Isolate* isolate, InterruptibleState* state);
|
|
~ActiveInterruptibleStateHelper();
|
|
void unlock() const;
|
|
Isolate* getIsolate() const;
|
|
|
|
|
|
#if defined(DEBUG_INTERRUPTIBLE_STATE) || defined(DEBUG_CONDITION_MUTEX)
|
|
Isolate::descriptor_t gid;
|
|
#endif // DEBUG_INTERRUPTIBLE_STATE
|
|
private:
|
|
|
|
Isolate* m_isolate;
|
|
// reference to the mutex that is used to
|
|
// protect the active state on isolate
|
|
vmpi_mutex_t* m_mutex;
|
|
};
|
|
// we must always set the isolate's active state
|
|
// before getting a lock on the monitor
|
|
// *and* release them in the opposite order
|
|
// we are relying on the initialization and
|
|
// destruction order for this.
|
|
ActiveInterruptibleStateHelper m_stateSetter;
|
|
vmbase::MonitorLocker< vmbase::IMPLICIT_SAFEPOINT > m_monitor;
|
|
InterruptibleState* m_state;
|
|
WaitRecord* m_waitRecord;
|
|
};
|
|
|
|
vmbase::WaitNotifyMonitor& getMonitor() { return m_condition; }
|
|
|
|
#if defined (DEBUG_INTERRUPTIBLE_STATE) || defined (DEBUG_CONDITION_MUTEX)
|
|
int32_t gid;
|
|
#endif // DEBUG_INTERRUPTIBLE_STATE
|
|
|
|
private:
|
|
friend class Enter;
|
|
// to distingush between a call to signal from runtime
|
|
// code and user code calling notify/notifyAll
|
|
// a waiter count and signaled waiter counts along with
|
|
// the wait list are used.
|
|
// when a signal cycle starts it sets the singaled waiters
|
|
// count to the current waiters count.
|
|
// when a signal is requested it must first check that
|
|
// a signal has not already been requested and wait until
|
|
// it is complete.
|
|
vmbase::WaitNotifyMonitor m_condition;
|
|
volatile int32_t m_waiterCount;
|
|
uint32_t m_signaledWaiters;
|
|
// list of threads waiting on this state, in FIFO order
|
|
WaitRecord* m_head;
|
|
WaitRecord* m_tail;
|
|
|
|
#ifdef DEBUG_INTERRUPTIBLE_STATE
|
|
static int32_t globalId; // global id counter
|
|
#endif // DEBUG_INTERRUPTIBLE_STATE
|
|
};
|
|
|
|
private:
|
|
friend class InterruptibleState::Enter::ActiveInterruptibleStateHelper;
|
|
|
|
static void lockInSafepoint(vmpi_mutex_t* mutex);
|
|
static void lockInSafepointGate(void* stackPointer, void* mutex);
|
|
|
|
AvmCore* m_core;
|
|
|
|
// when an isolate is blocked from ActionScript either due to a
|
|
// Condition.wait or a Mutex.lock or other blocking operation
|
|
// the activeWaitRecord is used. The activeWaitRecord provides
|
|
// a simple way for the runtime to "wake" a blocked thread so that
|
|
// operations like Worker.terminate, shutdown, and script timeout can
|
|
// occur.
|
|
InterruptibleState* m_interruptibleState;
|
|
vmpi_mutex_t m_interruptibleStateMutex;
|
|
|
|
FixedHeapRef<Aggregate> m_aggregate;
|
|
Isolate::State m_state;
|
|
bool m_failed; // only accessed by the parent isolate.
|
|
bool m_interrupted;
|
|
|
|
// *** end data ***
|
|
};
|
|
|
|
|
|
/* An aggregate is a collection of isolates that have been transitively created from
|
|
* a single isolate (the primordial isolate).
|
|
* In the avm shell build there is only one of these but in the Flash Player there would be one
|
|
* for each Player instance (for each <object/> or <embed/> tag).
|
|
*/
|
|
class Aggregate : public FixedHeapRCObject
|
|
{
|
|
private:
|
|
class Globals
|
|
{
|
|
friend class Aggregate;
|
|
public:
|
|
Globals()
|
|
: m_nextGlobalIsolateId(1)
|
|
, m_idsWrapped(false)
|
|
, m_isolateMap(16)
|
|
{}
|
|
|
|
Isolate::descriptor_t getNewID()
|
|
{
|
|
Isolate::descriptor_t newID = m_nextGlobalIsolateId;
|
|
bool hadWrap = (newID == Isolate::POISON_DESC);
|
|
m_idsWrapped = m_idsWrapped || hadWrap;
|
|
|
|
if (m_idsWrapped)
|
|
{
|
|
if (hadWrap)
|
|
m_nextGlobalIsolateId = 1;
|
|
|
|
FixedHeapRef<Isolate> isolateRef(NULL);
|
|
while (m_isolateMap.LookupItem(m_nextGlobalIsolateId, &isolateRef))
|
|
{
|
|
m_nextGlobalIsolateId++;
|
|
}
|
|
newID = m_nextGlobalIsolateId;
|
|
}
|
|
|
|
m_nextGlobalIsolateId++;
|
|
return newID;
|
|
}
|
|
|
|
void orphanFor(Isolate::descriptor_t giid)
|
|
{
|
|
class Orphanize: public Globals::IsolateMap::Iterator
|
|
{
|
|
private:
|
|
const Isolate::descriptor_t m_TargetID;
|
|
|
|
public:
|
|
Orphanize(Isolate::descriptor_t target)
|
|
: m_TargetID(target)
|
|
{}
|
|
|
|
virtual void each(Isolate::descriptor_t, FixedHeapRef<Isolate> isolate)
|
|
{
|
|
if (isolate->getParentDesc() == m_TargetID)
|
|
{
|
|
isolate->resetParent();
|
|
}
|
|
}
|
|
};
|
|
|
|
Orphanize makeOrphansFor(giid);
|
|
SCOPE_LOCK(m_isolateMap.m_lock) {
|
|
m_isolateMap.ForEach(makeOrphansFor);
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
Isolate* getIsolateForID(Isolate::descriptor_t giid); // cannot be const because m_isolateMap is ill defined;
|
|
|
|
vmbase::WaitNotifyMonitor m_lock; // protects m_globals and all Aggregate instances (curently).
|
|
|
|
Isolate::descriptor_t m_nextGlobalIsolateId;
|
|
bool m_idsWrapped;
|
|
|
|
class IsolateMap: public FixedHeapHashTable<Isolate::descriptor_t, FixedHeapRef<Isolate> >
|
|
{
|
|
public:
|
|
IsolateMap(int initialSize);
|
|
virtual ~IsolateMap();
|
|
vmbase::WaitNotifyMonitor m_lock;
|
|
};
|
|
|
|
IsolateMap m_isolateMap;
|
|
}; // Globals
|
|
|
|
//
|
|
// Provides a container for all threads that are still
|
|
// executing code and will need to be waited on before
|
|
// shutting down the associated Aggregate.
|
|
//
|
|
class ActiveIsolateThreadMap: public FixedHeapHashTable<Isolate::descriptor_t, vmbase::VMThread*>
|
|
{
|
|
public:
|
|
ActiveIsolateThreadMap(int initialSize);
|
|
void cleanup();
|
|
};
|
|
|
|
public:
|
|
Aggregate();
|
|
virtual ~Aggregate();
|
|
virtual void destroy();
|
|
|
|
void signalOutOfMemoryShutdown();
|
|
bool isPrimordial(Isolate::descriptor_t giid) const;
|
|
|
|
// If parent == NULL, the primordial isolate will be created.
|
|
Isolate* newIsolate(Isolate* parent);
|
|
|
|
bool spawnIsolateThread(Isolate* isolate);
|
|
|
|
void initialize(AvmCore* targetCore, Isolate* isolate);
|
|
|
|
void beforeCoreDeletion(Isolate* current);
|
|
void afterGCDeletion(Isolate* current);
|
|
|
|
/* True if request caused exit */
|
|
bool requestIsolateExit(Isolate::descriptor_t desc, Toplevel* currentToplevel);
|
|
void requestAggregateExit();
|
|
void waitUntilNoIsolates();
|
|
|
|
void processWorkerInterrupt(Toplevel* toplevel);
|
|
virtual void throwWorkerTerminatedException(Toplevel* toplevel);
|
|
|
|
GCRef<ObjectVectorObject> listWorkers(Toplevel* toplevel);
|
|
void runSafepointTaskHoldingIsolateMapLock(vmbase::SafepointTask* task);
|
|
void reloadGlobalMemories();
|
|
void cleanupIsolate(Isolate* isolate);
|
|
|
|
Isolate* getIsolate(Isolate::descriptor_t desc);
|
|
Isolate::State queryState(Isolate* isolate);
|
|
|
|
void stateTransition(Isolate* isolate, enum Isolate::State to);
|
|
vmbase::SafepointManager* safepointManager()
|
|
{
|
|
return &m_safepointMgr;
|
|
}
|
|
|
|
bool inShutdown() const
|
|
{
|
|
return m_inShutdown;
|
|
}
|
|
|
|
private:
|
|
void setIsolateAsInterrupted(Isolate* isolate);
|
|
|
|
Globals m_globals;
|
|
Isolate::descriptor_t m_primordialGiid;
|
|
int m_activeIsolateCount;
|
|
vmbase::SafepointManager m_safepointMgr; // Currently for shared byte array only.
|
|
bool m_inShutdown;
|
|
ActiveIsolateThreadMap m_activeIsolateThreadMap;
|
|
};
|
|
|
|
// Stack allocated, RAII pattern.
|
|
class EnterSafepointManager
|
|
{
|
|
public:
|
|
EnterSafepointManager(AvmCore* core);
|
|
void leaveSafepoint(); // If manual exit/cleanup needed b/c of longjmp.
|
|
~EnterSafepointManager();
|
|
private:
|
|
FixedHeapRef<Aggregate> m_aggregate; // to keep the safepoint mgr alive during shutdown, etc.
|
|
vmbase::SafepointManager* m_safepointMgr;
|
|
vmbase::SafepointRecord m_spRecord;
|
|
};
|
|
|
|
template <class T>
|
|
class WorkerDomainObjectBase
|
|
{
|
|
public:
|
|
T* self();
|
|
ObjectVectorObject* listWorkers();
|
|
};
|
|
|
|
|
|
template <class T>
|
|
class WorkerObjectBase
|
|
{
|
|
T* self();
|
|
public:
|
|
WorkerObjectBase();
|
|
// If "thisIsolate == NULL" then create a new isolate. Otherwise, use thisIsolate to construct the worker
|
|
void initialize(Isolate *isolate = NULL);
|
|
|
|
GCRef<ScriptObject> setIsolate(Isolate* isolate);
|
|
|
|
virtual ~WorkerObjectBase();
|
|
Isolate::descriptor_t descriptor() const;
|
|
Stringp get_state();
|
|
|
|
bool isParentOf(WorkerObjectBase* worker);
|
|
bool isPrimordial() const;
|
|
void start();
|
|
void setSharedProperty(String* key, Atom value);
|
|
Atom getSharedProperty(String* key);
|
|
static void throwIllegalOperationError(int errorID);
|
|
|
|
protected:
|
|
virtual void internalStart();
|
|
bool internalStop();
|
|
Isolate* getIsolate() const { return m_isolate; }
|
|
|
|
private:
|
|
FixedHeapRef<Isolate> m_isolate;
|
|
|
|
};
|
|
|
|
}
|
|
#endif
|
|
|