diff --git a/js/src/ion/AsmJS.cpp b/js/src/ion/AsmJS.cpp index ddbee04c707..29abb895f0c 100644 --- a/js/src/ion/AsmJS.cpp +++ b/js/src/ion/AsmJS.cpp @@ -932,6 +932,7 @@ class MOZ_STACK_CLASS ModuleCompiler MIRTypeVector argTypes_; RetType returnType_; mutable Label code_; + unsigned compileTime_; public: Func(ParseNode *fn, ParseNode *body, MoveRef types, RetType returnType) @@ -939,7 +940,8 @@ class MOZ_STACK_CLASS ModuleCompiler body_(body), argTypes_(types), returnType_(returnType), - code_() + code_(), + compileTime_(0) {} Func(MoveRef rhs) @@ -947,7 +949,8 @@ class MOZ_STACK_CLASS ModuleCompiler body_(rhs->body_), argTypes_(Move(rhs->argTypes_)), returnType_(rhs->returnType_), - code_(rhs->code_) + code_(rhs->code_), + compileTime_(rhs->compileTime_) {} ~Func() @@ -964,6 +967,8 @@ class MOZ_STACK_CLASS ModuleCompiler const MIRTypeVector &argMIRTypes() const { return argTypes_; } RetType returnType() const { return returnType_; } Label *codeLabel() const { return &code_; } + unsigned compileTime() const { return compileTime_; } + void accumulateCompileTime(unsigned ms) { compileTime_ += ms; } }; class Global @@ -1100,10 +1105,19 @@ class MOZ_STACK_CLASS ModuleCompiler typedef HashMap ExitMap; private: + struct SlowFunction + { + PropertyName *name; + unsigned ms; + unsigned line; + unsigned column; + }; + typedef HashMap MathNameMap; typedef HashMap GlobalMap; typedef Vector FuncVector; typedef Vector GlobalAccessVector; + typedef Vector SlowFunctionVector; JSContext * cx_; MacroAssembler masm_; @@ -1123,6 +1137,10 @@ class MOZ_STACK_CLASS ModuleCompiler char * errorString_; ParseNode * errorNode_; + + int64_t usecBefore_; + SlowFunctionVector slowFunctions_; + TokenStream & tokenStream_; DebugOnly currentPass_; @@ -1147,6 +1165,8 @@ class MOZ_STACK_CLASS ModuleCompiler globalAccesses_(cx), errorString_(NULL), errorNode_(NULL), + usecBefore_(PRMJ_Now()), + slowFunctions_(cx), tokenStream_(ts), currentPass_(1) {} @@ -1156,7 +1176,7 @@ class MOZ_STACK_CLASS ModuleCompiler tokenStream_.reportAsmJSError(errorNode_->pn_pos.begin, JSMSG_USE_ASM_TYPE_FAIL, errorString_); - JS_smprintf_free(errorString_); + js_free(errorString_); } // Avoid spurious Label assertions on compilation failure. @@ -1235,6 +1255,18 @@ class MOZ_STACK_CLASS ModuleCompiler return false; } + static const unsigned SLOW_FUNCTION_THRESHOLD_MS = 250; + + bool maybeReportCompileTime(ParseNode *fn, unsigned ms) { + if (ms < SLOW_FUNCTION_THRESHOLD_MS) + return true; + SlowFunction sf; + sf.name = FunctionName(fn); + sf.ms = ms; + tokenStream_.srcCoords.lineNumAndColumnIndex(fn->pn_pos.begin, &sf.line, &sf.column); + return slowFunctions_.append(sf); + } + /*************************************************** Read-only interface */ JSContext *cx() const { return cx_; } @@ -1456,6 +1488,31 @@ class MOZ_STACK_CLASS ModuleCompiler module_->exportedFunction(exportIndex).initCodeOffset(masm_.size()); } + void buildCompilationTimeReport(ScopedJSFreePtr *out) { + int64_t usecAfter = PRMJ_Now(); + int msTotal = (usecAfter - usecBefore_) / PRMJ_USEC_PER_MSEC; + ScopedJSFreePtr slowFuns; + if (!slowFunctions_.empty()) { + slowFuns.reset(JS_smprintf("; %d functions compiled slowly: ", slowFunctions_.length())); + if (!slowFuns) + return; + for (unsigned i = 0; i < slowFunctions_.length(); i++) { + SlowFunction &func = slowFunctions_[i]; + JSAutoByteString name; + if (!js_AtomToPrintableString(cx_, func.name, &name)) + return; + slowFuns.reset(JS_smprintf("%s%s:%u:%u (%ums, %g%%)%s", slowFuns.get(), + name.ptr(), func.line, func.column, func.ms, + double(func.ms)/double(msTotal), + i+1 < slowFunctions_.length() ? ", " : "")); + if (!slowFuns) + return; + } + } + out->reset(JS_smprintf("total compilation time %dms%s", + msTotal, slowFuns ? slowFuns.get() : "")); + } + bool finish(ScopedJSDeletePtr *module) { // After finishing, the only valid operation on an ModuleCompiler is // destruction. @@ -4633,6 +4690,8 @@ CheckVariableDecls(ModuleCompiler &m, FunctionCompiler::LocalMap *locals, ParseN static MIRGenerator * CheckFunctionBody(ModuleCompiler &m, ModuleCompiler::Func &func, LifoAlloc &lifo) { + int64_t before = PRMJ_Now(); + // CheckFunctionSignature already has already checked the // function head as well as argument type declarations. The ParseNode* // stored in f.body points to the first non-argument statement. @@ -4675,6 +4734,8 @@ CheckFunctionBody(ModuleCompiler &m, ModuleCompiler::Func &func, LifoAlloc &lifo f.returnVoid(); JS_ASSERT(!tempAlloc->rootList()); + func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC); + return mirGen; } @@ -4682,6 +4743,8 @@ static bool GenerateAsmJSCode(ModuleCompiler &m, ModuleCompiler::Func &func, MIRGenerator &mirGen, LIRGraph &lir) { + int64_t before = PRMJ_Now(); + m.masm().bind(func.codeLabel()); ScopedJSDeletePtr codegen(GenerateCode(&mirGen, &lir, &m.masm())); @@ -4714,6 +4777,10 @@ GenerateAsmJSCode(ModuleCompiler &m, ModuleCompiler::Func &func, // Align internal function headers. m.masm().align(CodeAlignment); + func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC); + if (!m.maybeReportCompileTime(func.fn(), func.compileTime())) + return false; + // Unlike regular IonMonkey which links and generates a new IonCode for // every function, we accumulate all the functions in the module in a // single MacroAssembler and link at end. Linking asm.js doesn't require a @@ -4743,6 +4810,8 @@ CheckFunctionBodiesSequential(ModuleCompiler &m) IonContext icx(m.cx()->compartment(), &mirGen->temp()); + int64_t before = PRMJ_Now(); + if (!OptimizeMIR(mirGen)) return m.fail(func.fn(), "internal compiler failure (probably out of memory)"); @@ -4750,6 +4819,8 @@ CheckFunctionBodiesSequential(ModuleCompiler &m) if (!lir) return m.fail(func.fn(), "internal compiler failure (probably out of memory)"); + func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC); + if (!GenerateAsmJSCode(m, func, *mirGen, *lir)) return false; @@ -4798,8 +4869,12 @@ GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSPa if (!task) return false; + ModuleCompiler::Func &func = m.function(task->funcNum); + + func.accumulateCompileTime(task->compileTime); + // Perform code generation on the main thread. - if (!GenerateAsmJSCode(m, m.function(task->funcNum), *task->mir, *task->lir)) + if (!GenerateAsmJSCode(m, func, *task->mir, *task->lir)) return false; group.compiledJobs++; @@ -5996,7 +6071,8 @@ GenerateExits(ModuleCompiler &m) } static bool -CheckModule(JSContext *cx, TokenStream &ts, ParseNode *fn, ScopedJSDeletePtr *module) +CheckModule(JSContext *cx, TokenStream &ts, ParseNode *fn, ScopedJSDeletePtr *module, + ScopedJSFreePtr *compilationTimeReport) { ModuleCompiler m(cx, ts); if (!m.init()) @@ -6057,11 +6133,15 @@ CheckModule(JSContext *cx, TokenStream &ts, ParseNode *fn, ScopedJSDeletePtr compilationTimeReport; ScopedJSDeletePtr module; - if (!CheckModule(cx, ts, fn, &module)) + if (!CheckModule(cx, ts, fn, &module, &compilationTimeReport)) return !cx->isExceptionPending(); module->initPostLinkFailureInfo(options, scriptSource, bufStart, bufEnd); @@ -6118,7 +6199,7 @@ js::CompileAsmJS(JSContext *cx, TokenStream &ts, ParseNode *fn, const CompileOpt SetAsmJSModuleObject(moduleFun, moduleObj); - return Warn(cx, JSMSG_USE_ASM_TYPE_OK); + return Warn(cx, JSMSG_USE_ASM_TYPE_OK, compilationTimeReport); } JSBool diff --git a/js/src/ion/AsmJS.h b/js/src/ion/AsmJS.h index c0147c7be93..7e7c1701ff3 100644 --- a/js/src/ion/AsmJS.h +++ b/js/src/ion/AsmJS.h @@ -139,10 +139,11 @@ struct AsmJSParallelTask uint32_t funcNum; // Index |i| of function in |Module.function(i)|. ion::MIRGenerator *mir; // Passed from main thread to worker. ion::LIRGraph *lir; // Passed from worker to main thread. + unsigned compileTime; AsmJSParallelTask(size_t defaultChunkSize) : lifo(defaultChunkSize), - funcNum(0), mir(NULL), lir(NULL) + funcNum(0), mir(NULL), lir(NULL), compileTime(0) { } void init(uint32_t newFuncNum, ion::MIRGenerator *newMir) { diff --git a/js/src/js.msg b/js/src/js.msg index 6be024d6e47..5fb4ecc2ba3 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -394,7 +394,7 @@ MSG_DEF(JSMSG_CURLY_AFTER_MODULE, 340, 0, JSEXN_SYNTAXERR, "missing } after MSG_DEF(JSMSG_USE_ASM_DIRECTIVE_FAIL, 341, 0, JSEXN_SYNTAXERR, "'use asm' directive only works on function code") MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 342, 1, JSEXN_TYPEERR, "asm.js type error: {0}") MSG_DEF(JSMSG_USE_ASM_LINK_FAIL, 343, 1, JSEXN_TYPEERR, "asm.js link error: {0}") -MSG_DEF(JSMSG_USE_ASM_TYPE_OK, 344, 0, JSEXN_ERR, "successfully compiled asm.js code") +MSG_DEF(JSMSG_USE_ASM_TYPE_OK, 344, 1, JSEXN_ERR, "successfully compiled asm.js code ({0})") MSG_DEF(JSMSG_BAD_ARROW_ARGS, 345, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)") MSG_DEF(JSMSG_YIELD_IN_ARROW, 346, 0, JSEXN_SYNTAXERR, "arrow function may not contain yield") MSG_DEF(JSMSG_WRONG_VALUE, 347, 2, JSEXN_ERR, "expected {0} but found {1}") diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp index 1835cc9503d..0585c03a742 100644 --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -346,6 +346,8 @@ WorkerThread::handleAsmJSWorkload(WorkerThreadState &state) do { ion::IonContext icx(asmData->mir->compartment, &asmData->mir->temp()); + int64_t before = PRMJ_Now(); + if (!OptimizeMIR(asmData->mir)) break; @@ -353,6 +355,9 @@ WorkerThread::handleAsmJSWorkload(WorkerThreadState &state) if (!asmData->lir) break; + int64_t after = PRMJ_Now(); + asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC; + success = true; } while(0); state.lock();