mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 20:17:37 +00:00
Allow compiling scripts off thread with Ion, bug 774253. r=dvander
This commit is contained in:
parent
c096ec1828
commit
6b847aacbd
@ -106,6 +106,7 @@ CPPSRCS = \
|
||||
jsutil.cpp \
|
||||
jswatchpoint.cpp \
|
||||
jsweakmap.cpp \
|
||||
jsworkers.cpp \
|
||||
jswrapper.cpp \
|
||||
jsxml.cpp \
|
||||
prmjtime.cpp \
|
||||
|
@ -2803,7 +2803,7 @@ CodeGenerator::generate()
|
||||
encodeSafepoints();
|
||||
|
||||
JSScript *script = gen->info().script();
|
||||
JS_ASSERT(!script->ion);
|
||||
JS_ASSERT(!script->hasIonScript());
|
||||
|
||||
uint32 scriptFrameSize = frameClass_ == FrameSizeClass::None()
|
||||
? frameDepth_
|
||||
|
@ -35,12 +35,12 @@ class CompilerRoot : public CompilerRootNode
|
||||
public:
|
||||
// Sets the pointer and inserts into root list. The pointer becomes read-only.
|
||||
void setRoot(T root) {
|
||||
JSRuntime *rt = root->compartment()->rt;
|
||||
JS::CompilerRootNode *&rootList = GetIonContext()->temp->rootList();
|
||||
|
||||
JS_ASSERT(!ptr);
|
||||
ptr = root;
|
||||
next = rt->ionCompilerRootList;
|
||||
rt->ionCompilerRootList = this;
|
||||
next = rootList;
|
||||
rootList = this;
|
||||
}
|
||||
|
||||
public:
|
||||
@ -53,24 +53,6 @@ typedef CompilerRoot<JSFunction*> CompilerRootFunction;
|
||||
typedef CompilerRoot<PropertyName*> CompilerRootPropertyName;
|
||||
typedef CompilerRoot<Value> CompilerRootValue;
|
||||
|
||||
// Automatically clears the compiler root list when compilation finishes.
|
||||
class AutoCompilerRoots
|
||||
{
|
||||
JSRuntime *rt_;
|
||||
|
||||
public:
|
||||
AutoCompilerRoots(JSRuntime *rt)
|
||||
: rt_(rt)
|
||||
{
|
||||
JS_ASSERT(rt_->ionCompilerRootList == NULL);
|
||||
}
|
||||
|
||||
~AutoCompilerRoots()
|
||||
{
|
||||
rt_->ionCompilerRootList = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ion
|
||||
} // namespace js
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "RangeAnalysis.h"
|
||||
#include "LinearScan.h"
|
||||
#include "jscompartment.h"
|
||||
#include "jsworkers.h"
|
||||
#include "IonCompartment.h"
|
||||
#include "CodeGenerator.h"
|
||||
|
||||
@ -146,6 +147,29 @@ IonCompartment::initialize(JSContext *cx)
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ion::FinishOffThreadBuilder(IonBuilder *builder)
|
||||
{
|
||||
if (builder->script->isIonCompilingOffThread()) {
|
||||
types::TypeCompartment &types = builder->script->compartment()->types;
|
||||
builder->recompileInfo.compilerOutput(types)->invalidate();
|
||||
builder->script->ion = NULL;
|
||||
}
|
||||
Foreground::delete_(builder->temp().lifoAlloc());
|
||||
}
|
||||
|
||||
static inline void
|
||||
FinishAllOffThreadCompilations(IonCompartment *ion)
|
||||
{
|
||||
OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
|
||||
|
||||
for (size_t i = 0; i < compilations.length(); i++) {
|
||||
IonBuilder *builder = compilations[i];
|
||||
FinishOffThreadBuilder(builder);
|
||||
}
|
||||
compilations.clear();
|
||||
}
|
||||
|
||||
void
|
||||
IonCompartment::mark(JSTracer *trc, JSCompartment *compartment)
|
||||
{
|
||||
@ -179,6 +203,10 @@ IonCompartment::mark(JSTracer *trc, JSCompartment *compartment)
|
||||
|
||||
// functionWrappers_ are not marked because this is a WeakCache of VM
|
||||
// function implementations.
|
||||
|
||||
// Cancel any active or pending off thread compilations.
|
||||
CancelOffThreadIonCompile(compartment, NULL);
|
||||
FinishAllOffThreadCompilations(this);
|
||||
}
|
||||
|
||||
void
|
||||
@ -700,16 +728,16 @@ ion::ToggleBarriers(JSCompartment *comp, bool needs)
|
||||
namespace js {
|
||||
namespace ion {
|
||||
|
||||
static bool
|
||||
BuildMIR(IonBuilder &builder, MIRGraph &graph)
|
||||
bool
|
||||
CompileBackEnd(IonBuilder *builder)
|
||||
{
|
||||
if (!builder.build())
|
||||
return false;
|
||||
IonSpewPass("BuildSSA");
|
||||
// Note: don't call AssertGraphCoherency before SplitCriticalEdges,
|
||||
// the graph is not in RPO at this point.
|
||||
|
||||
if (!SplitCriticalEdges(&builder, graph))
|
||||
MIRGraph &graph = builder->graph();
|
||||
|
||||
if (!SplitCriticalEdges(builder, graph))
|
||||
return false;
|
||||
IonSpewPass("Split Critical Edges");
|
||||
AssertGraphCoherency(graph);
|
||||
@ -813,50 +841,141 @@ BuildMIR(IonBuilder &builder, MIRGraph &graph)
|
||||
IonSpewPass("Bounds Check Elimination");
|
||||
AssertGraphCoherency(graph);
|
||||
|
||||
return true;
|
||||
}
|
||||
LIRGraph *lir = builder->temp().lifoAlloc()->new_<LIRGraph>(graph);
|
||||
if (!lir)
|
||||
return false;
|
||||
|
||||
static bool
|
||||
GenerateCode(IonBuilder &builder, MIRGraph &graph)
|
||||
{
|
||||
LIRGraph lir(graph);
|
||||
LIRGenerator lirgen(&builder, graph, lir);
|
||||
LIRGenerator lirgen(builder, graph, *lir);
|
||||
if (!lirgen.generate())
|
||||
return false;
|
||||
IonSpewPass("Generate LIR");
|
||||
|
||||
if (js_IonOptions.lsra) {
|
||||
LinearScanAllocator regalloc(&lirgen, lir);
|
||||
LinearScanAllocator regalloc(&lirgen, *lir);
|
||||
if (!regalloc.go())
|
||||
return false;
|
||||
IonSpewPass("Allocate Registers", ®alloc);
|
||||
}
|
||||
|
||||
CodeGenerator codegen(&builder, lir);
|
||||
if (!codegen.generate())
|
||||
return false;
|
||||
// No spew: graph not changed.
|
||||
|
||||
builder->lir = lir;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
TestCompiler(IonBuilder &builder, MIRGraph &graph)
|
||||
class AutoDestroyAllocator
|
||||
{
|
||||
IonSpewNewFunction(&graph, builder.script);
|
||||
LifoAlloc *alloc;
|
||||
|
||||
if (!BuildMIR(builder, graph))
|
||||
public:
|
||||
AutoDestroyAllocator(LifoAlloc *alloc) : alloc(alloc) {}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
alloc = NULL;
|
||||
}
|
||||
|
||||
~AutoDestroyAllocator()
|
||||
{
|
||||
if (alloc)
|
||||
Foreground::delete_(alloc);
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
TestCompiler(IonBuilder *builder, MIRGraph *graph, AutoDestroyAllocator &autoDestroy)
|
||||
{
|
||||
JS_ASSERT(!builder->script->ion);
|
||||
JSContext *cx = GetIonContext()->cx;
|
||||
|
||||
IonSpewNewFunction(graph, builder->script);
|
||||
|
||||
if (!builder->build())
|
||||
return false;
|
||||
builder->clearForBackEnd();
|
||||
|
||||
if (js_IonOptions.parallelCompilation) {
|
||||
builder->script->ion = ION_COMPILING_SCRIPT;
|
||||
|
||||
if (!StartOffThreadIonCompile(cx, builder))
|
||||
return false;
|
||||
|
||||
// The allocator and associated data will be destroyed after being
|
||||
// processed in the finishedOffThreadCompilations list.
|
||||
autoDestroy.cancel();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CompileBackEnd(builder))
|
||||
return false;
|
||||
|
||||
if (!GenerateCode(builder, graph))
|
||||
CodeGenerator codegen(builder, *builder->lir);
|
||||
if (!codegen.generate())
|
||||
return false;
|
||||
|
||||
if (builder->script->hasIonScript()) {
|
||||
// After Ion has finished compiling a script, remove any JITScripts it
|
||||
// has to force continued execution in Ion code.
|
||||
mjit::ReleaseScriptCodeFromVM(cx, builder->script);
|
||||
}
|
||||
|
||||
IonSpewEndFunction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool Compiler(IonBuilder &, MIRGraph &)>
|
||||
void
|
||||
AttachFinishedCompilations(JSContext *cx)
|
||||
{
|
||||
IonCompartment *ion = cx->compartment->ionCompartment();
|
||||
if (!ion)
|
||||
return;
|
||||
|
||||
AutoLockWorkerThreadState lock(cx->runtime);
|
||||
|
||||
OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
|
||||
|
||||
// Incorporate any off thread compilations which have finished, failed or
|
||||
// have been cancelled, and destroy JM jitcode for any compilations which
|
||||
// succeeded, to allow entering the Ion code from the interpreter.
|
||||
while (!compilations.empty()) {
|
||||
IonBuilder *builder = compilations.popCopy();
|
||||
|
||||
if (builder->lir) {
|
||||
JSScript *script = builder->script;
|
||||
IonContext ictx(cx, cx->compartment, &builder->temp());
|
||||
|
||||
CodeGenerator codegen(builder, *builder->lir);
|
||||
|
||||
types::AutoEnterCompilation enterCompiler(cx, types::AutoEnterCompilation::Ion);
|
||||
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();
|
||||
}
|
||||
|
||||
if (success) {
|
||||
if (script->hasIonScript())
|
||||
mjit::ReleaseScriptCodeFromVM(cx, script);
|
||||
} else {
|
||||
// Silently ignore OOM during code generation, we're at an
|
||||
// operation callback and can't propagate failures.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
|
||||
FinishOffThreadBuilder(builder);
|
||||
}
|
||||
|
||||
compilations.clear();
|
||||
}
|
||||
|
||||
static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
|
||||
|
||||
template <bool Compiler(IonBuilder *, MIRGraph *, AutoDestroyAllocator &)>
|
||||
static bool
|
||||
IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
|
||||
{
|
||||
@ -867,14 +986,23 @@ IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc,
|
||||
script);
|
||||
#endif
|
||||
|
||||
TempAllocator temp(&cx->tempLifoAlloc());
|
||||
IonContext ictx(cx, cx->compartment, &temp);
|
||||
LifoAlloc *alloc = cx->new_<LifoAlloc>(BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
|
||||
if (!alloc)
|
||||
return false;
|
||||
|
||||
AutoDestroyAllocator autoDestroy(alloc);
|
||||
|
||||
TempAllocator *temp = alloc->new_<TempAllocator>(alloc);
|
||||
if (!temp)
|
||||
return false;
|
||||
|
||||
IonContext ictx(cx, cx->compartment, temp);
|
||||
|
||||
if (!cx->compartment->ensureIonCompartmentExists(cx))
|
||||
return false;
|
||||
|
||||
MIRGraph graph(&temp);
|
||||
CompileInfo *info = cx->tempLifoAlloc().new_<CompileInfo>(script, fun, osrPc, constructing);
|
||||
MIRGraph *graph = alloc->new_<MIRGraph>(temp);
|
||||
CompileInfo *info = alloc->new_<CompileInfo>(script, fun, osrPc, constructing);
|
||||
if (!info)
|
||||
return false;
|
||||
|
||||
@ -888,10 +1016,11 @@ IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc,
|
||||
|
||||
types::AutoEnterCompilation enterCompiler(cx, types::AutoEnterCompilation::Ion);
|
||||
enterCompiler.init(script, false, 0);
|
||||
AutoCompilerRoots roots(script->compartment()->rt);
|
||||
|
||||
IonBuilder builder(cx, &temp, &graph, &oracle, info);
|
||||
if (!Compiler(builder, graph)) {
|
||||
AutoTempAllocatorRooter root(cx, temp);
|
||||
|
||||
IonBuilder *builder = alloc->new_<IonBuilder>(cx, temp, graph, &oracle, info);
|
||||
if (!Compiler(builder, graph, autoDestroy)) {
|
||||
IonSpew(IonSpew_Abort, "IM Compilation failed.");
|
||||
return false;
|
||||
}
|
||||
@ -899,6 +1028,17 @@ IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TestIonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
|
||||
{
|
||||
if (!IonCompile<TestCompiler>(cx, script, fun, osrPc, constructing)) {
|
||||
if (!cx->isExceptionPending())
|
||||
ForbidCompilation(script);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
CheckFrame(StackFrame *fp)
|
||||
{
|
||||
@ -979,7 +1119,7 @@ CheckScriptSize(JSScript *script)
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool Compiler(IonBuilder &, MIRGraph &)>
|
||||
template <bool Compiler(IonBuilder *, MIRGraph *, AutoDestroyAllocator &)>
|
||||
static MethodStatus
|
||||
Compile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
|
||||
{
|
||||
@ -1034,6 +1174,10 @@ ion::CanEnterAtBranch(JSContext *cx, JSScript *script, StackFrame *fp, jsbytecod
|
||||
if (script->ion == ION_DISABLED_SCRIPT)
|
||||
return Method_Skipped;
|
||||
|
||||
// Skip if the script is being compiled off thread.
|
||||
if (script->ion == ION_COMPILING_SCRIPT)
|
||||
return Method_Skipped;
|
||||
|
||||
// Skip if the code is expected to result in a bailout.
|
||||
if (script->ion && script->ion->bailoutExpected())
|
||||
return Method_Skipped;
|
||||
@ -1079,6 +1223,10 @@ ion::CanEnter(JSContext *cx, JSScript *script, StackFrame *fp, bool newType)
|
||||
if (script->ion == ION_DISABLED_SCRIPT)
|
||||
return Method_Skipped;
|
||||
|
||||
// Skip if the script is being compiled off thread.
|
||||
if (script->ion == ION_COMPILING_SCRIPT)
|
||||
return Method_Skipped;
|
||||
|
||||
// Skip if the code is expected to result in a bailout.
|
||||
if (script->ion && script->ion->bailoutExpected())
|
||||
return Method_Skipped;
|
||||
@ -1379,6 +1527,13 @@ InvalidateActivation(FreeOp *fop, uint8 *ionTop, bool invalidateAll)
|
||||
void
|
||||
ion::InvalidateAll(FreeOp *fop, JSCompartment *c)
|
||||
{
|
||||
if (!c->ionCompartment())
|
||||
return;
|
||||
|
||||
CancelOffThreadIonCompile(c, NULL);
|
||||
|
||||
FinishAllOffThreadCompilations(c->ionCompartment());
|
||||
|
||||
for (IonActivationIterator iter(fop->runtime()); iter.more(); ++iter) {
|
||||
if (iter.activation()->compartment() == c) {
|
||||
AutoFlushCache afc ("InvalidateAll", c->ionCompartment());
|
||||
|
@ -67,6 +67,11 @@ struct IonOptions
|
||||
// Default: false
|
||||
bool rangeAnalysis;
|
||||
|
||||
// Toggles whether compilation occurs off the main thread.
|
||||
//
|
||||
// Default: true iff there are at least two CPUs available
|
||||
bool parallelCompilation;
|
||||
|
||||
// How many invocations or loop iterations are needed before functions
|
||||
// are compiled.
|
||||
//
|
||||
@ -140,6 +145,8 @@ struct IonOptions
|
||||
// Eagerly inline calls to improve test coverage.
|
||||
usesBeforeInlining = 0;
|
||||
smallFunctionUsesBeforeInlining = 0;
|
||||
|
||||
parallelCompilation = false;
|
||||
}
|
||||
|
||||
IonOptions()
|
||||
@ -152,6 +159,7 @@ struct IonOptions
|
||||
inlining(true),
|
||||
edgeCaseAnalysis(true),
|
||||
rangeAnalysis(false),
|
||||
parallelCompilation(false),
|
||||
usesBeforeCompile(10240),
|
||||
usesBeforeCompileNoJaeger(40),
|
||||
usesBeforeInlining(usesBeforeCompile),
|
||||
@ -163,7 +171,8 @@ struct IonOptions
|
||||
inlineMaxTotalBytecodeLength(800),
|
||||
eagerCompilation(false),
|
||||
slowCallLimit(512)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
enum MethodStatus
|
||||
@ -230,6 +239,13 @@ void MarkFromIon(JSCompartment *comp, Value *vp);
|
||||
|
||||
void ToggleBarriers(JSCompartment *comp, bool needs);
|
||||
|
||||
class IonBuilder;
|
||||
|
||||
bool CompileBackEnd(IonBuilder *builder);
|
||||
void AttachFinishedCompilations(JSContext *cx);
|
||||
void FinishOffThreadBuilder(IonBuilder *builder);
|
||||
bool TestIonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing);
|
||||
|
||||
static inline bool IsEnabled(JSContext *cx)
|
||||
{
|
||||
return cx->hasRunOption(JSOPTION_ION) && cx->typeInferenceEnabled();
|
||||
|
@ -22,10 +22,14 @@ class TempAllocator
|
||||
LifoAlloc *lifoAlloc_;
|
||||
void *mark_;
|
||||
|
||||
// Linked list of GCThings rooted by this allocator.
|
||||
JS::CompilerRootNode *rootList_;
|
||||
|
||||
public:
|
||||
TempAllocator(LifoAlloc *lifoAlloc)
|
||||
: lifoAlloc_(lifoAlloc),
|
||||
mark_(lifoAlloc->mark())
|
||||
mark_(lifoAlloc->mark()),
|
||||
rootList_(NULL)
|
||||
{ }
|
||||
|
||||
~TempAllocator()
|
||||
@ -53,6 +57,11 @@ class TempAllocator
|
||||
return lifoAlloc_;
|
||||
}
|
||||
|
||||
JS::CompilerRootNode *&rootList()
|
||||
{
|
||||
return rootList_;
|
||||
}
|
||||
|
||||
bool ensureBallast() {
|
||||
// Most infallible Ion allocations are small, so we use a ballast of
|
||||
// ~16K for now.
|
||||
@ -60,6 +69,25 @@ class TempAllocator
|
||||
}
|
||||
};
|
||||
|
||||
// Stack allocated rooter for all roots associated with a TempAllocator
|
||||
class AutoTempAllocatorRooter : private AutoGCRooter
|
||||
{
|
||||
public:
|
||||
explicit AutoTempAllocatorRooter(JSContext *cx, TempAllocator *temp
|
||||
JS_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: AutoGCRooter(cx, IONALLOC), temp(temp)
|
||||
{
|
||||
JS_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
friend void AutoGCRooter::trace(JSTracer *trc);
|
||||
void trace(JSTracer *trc);
|
||||
|
||||
private:
|
||||
TempAllocator *temp;
|
||||
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
};
|
||||
|
||||
class IonAllocPolicy
|
||||
{
|
||||
public:
|
||||
|
@ -27,6 +27,8 @@ IonBuilder::IonBuilder(JSContext *cx, TempAllocator *temp, MIRGraph *graph,
|
||||
TypeOracle *oracle, CompileInfo *info, size_t inliningDepth, uint32 loopDepth)
|
||||
: MIRGenerator(cx->compartment, temp, graph, info),
|
||||
script(info->script()),
|
||||
recompileInfo(cx->compartment->types.compiledInfo),
|
||||
lir(NULL),
|
||||
cx(cx),
|
||||
loopDepth_(loopDepth),
|
||||
callerResumePoint_(NULL),
|
||||
|
@ -17,6 +17,8 @@
|
||||
namespace js {
|
||||
namespace ion {
|
||||
|
||||
class LIRGraph;
|
||||
|
||||
class IonBuilder : public MIRGenerator
|
||||
{
|
||||
enum ControlStatus {
|
||||
@ -425,6 +427,12 @@ class IonBuilder : public MIRGenerator
|
||||
// A builder is inextricably tied to a particular script.
|
||||
JSScript * const script;
|
||||
|
||||
// Compilation index for this attempt.
|
||||
types::RecompileInfo const recompileInfo;
|
||||
|
||||
// If off thread compilation is successful, final LIR is attached here.
|
||||
LIRGraph *lir;
|
||||
|
||||
void clearForBackEnd();
|
||||
|
||||
private:
|
||||
|
@ -23,6 +23,9 @@ typedef void (*EnterIonCode)(void *code, int argc, Value *argv, StackFrame *fp,
|
||||
CalleeToken calleeToken, Value *vp);
|
||||
|
||||
class IonActivation;
|
||||
class IonBuilder;
|
||||
|
||||
typedef Vector<IonBuilder*, 0, SystemAllocPolicy> OffThreadCompilationVector;
|
||||
|
||||
class IonCompartment
|
||||
{
|
||||
@ -37,7 +40,7 @@ class IonCompartment
|
||||
ReadBarriered<IonCode> enterJIT_;
|
||||
|
||||
// Vector mapping frame class sizes to bailout tables.
|
||||
js::Vector<ReadBarriered<IonCode>, 4, SystemAllocPolicy> bailoutTables_;
|
||||
Vector<ReadBarriered<IonCode>, 4, SystemAllocPolicy> bailoutTables_;
|
||||
|
||||
// Generic bailout table; used if the bailout table overflows.
|
||||
ReadBarriered<IonCode> bailoutHandler_;
|
||||
@ -55,8 +58,14 @@ class IonCompartment
|
||||
// Map VMFunction addresses to the IonCode of the wrapper.
|
||||
VMWrapperMap *functionWrappers_;
|
||||
|
||||
// Any scripts for which off thread compilation has successfully finished,
|
||||
// failed, or been cancelled. All off thread compilations which are started
|
||||
// will eventually appear in this list asynchronously. Protected by the
|
||||
// runtime's analysis lock.
|
||||
OffThreadCompilationVector finishedOffThreadCompilations_;
|
||||
|
||||
// Keep track of memoryregions that are going to be flushed.
|
||||
js::ion::AutoFlushCache *flusher_;
|
||||
AutoFlushCache *flusher_;
|
||||
|
||||
private:
|
||||
IonCode *generateEnterJIT(JSContext *cx);
|
||||
@ -70,6 +79,10 @@ class IonCompartment
|
||||
public:
|
||||
IonCode *generateVMWrapper(JSContext *cx, const VMFunction &f);
|
||||
|
||||
OffThreadCompilationVector &finishedOffThreadCompilations() {
|
||||
return finishedOffThreadCompilations_;
|
||||
}
|
||||
|
||||
public:
|
||||
bool initialize(JSContext *cx);
|
||||
IonCompartment();
|
||||
|
@ -621,10 +621,9 @@ ion::MarkIonActivations(JSRuntime *rt, JSTracer *trc)
|
||||
}
|
||||
|
||||
void
|
||||
ion::MarkIonCompilerRoots(JSTracer *trc)
|
||||
ion::AutoTempAllocatorRooter::trace(JSTracer *trc)
|
||||
{
|
||||
JSRuntime *rt = trc->runtime;
|
||||
for (CompilerRootNode *root = rt->ionCompilerRootList; root != NULL; root = root->next)
|
||||
for (CompilerRootNode *root = temp->rootList(); root != NULL; root = root->next)
|
||||
gc::MarkGCThingRoot(trc, root->address(), "ion-compiler-root");
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#include "Ion.h"
|
||||
#include "IonSpewer.h"
|
||||
|
||||
#ifndef ION_SPEW_DIR
|
||||
@ -42,25 +43,29 @@ ion::EnableIonDebugLogging()
|
||||
void
|
||||
ion::IonSpewNewFunction(MIRGraph *graph, JSScript *function)
|
||||
{
|
||||
ionspewer.beginFunction(graph, function);
|
||||
if (!js_IonOptions.parallelCompilation)
|
||||
ionspewer.beginFunction(graph, function);
|
||||
}
|
||||
|
||||
void
|
||||
ion::IonSpewPass(const char *pass)
|
||||
{
|
||||
ionspewer.spewPass(pass);
|
||||
if (!js_IonOptions.parallelCompilation)
|
||||
ionspewer.spewPass(pass);
|
||||
}
|
||||
|
||||
void
|
||||
ion::IonSpewPass(const char *pass, LinearScanAllocator *ra)
|
||||
{
|
||||
ionspewer.spewPass(pass, ra);
|
||||
if (!js_IonOptions.parallelCompilation)
|
||||
ionspewer.spewPass(pass, ra);
|
||||
}
|
||||
|
||||
void
|
||||
ion::IonSpewEndFunction()
|
||||
{
|
||||
ionspewer.endFunction();
|
||||
if (!js_IonOptions.parallelCompilation)
|
||||
ionspewer.endFunction();
|
||||
}
|
||||
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "jsstr.h"
|
||||
#include "prmjtime.h"
|
||||
#include "jsweakmap.h"
|
||||
#include "jsworkers.h"
|
||||
#include "jswrapper.h"
|
||||
#include "jstypedarray.h"
|
||||
#include "jsxml.h"
|
||||
@ -864,7 +865,6 @@ JSRuntime::JSRuntime()
|
||||
ionJSContext(NULL),
|
||||
ionStackLimit(0),
|
||||
ionActivation(NULL),
|
||||
ionCompilerRootList(NULL),
|
||||
ionReturnOverride_(MagicValue(JS_ARG_POISON))
|
||||
{
|
||||
/* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
|
||||
@ -928,6 +928,10 @@ JSRuntime::init(uint32_t maxbytes)
|
||||
return false;
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
workerThreadState = this->new_<WorkerThreadState>();
|
||||
if (!workerThreadState || !workerThreadState->init(this))
|
||||
return false;
|
||||
|
||||
if (!sourceCompressorThread.init())
|
||||
return false;
|
||||
#endif
|
||||
@ -958,6 +962,7 @@ JSRuntime::~JSRuntime()
|
||||
FreeScriptFilenames(this);
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
delete_(workerThreadState);
|
||||
sourceCompressorThread.finish();
|
||||
#endif
|
||||
|
||||
|
@ -1064,7 +1064,8 @@ class JS_PUBLIC_API(AutoGCRooter) {
|
||||
REGEXPSTATICS=-25, /* js::RegExpStatics::AutoRooter */
|
||||
NAMEVECTOR = -26, /* js::AutoNameVector */
|
||||
HASHABLEVALUE=-27,
|
||||
IONMASM = -28 /* js::ion::MacroAssembler */
|
||||
IONMASM = -28, /* js::ion::MacroAssembler */
|
||||
IONALLOC = -29 /* js::ion::AutoTempAllocatorRooter */
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include "jsscope.h"
|
||||
#include "jsscript.h"
|
||||
#include "jsstr.h"
|
||||
#include "jsworkers.h"
|
||||
#include "ion/Ion.h"
|
||||
#include "ion/IonFrames.h"
|
||||
|
||||
#ifdef JS_METHODJIT
|
||||
@ -389,6 +391,10 @@ js::DestroyContext(JSContext *cx, DestroyContextMode mode)
|
||||
for (CompartmentsIter c(rt); !c.done(); c.next())
|
||||
c->types.print(cx, false);
|
||||
|
||||
/* Off thread ion compilations depend on atoms still existing. */
|
||||
for (CompartmentsIter c(rt); !c.done(); c.next())
|
||||
CancelOffThreadIonCompile(c, NULL);
|
||||
|
||||
/* Unpin all common atoms before final GC. */
|
||||
FinishCommonAtoms(rt);
|
||||
|
||||
@ -1010,6 +1016,12 @@ js_InvokeOperationCallback(JSContext *cx)
|
||||
if (rt->gcIsNeeded)
|
||||
GCSlice(rt, GC_NORMAL, rt->gcTriggerReason);
|
||||
|
||||
/*
|
||||
* A worker thread may have set the callback after finishing an Ion
|
||||
* compilation.
|
||||
*/
|
||||
ion::AttachFinishedCompilations(cx);
|
||||
|
||||
/*
|
||||
* Important: Additional callbacks can occur inside the callback handler
|
||||
* if it re-enters the JS engine. The embedding must ensure that the
|
||||
|
@ -77,6 +77,7 @@ class IonActivation;
|
||||
class WeakMapBase;
|
||||
class InterpreterFrames;
|
||||
class DebugScopes;
|
||||
class WorkerThreadState;
|
||||
|
||||
/*
|
||||
* GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
|
||||
@ -789,6 +790,8 @@ struct JSRuntime : js::RuntimeFriendFields
|
||||
js::GCHelperThread gcHelperThread;
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
js::WorkerThreadState *workerThreadState;
|
||||
|
||||
js::SourceCompressorThread sourceCompressorThread;
|
||||
#endif
|
||||
|
||||
@ -894,9 +897,6 @@ struct JSRuntime : js::RuntimeFriendFields
|
||||
// This points to the most recent Ion activation running on the thread.
|
||||
js::ion::IonActivation *ionActivation;
|
||||
|
||||
// Linked list of GCThings rooted for the current compilation.
|
||||
JS::CompilerRootNode *ionCompilerRootList;
|
||||
|
||||
private:
|
||||
// In certain cases, we want to optimize certain opcodes to typed instructions,
|
||||
// to avoid carrying an extra register to feed into an unbox. Unfortunately,
|
||||
|
@ -2491,6 +2491,13 @@ AutoGCRooter::trace(JSTracer *trc)
|
||||
case IONMASM: {
|
||||
#ifdef JS_ION
|
||||
static_cast<js::ion::MacroAssembler::AutoRooter *>(this)->masm()->trace(trc);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
case IONALLOC: {
|
||||
#ifdef JS_ION
|
||||
static_cast<js::ion::AutoTempAllocatorRooter *>(this)->trace(trc);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
@ -2623,7 +2630,6 @@ MarkRuntime(JSTracer *trc, bool useSavedRoots = false)
|
||||
|
||||
#ifdef JS_ION
|
||||
ion::MarkIonActivations(rt, trc);
|
||||
ion::MarkIonCompilerRoots(trc);
|
||||
#endif
|
||||
|
||||
for (CompartmentsIter c(rt); !c.done(); c.next())
|
||||
@ -2918,7 +2924,7 @@ AssertBackgroundSweepingFinshed(JSRuntime *rt)
|
||||
#endif
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
static unsigned
|
||||
unsigned
|
||||
GetCPUCount()
|
||||
{
|
||||
static unsigned ncpus = 0;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "jsscope.h"
|
||||
#include "jsstr.h"
|
||||
#include "jsiter.h"
|
||||
#include "jsworkers.h"
|
||||
|
||||
#include "ion/Ion.h"
|
||||
#include "ion/IonCompartment.h"
|
||||
@ -2253,6 +2254,9 @@ TypeCompartment::addPendingRecompile(JSContext *cx, const RecompileInfo &info)
|
||||
if (co->pendingRecompilation)
|
||||
return;
|
||||
|
||||
if (co->isValid())
|
||||
CancelOffThreadIonCompile(cx->compartment, co->script);
|
||||
|
||||
if (!co->isValid()) {
|
||||
JS_ASSERT(co->script == NULL);
|
||||
return;
|
||||
@ -2319,6 +2323,8 @@ TypeCompartment::addPendingRecompile(JSContext *cx, JSScript *script, jsbytecode
|
||||
}
|
||||
|
||||
# ifdef JS_ION
|
||||
CancelOffThreadIonCompile(cx->compartment, script);
|
||||
|
||||
if (script->hasIonScript())
|
||||
addPendingRecompile(cx, script->ionScript()->recompileInfo());
|
||||
# endif
|
||||
@ -5849,7 +5855,6 @@ TypeCompartment::sweep(FreeOp *fop)
|
||||
void
|
||||
TypeCompartment::sweepCompilerOutputs(FreeOp *fop)
|
||||
{
|
||||
|
||||
if (constrainedOutputs) {
|
||||
bool isCompiling = compiledInfo.outputIndex != RecompileInfo::NoCompilerRunning;
|
||||
if (isCompiling && !compartment()->activeAnalysis)
|
||||
@ -5860,6 +5865,7 @@ TypeCompartment::sweepCompilerOutputs(FreeOp *fop)
|
||||
JS_ASSERT(!co.isValid());
|
||||
}
|
||||
#endif
|
||||
|
||||
fop->delete_(constrainedOutputs);
|
||||
constrainedOutputs = NULL;
|
||||
} else {
|
||||
@ -5868,14 +5874,12 @@ TypeCompartment::sweepCompilerOutputs(FreeOp *fop)
|
||||
// potentially created multiple types with this index. Instead, we
|
||||
// invalidate all compilations except the one running now.
|
||||
size_t len = constrainedOutputs->length();
|
||||
if (isCompiling) {
|
||||
len--;
|
||||
JS_ASSERT(compiledInfo.outputIndex == len);
|
||||
}
|
||||
for (unsigned i = 0; i < len; i++) {
|
||||
CompilerOutput &co = (*constrainedOutputs)[i];
|
||||
JS_ASSERT(!co.isValid());
|
||||
co.invalidate();
|
||||
if (i != compiledInfo.outputIndex) {
|
||||
CompilerOutput &co = (*constrainedOutputs)[i];
|
||||
JS_ASSERT(!co.isValid());
|
||||
co.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,10 +80,13 @@ CompilerOutput::isValid() const
|
||||
#endif
|
||||
|
||||
if (isIon()) {
|
||||
if (!script->hasIonScript())
|
||||
return false;
|
||||
JS_ASSERT(this == script->ion->recompileInfo().compilerOutput(types));
|
||||
return true;
|
||||
if (script->hasIonScript()) {
|
||||
JS_ASSERT(this == script->ion->recompileInfo().compilerOutput(types));
|
||||
return true;
|
||||
}
|
||||
if (script->isIonCompilingOffThread())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -372,6 +375,13 @@ struct AutoEnterCompilation
|
||||
return true;
|
||||
}
|
||||
|
||||
void initExisting(RecompileInfo oldInfo)
|
||||
{
|
||||
// Initialize the active compilation index from that produced during a
|
||||
// previous compilation, for finishing an off thread compilation.
|
||||
info = oldInfo;
|
||||
}
|
||||
|
||||
~AutoEnterCompilation()
|
||||
{
|
||||
CompilerOutput *co = info.compilerOutput(cx);
|
||||
|
@ -21,6 +21,10 @@
|
||||
# define JS_ATOMIC_ADD(p,v) PR_ATOMIC_ADD((int32_t *)(p), (int32_t)(v))
|
||||
# define JS_ATOMIC_SET(p,v) PR_ATOMIC_SET((int32_t *)(p), (int32_t)(v))
|
||||
|
||||
namespace js {
|
||||
unsigned GetCPUCount();
|
||||
}
|
||||
|
||||
#else /* JS_THREADSAFE */
|
||||
|
||||
typedef struct PRThread PRThread;
|
||||
|
@ -551,11 +551,14 @@ struct JSScript : public js::gc::Cell
|
||||
#endif
|
||||
|
||||
bool hasIonScript() const {
|
||||
return ion && ion != ION_DISABLED_SCRIPT;
|
||||
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;
|
||||
|
294
js/src/jsworkers.cpp
Normal file
294
js/src/jsworkers.cpp
Normal file
@ -0,0 +1,294 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
|
||||
/* vim: set ts=40 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/. */
|
||||
|
||||
#include "jsworkers.h"
|
||||
|
||||
#include "ion/IonBuilder.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
|
||||
bool
|
||||
js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
|
||||
{
|
||||
WorkerThreadState &state = *cx->runtime->workerThreadState;
|
||||
|
||||
JS_ASSERT(state.numThreads);
|
||||
|
||||
AutoLockWorkerThreadState lock(cx->runtime);
|
||||
|
||||
if (!state.ionWorklist.append(builder))
|
||||
return false;
|
||||
|
||||
state.notify(WorkerThreadState::WORKER);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move an IonBuilder for which compilation has either finished, failed, or
|
||||
* been cancelled into the Ion compartment's finished compilations list.
|
||||
* All off thread compilations which are started must eventually be finished.
|
||||
*/
|
||||
static void
|
||||
FinishOffThreadIonCompile(ion::IonBuilder *builder)
|
||||
{
|
||||
JSCompartment *compartment = builder->script->compartment();
|
||||
JS_ASSERT(compartment->rt->workerThreadState->isLocked());
|
||||
|
||||
compartment->ionCompartment()->finishedOffThreadCompilations().append(builder);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
|
||||
{
|
||||
if (script)
|
||||
return target == script;
|
||||
return target->compartment() == compartment;
|
||||
}
|
||||
|
||||
void
|
||||
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
|
||||
{
|
||||
WorkerThreadState &state = *compartment->rt->workerThreadState;
|
||||
|
||||
ion::IonCompartment *ion = compartment->ionCompartment();
|
||||
if (!ion)
|
||||
return;
|
||||
|
||||
AutoLockWorkerThreadState lock(compartment->rt);
|
||||
|
||||
/* Cancel any pending entries for which processing hasn't started. */
|
||||
for (size_t i = 0; i < state.ionWorklist.length(); i++) {
|
||||
ion::IonBuilder *builder = state.ionWorklist[i];
|
||||
if (CompiledScriptMatches(compartment, script, builder->script)) {
|
||||
FinishOffThreadIonCompile(builder);
|
||||
state.ionWorklist[i--] = state.ionWorklist.back();
|
||||
state.ionWorklist.popBack();
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for in progress entries to finish up. */
|
||||
for (size_t i = 0; i < state.numThreads; i++) {
|
||||
const WorkerThread &helper = state.threads[i];
|
||||
while (helper.ionScript && CompiledScriptMatches(compartment, script, helper.ionScript))
|
||||
state.wait(WorkerThreadState::MAIN);
|
||||
}
|
||||
|
||||
ion::OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
|
||||
|
||||
/* Cancel code generation for any completed entries. */
|
||||
for (size_t i = 0; i < compilations.length(); i++) {
|
||||
ion::IonBuilder *builder = compilations[i];
|
||||
if (CompiledScriptMatches(compartment, script, builder->script)) {
|
||||
ion::FinishOffThreadBuilder(builder);
|
||||
compilations[i--] = compilations.back();
|
||||
compilations.popBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
WorkerThreadState::init(JSRuntime *rt)
|
||||
{
|
||||
workerLock = PR_NewLock();
|
||||
if (!workerLock)
|
||||
return false;
|
||||
|
||||
mainWakeup = PR_NewCondVar(workerLock);
|
||||
if (!mainWakeup)
|
||||
return false;
|
||||
|
||||
helperWakeup = PR_NewCondVar(workerLock);
|
||||
if (!helperWakeup)
|
||||
return false;
|
||||
|
||||
numThreads = GetCPUCount() - 1;
|
||||
|
||||
threads = (WorkerThread*) rt->calloc_(sizeof(WorkerThread) * numThreads);
|
||||
if (!threads) {
|
||||
numThreads = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numThreads; i++) {
|
||||
WorkerThread &helper = threads[i];
|
||||
helper.runtime = rt;
|
||||
helper.thread = PR_CreateThread(PR_USER_THREAD,
|
||||
WorkerThread::ThreadMain, &helper,
|
||||
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
if (!helper.thread) {
|
||||
for (size_t j = 0; j < numThreads; j++)
|
||||
threads[j].destroy();
|
||||
Foreground::free_(threads);
|
||||
threads = NULL;
|
||||
numThreads = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WorkerThreadState::~WorkerThreadState()
|
||||
{
|
||||
/*
|
||||
* Join created threads first, which needs locks and condition variables
|
||||
* to be intact.
|
||||
*/
|
||||
if (threads) {
|
||||
for (size_t i = 0; i < numThreads; i++)
|
||||
threads[i].destroy();
|
||||
Foreground::free_(threads);
|
||||
}
|
||||
|
||||
if (workerLock)
|
||||
PR_DestroyLock(workerLock);
|
||||
|
||||
if (mainWakeup)
|
||||
PR_DestroyCondVar(mainWakeup);
|
||||
|
||||
if (helperWakeup)
|
||||
PR_DestroyCondVar(helperWakeup);
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThreadState::lock()
|
||||
{
|
||||
JS_ASSERT(!isLocked());
|
||||
PR_Lock(workerLock);
|
||||
#ifdef DEBUG
|
||||
lockOwner = PR_GetCurrentThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThreadState::unlock()
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
#ifdef DEBUG
|
||||
lockOwner = NULL;
|
||||
#endif
|
||||
PR_Unlock(workerLock);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool
|
||||
WorkerThreadState::isLocked()
|
||||
{
|
||||
return lockOwner == PR_GetCurrentThread();
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
WorkerThreadState::wait(CondVar which, uint32_t millis)
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
#ifdef DEBUG
|
||||
lockOwner = NULL;
|
||||
#endif
|
||||
PR_WaitCondVar((which == MAIN) ? mainWakeup : helperWakeup,
|
||||
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
|
||||
#ifdef DEBUG
|
||||
lockOwner = PR_GetCurrentThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThreadState::notify(CondVar which)
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
PR_NotifyAllCondVar((which == MAIN) ? mainWakeup : helperWakeup);
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThread::destroy()
|
||||
{
|
||||
WorkerThreadState &state = *runtime->workerThreadState;
|
||||
|
||||
if (!thread)
|
||||
return;
|
||||
|
||||
terminate = true;
|
||||
{
|
||||
AutoLockWorkerThreadState lock(runtime);
|
||||
state.notify(WorkerThreadState::WORKER);
|
||||
}
|
||||
PR_JoinThread(thread);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
WorkerThread::ThreadMain(void *arg)
|
||||
{
|
||||
PR_SetCurrentThreadName("Analysis Helper");
|
||||
static_cast<WorkerThread *>(arg)->threadLoop();
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThread::threadLoop()
|
||||
{
|
||||
WorkerThreadState &state = *runtime->workerThreadState;
|
||||
state.lock();
|
||||
|
||||
while (true) {
|
||||
JS_ASSERT(!ionScript);
|
||||
|
||||
while (state.ionWorklist.empty()) {
|
||||
state.wait(WorkerThreadState::WORKER);
|
||||
if (terminate) {
|
||||
state.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ion::IonBuilder *builder = state.ionWorklist.popCopy();
|
||||
ionScript = builder->script;
|
||||
|
||||
JS_ASSERT(ionScript->ion == ION_COMPILING_SCRIPT);
|
||||
|
||||
state.unlock();
|
||||
|
||||
{
|
||||
ion::IonContext ictx(NULL, ionScript->compartment(), &builder->temp());
|
||||
ion::CompileBackEnd(builder);
|
||||
}
|
||||
|
||||
state.lock();
|
||||
|
||||
ionScript = NULL;
|
||||
FinishOffThreadIonCompile(builder);
|
||||
|
||||
/*
|
||||
* Notify the main thread in case it is waiting for the compilation to
|
||||
* finish.
|
||||
*/
|
||||
state.notify(WorkerThreadState::MAIN);
|
||||
|
||||
/*
|
||||
* Ping the main thread so that the compiled code can be incorporated
|
||||
* at the next operation callback.
|
||||
*/
|
||||
runtime->triggerOperationCallback();
|
||||
}
|
||||
}
|
||||
|
||||
#else /* JS_THREADSAFE */
|
||||
|
||||
bool
|
||||
js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
|
||||
{
|
||||
JS_NOT_REACHED("Off thread compilation not available in non-THREADSAFE builds");
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* JS_THREADSAFE */
|
162
js/src/jsworkers.h
Normal file
162
js/src/jsworkers.h
Normal file
@ -0,0 +1,162 @@
|
||||
//* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
|
||||
/* vim: set ts=40 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/. */
|
||||
|
||||
/*
|
||||
* Definitions for managing off-main-thread work using a shared, per runtime
|
||||
* worklist. Worklist items are engine internal, and are distinct from e.g.
|
||||
* web workers.
|
||||
*/
|
||||
|
||||
#ifndef jsworkers_h___
|
||||
#define jsworkers_h___
|
||||
|
||||
#include "jscntxt.h"
|
||||
#include "jslock.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
namespace ion {
|
||||
class IonBuilder;
|
||||
}
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
|
||||
struct WorkerThread;
|
||||
|
||||
/* Per-runtime state for off thread work items. */
|
||||
struct WorkerThreadState
|
||||
{
|
||||
/* Available threads. */
|
||||
WorkerThread *threads;
|
||||
size_t numThreads;
|
||||
|
||||
enum CondVar {
|
||||
MAIN,
|
||||
WORKER
|
||||
};
|
||||
|
||||
/* Shared worklist for helper threads. */
|
||||
js::Vector<ion::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
|
||||
|
||||
WorkerThreadState() { PodZero(this); }
|
||||
~WorkerThreadState();
|
||||
|
||||
bool init(JSRuntime *rt);
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
#ifdef DEBUG
|
||||
bool isLocked();
|
||||
#endif
|
||||
|
||||
void wait(CondVar which, uint32_t timeoutMillis = 0);
|
||||
void notify(CondVar which);
|
||||
|
||||
private:
|
||||
|
||||
/*
|
||||
* Lock protecting all mutable shared state accessed by helper threads, and
|
||||
* used by all condition variables.
|
||||
*/
|
||||
PRLock *workerLock;
|
||||
|
||||
#ifdef DEBUG
|
||||
PRThread *lockOwner;
|
||||
#endif
|
||||
|
||||
/* Condvar to notify the main thread that work has been completed. */
|
||||
PRCondVar *mainWakeup;
|
||||
|
||||
/* Condvar to notify helper threads that they may be able to make progress. */
|
||||
PRCondVar *helperWakeup;
|
||||
};
|
||||
|
||||
/* Individual helper thread, one allocated per core. */
|
||||
struct WorkerThread
|
||||
{
|
||||
JSRuntime *runtime;
|
||||
PRThread *thread;
|
||||
|
||||
/* Indicate to an idle thread that it should finish executing. */
|
||||
bool terminate;
|
||||
|
||||
/* Any script currently being compiled for Ion on this thread. */
|
||||
JSScript *ionScript;
|
||||
|
||||
void destroy();
|
||||
|
||||
static void ThreadMain(void *arg);
|
||||
void threadLoop();
|
||||
};
|
||||
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
/* Methods for interacting with worker threads. */
|
||||
|
||||
/*
|
||||
* Schedule an Ion compilation for a script, given a builder which has been
|
||||
* generated and read everything needed from the VM state.
|
||||
*/
|
||||
bool StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder);
|
||||
|
||||
/*
|
||||
* Cancel a scheduled or in progress Ion compilation for script. If script is
|
||||
* NULL, all compilations for the compartment are cancelled.
|
||||
*/
|
||||
void CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script);
|
||||
|
||||
class AutoLockWorkerThreadState
|
||||
{
|
||||
JSRuntime *rt;
|
||||
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
public:
|
||||
|
||||
AutoLockWorkerThreadState(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: rt(rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
rt->workerThreadState->lock();
|
||||
#endif
|
||||
JS_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
~AutoLockWorkerThreadState()
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
rt->workerThreadState->unlock();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
class AutoUnlockWorkerThreadState
|
||||
{
|
||||
JSRuntime *rt;
|
||||
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
public:
|
||||
|
||||
AutoUnlockWorkerThreadState(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: rt(rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
rt->workerThreadState->unlock();
|
||||
#endif
|
||||
JS_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
~AutoUnlockWorkerThreadState()
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
rt->workerThreadState->lock();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif // jsworkers_h___
|
@ -51,12 +51,6 @@ using namespace js::analyze;
|
||||
return retval; \
|
||||
JS_END_MACRO
|
||||
|
||||
/*
|
||||
* Number of times a script must be called or had a backedge before we try to
|
||||
* inline its calls. This number should match IonMonkey's usesBeforeCompile.
|
||||
*/
|
||||
static const size_t USES_BEFORE_INLINING = 10240;
|
||||
|
||||
mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript,
|
||||
unsigned chunkIndex, bool isConstructing)
|
||||
: BaseCompiler(cx),
|
||||
@ -104,12 +98,6 @@ mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript,
|
||||
gcNumber(cx->runtime->gcNumber),
|
||||
pcLengths(NULL)
|
||||
{
|
||||
/* Once a script starts getting really hot we will inline calls in it. */
|
||||
if (!debugMode() && cx->typeInferenceEnabled() && globalObj &&
|
||||
(outerScript->getUseCount() >= USES_BEFORE_INLINING ||
|
||||
cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS))) {
|
||||
inlining_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
CompileStatus
|
||||
@ -961,6 +949,11 @@ IonGetsFirstChance(JSContext *cx, JSScript *script, CompileRequest request)
|
||||
if (script->hasIonScript() && script->ion->bailoutExpected())
|
||||
return false;
|
||||
|
||||
// If ion compilation is pending or in progress on another thread, continue
|
||||
// using JM until that compilation finishes.
|
||||
if (script->ion == ION_COMPILING_SCRIPT)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
@ -1036,19 +1029,6 @@ mjit::CanMethodJIT(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
unsigned chunkIndex = jit->chunkIndex(pc);
|
||||
ChunkDescriptor &desc = jit->chunkDescriptor(chunkIndex);
|
||||
|
||||
if (jit->mustDestroyEntryChunk) {
|
||||
// We kept this chunk around so that Ion can get info from its caches.
|
||||
// If we end up here, we decided not to use Ion so we can destroy the
|
||||
// chunk now.
|
||||
JS_ASSERT(jit->nchunks == 1);
|
||||
jit->mustDestroyEntryChunk = false;
|
||||
|
||||
if (desc.chunk) {
|
||||
jit->destroyChunk(cx->runtime->defaultFreeOp(), chunkIndex, /* resetUses = */ false);
|
||||
return Compile_Skipped;
|
||||
}
|
||||
}
|
||||
|
||||
if (desc.chunk)
|
||||
return Compile_Okay;
|
||||
|
||||
@ -1230,7 +1210,7 @@ mjit::Compiler::generatePrologue()
|
||||
|
||||
CompileStatus status = methodEntryHelper();
|
||||
if (status == Compile_Okay)
|
||||
recompileCheckHelper();
|
||||
ionCompileHelper();
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -3269,7 +3249,7 @@ mjit::Compiler::generateMethod()
|
||||
// Insert the recompile check here so that we can immediately
|
||||
// enter Ion.
|
||||
if (loop)
|
||||
recompileCheckHelper();
|
||||
ionCompileHelper();
|
||||
END_CASE(JSOP_LOOPENTRY)
|
||||
|
||||
BEGIN_CASE(JSOP_DEBUGGER)
|
||||
@ -3971,56 +3951,61 @@ MaybeIonCompileable(JSContext *cx, JSScript *script, bool *recompileCheckForIon)
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mjit::Compiler::recompileCheckHelper()
|
||||
{
|
||||
if (inlining() || debugMode() || !globalObj || !cx->typeInferenceEnabled())
|
||||
return;
|
||||
|
||||
bool recompileCheckForIon = true;
|
||||
bool maybeIonCompileable = MaybeIonCompileable(cx, outerScript, &recompileCheckForIon);
|
||||
bool hasFunctionCalls = analysis->hasFunctionCalls();
|
||||
|
||||
// Insert a recompile check if either:
|
||||
// 1) IonMonkey is enabled, to optimize the function when it becomes hot.
|
||||
// 2) The script contains function calls JM can inline.
|
||||
if (!maybeIonCompileable && !hasFunctionCalls)
|
||||
return;
|
||||
|
||||
uint32_t minUses = USES_BEFORE_INLINING;
|
||||
|
||||
#ifdef JS_ION
|
||||
if (recompileCheckForIon)
|
||||
minUses = ion::UsesBeforeIonRecompile(outerScript, PC);
|
||||
#endif
|
||||
|
||||
uint32_t *addr = script->addressOfUseCount();
|
||||
masm.add32(Imm32(1), AbsoluteAddress(addr));
|
||||
|
||||
// If there are no function calls, and we don't want to do a recompileCheck for
|
||||
// Ion, then this just needs to increment the useCount so that we know when to
|
||||
// recompile this function from an Ion call. No need to call out to recompiler
|
||||
// stub.
|
||||
if (!hasFunctionCalls && !recompileCheckForIon)
|
||||
void
|
||||
mjit::Compiler::ionCompileHelper()
|
||||
{
|
||||
if (debugMode() || !globalObj || !cx->typeInferenceEnabled())
|
||||
return;
|
||||
|
||||
bool recompileCheckForIon = false;
|
||||
if (!MaybeIonCompileable(cx, outerScript, &recompileCheckForIon))
|
||||
return;
|
||||
|
||||
uint32_t minUses = ion::UsesBeforeIonRecompile(outerScript, PC);
|
||||
|
||||
uint32_t *useCountAddress = script->addressOfUseCount();
|
||||
masm.add32(Imm32(1), AbsoluteAddress(useCountAddress));
|
||||
|
||||
// If we don't want to do a recompileCheck for Ion, then this just needs to
|
||||
// increment the useCount so that we know when to recompile this function
|
||||
// from an Ion call. No need to call out to recompiler stub.
|
||||
if (!recompileCheckForIon)
|
||||
return;
|
||||
|
||||
void *ionScriptAddress = &script->ion;
|
||||
|
||||
// Trigger ion compilation if (a) the script has been used enough times for
|
||||
// this opcode, and (b) the script does not already have ion information
|
||||
// (whether successful, failed, or in progress off thread compilation).
|
||||
#if defined(JS_CPU_X86) || defined(JS_CPU_ARM)
|
||||
Jump jump = masm.branch32(Assembler::GreaterThanOrEqual, AbsoluteAddress(addr),
|
||||
Imm32(minUses));
|
||||
Jump first = masm.branch32(Assembler::LessThan, AbsoluteAddress(useCountAddress),
|
||||
Imm32(minUses));
|
||||
Jump second = masm.branch32(Assembler::Equal, AbsoluteAddress(ionScriptAddress),
|
||||
Imm32(0));
|
||||
#else
|
||||
/* Handle processors that can't load from absolute addresses. */
|
||||
RegisterID reg = frame.allocReg();
|
||||
masm.move(ImmPtr(addr), reg);
|
||||
Jump jump = masm.branch32(Assembler::GreaterThanOrEqual, Address(reg, 0),
|
||||
Imm32(minUses));
|
||||
masm.move(ImmPtr(useCountAddress), reg);
|
||||
Jump first = masm.branch32(Assembler::LessThan, Address(reg), Imm32(minUses));
|
||||
masm.move(ImmPtr(ionScriptAddress), reg);
|
||||
Jump second = masm.branchPtr(Assembler::Equal, Address(reg), ImmPtr(NULL));
|
||||
frame.freeReg(reg);
|
||||
#endif
|
||||
stubcc.linkExit(jump, Uses(0));
|
||||
first.linkTo(masm.label(), &masm);
|
||||
|
||||
stubcc.linkExit(second, Uses(0));
|
||||
stubcc.leave();
|
||||
|
||||
OOL_STUBCALL(stubs::RecompileForInline, REJOIN_RESUME);
|
||||
OOL_STUBCALL(stubs::TriggerIonCompile, REJOIN_RESUME);
|
||||
stubcc.rejoin(Changes(0));
|
||||
}
|
||||
#else /* JS_ION */
|
||||
void
|
||||
mjit::Compiler::ionCompileHelper()
|
||||
{
|
||||
}
|
||||
#endif /* JS_ION */
|
||||
|
||||
CompileStatus
|
||||
mjit::Compiler::methodEntryHelper()
|
||||
|
@ -631,7 +631,7 @@ private:
|
||||
void emitInlineReturnValue(FrameEntry *fe);
|
||||
void dispatchCall(VoidPtrStubUInt32 stub, uint32_t argc);
|
||||
void interruptCheckHelper();
|
||||
void recompileCheckHelper();
|
||||
void ionCompileHelper();
|
||||
CompileStatus methodEntryHelper();
|
||||
CompileStatus profilingPushHelper();
|
||||
void profilingPopHelper();
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "jsscope.h"
|
||||
#include "ion/Ion.h"
|
||||
#include "ion/IonCompartment.h"
|
||||
#include "methodjit/Retcon.h"
|
||||
|
||||
#include "jsgcinlines.h"
|
||||
#include "jsinterpinlines.h"
|
||||
@ -1510,6 +1511,16 @@ JSScript::ReleaseCode(FreeOp *fop, JITScriptHandle *jith)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mjit::ReleaseScriptCodeFromVM(JSContext *cx, JSScript *script)
|
||||
{
|
||||
if (script->hasMJITInfo()) {
|
||||
ExpandInlineFrames(cx->compartment);
|
||||
Recompiler::clearStackReferences(cx->runtime->defaultFreeOp(), script);
|
||||
ReleaseScriptCode(cx->runtime->defaultFreeOp(), script);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef JS_METHODJIT_PROFILE_STUBS
|
||||
void JS_FASTCALL
|
||||
mjit::ProfileStubCall(VMFrame &f)
|
||||
|
@ -795,13 +795,6 @@ struct JITScript
|
||||
*/
|
||||
uint32_t ionCalls;
|
||||
|
||||
/*
|
||||
* If set, we decided to keep the JITChunk so that Ion can access its caches.
|
||||
* The chunk has to be destroyed the next time the script runs in JM.
|
||||
* Note that this flag implies nchunks == 1.
|
||||
*/
|
||||
bool mustDestroyEntryChunk;
|
||||
|
||||
#ifdef JS_MONOIC
|
||||
/* Inline cache at function entry for checking this/argument types. */
|
||||
JSC::CodeLocationLabel argsCheckStub;
|
||||
@ -909,6 +902,10 @@ ReleaseScriptCode(FreeOp *fop, JSScript *script)
|
||||
script->destroyMJITInfo(fop);
|
||||
}
|
||||
|
||||
/* Can be called at any time. */
|
||||
void
|
||||
ReleaseScriptCodeFromVM(JSContext *cx, JSScript *script);
|
||||
|
||||
// Expand all stack frames inlined by the JIT within a compartment.
|
||||
void
|
||||
ExpandInlineFrames(JSCompartment *compartment);
|
||||
|
@ -591,7 +591,7 @@ class CallCompiler : public BaseCompiler
|
||||
|
||||
/* Guard that the ion pointer is valid. */
|
||||
Jump noIonCode = masm.branchPtr(Assembler::BelowOrEqual, ionScript,
|
||||
ImmPtr(ION_DISABLED_SCRIPT));
|
||||
ImmPtr(ION_COMPILING_SCRIPT));
|
||||
|
||||
RegisterID t0 = regs.takeAnyReg().reg();
|
||||
RegisterID t1 = Registers::ClobberInCall;
|
||||
|
@ -771,28 +771,19 @@ stubs::Interrupt(VMFrame &f, jsbytecode *pc)
|
||||
}
|
||||
|
||||
void JS_FASTCALL
|
||||
stubs::RecompileForInline(VMFrame &f)
|
||||
stubs::TriggerIonCompile(VMFrame &f)
|
||||
{
|
||||
JSScript *script = f.script();
|
||||
JS_ASSERT(!script->ion);
|
||||
|
||||
ExpandInlineFrames(f.cx->compartment);
|
||||
Recompiler::clearStackReferences(f.cx->runtime->defaultFreeOp(), script);
|
||||
jsbytecode *osrPC = f.regs.pc;
|
||||
if (*osrPC != JSOP_LOOPENTRY)
|
||||
osrPC = NULL;
|
||||
|
||||
#ifdef JS_ION
|
||||
if (ion::IsEnabled(f.cx) && f.jit()->nchunks == 1 &&
|
||||
script->canIonCompile() && !script->hasIonScript())
|
||||
{
|
||||
// After returning to the interpreter, IonMonkey will try to compile
|
||||
// this script. Don't destroy the JITChunk immediately so that Ion
|
||||
// still has access to its ICs.
|
||||
JS_ASSERT(!f.jit()->mustDestroyEntryChunk);
|
||||
f.jit()->mustDestroyEntryChunk = true;
|
||||
f.jit()->disableScriptEntry();
|
||||
return;
|
||||
if (!ion::TestIonCompile(f.cx, script, script->function(), osrPC, f.fp()->isConstructing())) {
|
||||
if (f.cx->isExceptionPending())
|
||||
THROW();
|
||||
}
|
||||
#endif
|
||||
|
||||
f.jit()->destroyChunk(f.cx->runtime->defaultFreeOp(), f.chunkIndex(), /* resetUses = */ false);
|
||||
}
|
||||
|
||||
void JS_FASTCALL
|
||||
|
@ -26,7 +26,7 @@ void JS_FASTCALL NewInitObject(VMFrame &f, JSObject *base);
|
||||
void JS_FASTCALL Trap(VMFrame &f, uint32_t trapTypes);
|
||||
void JS_FASTCALL DebuggerStatement(VMFrame &f, jsbytecode *pc);
|
||||
void JS_FASTCALL Interrupt(VMFrame &f, jsbytecode *pc);
|
||||
void JS_FASTCALL RecompileForInline(VMFrame &f);
|
||||
void JS_FASTCALL TriggerIonCompile(VMFrame &f);
|
||||
void JS_FASTCALL InitElem(VMFrame &f, uint32_t last);
|
||||
void JS_FASTCALL InitProp(VMFrame &f, PropertyName *name);
|
||||
|
||||
|
@ -4878,7 +4878,24 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op)
|
||||
|
||||
if (op->getBoolOption("ion-eager"))
|
||||
ion::js_IonOptions.setEagerCompilation();
|
||||
#endif
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
if (const char *str = op->getStringOption("ion-parallel-compile")) {
|
||||
if (strcmp(str, "on") == 0) {
|
||||
if (GetCPUCount() <= 1) {
|
||||
fprintf(stderr, "Parallel compilation not available on single core machines");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
ion::js_IonOptions.parallelCompilation = true;
|
||||
} else if (strcmp(str, "off") == 0) {
|
||||
ion::js_IonOptions.parallelCompilation = false;
|
||||
} else {
|
||||
return OptionFailure("ion-parallel-compile", str);
|
||||
}
|
||||
}
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
#endif /* JS_ION */
|
||||
|
||||
/* |scriptArgs| gets bound on the global before any code is run. */
|
||||
if (!BindScriptArgs(cx, obj, op))
|
||||
@ -5084,6 +5101,10 @@ main(int argc, char **argv, char **envp)
|
||||
"Specify Ion register allocation:\n"
|
||||
" lsra: Linear Scan register allocation (default)")
|
||||
|| !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods")
|
||||
#ifdef JS_THREADSAFE
|
||||
|| !op.addStringOption('\0', "ion-parallel-compile", "on/off",
|
||||
"Compile scripts off thread (default: off)")
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
|
Loading…
Reference in New Issue
Block a user