/* -*- 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 "avmshell.h" #ifdef VMCFG_NANOJIT #include "../nanojit/nanojit.h" #endif #include #include "extensions-tracers.hh" #include "avmshell-tracers.hh" #ifdef VMCFG_HALFMOON #include "../halfmoon/hm-main.h" // halfmoon::enable_mode #endif namespace avmshell { #ifdef AVMPLUS_WIN32 extern bool show_error; // in avmshellWin.cpp #endif ShellSettings::ShellSettings() : ShellCoreSettings() , programFilename(NULL) , filenames(NULL) , numfiles(-1) , do_selftest(false) , do_repl(false) , do_log(false) , do_projector(false) , numthreads(1) , numworkers(1) , repeats(1) , stackSize(0) { } ShellCoreImpl::ShellCoreImpl(MMgc::GC* gc, ShellSettings& settings, bool mainthread) : ShellCore(gc, settings.apiVersionSeries) , settings(settings) , mainthread(mainthread) { } /* virtual */ void ShellCoreImpl::setStackLimit() { uintptr_t minstack; if (settings.stackSize != 0) { // Here we really depend on being called fairly high up on // the thread's stack, because we don't know where the highest // stack address is. minstack = uintptr_t(&minstack) - settings.stackSize + avmshell::kStackMargin; } else { #ifdef VMCFG_WORKERTHREADS if (mainthread) minstack = Platform::GetInstance()->getMainThreadStackLimit(); else { // Here we really depend on being called fairly high up on // the thread's stack, because we don't know where the highest // stack address is. // In order to be platform-independent, we can no // longer rely on getting the default stack height // from a pthread call. Instead, we get it from the // VMPI_threadAttrDefaultStackSize() call. size_t stackheight = VMPI_threadAttrDefaultStackSize(); minstack = uintptr_t(&stackheight) - stackheight + avmshell::kStackMargin; } #else minstack = Platform::GetInstance()->getMainThreadStackLimit(); #endif } // call the non-virtual setter on AvmCore AvmCore::setStackLimit(minstack); } /* static */ int Shell::run(int argc, char *argv[]) { MMgc::GCHeap::EnterLockInit(); MMgc::GCHeapConfig conf; //conf.verbose = AvmCore::DEFAULT_VERBOSE_ON; MMgc::GCHeap::Init(conf); // Note that output from the command line parser (usage messages, error messages, // and printed version number / feature list) will not go to the log file. We // could fix this if it's a hardship. { MMGC_ENTER_RETURN(OUT_OF_MEMORY); { // code coverage/cheap test MMGC_ENTER_SUSPEND; } avmplus::FixedHeapRef instance(mmfx_new(Shell)); instance->parseCommandLine(argc, argv); if (instance->settings.do_log) initializeLogging(instance->settings.numfiles > 0 ? instance->settings.filenames[0] : "AVMLOG"); #ifdef VMCFG_WORKERTHREADS if (instance->settings.numworkers == 1 && instance->settings.numthreads == 1 && instance->settings.repeats == 1) { avmplus::Isolate* isolate = instance->newIsolate(NULL); instance->stateTransition(isolate, avmplus::Isolate::CANSTART); isolate->IncrementRef(); isolate->run(); } else { instance->multiWorker(instance->settings); } #else avmplus::Isolate* isolate = instance->newIsolate(NULL); instance->stateTransition(isolate, avmplus::Isolate::CANSTART); isolate->IncrementRef(); isolate->run(); #endif instance->waitUntilNoIsolates(); // Shell is refcounted now //mmfx_delete(instance); } MMgc::GCHeap::Destroy(); MMgc::GCHeap::EnterLockDestroy(); return 0; } ShellIsolate::ShellIsolate(int32_t desc, int32_t parentDesc, avmplus::Aggregate* aggregate) : Isolate(desc, parentDesc, aggregate) {} void ShellIsolate::copyByteCode(avmplus::ByteArrayObject* ba) { if (ba != NULL) { Isolate::copyByteCode(ba); } else { Shell* shell = static_cast (getAggregate()); int numfiles = shell->settings.numfiles; char** filenames = shell->settings.filenames; m_code.allocate(numfiles); for (int i = 0; i < numfiles; i++) { char* filename = filenames[i]; FileInputStream f(filename); const bool isValid = f.valid() && ((uint64_t)f.length() < UINT32_T_MAX); //currently we cannot read files > 4GB if (isValid) { // parse new bytecode //avmplus::ScriptBuffer code = targetCore()->newScriptBuffer((size_t)f.available()); //(f.read(buf), (size_t)f.available(); size_t avail = (size_t)f.available(); m_code.values[i].allocate((int)avail); f.read(m_code.values[i].values, avail); } } } } /*virtual*/ void ShellIsolate::doRun() { MMGC_ENTER_VOID; Shell* aggregate = static_cast (getAggregate()); ShellSettings& settings = aggregate->settings; MMgc::GCConfig gcconfig; gcconfig.mode = settings.gcMode(); MMgc::GC* gc = mmfx_new(MMgc::GC(MMgc::GCHeap::GetGCHeap(), gcconfig)); ShellToplevel* toplevel = NULL; { MMGC_GCENTER(gc); ShellCoreImpl* core = new ShellCoreImpl(gc, settings, false); aggregate->initialize(core, this); avmplus::EnterSafepointManager enterSafepointManager(core); toplevel = core->setup(settings); if (toplevel != NULL) { // setup OK avmplus::Isolate::State state = aggregate->queryState(this); if (state == avmplus::Isolate::RUNNING) { this->evalCodeBlobs(settings.enter_debugger_on_launch); } } aggregate->beforeCoreDeletion(this); gc->Collect(); delete core; } mmfx_delete(gc); if (toplevel == NULL) { aggregate->stateTransition(this, avmplus::Isolate::ABORTED); // TODO // Harshness: kill the program if setup does not go right. Are there // alternatives? Do we even want to contemplate isolate creation // failing? Recall that start() actually throws an exception if the // process limit is exceeded, but that's a controlled resource exhaustion. // In the case of MMGC_ENTER and ShellCore::setup() we should never fail. avmshell::Platform::GetInstance()->exit(1); } aggregate->afterGCDeletion(this); } avmplus::ScriptObject* ShellIsolate::newWorkerObject(avmplus::Toplevel* toplevel) { GCRef workerObject = toplevel->workerClass().staticCast()->constructObject().staticCast(); return workerObject->setIsolate(this); } void ShellIsolate::evalCodeBlobs(bool enter_debugger_on_launch) { // The isolate is now live and operating independently. AvmAssert(avmplus::AvmCore::getActiveCore()->getIsolate() == this); AvmAssert(getAggregate()->queryState(this) == RUNNING); avmplus::AvmCore* core = getAvmCore(); for (int i = 0; i < m_code.length; i++ ) { // execute event loop at the end of the last script if (i == (m_code.length - 1) && !isPrimordial()) core->enterEventLoop = true; // parse new bytecode avmplus::ReadOnlyScriptBufferImpl* codeimpl = new (core->gc) avmplus::ReadOnlyScriptBufferImpl(m_code.values[i].values, m_code.values[i].length); avmplus::ScriptBuffer code(codeimpl); core->evaluateScriptBuffer(code, enter_debugger_on_launch); // release bytes regardless of success/failure m_code.values[i].deallocate(); } m_code.deallocate(); } class PrimordialShellIsolate : public ShellIsolate { public: PrimordialShellIsolate(int32_t desc, int32_t parentDesc, avmplus::Aggregate* aggregate) : ShellIsolate(desc, parentDesc, aggregate) {} virtual void doRun() { Shell* aggregate = static_cast(getAggregate()); ShellSettings& settings = aggregate->settings; MMgc::GCConfig gcconfig; if (settings.gcthreshold != 0) // (zero means use default value already in gcconfig.) gcconfig.collectionThreshold = settings.gcthreshold; gcconfig.exactTracing = settings.exactgc; gcconfig.markstackAllowance = settings.markstackAllowance; gcconfig.drc = settings.drc; gcconfig.mode = settings.gcMode(); gcconfig.validateDRC = settings.drcValidation; MMgc::GC *gc = mmfx_new( MMgc::GC(MMgc::GCHeap::GetGCHeap(), gcconfig) ); MMGC_GCENTER(gc); ShellCore* shell = new ShellCoreImpl(gc, settings, true); this->initialize(shell); avmplus::EnterSafepointManager enterSafepointManager(shell); ShellToplevel* toplevel = shell->setup(settings); // inlined singleWorkerHelper if (toplevel == NULL) // FIXME abort? Platform::GetInstance()->exit(1); #ifdef VMCFG_SELFTEST if (settings.do_selftest) { shell->executeSelftest(settings); aggregate->beforeCoreDeletion(this); delete shell; mmfx_delete( gc ); aggregate->afterGCDeletion(this); return; } #endif #ifdef AVMSHELL_PROJECTOR_SUPPORT if (settings.do_projector) { AvmAssert(settings.programFilename != NULL); int exitCode = shell->executeProjector(settings.programFilename); if (exitCode != 0) Platform::GetInstance()->exit(exitCode); } #endif #ifdef VMCFG_AOT int exitCode = shell->evaluateFile(settings, NULL); aggregate->beforeCoreDeletion(this); delete shell; mmfx_delete( gc ); aggregate->afterGCDeletion(this); if (exitCode != 0) Platform::GetInstance()->exit(exitCode); return; #endif // For -testswf we must have exactly one file if (settings.do_testSWFHasAS3 && settings.numfiles != 1) Platform::GetInstance()->exit(1); // execute each abc file for (int i=0 ; i < settings.numfiles ; i++ ) { int exitCode = shell->evaluateFile(settings, settings.filenames[i]); if (exitCode != 0) Platform::GetInstance()->exit(exitCode); } #ifdef VMCFG_EVAL if (settings.do_repl) Shell::repl(shell); #endif aggregate->requestAggregateExit(); aggregate->beforeCoreDeletion(this); delete shell; mmfx_delete( gc ); aggregate->afterGCDeletion(this); } }; #ifdef VMCFG_WORKERTHREADS //#define LOGGING(x) x #define LOGGING(x) // When we have more than one worker then we spawn as many threads as // called for and create a number of ShellCores corresponding to // the number of workers. Those cores are scheduled on the threads. // The main thread acts as a supervisor, handing out work and scheduling // cores on the threads. struct MultiworkerState; struct CoreNode { CoreNode(ShellCore* core, int id) : core(core) , id(id) , next(NULL) { } ~CoreNode() { // Destruction order matters. MMgc::GC* gc = core->GetGC(); { MMGC_GCENTER(gc); #ifdef _DEBUG core->codeContextThread = VMPI_currentThread(); #endif delete core; } delete gc; } ShellCore * const core; const int id; CoreNode * next; // For the LRU list of available cores }; struct ThreadNode { ThreadNode(MultiworkerState& state, int id) : state(state) , id(id) , pendingWork(false) , corenode(NULL) , filename(NULL) , next(NULL) { } // Called from master, which should not be holding // thread_monitor but may hold global_monitor void startWork(CoreNode* corenode, const char* filename) { SCOPE_LOCK_NAMED(locker, thread_monitor) { this->corenode = corenode; this->filename = filename; this->pendingWork = true; locker.notify(); } } vmbase::WaitNotifyMonitor thread_monitor; MultiworkerState& state; vmbase::VMThread* thread; const int id; bool pendingWork; CoreNode* corenode; // The core running (or about to run, or just finished running) on this thread const char* filename; // The work given to that core ThreadNode * next; // For the LRU list of available threads }; struct MultiworkerState { MultiworkerState(ShellSettings& settings) : settings(settings) , numthreads(settings.numthreads) , numcores(settings.numworkers) , free_threads(NULL) , free_threads_last(NULL) , free_cores(NULL) , free_cores_last(NULL) , num_free_threads(0) { } // Called from the slave threads, which should be holding // neither thread_monitor nor global_monitor. void freeThread(ThreadNode* t) { SCOPE_LOCK_NAMED(locker, global_monitor) { if (free_threads_last != NULL) free_threads_last->next = t; else free_threads = t; free_threads_last = t; if (t->corenode != NULL) { if (free_cores_last != NULL) free_cores_last->next = t->corenode; else free_cores = t->corenode; free_cores_last = t->corenode; } num_free_threads++; locker.notify(); } } // Called from the master thread, which must already hold global_monitor. bool getThreadAndCore(ThreadNode** t, CoreNode** c) { if (free_threads == NULL || free_cores == NULL) return false; *t = free_threads; free_threads = free_threads->next; if (free_threads == NULL) free_threads_last = NULL; (*t)->next = NULL; *c = free_cores; free_cores = free_cores->next; if (free_cores == NULL) free_cores_last = NULL; (*c)->next = NULL; num_free_threads--; return true; } vmbase::WaitNotifyMonitor global_monitor; ShellSettings& settings; int numthreads; int numcores; // Queues of available threads and cores. Protected by // global_monitor, also used to signal availability ThreadNode* free_threads; ThreadNode* free_threads_last; CoreNode* free_cores; CoreNode* free_cores_last; int num_free_threads; }; static void masterThread(MultiworkerState& state); class SlaveThread : public vmbase::VMThread { public: SlaveThread(ThreadNode* tn) : self(tn) {} virtual void run(); private: ThreadNode* self; }; /* static */ void Shell::multiWorker(ShellSettings& settings) { AvmAssert(!settings.do_repl && !settings.do_projector && !settings.do_selftest); MultiworkerState state(settings); const int numthreads(state.numthreads); const int numcores(state.numcores); ThreadNode** const threads(new ThreadNode*[numthreads]); CoreNode** const cores(new CoreNode*[numcores]); MMgc::GCConfig gcconfig; gcconfig.collectionThreshold = settings.gcthreshold; gcconfig.exactTracing = settings.exactgc; gcconfig.markstackAllowance = settings.markstackAllowance; gcconfig.mode = settings.gcMode(); // Going multi-threaded. // Create and start threads. They add themselves to the free list. for ( int i=0 ; i < numthreads ; i++ ) { threads[i] = new ThreadNode(state, i); threads[i]->thread = new SlaveThread(threads[i]); threads[i]->thread->start(); } // Create collectors and cores. // Extra credit: perform setup in parallel on the threads. for ( int i=0 ; i < numcores ; i++ ) { MMgc::GC* gc = new MMgc::GC(MMgc::GCHeap::GetGCHeap(), gcconfig); MMGC_GCENTER(gc); cores[i] = new CoreNode(new ShellCoreImpl(gc, settings, false), i); if (!cores[i]->core->setup(settings)) Platform::GetInstance()->exit(1); } // Add the cores to the free list. for ( int i=numcores-1 ; i >= 0 ; i-- ) { cores[i]->next = state.free_cores; state.free_cores = cores[i]; } state.free_cores_last = cores[numcores-1]; // No locks are held by the master at this point masterThread(state); // No locks are held by the master at this point // Some threads may still be computing, so just wait for them SCOPE_LOCK_NAMED(locker, state.global_monitor) { while (state.num_free_threads < numthreads) locker.wait(); } // Shutdown: feed NULL to all threads to make them exit. for ( int i=0 ; i < numthreads ; i++ ) threads[i]->startWork(NULL,NULL); // Wait for all threads to exit. for ( int i=0 ; i < numthreads ; i++ ) { threads[i]->thread->join(); LOGGING( avmplus::AvmLog("T%d: joined the main thread\n", i); ) } // Single threaded again. for ( int i=0 ; i < numthreads ; i++ ) { delete threads[i]->thread; delete threads[i]; } for ( int i=0 ; i < numcores ; i++ ) delete cores[i]; delete [] threads; delete [] cores; } static void masterThread(MultiworkerState& state) { const int numfiles(state.settings.numfiles); const int repeats(state.settings.repeats); char** const filenames(state.settings.filenames); SCOPE_LOCK_NAMED(locker, state.global_monitor) { int r=0; for (bool finish = false; !finish;) { int nextfile = 0; for (;;) { ThreadNode* threadnode; CoreNode* corenode; while (state.getThreadAndCore(&threadnode, &corenode) && !finish) { LOGGING( avmplus::AvmLog("Scheduling %s on T%d with C%d\n", filenames[nextfile], threadnode->id, corenode->id); ) threadnode->startWork(corenode, filenames[nextfile]); nextfile++; if (nextfile == numfiles) { r++; if (r == repeats) finish = true; else nextfile = 0; } } // The original algorithm was jumping directly to // the end of critical section upon discovering // that it should terminate all the // loops. Consequently, we have to break out of // this for loop to bypass the locker.wait() // statement. if (finish) break; locker.wait(); } } } } void SlaveThread::run() { MMGC_ENTER_VOID; MultiworkerState& state = self->state; for (;;) { // Signal that we're ready for more work: add self to the list of free threads state.freeThread(self); // Obtain more work. We have to hold self->thread_monitor here but the master won't touch corenode and // filename until we register for more work, so they don't have to be copied out of // the thread structure. SCOPE_LOCK_NAMED(locker, self->thread_monitor) { // Don't wait when pendingWork == true, // slave might have been already signalled but it didn't notice because it wasn't waiting yet. while (self->pendingWork == false) locker.wait(); } if (self->corenode == NULL) { LOGGING( avmplus::AvmLog("T%d: Exiting\n", self->id); ) return; } // Perform work LOGGING( avmplus::AvmLog("T%d: Work starting\n", self->id); ) { MMGC_GCENTER(self->corenode->core->GetGC()); #ifdef _DEBUG self->corenode->core->codeContextThread = VMPI_currentThread(); #endif self->corenode->core->evaluateFile(state.settings, self->filename); // Ignore the exit code for now } LOGGING( avmplus::AvmLog("T%d: Work completed\n", self->id); ) SCOPE_LOCK(self->thread_monitor) { self->pendingWork = false; } } return; } #endif // VMCFG_WORKERTHREADS #ifdef VMCFG_EVAL /* static */ void Shell::repl(ShellCore* shellCore) { const int kMaxCommandLine = 1024; char commandLine[kMaxCommandLine]; avmplus::String* input; avmplus::AvmLog("avmplus interactive shell\n" "Type '?' for help\n\n"); for (;;) { bool record_time = false; avmplus::AvmLog("> "); if(Platform::GetInstance()->getUserInput(commandLine, kMaxCommandLine) == NULL) return; commandLine[kMaxCommandLine-1] = 0; if (VMPI_strncmp(commandLine, "?", 1) == 0) { avmplus::AvmLog("Text entered at the prompt is compiled and evaluated unless\n" "it is one of these commands:\n\n" " ? print help\n" " .input collect lines until a line that reads '.end',\n" " then eval the collected lines\n" " .load file load the file (source or compiled)\n" " .quit leave the repl\n" " .time expr evaluate expr and report the time it took.\n\n"); continue; } if (VMPI_strncmp(commandLine, ".load", 5) == 0) { const char* s = commandLine+5; while (*s == ' ' || *s == '\t') s++; // wrong, handles only source code //readFileForEval(NULL, newStringLatin1(s)); // FIXME: implement .load // Small amount of generalization of the code currently in the main loop should // take care of it. avmplus::AvmLog("The .load command is not implemented\n"); continue; } if (VMPI_strncmp(commandLine, ".input", 6) == 0) { input = shellCore->newStringLatin1(""); for (;;) { if(Platform::GetInstance()->getUserInput(commandLine, kMaxCommandLine) == NULL) return; commandLine[kMaxCommandLine-1] = 0; if (VMPI_strncmp(commandLine, ".end", 4) == 0) break; input->appendLatin1(commandLine); } goto compute; } if (VMPI_strncmp(commandLine, ".quit", 5) == 0) { return; } if (VMPI_strncmp(commandLine, ".time", 5) == 0) { record_time = true; input = shellCore->newStringLatin1(commandLine+5); goto compute; } input = shellCore->newStringLatin1(commandLine); compute: shellCore->evaluateString(input, record_time); } } #endif // VMCFG_EVAL // open logfile based on a filename void Shell::initializeLogging(const char* basename) { const char* lastDot = VMPI_strrchr(basename, '.'); if(lastDot) { //filename could contain '/' or '\' as their separator, look for both const char* lastPathSep1 = VMPI_strrchr(basename, '/'); const char* lastPathSep2 = VMPI_strrchr(basename, '\\'); if(lastPathSep1 < lastPathSep2) //determine the right-most lastPathSep1 = lastPathSep2; //if dot is before the separator, the filename does not have an extension if(lastDot < lastPathSep1) lastDot = NULL; } //if no extension then take the entire filename or size_t logFilenameLen = (lastDot == NULL) ? VMPI_strlen(basename) : (lastDot - basename); char* logFilename = new char[logFilenameLen + 5]; // 5 bytes for ".log" + null char VMPI_strncpy(logFilename,basename,logFilenameLen); VMPI_strcpy(logFilename+logFilenameLen,".log"); Platform::GetInstance()->initializeLogging(logFilename); delete [] logFilename; } /* static */ void Shell::parseCommandLine(int argc, char* argv[]) { bool print_version = false; // options filenames -- args settings.programFilename = argv[0]; // How portable / reliable is this? for (int i=1; i < argc ; i++) { const char * const arg = argv[i]; bool mmgcSaysArgIsWrong = false; if (arg[0] == '-') { if (arg[1] == '-' && arg[2] == 0) { if (settings.filenames == NULL) settings.filenames = &argv[i]; settings.numfiles = int(&argv[i] - settings.filenames); i++; settings.arguments = &argv[i]; settings.numargs = argc - i; break; } if (arg[1] == 'D') { if (!VMPI_strcmp(arg+2, "timeout")) { settings.interrupts = true; } else if (!VMPI_strcmp(arg+2, "version")) { print_version = true; } else if (!VMPI_strcmp(arg+2, "nodebugger")) { // allow this option even in non-DEBUGGER builds to make test scripts simpler settings.nodebugger = true; } #if defined(AVMPLUS_IA32) && defined(VMCFG_NANOJIT) else if (!VMPI_strcmp(arg+2, "nosse")) { settings.njconfig.i386_sse2 = false; } else if (!VMPI_strcmp(arg+2, "fixedesp")) { settings.njconfig.i386_fixed_esp = true; } #endif /* AVMPLUS_IA32 */ #if defined(AVMPLUS_ARM) && defined(VMCFG_NANOJIT) else if (!VMPI_strcmp(arg+2, "arm_arch") && i+1 < argc ) { settings.njconfig.arm_arch = (uint8_t)VMPI_strtol(argv[++i], 0, 10); } else if (!VMPI_strcmp(arg+2, "arm_vfp")) { settings.njconfig.arm_vfp = true; } #endif /* AVMPLUS_IA32 */ #ifdef VMCFG_VERIFYALL else if (!VMPI_strcmp(arg+2, "verifyall")) { settings.verifyall = true; settings.verifyquiet = false; } else if (!VMPI_strcmp(arg+2, "verifyonly")) { settings.verifyall = true; settings.verifyonly = true; settings.verifyquiet = false; } else if (!VMPI_strcmp(arg+2, "verifyquiet")) { settings.verifyall = true; settings.verifyonly = true; settings.verifyquiet = true; } #endif /* VMCFG_VERIFYALL */ else if (!VMPI_strcmp(arg+2, "greedy")) { settings.greedy = true; } else if (!VMPI_strcmp(arg+2, "nogc")) { settings.nogc = true; } else if (!VMPI_strcmp(arg+2, "noincgc")) { settings.incremental = false; } else if (!VMPI_strcmp(arg+2, "drcvalidation")) { settings.drcValidation = true; } #ifdef VMCFG_SELECTABLE_EXACT_TRACING else if (!VMPI_strcmp(arg+2, "noexactgc")) { settings.exactgc = false; } #endif else if (!VMPI_strcmp(arg+2, "nodrc")) { settings.drc = false; } else if (!VMPI_strcmp(arg+2, "nofixedcheck")) { settings.fixedcheck = false; } else if (!VMPI_strcmp(arg+2, "gcthreshold") && i+1 < argc) { settings.gcthreshold = VMPI_strtol(argv[++i], 0, 10); } #if defined(DEBUGGER) && !defined(VMCFG_DEBUGGER_STUB) else if (!VMPI_strcmp(arg+2, "astrace") && i+1 < argc ) { settings.astrace_console = VMPI_strtol(argv[++i], 0, 10); } else if (!VMPI_strcmp(arg+2, "language") && i+1 < argc ) { settings.langID=-1; for (int j=0;j 2) { avmplus::AvmLog("Bad value to -prof: %s", arg + 6); usage(); } settings.jitprof_level = level; } #endif /* VMCFG_NANOJIT */ else if (MMgc::GCHeap::GetGCHeap()->Config().IsGCOptionWithParam(arg) && i+1 < argc ) { const char *val = argv[++i]; if (MMgc::GCHeap::GetGCHeap()->Config().ParseAndApplyOption(arg, mmgcSaysArgIsWrong, val)) { if (mmgcSaysArgIsWrong) { avmplus::AvmLog("Invalid GC option: %s %s\n", arg, val); usage(); } // otherwise, option was implicitly applied during the // ParseAndApplyOption invocation in the test above. } else { AvmAssertMsg(false, "IsGCOptionWithParam inconsistent with ParseAndApplyOption."); } } else if (MMgc::GCHeap::GetGCHeap()->Config().ParseAndApplyOption(arg, mmgcSaysArgIsWrong)) { if (mmgcSaysArgIsWrong) { avmplus::AvmLog("Invalid GC option: %s\n", arg); usage(); } } else if (!VMPI_strcmp(arg, "-stack") && i+1 < argc ) { unsigned stack; int nchar; const char* val = argv[++i]; if (VMPI_sscanf(val, "%u%n", &stack, &nchar) == 1 && size_t(nchar) == VMPI_strlen(val) && stack > avmshell::kStackMargin) { settings.stackSize = uint32_t(stack); } else { avmplus::AvmLog("Bad argument to -stack\n"); usage(); } } #ifdef MMGC_MARKSTACK_ALLOWANCE else if (!VMPI_strcmp(arg, "-gcstack") && i+1 < argc ) { int stack; int nchar; const char* val = argv[++i]; if (VMPI_sscanf(val, "%d%n", &stack, &nchar) == 1 && size_t(nchar) == VMPI_strlen(val) && stack >= 0) { settings.markstackAllowance = int32_t(stack); } else { avmplus::AvmLog("Bad argument to -gcstack\n"); usage(); } } #endif else if (!VMPI_strcmp(arg, "-log")) { settings.do_log = true; } #ifdef VMCFG_EVAL else if (!VMPI_strcmp(arg, "-repl")) { settings.do_repl = true; } #endif /* VMCFG_EVAL */ #ifdef VMCFG_WORKERTHREADS else if (!VMPI_strcmp(arg, "-workers") && i+1 < argc ) { const char *val = argv[++i]; int nchar; if (val == NULL) val = ""; if (VMPI_sscanf(val, "%d,%d,%d%n", &settings.numworkers, &settings.numthreads, &settings.repeats, &nchar) != 3) if (VMPI_sscanf(val, "%d,%d%n", &settings.numworkers, &settings.numthreads, &nchar) != 2) { avmplus::AvmLog("Bad value to -workers: %s\n", val); usage(); } if (settings.numthreads < 1 || settings.numworkers < settings.numthreads || settings.repeats < 1 || size_t(nchar) != VMPI_strlen(val)) { avmplus::AvmLog("Bad value to -workers: %s\n", val); usage(); } } #endif // VMCFG_WORKERTHREADS #ifdef AVMPLUS_WIN32 else if (!VMPI_strcmp(arg, "-error")) { show_error = true; #ifndef UNDER_CE SetErrorMode(0); // set to default #endif } #endif // AVMPLUS_WIN32 #ifdef DEBUGGER else if (!VMPI_strcmp(arg, "-d")) { settings.enter_debugger_on_launch = true; } #endif /* DEBUGGER */ else if (!VMPI_strcmp(arg, "-api") && i+1 < argc) { if (!avmplus::AvmCore::parseApiVersion(argv[i+1], settings.apiVersion, settings.apiVersionSeries)) { avmplus::AvmLog("Unknown api version'%s'\n", argv[i+1]); usage(); } i++; } else if (VMPI_strcmp(arg, "-swfversion") == 0 && i+1 < argc) { int j = avmplus::BugCompatibility::VersionCount; unsigned swfVersion; int nchar; const char* val = argv[++i]; if (VMPI_sscanf(val, "%u%n", &swfVersion, &nchar) == 1 && size_t(nchar) == VMPI_strlen(val)) { for (j = 0; j < avmplus::BugCompatibility::VersionCount; ++j) { if (avmplus::BugCompatibility::kNames[j] == swfVersion) { settings.swfVersion = (avmplus::BugCompatibility::Version)j; break; } } } if (j == avmplus::BugCompatibility::VersionCount) { avmplus::AvmLog("Unrecognized -swfversion version %s\n", val); usage(); } } else { // Unrecognized command line option avmplus::AvmLog("Unrecognized option %s\n", arg); usage(); } } else { if (settings.filenames == NULL) settings.filenames = &argv[i]; } } if (settings.filenames == NULL) settings.filenames = &argv[argc]; if (settings.numfiles == -1) settings.numfiles = int(&argv[argc] - settings.filenames); if (settings.arguments == NULL) { settings.arguments = &argv[argc]; settings.numargs = 0; } AvmAssert(settings.filenames != NULL && settings.numfiles != -1); AvmAssert(settings.arguments != NULL && settings.numargs != -1); if (print_version) { avmplus::AvmLog("shell " AVMPLUS_VERSION_USER " " AVMPLUS_BIN_TYPE ); if (RUNNING_ON_VALGRIND) avmplus::AvmLog("-valgrind"); avmplus::AvmLog(" build " AVMPLUS_BUILD_CODE "\n"); #ifdef AVMPLUS_DESC_STRING if (VMPI_strcmp(AVMPLUS_DESC_STRING, "")) { avmplus::AvmLog("Description: " AVMPLUS_DESC_STRING "\n"); } #endif avmplus::AvmLog("features %s\n", avmfeatures); Platform::GetInstance()->exit(1); } // Vetting the options #ifdef AVMSHELL_PROJECTOR_SUPPORT if (settings.programFilename != NULL && ShellCore::isValidProjectorFile(settings.programFilename)) { if (settings.do_selftest || settings.do_repl || settings.numfiles > 0) { avmplus::AvmLog("Projector files can't be used with -repl, -Dselftest, or program file arguments.\n"); usage(); } if (settings.numthreads > 1 || settings.numworkers > 1) { avmplus::AvmLog("A projector requires exactly one worker on one thread.\n"); usage(); } settings.do_projector = 1; return; } #endif #ifndef VMCFG_AOT if (settings.numfiles == 0) { // no files, so we need something more if (!settings.do_selftest && !settings.do_repl) { avmplus::AvmLog("You must provide input files, -repl, or -Dselftest, or the executable must be a projector file.\n"); usage(); } } #endif #ifdef VMCFG_EVAL if (settings.do_repl) { if (settings.numthreads > 1 || settings.numworkers > 1) { avmplus::AvmLog("The REPL requires exactly one worker on one thread.\n"); usage(); } } #endif #ifdef VMCFG_SELFTEST if (settings.do_selftest) { // Presumably we'd want to use the selftest harness to test multiple workers eventually. if (settings.numthreads > 1 || settings.numworkers > 1 || settings.numfiles > 0) { avmplus::AvmLog("The selftest harness requires exactly one worker on one thread, and no input files.\n"); usage(); } } #endif } // Does not return /*static*/ void Shell::usage() { avmplus::AvmLog("avmplus shell " AVMPLUS_VERSION_USER " " AVMPLUS_BIN_TYPE " build " AVMPLUS_BUILD_CODE "\n\n"); #ifdef AVMPLUS_DESC_STRING if (VMPI_strcmp(AVMPLUS_DESC_STRING, "")) { avmplus::AvmLog("Description: " AVMPLUS_DESC_STRING "\n\n"); } #endif avmplus::AvmLog("usage: avmplus\n"); #ifdef DEBUGGER avmplus::AvmLog(" [-d] enter debugger on start\n"); #endif avmplus::AvmLog(" [-memstats] generate statistics on memory usage\n"); avmplus::AvmLog(" [-memstats-verbose]\n" " generate more statistics on memory usage\n"); avmplus::AvmLog(" [-memlimit d] limit the heap size to d pages\n"); avmplus::AvmLog(" [-eagersweep] sweep the heap synchronously at the end of GC;\n" " improves usage statistics.\n"); #ifdef MMGC_POLICY_PROFILING avmplus::AvmLog(" [-gcbehavior] summarize GC behavior and policy, after every gc\n"); avmplus::AvmLog(" [-gcsummary] summarize GC behavior and policy, at end only\n"); #endif avmplus::AvmLog(" [-load L,B, ...\n" " GC load factor L up to a post-GC heap size of B megabytes.\n" " Up to %d pairs can be accommodated, the limit for the last pair\n" " will be ignored and can be omitted\n", int(MMgc::GCHeapConfig::kNumLoadFactors)); avmplus::AvmLog(" [-loadCeiling X] GC load multiplier ceiling (default 1.0)\n"); avmplus::AvmLog(" [-gcwork G] Max fraction of time (default 0.25) we're willing to spend in GC\n"); avmplus::AvmLog(" [-stack N] Stack size in bytes (will be honored approximately).\n" " Be aware of the stack margin: %u\n", avmshell::kStackMargin); #ifdef MMGC_MARKSTACK_ALLOWANCE avmplus::AvmLog(" [-gcstack N] Mark stack size allowance (# of segments), for testing.\n"); #endif avmplus::AvmLog(" [-cache_bindings N] size of bindings cache (0 = unlimited)\n"); avmplus::AvmLog(" [-cache_metadata N] size of metadata cache (0 = unlimited)\n"); avmplus::AvmLog(" [-cache_methods N] size of method cache (0 = unlimited)\n"); avmplus::AvmLog(" [-Dgreedy] collect before every allocation\n"); avmplus::AvmLog(" [-Dnogc] don't collect (including DRC)\n"); avmplus::AvmLog(" [-Dnodrc] don't use DRC (only use mark/sweep)\n"); avmplus::AvmLog(" [-Ddrcvalidation] Perform a mark before each Reap to seek out reachable zero count items\n"); #ifdef VMCFG_SELECTABLE_EXACT_TRACING avmplus::AvmLog(" [-Dnoexactgc] disable exact tracing\n"); #endif avmplus::AvmLog(" [-Dnoincgc] don't use incremental collection\n"); avmplus::AvmLog(" [-Dnodebugger] do not initialize the debugger (in DEBUGGER builds)\n"); avmplus::AvmLog(" [-Dgcthreshold N] lower bound on allocation budget, in blocks, between collection completions\n"); avmplus::AvmLog(" [-Dnofixedcheck] don't check FixedMalloc deallocations for correctness (sometimes expensive)\n"); #ifdef DEBUGGER avmplus::AvmLog(" [-Dastrace N] display AS execution information, where N is [1..4]\n"); avmplus::AvmLog(" [-Dlanguage l] localize runtime errors, languages are:\n"); avmplus::AvmLog(" en,de,es,fr,it,ja,ko,zh-CN,zh-TW\n"); #endif #ifdef AVMPLUS_VERBOSE avmplus::AvmLog(" [-Dverbose[=[parse,verify,interp,traits,builtins,memstats,sweep,occupancy,execpolicy" # ifdef VMCFG_NANOJIT ",jit,opt,regs,raw,bytes,lir,lircfg[-bb|-ins]" # endif "]]\n"); avmplus::AvmLog(" With no options, enables extreme! output mode. Otherwise the\n"); avmplus::AvmLog(" options are mostly self-descriptive except for the following: \n"); avmplus::AvmLog(" builtins - includes output from builtin methods\n"); avmplus::AvmLog(" memstats - generate statistics on memory usage \n"); avmplus::AvmLog(" sweep - [memstats] include detailed sweep information \n"); avmplus::AvmLog(" occupancy - [memstats] include occupancy bit graph \n"); avmplus::AvmLog(" execpolicy - shows which execution method (interpretation, compilation) was chosen and why \n"); # ifdef VMCFG_NANOJIT avmplus::AvmLog(" jit - output LIR as it is generated, and final assembly code\n"); avmplus::AvmLog(" lir - [jit] only write out the LIR instructions\n"); avmplus::AvmLog(" opt - [jit] show details about each optimization pass\n"); avmplus::AvmLog(" regs - [jit] show register allocation state after each assembly instruction\n"); avmplus::AvmLog(" raw - [jit] assembly code is displayed in raw (i.e unbuffered bottom-up) fashion. \n"); avmplus::AvmLog(" bytes - [jit] display the byte values of the assembly code. \n"); avmplus::AvmLog(" lircfg - [jit] write a gml representation of the LIR control-flow graph to a file.\n"); avmplus::AvmLog(" lircfg-bb - [jit] same as above, but each vertex is a basic blocks rather than an EBBs.\n"); avmplus::AvmLog(" lircfg-ins - [jit] same as above, but each vertex is an instruction.\n"); # endif avmplus::AvmLog(" Note that ordering matters for options with dependencies. Dependencies \n"); avmplus::AvmLog(" are contained in [ ] For example, 'sweep' requires 'memstats' \n"); avmplus::AvmLog(" [-Dverbose-only [{id|name|%%regex%%}]+ ]\n"); avmplus::AvmLog(" enable verbose output only for the methods identified. Valid only for verify,interp and jit\n"); avmplus::AvmLog(" options. The method identification follows the same syntax as -policy (see below)\n"); avmplus::AvmLog(" e.g. -Dverbose-only \"Function/prime_val.as\\$1:prime,%%global%%\" \n"); #endif #ifdef VMCFG_NANOJIT avmplus::AvmLog(" [-Dinterp] do not generate machine code, interpret instead\n"); avmplus::AvmLog(" [-Ojit] use jit always, never interp (except when the jit fails)\n"); avmplus::AvmLog(" [-Dcheckjitpageflags] check page protection flags on JIT memory allocation (sometimes expensive)\n"); #ifdef VMCFG_HALFMOON avmplus::AvmLog(" [-Dhalfmoon use experimental 'halfmoon' jit (development only)\n"); #endif avmplus::AvmLog(" [-Djitordie] use jit always, and abort when the jit fails\n"); avmplus::AvmLog(" [-Dnocse] disable CSE optimization\n"); avmplus::AvmLog(" [-Dnoinline] disable speculative inlining\n"); avmplus::AvmLog(" [-jitharden] enable jit hardening techniques\n"); avmplus::AvmLog(" [-osr=T] enable OSR with invocation threshold T; disable with -osr=0; default is -osr=%d\n", avmplus::AvmCore::osr_threshold_default); avmplus::AvmLog(" [-prof=L] enable jit profile level L; default 0=disabled; 1=function ranges, 2=functions+native asm)\n"); #ifdef AVMPLUS_IA32 avmplus::AvmLog(" [-Dnosse] use FPU stack instead of SSE2 instructions\n"); avmplus::AvmLog(" [-Dfixedesp] pre-decrement stack for all needed call usage upon method entry\n"); #endif #ifdef AVMPLUS_ARM avmplus::AvmLog(" [-Darm_arch N] nanojit assumes ARMvN architecture (default=5)\n"); avmplus::AvmLog(" [-Darm_vfp] nanojit uses VFP rather than SoftFloat\n"); #endif #ifdef VMCFG_COMPILEPOLICY avmplus::AvmLog(" [-policy [{interp|jit}={id|name|%%regex%%}]+ ]\n"); avmplus::AvmLog(" override the default policy using method identifiers (see -Dverbose=execpolicy,builtins)\n"); avmplus::AvmLog(" and/or method names and/or regular expressions. ids can be specified either\n"); avmplus::AvmLog(" singly or as a range, while names will attempt to match exactly. regex follows\n"); avmplus::AvmLog(" perl regular expression syntax. Note: rules are *not* checked for consistency\n"); avmplus::AvmLog(" e.g. -Dverbose=execpolicy,builtins -policy \"jit=%%(avmshell)|(global)%%,interp=Function/prime\",jit=-3,interp=5-9,jit=42\n"); #endif /* VMCFG_COMPILEPOLICY */ #endif #ifdef VMCFG_VERIFYALL avmplus::AvmLog(" [-Dverifyall] verify greedily instead of lazily\n"); avmplus::AvmLog(" [-Dverifyonly] verify greedily and don't execute anything\n"); avmplus::AvmLog(" [-Dverifyquiet] verify greedily and don't execute anything w/o re-queue msgs\n"); #endif #ifdef VMCFG_SELFTEST avmplus::AvmLog(" [-Dselftest[=component,category,test]]\n"); avmplus::AvmLog(" run selftests\n"); #endif avmplus::AvmLog(" [-Dtimeout] enforce maximum 15 seconds execution\n"); avmplus::AvmLog(" [-Dversion] print the version and the list of compiled-in features and then exit\n"); #ifdef AVMPLUS_WIN32 avmplus::AvmLog(" [-error] crash opens debug dialog, instead of dumping\n"); #endif #ifdef VMCFG_EVAL avmplus::AvmLog(" [-repl] read-eval-print mode\n"); #endif #ifdef VMCFG_WORKERTHREADS avmplus::AvmLog(" [-workers W,T[,R]]\n"); avmplus::AvmLog(" Spawn W worker VMs on T threads where W >= T and T >= 1.\n"); avmplus::AvmLog(" The files provided are handed off to the workers in the order given,\n"); avmplus::AvmLog(" as workers become available, and these workers are scheduled onto threads\n"); avmplus::AvmLog(" in a deterministic order that prevents them from having affinity to a thread.\n"); avmplus::AvmLog(" To test this functionality you want many more files than workers and many more\n"); avmplus::AvmLog(" workers than threads, and at least two threads.\n"); avmplus::AvmLog(" If R > 0 is provided then it is the number of times the list of files is repeated.\n"); #endif avmplus::AvmLog(" [-swfHasAS3] Exit with code 0 if the single file argument is a swf that contains a DoABC or DoABC2 tag,\n"); avmplus::AvmLog(" otherwise exit with code 1. Do not execute or verify anything.\n"); avmplus::AvmLog(" [-swfversion version]\n"); avmplus::AvmLog(" Run with a given bug-compatibility version in use by default.\n"); avmplus::AvmLog(" (This can be overridden on a per-ABC basis by embedded metadata.)\n"); avmplus::AvmLog(" Legal versions are:\n"); for (int j = 0; j < avmplus::BugCompatibility::VersionCount; ++j) { avmplus::AvmLog(" %d\n",avmplus::BugCompatibility::kNames[j]); } avmplus::AvmLog(" [-log]\n"); avmplus::AvmLog(" [-api N] execute ABCs as version N (see api-versions.h)\n"); avmplus::AvmLog(" [filename.{abc,swf} ...\n"); avmplus::AvmLog(" [-- application argument ...]\n"); Platform::GetInstance()->exit(1); } } namespace avmplus { Isolate* Isolate::newIsolate(int32_t desc, int32_t parentDesc, Aggregate* aggregate) { if (parentDesc == Isolate::INVALID_DESC) { return mmfx_new(avmshell::PrimordialShellIsolate(desc, parentDesc, aggregate)); } else { return mmfx_new(avmshell::ShellIsolate(desc, parentDesc, aggregate)); } } /*static*/ bool MutexClass::getMutexSupported (GCRef toplevel) { AvmAssert(toplevel != NULL); return toplevel->core()->getIsolate() != NULL;; } }