# HG changeset patch # Parent b857905d82b45456853866330c98b372b14f20db # User Nicholas D. Matsakis Permit multiple ion compilation modes. This implies the possibility of multiple ion scripts per JSScript. * * * Add abstractions for cases that handle all comp. modes at once. * * * split out separate fields for seq, par * * * Convert Invalidate() to assert that it is being run in sequential mode. diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -8,16 +8,17 @@ #include "CodeGenerator.h" #include "IonLinker.h" #include "IonSpewer.h" #include "MIRGenerator.h" #include "shared/CodeGenerator-shared-inl.h" #include "jsnum.h" #include "jsmath.h" #include "jsinterpinlines.h" +#include "ExecutionModeInlines.h" #include "vm/StringObject-inl.h" using namespace js; using namespace js::ion; using mozilla::DebugOnly; @@ -793,16 +794,27 @@ CodeGenerator::emitCallInvokeFunction(LI if (!callVM(InvokeFunctionInfo, call)) return false; // Un-nestle %esp from the argument vector. No prefix was pushed. masm.reserveStack(unusedStack); return true; } +static inline int32_t ionOffset(ExecutionMode executionMode) +{ + switch (executionMode) { + case SequentialExecution: return offsetof(JSScript, ion); + case ParallelExecution: return offsetof(JSScript, parallelIon); + } + + JS_ASSERT(false); + return offsetof(JSScript, ion); +} + bool CodeGenerator::visitCallGeneric(LCallGeneric *call) { Register calleereg = ToRegister(call->getFunction()); Register objreg = ToRegister(call->getTempObject()); Register nargsreg = ToRegister(call->getNargsReg()); uint32 unusedStack = StackOffsetOfPassedArg(call->argslot()); Label invoke, thunk, makeCall, end; @@ -826,17 +838,18 @@ CodeGenerator::visitCallGeneric(LCallGen if (!bailoutIf(Assembler::NotEqual, call->snapshot())) return false; // Guard that calleereg is a non-native function: masm.branchIfFunctionIsNative(calleereg, &invoke); // Knowing that calleereg is a non-native function, load the JSScript. masm.movePtr(Address(calleereg, offsetof(JSFunction, u.i.script_)), objreg); - masm.movePtr(Address(objreg, offsetof(JSScript, ion)), objreg); + ExecutionMode executionMode = gen->info().executionMode(); + masm.movePtr(Address(objreg, ionOffset(executionMode)), objreg); // Guard that the IonScript has been compiled. masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &invoke); // Nestle the StackPointer up to the argument vector. masm.freeStack(unusedStack); // Construct the IonFramePrefix. @@ -884,48 +897,51 @@ CodeGenerator::visitCallGeneric(LCallGen masm.bind(&end); dropArguments(call->numStackArgs() + 1); return true; } bool CodeGenerator::visitCallKnown(LCallKnown *call) { + JSContext *cx = GetIonContext()->cx; Register calleereg = ToRegister(call->getFunction()); Register objreg = ToRegister(call->getTempObject()); uint32 unusedStack = StackOffsetOfPassedArg(call->argslot()); JSFunction *target = call->getSingleTarget(); Label end, invoke; // Native single targets are handled by LCallNative. JS_ASSERT(!target->isNative()); // Missing arguments must have been explicitly appended by the IonBuilder. JS_ASSERT(target->nargs <= call->numStackArgs()); masm.checkStackAlignment(); // If the function is known to be uncompilable, only emit the call to InvokeFunction. - if (target->script()->ion == ION_DISABLED_SCRIPT) { + ExecutionMode executionMode = gen->info().executionMode(); + RootedScript targetScript(cx, target->script()); + if (GetIonScript(targetScript, executionMode) == ION_DISABLED_SCRIPT) { if (!emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack)) return false; if (call->mir()->isConstructing()) { Label notPrimitive; masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, ¬Primitive); masm.loadValue(Address(StackPointer, unusedStack), JSReturnOperand); masm.bind(¬Primitive); } dropArguments(call->numStackArgs() + 1); return true; } // Knowing that calleereg is a non-native function, load the JSScript. masm.movePtr(Address(calleereg, offsetof(JSFunction, u.i.script_)), objreg); - masm.movePtr(Address(objreg, offsetof(JSScript, ion)), objreg); + masm.movePtr(Address(objreg, ionOffset(executionMode)), objreg); // Guard that the IonScript has been compiled. masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &invoke); // Load the start of the target IonCode. masm.movePtr(Address(objreg, IonScript::offsetOfMethod()), objreg); masm.movePtr(Address(objreg, IonCode::offsetOfCode()), objreg); @@ -1083,16 +1099,18 @@ CodeGenerator::emitPopArguments(LApplyAr { // Pop |this| and Arguments. masm.freeStack(extraStackSpace); } bool CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric *apply) { + JSContext *cx = GetIonContext()->cx; + // Holds the function object. Register calleereg = ToRegister(apply->getFunction()); // Temporary register for modifying the function object. Register objreg = ToRegister(apply->getTempObject()); Register copyreg = ToRegister(apply->getTempCopy()); // Holds the function nargs. Initially undefined. @@ -1107,39 +1125,40 @@ CodeGenerator::visitApplyArgsGeneric(LAp } // Copy the arguments of the current function. emitPushArguments(apply, copyreg); masm.checkStackAlignment(); // If the function is known to be uncompilable, only emit the call to InvokeFunction. - if (apply->hasSingleTarget() && - (!apply->getSingleTarget()->isInterpreted() || - apply->getSingleTarget()->script()->ion == ION_DISABLED_SCRIPT)) - { - if (!emitCallInvokeFunction(apply, copyreg)) - return false; - emitPopArguments(apply, copyreg); - return true; + ExecutionMode executionMode = gen->info().executionMode(); + if (apply->hasSingleTarget()) { + RootedFunction target(cx, apply->getSingleTarget()); + if (!CanIonCompile(cx, target, executionMode)) { + if (!emitCallInvokeFunction(apply, copyreg)) + return false; + emitPopArguments(apply, copyreg); + return true; + } } Label end, invoke; // Guard that calleereg is a non-native function: if (!apply->hasSingleTarget()) { masm.branchIfFunctionIsNative(calleereg, &invoke); } else { // Native single targets are handled by LCallNative. JS_ASSERT(!apply->getSingleTarget()->isNative()); } // Knowing that calleereg is a non-native function, load the JSScript. masm.movePtr(Address(calleereg, offsetof(JSFunction, u.i.script_)), objreg); - masm.movePtr(Address(objreg, offsetof(JSScript, ion)), objreg); + masm.movePtr(Address(objreg, ionOffset(executionMode)), objreg); // Guard that the IonScript has been compiled. masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &invoke); // Call with an Ion frame or a rectifier frame. { // Create the frame descriptor. unsigned pushed = masm.framePushed(); @@ -2980,77 +2999,81 @@ CodeGenerator::generate() IonCode *code = linker.newCode(cx); if (!code) return false; // We encode safepoints after the OSI-point offsets have been determined. encodeSafepoints(); RootedScript script(cx, gen->info().script()); - JS_ASSERT(!script->hasIonScript()); + ExecutionMode executionMode = gen->info().executionMode(); + JS_ASSERT(!HasIonScript(script, executionMode)); uint32 scriptFrameSize = frameClass_ == FrameSizeClass::None() ? frameDepth_ : FrameSizeClass::FromDepth(frameDepth_).frameSize(); // Check to make sure we didn't have a mid-build invalidation. If so, we // will trickle to ion::Compile() and return Method_Skipped. if (cx->compartment->types.compiledInfo.compilerOutput(cx)->isInvalidated()) return true; - script->ion = IonScript::New(cx, slots, scriptFrameSize, snapshots_.size(), - bailouts_.length(), graph.numConstants(), - safepointIndices_.length(), osiIndices_.length(), - cacheList_.length(), barrierOffsets_.length(), - safepoints_.size(), graph.mir().numScripts()); - if (!script->ion) + IonScript *ionScript = + IonScript::New(cx, slots, scriptFrameSize, snapshots_.size(), + bailouts_.length(), graph.numConstants(), + safepointIndices_.length(), osiIndices_.length(), + cacheList_.length(), barrierOffsets_.length(), + safepoints_.size(), graph.mir().numScripts()); + SetIonScript(script, executionMode, ionScript); + + if (!ionScript) return false; invalidateEpilogueData_.fixup(&masm); Assembler::patchDataWithValueCheck(CodeLocationLabel(code, invalidateEpilogueData_), - ImmWord(uintptr_t(script->ion)), + ImmWord(uintptr_t(ionScript)), ImmWord(uintptr_t(-1))); IonSpew(IonSpew_Codegen, "Created IonScript %p (raw %p)", - (void *) script->ion, (void *) code->raw()); - - script->ion->setInvalidationEpilogueDataOffset(invalidateEpilogueData_.offset()); - script->ion->setOsrPc(gen->info().osrPc()); - script->ion->setOsrEntryOffset(getOsrEntryOffset()); + (void *) ionScript, (void *) code->raw()); + + ionScript->setInvalidationEpilogueDataOffset(invalidateEpilogueData_.offset()); + ionScript->setOsrPc(gen->info().osrPc()); + ionScript->setOsrEntryOffset(getOsrEntryOffset()); ptrdiff_t real_invalidate = masm.actualOffset(invalidate_.offset()); - script->ion->setInvalidationEpilogueOffset(real_invalidate); - - script->ion->setMethod(code); - script->ion->setDeoptTable(deoptTable_); + ionScript->setInvalidationEpilogueOffset(real_invalidate); + + ionScript->setMethod(code); + ionScript->setDeoptTable(deoptTable_); if (snapshots_.size()) - script->ion->copySnapshots(&snapshots_); + ionScript->copySnapshots(&snapshots_); if (bailouts_.length()) - script->ion->copyBailoutTable(&bailouts_[0]); + ionScript->copyBailoutTable(&bailouts_[0]); if (graph.numConstants()) - script->ion->copyConstants(graph.constantPool()); + ionScript->copyConstants(graph.constantPool()); if (safepointIndices_.length()) - script->ion->copySafepointIndices(&safepointIndices_[0], masm); + ionScript->copySafepointIndices(&safepointIndices_[0], masm); if (osiIndices_.length()) - script->ion->copyOsiIndices(&osiIndices_[0], masm); + ionScript->copyOsiIndices(&osiIndices_[0], masm); if (cacheList_.length()) - script->ion->copyCacheEntries(&cacheList_[0], masm); + ionScript->copyCacheEntries(&cacheList_[0], masm); if (barrierOffsets_.length()) - script->ion->copyPrebarrierEntries(&barrierOffsets_[0], masm); + ionScript->copyPrebarrierEntries(&barrierOffsets_[0], masm); if (safepoints_.size()) - script->ion->copySafepoints(&safepoints_); + ionScript->copySafepoints(&safepoints_); JS_ASSERT(graph.mir().numScripts() > 0); - script->ion->copyScriptEntries(graph.mir().scripts()); + ionScript->copyScriptEntries(graph.mir().scripts()); linkAbsoluteLabels(); // The correct state for prebarriers is unknown until the end of compilation, // since a GC can occur during code generation. All barriers are emitted // off-by-default, and are toggled on here if necessary. if (cx->compartment->needsBarrier()) - script->ion->toggleBarriers(true); + ionScript->toggleBarriers(true); return true; } // An out-of-line path to convert a boxed int32 to a double. class OutOfLineUnboxDouble : public OutOfLineCodeBase { LUnboxDouble *unboxDouble_; diff --git a/js/src/ion/CompileInfo.h b/js/src/ion/CompileInfo.h --- a/js/src/ion/CompileInfo.h +++ b/js/src/ion/CompileInfo.h @@ -12,22 +12,33 @@ namespace js { namespace ion { inline unsigned CountArgSlots(JSFunction *fun) { return fun ? fun->nargs + 2 : 1; // +2 for |scopeChain| and |this|, or +1 for |scopeChain| } +enum ExecutionMode { + // Normal JavaScript execution + SequentialExecution, + + // JavaScript code to be executed in parallel worker threads, + // e.g. by ParallelArray + ParallelExecution +}; + // Contains information about the compilation source for IR being generated. class CompileInfo { public: - CompileInfo(JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing) - : script_(script), fun_(fun), osrPc_(osrPc), constructing_(constructing) + CompileInfo(JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing, + ExecutionMode executionMode) + : script_(script), fun_(fun), osrPc_(osrPc), constructing_(constructing), + executionMode_(executionMode) { JS_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOP_LOOPENTRY); nslots_ = script->nslots + CountArgSlots(fun); } JSScript *script() const { return script_; } @@ -128,20 +139,29 @@ class CompileInfo uint32 stackSlot(uint32 i) const { return firstStackSlot() + i; } bool hasArguments() { return script()->argumentsHasVarBinding(); } + ExecutionMode executionMode() const { + return executionMode_; + } + + bool isParallelExecution() const { + return executionMode_ == ParallelExecution; + } + private: JSScript *script_; JSFunction *fun_; unsigned nslots_; jsbytecode *osrPc_; bool constructing_; + ExecutionMode executionMode_; }; } // namespace ion } // namespace js #endif diff --git a/js/src/ion/ExecutionModeInlines.h b/js/src/ion/ExecutionModeInlines.h new file mode 100644 --- /dev/null +++ b/js/src/ion/ExecutionModeInlines.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * 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 jsion_compilemode_h__ +#define jsion_compilemode_h__ + +namespace js { +namespace ion { + +static inline bool HasIonScript(JSScript *script, ExecutionMode cmode) +{ + switch (cmode) { + case SequentialExecution: return script->hasIonScript(); + case ParallelExecution: return script->hasParallelIonScript(); + } + JS_NOT_REACHED("No such execution mode"); + return false; +} + +static inline IonScript *GetIonScript(JSScript *script, ExecutionMode cmode) +{ + switch (cmode) { + case SequentialExecution: return script->ion; + case ParallelExecution: return script->parallelIon; + } + JS_NOT_REACHED("No such execution mode"); + return NULL; +} + +static inline void SetIonScript(JSScript *script, ExecutionMode cmode, IonScript *ionScript) +{ + switch (cmode) { + case SequentialExecution: script->ion = ionScript; return; + case ParallelExecution: script->parallelIon = ionScript; return; + } + JS_NOT_REACHED("No such execution mode"); +} + +static inline bool CanIonCompile(HandleScript script, ExecutionMode cmode) +{ + switch (cmode) { + case SequentialExecution: return script->canIonCompile(); + case ParallelExecution: return script->canParallelIonCompile(); + } + JS_NOT_REACHED("No such execution mode"); + return false; +} + +static inline bool CanIonCompile(JSContext *cx, HandleFunction fun, ExecutionMode cmode) +{ + if (!fun->isInterpreted()) + return false; + RootedScript script(cx, fun->script()); + return CanIonCompile(script, cmode); +} + +static inline bool CompilingOffThread(JSScript *script, ExecutionMode cmode) +{ + switch (cmode) { + case SequentialExecution: return script->isIonCompilingOffThread(); + case ParallelExecution: return script->isParallelIonCompilingOffThread(); + } + JS_NOT_REACHED("No such execution mode"); + return false; +} + +static inline bool CompilingOffThread(HandleScript script, ExecutionMode cmode) +{ + switch (cmode) { + case SequentialExecution: return script->isIonCompilingOffThread(); + case ParallelExecution: return script->isParallelIonCompilingOffThread(); + } + JS_NOT_REACHED("No such execution mode"); + return false; +} + +static inline bool Disabled(JSScript *script, ExecutionMode cmode) { + switch (cmode) { + case SequentialExecution: return script->isIonCompilingOffThread(); + case ParallelExecution: return script->isParallelIonCompilingOffThread(); + } + JS_NOT_REACHED("No such execution mode"); + return false; +} + +static inline types::CompilerOutput::Kind CompilerOutputKind(ExecutionMode cmode) +{ + switch (cmode) { + case SequentialExecution: return types::CompilerOutput::Ion; + case ParallelExecution: return types::CompilerOutput::ParallelIon; + } + JS_NOT_REACHED("No such execution mode"); + return types::CompilerOutput::Ion; +} + +} +} + +#endif diff --git a/js/src/ion/Ion.cpp b/js/src/ion/Ion.cpp --- a/js/src/ion/Ion.cpp +++ b/js/src/ion/Ion.cpp @@ -32,16 +32,17 @@ #include "gc/Marking.h" #include "jsgcinlines.h" #include "jsinferinlines.h" #include "jsobjinlines.h" #include "vm/Stack-inl.h" #include "ion/IonFrames-inl.h" #include "ion/CompilerRoot.h" #include "methodjit/Retcon.h" +#include "ExecutionModeInlines.h" #if JS_TRACE_LOGGING #include "TraceLogging.h" #endif using namespace js; using namespace js::ion; @@ -958,16 +959,26 @@ class AutoDestroyAllocator ~AutoDestroyAllocator() { if (alloc) js_delete(alloc); } }; +class SequentialCompileContext { +public: + ExecutionMode executionMode() { + return SequentialExecution; + } + + bool compile(IonBuilder *builder, MIRGraph *graph, + AutoDestroyAllocator &autoDestroy); +}; + void AttachFinishedCompilations(JSContext *cx) { #ifdef JS_THREADSAFE AssertCanGC(); IonCompartment *ion = cx->compartment->ionCompartment(); if (!ion || !cx->runtime->workerThreadState) return; @@ -983,17 +994,18 @@ AttachFinishedCompilations(JSContext *cx IonBuilder *builder = compilations.popCopy(); if (builder->backgroundCompiledLir) { RootedScript script(cx, builder->script()); IonContext ictx(cx, cx->compartment, &builder->temp()); CodeGenerator codegen(builder, *builder->backgroundCompiledLir); - types::AutoEnterCompilation enterCompiler(cx, types::AutoEnterCompilation::Ion); + ExecutionMode executionMode = builder->info().executionMode(); + types::AutoEnterCompilation enterCompiler(cx, CompilerOutputKind(executionMode)); enterCompiler.initExisting(builder->recompileInfo); bool success; { // Release the worker thread lock and root the compiler for GC. AutoTempAllocatorRooter root(cx, &builder->temp()); AutoUnlockWorkerThreadState unlock(cx->runtime); success = codegen.generate(); @@ -1013,18 +1025,20 @@ AttachFinishedCompilations(JSContext *cx } compilations.clear(); #endif } static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12; +template static bool -IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing) +IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing, + CompileContext &compileContext) { AssertCanGC(); #if JS_TRACE_LOGGING AutoTraceLog logger(TraceLogging::defaultLogger(), TraceLogging::ION_COMPILE_START, TraceLogging::ION_COMPILE_STOP, script); #endif @@ -1040,35 +1054,50 @@ IonCompile(JSContext *cx, JSScript *scri return false; IonContext ictx(cx, cx->compartment, temp); if (!cx->compartment->ensureIonCompartmentExists(cx)) return false; MIRGraph *graph = alloc->new_(temp); - CompileInfo *info = alloc->new_(script, fun, osrPc, constructing); + ExecutionMode executionMode = compileContext.executionMode(); + CompileInfo *info = alloc->new_(script, fun, osrPc, constructing, + executionMode); if (!info) return false; types::AutoEnterTypeInference enter(cx, true); TypeInferenceOracle oracle; if (!oracle.init(cx, script)) return false; AutoFlushCache afc("IonCompile"); - types::AutoEnterCompilation enterCompiler(cx, types::AutoEnterCompilation::Ion); + types::AutoEnterCompilation enterCompiler(cx, CompilerOutputKind(executionMode)); enterCompiler.init(script, false, 0); AutoTempAllocatorRooter root(cx, temp); IonBuilder *builder = alloc->new_(cx, temp, graph, &oracle, info); + if (!compileContext.compile(builder, graph, autoDestroy)) { + IonSpew(IonSpew_Abort, "IM Compilation failed."); + return false; + } + + return true; +} + +bool +SequentialCompileContext::compile(IonBuilder *builder, MIRGraph *graph, + AutoDestroyAllocator &autoDestroy) +{ JS_ASSERT(!builder->script()->ion); + JSContext *cx = GetIonContext()->cx; IonSpewNewFunction(graph, builder->script().unsafeGet()); if (!builder->build()) { IonSpew(IonSpew_Abort, "Builder failed to build."); return false; } builder->clearForBackEnd(); @@ -1103,17 +1132,18 @@ IonCompile(JSContext *cx, JSScript *scri IonSpewEndFunction(); return true; } bool TestIonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing) { - if (!IonCompile(cx, script, fun, osrPc, constructing)) { + SequentialCompileContext compileContext; + if (!IonCompile(cx, script, fun, osrPc, constructing, compileContext)) { if (!cx->isExceptionPending()) ForbidCompilation(cx, script); return false; } return true; } static bool @@ -1223,17 +1253,18 @@ Compile(JSContext *cx, JSScript *script, // bumping the use count twice. if (script->getUseCount() < js_IonOptions.usesBeforeCompile) return Method_Skipped; } else { if (script->incUseCount() < js_IonOptions.usesBeforeCompileNoJaeger) return Method_Skipped; } - if (!IonCompile(cx, script, fun, osrPc, constructing)) + SequentialCompileContext compileContext; + if (!IonCompile(cx, script, fun, osrPc, constructing, compileContext)) return Method_CantCompile; // Compilation succeeded, but we invalidated right away. return script->hasIonScript() ? Method_Compiled : Method_Skipped; } } // namespace ion } // namespace js @@ -1719,17 +1750,21 @@ ion::Invalidate(types::TypeCompartment & IonSpew(IonSpew_Invalidate, "Start invalidation."); AutoFlushCache afc ("Invalidate"); // Add an invalidation reference to all invalidated IonScripts to indicate // to the traversal which frames have been invalidated. bool anyInvalidation = false; for (size_t i = 0; i < invalid.length(); i++) { const types::CompilerOutput &co = *invalid[i].compilerOutput(types); - if (co.isIon()) { + switch (co.kind()) { + case types::CompilerOutput::MethodJIT: + break; + case types::CompilerOutput::Ion: + case types::CompilerOutput::ParallelIon: JS_ASSERT(co.isValid()); IonSpew(IonSpew_Invalidate, " Invalidate %s:%u, IonScript %p", co.script->filename, co.script->lineno, co.ion()); // Keep the ion script alive during the invalidation and flag this // ionScript as being invalidated. This increment is removed by the // loop after the calls to InvalidateActivation. co.ion()->incref(); @@ -1745,39 +1780,48 @@ ion::Invalidate(types::TypeCompartment & for (IonActivationIterator iter(fop->runtime()); iter.more(); ++iter) InvalidateActivation(fop, iter.top(), false); // Drop the references added above. If a script was never active, its // IonScript will be immediately destroyed. Otherwise, it will be held live // until its last invalidated frame is destroyed. for (size_t i = 0; i < invalid.length(); i++) { types::CompilerOutput &co = *invalid[i].compilerOutput(types); - if (co.isIon()) { - JS_ASSERT(co.isValid()); - JSScript *script = co.script; - IonScript *ionScript = script->ionScript(); + ExecutionMode executionMode; + switch (co.kind()) { + case types::CompilerOutput::MethodJIT: + continue; + case types::CompilerOutput::Ion: + executionMode = SequentialExecution; + break; + case types::CompilerOutput::ParallelIon: + executionMode = ParallelExecution; + break; + } + JS_ASSERT(co.isValid()); + JSScript *script = co.script; + IonScript *ionScript = GetIonScript(script, executionMode); - JSCompartment *compartment = script->compartment(); - if (compartment->needsBarrier()) { - // We're about to remove edges from the JSScript to gcthings - // embedded in the IonScript. Perform one final trace of the - // IonScript for the incremental GC, as it must know about - // those edges. - IonScript::Trace(compartment->barrierTracer(), ionScript); - } + JSCompartment *compartment = script->compartment(); + if (compartment->needsBarrier()) { + // We're about to remove edges from the JSScript to gcthings + // embedded in the IonScript. Perform one final trace of the + // IonScript for the incremental GC, as it must know about + // those edges. + IonScript::Trace(compartment->barrierTracer(), ionScript); + } - ionScript->decref(fop); - script->ion = NULL; - co.invalidate(); + ionScript->decref(fop); + SetIonScript(script, executionMode, NULL); + co.invalidate(); - // Wait for the scripts to get warm again before doing another - // compile, unless we are recompiling *because* a script got hot. - if (resetUses) - script->resetUseCount(); - } + // Wait for the scripts to get warm again before doing another + // compile, unless we are recompiling *because* a script got hot. + if (resetUses) + script->resetUseCount(); } } void ion::Invalidate(JSContext *cx, const Vector &invalid, bool resetUses) { ion::Invalidate(cx->compartment->types, cx->runtime->defaultFreeOp(), invalid, resetUses); } @@ -1886,8 +1930,47 @@ AutoFlushCache::AutoFlushCache(const cha comp->setFlusher(this); } else { IonSpew(IonSpew_CacheFlush, "<%s DEAD>\n", nonce); } myCompartment_ = comp; } int js::ion::LabelBase::id_count = 0; +void +ion::PurgeCaches(JSScript *script, JSCompartment *c) { + if (script->hasIonScript()) + script->ion->purgeCaches(c); + + if (script->hasParallelIonScript()) + script->ion->purgeCaches(c); +} + +size_t +ion::MemoryUsed(JSScript *script, JSMallocSizeOfFun mallocSizeOf) { + size_t result = 0; + + if (script->hasIonScript()) + result += script->ion->sizeOfIncludingThis(mallocSizeOf); + + if (script->hasParallelIonScript()) + result += script->parallelIon->sizeOfIncludingThis(mallocSizeOf); + + return result; +} + +void +ion::DestroyIonScripts(FreeOp *fop, JSScript *script) { + if (script->hasIonScript()) + ion::IonScript::Destroy(fop, script->ion); + + if (script->hasParallelIonScript()) + ion::IonScript::Destroy(fop, script->parallelIon); +} + +void +ion::TraceIonScripts(JSTracer* trc, JSScript *script) { + if (script->hasIonScript()) + ion::IonScript::Trace(trc, script->ion); + + if (script->hasParallelIonScript()) + ion::IonScript::Trace(trc, script->parallelIon); +} diff --git a/js/src/ion/Ion.h b/js/src/ion/Ion.h --- a/js/src/ion/Ion.h +++ b/js/src/ion/Ion.h @@ -263,13 +263,18 @@ bool TestIonCompile(JSContext *cx, JSScr static inline bool IsEnabled(JSContext *cx) { return cx->hasRunOption(JSOPTION_ION) && cx->typeInferenceEnabled(); } void ForbidCompilation(JSContext *cx, JSScript *script); uint32_t UsesBeforeIonRecompile(JSScript *script, jsbytecode *pc); +void PurgeCaches(JSScript *script, JSCompartment *c); +size_t MemoryUsed(JSScript *script, JSMallocSizeOfFun mallocSizeOf); +void DestroyIonScripts(FreeOp *fop, JSScript *script); +void TraceIonScripts(JSTracer* trc, JSScript *script); + } // namespace ion } // namespace js #endif // jsion_ion_h__ diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -10,16 +10,17 @@ #include "MIRGraph.h" #include "Ion.h" #include "IonAnalysis.h" #include "IonSpewer.h" #include "frontend/BytecodeEmitter.h" #include "jsscriptinlines.h" #include "jstypedarrayinlines.h" +#include "ExecutionModeInlines.h" #ifdef JS_THREADSAFE # include "prthread.h" #endif using namespace js; using namespace js::ion; @@ -199,18 +200,18 @@ IonBuilder::canInlineTarget(JSFunction * } if (target->getParent() != &script_->global()) { IonSpew(IonSpew_Inlining, "Cannot inline due to scope mismatch"); return false; } RootedScript inlineScript(cx, target->script()); - - if (!inlineScript->canIonCompile()) { + ExecutionMode executionMode = info().executionMode(); + if (!CanIonCompile(inlineScript, executionMode)) { IonSpew(IonSpew_Inlining, "Cannot inline due to disable Ion compilation"); return false; } // Allow inlining of recursive calls, but only one level deep. IonBuilder *builder = callerBuilder_; while (builder) { if (builder->script() == inlineScript) { @@ -2824,17 +2825,18 @@ IonBuilder::jsop_call_inline(HandleFunct return false; for (int32 i = argc; i >= 0; i--) argv[i] = current->pop(); // Compilation information is allocated for the duration of the current tempLifoAlloc // lifetime. RootedScript calleeScript(cx, callee->script()); CompileInfo *info = cx->tempLifoAlloc().new_(calleeScript.get(), callee, - (jsbytecode *)NULL, constructing); + (jsbytecode *)NULL, constructing, + SequentialExecution); if (!info) return false; MIRGraphExits saveExits; AutoAccumulateExits aae(graph(), saveExits); TypeInferenceOracle oracle; if (!oracle.init(cx, calleeScript)) diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -547,18 +547,19 @@ JS_SetTopFrameAnnotation(JSContext *cx, // because we will never EnterIon on a frame with an annotation. fp->setAnnotation(annotation); RawScript script = fp->script().get(nogc); ReleaseAllJITCode(cx->runtime->defaultFreeOp()); // Ensure that we'll never try to compile this again. - JS_ASSERT(!script->hasIonScript()); + JS_ASSERT(!script->hasAnyIonScript()); script->ion = ION_DISABLED_SCRIPT; + script->parallelIon = ION_DISABLED_SCRIPT; } JS_PUBLIC_API(JSObject *) JS_GetFrameScopeChain(JSContext *cx, JSStackFrame *fpArg) { StackFrame *fp = Valueify(fpArg); JS_ASSERT(cx->stack.space().containsSlow(fp)); AutoCompartment ac(cx, fp->scopeChain()); diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -127,17 +127,17 @@ fun_getProperty(JSContext *cx, HandleObj #ifdef JS_ION AutoAssertNoGC nogc; // If this script hasn't been compiled yet, make sure it will never // be compiled. IonMonkey does not guarantee |f.arguments| can be // fully recovered, so we try to mitigate observing this behavior by // detecting its use early. RawScript script = iter.script().get(nogc); - if (!script->hasIonScript()) + if (!script->hasAnyIonScript()) ion::ForbidCompilation(cx, script); #endif vp.setObject(*argsobj); return true; } #ifdef JS_METHODJIT diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -5870,18 +5870,17 @@ PurgeJITCaches(JSCompartment *c) JSScript *script = i.get(); /* Discard JM caches. */ mjit::PurgeCaches(script); #ifdef JS_ION /* Discard Ion caches. */ - if (script->hasIonScript()) - script->ion->purgeCaches(c); + ion::PurgeCaches(script, c); #endif } #endif } AutoTransplantGC::AutoTransplantGC(JSContext *cx) : runtime(cx->runtime), diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -1992,17 +1992,17 @@ JITCodeHasCheck(HandleScript script, jsb found = true; } if (!found) return false; } } #endif - if (script->hasIonScript()) + if (script->hasAnyIonScript()) return false; return true; } /* * Force recompilation of any jitcode for script at pc, or of any other script * which this script was inlined into. @@ -2021,18 +2021,24 @@ AddPendingRecompile(JSContext *cx, Handl /* * Remind Ion not to save the compile code if generating type * inference information mid-compilation causes an invalidation of the * script being compiled. */ RecompileInfo& info = cx->compartment->types.compiledInfo; if (info.outputIndex != RecompileInfo::NoCompilerRunning) { CompilerOutput *co = info.compilerOutput(cx); - if (co->isIon() && co->script == script) { - co->invalidate(); + switch (co->kind()) { + case CompilerOutput::MethodJIT: + break; + case CompilerOutput::Ion: + case CompilerOutput::ParallelIon: + if (co->script == script) + co->invalidate(); + break; } } /* * When one script is inlined into another the caller listens to state * changes on the callee's script, so trigger these to force recompilation * of any such callers. */ @@ -2412,21 +2418,26 @@ TypeCompartment::processPendingRecompile JS_ASSERT(!pending->empty()); #ifdef JS_METHODJIT mjit::ExpandInlineFrames(compartment()); for (unsigned i = 0; i < pending->length(); i++) { CompilerOutput &co = *(*pending)[i].compilerOutput(*this); - if (co.isJM()) { + switch (co.kind()) { + case CompilerOutput::MethodJIT: JS_ASSERT(co.isValid()); mjit::Recompiler::clearStackReferences(fop, co.script); co.mjit()->destroyChunk(fop, co.chunkIndex); JS_ASSERT(co.script == NULL); + break; + case CompilerOutput::Ion: + case CompilerOutput::ParallelIon: + break; } } # ifdef JS_ION ion::Invalidate(*this, fop, *pending); # endif #endif /* JS_METHODJIT */ @@ -2513,17 +2524,17 @@ TypeCompartment::addPendingRecompile(JSC return; } #ifdef JS_METHODJIT mjit::JITScript *jit = co->script->getJIT(co->constructing, co->barriers); bool hasJITCode = jit && jit->chunkDescriptor(co->chunkIndex).chunk; # if defined(JS_ION) - hasJITCode |= !!co->script->hasIonScript(); + hasJITCode |= !!co->script->hasAnyIonScript(); # endif if (!hasJITCode) { /* Scripts which haven't been compiled yet don't need to be recompiled. */ return; } #endif @@ -2580,16 +2591,19 @@ TypeCompartment::addPendingRecompile(JSC } } # ifdef JS_ION CancelOffThreadIonCompile(cx->compartment, script); if (script->hasIonScript()) addPendingRecompile(cx, script->ionScript()->recompileInfo()); + + if (script->hasParallelIonScript()) + addPendingRecompile(cx, script->parallelIonScript()->recompileInfo()); # endif #endif } void TypeCompartment::monitorBytecode(JSContext *cx, HandleScript script, uint32_t offset, bool returnOnly) { diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -1208,27 +1208,37 @@ typedef HashMap(kindInt); } + void setKind(Kind k) { kindInt = k; } mjit::JITScript *mjit() const; ion::IonScript *ion() const; bool isValid() const; void setPendingRecompilation() { pendingRecompilation = true; diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -83,79 +83,95 @@ namespace types { ///////////////////////////////////////////////////////////////////// // CompilerOutput & RecompileInfo ///////////////////////////////////////////////////////////////////// inline CompilerOutput::CompilerOutput() : script(NULL), - isIonFlag(false), + kindInt(MethodJIT), constructing(false), barriers(false), chunkIndex(false) { } inline mjit::JITScript * CompilerOutput::mjit() const { #ifdef JS_METHODJIT - JS_ASSERT(isJM() && isValid()); + JS_ASSERT(kind() == MethodJIT && isValid()); return script->getJIT(constructing, barriers); #else return NULL; #endif } inline ion::IonScript * CompilerOutput::ion() const { #ifdef JS_ION - JS_ASSERT(isIon() && isValid()); - return script->ionScript(); -#else + JS_ASSERT(kind() != MethodJIT && isValid()); + switch (kind()) { + case MethodJIT: break; + case Ion: return script->ionScript(); + case ParallelIon: return script->parallelIonScript(); + } +#endif + JS_NOT_REACHED("Invalid kind of CompilerOutput"); return NULL; -#endif } inline bool CompilerOutput::isValid() const { if (!script) return false; #if defined(DEBUG) && (defined(JS_METHODJIT) || defined(JS_ION)) TypeCompartment &types = script->compartment()->types; #endif + switch (kind()) { + case MethodJIT: { #ifdef JS_METHODJIT - if (isJM()) { mjit::JITScript *jit = script->getJIT(constructing, barriers); if (!jit) return false; mjit::JITChunk *chunk = jit->chunkDescriptor(chunkIndex).chunk; if (!chunk) return false; JS_ASSERT(this == chunk->recompileInfo.compilerOutput(types)); return true; - } #endif + } + case Ion: #ifdef JS_ION - if (isIon()) { if (script->hasIonScript()) { JS_ASSERT(this == script->ion->recompileInfo().compilerOutput(types)); return true; } if (script->isIonCompilingOffThread()) return true; +#endif + return false; + + case ParallelIon: +#ifdef JS_ION + if (script->hasParallelIonScript()) { + JS_ASSERT(this == script->parallelIonScript()->recompileInfo().compilerOutput(types)); + return true; + } + if (script->isParallelIonCompilingOffThread()) + return true; +#endif return false; } -#endif return false; } inline CompilerOutput* RecompileInfo::compilerOutput(TypeCompartment &types) const { return &(*types.constrainedOutputs)[outputIndex]; } @@ -382,37 +398,32 @@ struct AutoEnterTypeInference /* * Structure marking the currently compiled script, for constraints which can * trigger recompilation. */ struct AutoEnterCompilation { JSContext *cx; RecompileInfo &info; + CompilerOutput::Kind kind; - enum Compiler { - JM, - Ion - }; - Compiler mode; - - AutoEnterCompilation(JSContext *cx, Compiler mode) + AutoEnterCompilation(JSContext *cx, CompilerOutput::Kind kind) : cx(cx), info(cx->compartment->types.compiledInfo), - mode(mode) + kind(kind) { JS_ASSERT(cx->compartment->activeAnalysis); JS_ASSERT(info.outputIndex == RecompileInfo::NoCompilerRunning); } bool init(JSScript *script, bool constructing, unsigned chunkIndex) { CompilerOutput co; co.script = script; - co.isIonFlag = (mode == Ion); + co.setKind(kind); co.constructing = constructing; co.barriers = cx->compartment->compileBarriers(); co.chunkIndex = chunkIndex; // This flag is used to prevent adding the current compiled script in // the list of compiler output which should be invalided. This is // necessary because we can run some analysis might discard the script // it-self, which can happen when the monitored value does not reflect diff --git a/js/src/jsinterpinlines.h b/js/src/jsinterpinlines.h --- a/js/src/jsinterpinlines.h +++ b/js/src/jsinterpinlines.h @@ -1002,21 +1002,21 @@ class FastInvokeGuard RootedScript script_; #ifdef JS_ION ion::IonContext ictx_; bool useIon_; #endif public: FastInvokeGuard(JSContext *cx, const Value &fval) - : fun_(cx), - script_(cx) + : fun_(cx) + , script_(cx) #ifdef JS_ION - , ictx_(cx, cx->compartment, NULL), - useIon_(ion::IsEnabled(cx)) + , ictx_(cx, cx->compartment, NULL) + , useIon_(ion::IsEnabled(cx)) #endif { initFunction(fval); } void initFunction(const Value &fval) { if (fval.isObject() && fval.toObject().isFunction()) { JSFunction *fun = fval.toObject().toFunction(); diff --git a/js/src/jsmemorymetrics.cpp b/js/src/jsmemorymetrics.cpp --- a/js/src/jsmemorymetrics.cpp +++ b/js/src/jsmemorymetrics.cpp @@ -15,16 +15,17 @@ #include "jsgc.h" #include "jsobj.h" #include "jsscope.h" #include "jsscript.h" #include "jsobjinlines.h" #include "ion/IonCode.h" +#include "ion/Ion.h" namespace js { size_t MemoryReportingSundriesThreshold() { return 8 * 1024; } @@ -227,18 +228,17 @@ StatsCellCallback(JSRuntime *rt, void *d case JSTRACE_SCRIPT: { JSScript *script = static_cast(thing); cStats->gcHeapScripts += thingSize; cStats->scriptData += script->sizeOfData(rtStats->mallocSizeOf); #ifdef JS_METHODJIT cStats->jaegerData += script->sizeOfJitScripts(rtStats->mallocSizeOf); # ifdef JS_ION - if (script->hasIonScript()) - cStats->ionData += script->ion->sizeOfIncludingThis(rtStats->mallocSizeOf); + cStats->ionData += ion::MemoryUsed(script, rtStats->mallocSizeOf); # endif #endif ScriptSource *ss = script->scriptSource(); SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss); if (!entry) { closure->seenSources.add(entry, ss); // Not much to be done on failure. rtStats->runtime.scriptSources += ss->sizeOfIncludingThis(rtStats->mallocSizeOf); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1901,18 +1901,17 @@ JSScript::finalize(FreeOp *fop) JS_DropPrincipals(fop->runtime(), originPrincipals); if (types) types->destroy(); #ifdef JS_METHODJIT mjit::ReleaseScriptCode(fop, this); # ifdef JS_ION - if (hasIonScript()) - ion::IonScript::Destroy(fop, ion); + ion::DestroyIonScripts(fop, this); # endif #endif destroyScriptCounts(fop); destroyDebugScript(fop); scriptSource_->decref(fop->runtime()); if (data) { @@ -2594,18 +2593,17 @@ JSScript::markChildren(JSTracer *trc) for (unsigned i = 0; i < length; i++) { BreakpointSite *site = debugScript()->breakpoints[i]; if (site && site->trapHandler) MarkValue(trc, &site->trapClosure, "trap closure"); } } #ifdef JS_ION - if (hasIonScript()) - ion::IonScript::Trace(trc, ion); + ion::TraceIonScripts(trc, this); #endif } void JSScript::setArgumentsHasVarBinding() { argsHasVarBinding_ = true; needsArgsAnalysis_ = true; diff --git a/js/src/jsscript.h b/js/src/jsscript.h --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -544,36 +544,60 @@ struct JSScript : public js::gc::Cell * canonical location for the arguments. Note: if a formal is aliased * through the scope chain, then script->formalIsAliased and JSOP_*ARG* * opcodes won't be emitted at all. */ bool argsObjAliasesFormals() const { return needsArgsObj() && !strictModeCode; } - js::ion::IonScript *ion; /* Information attached by Ion */ + bool hasAnyIonScript() const { + return hasIonScript() || hasParallelIonScript(); + } -#if defined(JS_METHODJIT) && JS_BITS_PER_WORD == 32 - void *padding_; -#endif + /* Information attached by Ion: script for sequential mode execution */ + js::ion::IonScript *ion; bool hasIonScript() const { return ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT; } + bool canIonCompile() const { return ion != ION_DISABLED_SCRIPT; } + bool isIonCompilingOffThread() const { return ion == ION_COMPILING_SCRIPT; } + js::ion::IonScript *ionScript() const { JS_ASSERT(hasIonScript()); return ion; } + /* Information attached by Ion: script for parallel mode execution */ + js::ion::IonScript *parallelIon; + + bool hasParallelIonScript() const { + return parallelIon && parallelIon != ION_DISABLED_SCRIPT && parallelIon != ION_COMPILING_SCRIPT; + } + + bool canParallelIonCompile() const { + return parallelIon != ION_DISABLED_SCRIPT; + } + + bool isParallelIonCompilingOffThread() const { + return parallelIon == ION_COMPILING_SCRIPT; + } + + js::ion::IonScript *parallelIonScript() const { + JS_ASSERT(hasParallelIonScript()); + return parallelIon; + } + /* * Original compiled function for the script, if it has a function. * NULL for global and eval scripts. */ JSFunction *function() const { return function_; } void setFunction(JSFunction *fun); JSFlatString *sourceData(JSContext *cx); diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -3,16 +3,17 @@ /* 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 "jsworkers.h" #if JS_ION # include "ion/IonBuilder.h" +# include "ion/ExecutionModeInlines.h" #endif using namespace js; using mozilla::DebugOnly; #ifdef JS_PARALLEL_COMPILATION @@ -289,17 +290,18 @@ WorkerThread::threadLoop() state.unlock(); return; } state.wait(WorkerThreadState::WORKER); } ionBuilder = state.ionWorklist.popCopy(); - JS_ASSERT(ionBuilder->script()->ion == ION_COMPILING_SCRIPT); + ion::ExecutionMode executionMode = ionBuilder->info().executionMode(); + JS_ASSERT(GetIonScript(ionBuilder->script().unsafeGet(), executionMode) == ION_COMPILING_SCRIPT); state.unlock(); { ion::IonContext ictx(NULL, ionBuilder->script()->compartment(), &ionBuilder->temp()); ionBuilder->backgroundCompiledLir = ion::CompileBackEnd(ionBuilder); } diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -534,17 +534,17 @@ mjit::Compiler::performCompilation() #ifdef JS_METHODJIT outerScript->debugMode = debugMode(); #endif JS_ASSERT(cx->compartment->activeInference); { - types::AutoEnterCompilation enter(cx, types::AutoEnterCompilation::JM); + types::AutoEnterCompilation enter(cx, types::CompilerOutput::MethodJIT); if (!enter.init(outerScript, isConstructing, chunkIndex)) { js_ReportOutOfMemory(cx); return Compile_Error; } CHECK_STATUS(checkAnalysis(outerScript)); if (inlining()) CHECK_STATUS(scanInlineCalls(CrossScriptSSA::OUTER_FRAME, 0));