Sunday, June 12, 2011

Triggering de-optimized execution in modern JavaScript engines

Yesterday at NodeCamp.eu I learned from Mr. Aleph how one can easily get a good idea for when V8 has to run a function in un-optimized mode:
curl -s http://v8.googlecode.com/svn/branches/bleeding_edge/src/hydrogen.cc | grep "Bailout("
does the trick.
The result right now is:
owner()->Bailout("bad value context for arguments value");
owner()->Bailout("bad value context for arguments object value");
builder->Bailout("arguments object value in a test context");
void HGraphBuilder::Bailout(const char* reason) {
Bailout("function with illegal redeclaration");
Bailout("Unsupported phi-use");
return Bailout("context-allocated arguments");
return Bailout("EnterWithContextStatement");
return Bailout("ExitContextStatement");
return Bailout("SwitchStatement: too many clauses");
return Bailout("SwitchStatement: non-literal switch label");
return Bailout("ForInStatement");
return Bailout("TryCatchStatement");
return Bailout("TryFinallyStatement");
return Bailout("DebuggerStatement");
return Bailout("SharedFunctionInfoLiteral");
return Bailout("reference to rewritten variable");
return Bailout("reference to uninitialized const variable");
return Bailout("reference to const context slot");
return Bailout("reference to a variable which requires dynamic lookup");
return Bailout("Object literal with complex property");
if (!Smi::IsValid(i)) return Bailout("Non-smi key in array literal");
return Bailout("unsupported const compound assignment");
return Bailout("compound assignment to lookup slot");
return Bailout("invalid lhs in compound assignment");
return Bailout("non-initializer assignment to const");
return Bailout("assignment to const context slot");
if (proxy->IsArguments()) return Bailout("assignment to arguments");
return Bailout("assignment to LOOKUP or const CONTEXT variable");
return Bailout("invalid left-hand side in assignment");
Bailout("arguments access in inlined function");
Bailout("Function.prototype.apply optimization in inlined function");
return Bailout("call to a JavaScript runtime function");
Bailout("delete with global variable");
Bailout("delete with non-global variable");
return Bailout("invalid lhs in count operation");
return Bailout("unsupported count operation with const");
return Bailout("lookup variable in count operation");
return Bailout("Unsupported non-primitive compare");
return Bailout("unsupported declaration");
return Bailout("inlined runtime function: IsNonNegativeSmi");
return Bailout(
return Bailout("inlined runtime function: ClassOf");
return Bailout("inlined runtime function: SetValueOf");
return Bailout("inlined runtime function: RandomHeapNumber");
return Bailout("inlined runtime function: GetFromCache");
return Bailout("inlined runtime function: SwapElements");
return Bailout("inlined runtime function: MathSqrt");
return Bailout("inlined runtime function: IsRegExpEquivalent");
return Bailout("inlined runtime function: FastAsciiArrayJoin");
I was also pleased to learn that my assumption that modern JS engines de-optimize when they see use of "arguments" is no longer true. E.g. uses like fn.apply(this, arguments) which is often used in AOP style function wrapping would be able to run in optimized code. I benchmarked various uses of arguments in this JSPerf testcase. The idea is to have a loop in each test which should be easily optimized but which would take so long to run that the actual usage of "arguments" in the first statement should not influence the benchmark itself. Suggestions how to make this better, appreciated :)

Does anyone know of similar ways to find out about which elements of JS will trigger running in non-optimized mode for other (open source) engines?

4 comments:

evilpies said...
This comment has been removed by the author.
Unknown said...

It's Mr Aleph rather than Mr Raleph. See http://en.wikipedia.org/wiki/Aleph I guess.

Malte said...

@Erik: Sorry, yeah. Typo corrected.

evilpies said...

Update: So extending this a bit.
SpiderMonkey code is at http://hg.mozilla.org/tracemonkey.
To understand the naming and some internals, i recommend reading Mapping the monkeysphere by cdleary.

For TraceMonkey (our tracing JIT) getting the cases we don't optimize is kinda easy. Please notice that code we don't trace is fed into our J├Ągermonkey (the whole method JIT), so it's most of the time equally and often even faster.

>grep LeaveTrace *
When we trace into some function, that can't be traced, we stop tracing.
>grep RETURN_STOP jstracer.cpp
General operations that can't be traced.

J├Ągermonkey accepts nearly everything, only a few things like |with| aren't compiled.
But you can get a list of stuff we can't "inline cache" (again read this article by cdleary, when you don't know what this is)
>grep disable methodjit/*