Hi,

On 04/20/2013 02:41 AM, 吴伟/Wei WU wrote:
On Fri, Apr 19, 2013 at 10:01 AM, Nicolas B. Pierron <
[email protected]> wrote:

- Clarifying our heuristics, to be able to make guesses while we are
compiling in IonMonkey, and recompile without the guarded assumptions if
the bailout paths are too costly.  Our current view is mostly black & white
and only one bad use case can destroy the performances.  We need to
introduce some gray view, saying that we are compiling for the likely
sub-set and accept bailouts as part of the normal lifetime of a script.  As
of today, IonMonkey is too much like a normal compiler, we should make it
an assumption based compiler.



I found a bug (#825268
<https://bugzilla.mozilla.org/show_bug.cgi?id=825268>) that
may related to this project. According to the description of that bug I
realized that my understanding of the term 'heuristics' is relatively naive
(the strategy used by an optimization algorithm to modify the intermediate
representation is based on a few expressions calculated from the structure
of source codes.) and I think the heuristics you mentioned are more generic
than that.

If I understand correctly, 'compiling for the the likely sub-set' means
that we can compile multiple versions for a method and execute one of them
based on which one's assumptions are satisfied. for example:

function g(x){
   ...use x...
}
function f(){
   for (var i = 0; i < 10000; i++){
     if (i % 1000){
       g(i);
     }else{
       g('string');
     }
   }
}

Currently IonMonkey compiles g(x) with the guard "assert(typeof x === int)"
and the jit code will be invalidated periodically. If g(x) can be compiled
with the assumption "assert(typeof x in {int,string}" then the bailouts
would be avoided.

Am I right?

If g is not inlined, IonMonkey will compile with a guard to ensure that x is an Int. As soon as a call is made with a string, we will discard the code, and later recompile it with a larger type set (int & string). The problem is that doing operations on something which can be either an int or a string will always be slow, as we fallback on VM function calls. In your example the Int case can be executed in the baseline compiler while the string case can remain in Ion-compiled code. Doing a bailout might still be worth it compared to the price of a recompilation and a slower execution for the most-likely use case.

The question which remain behind is: "Should we keep or discard the code?". To be able to answer this question we need some kind of heuristics. And before making heuristics, we need a meaningful metric. The only metric that is meaningful in such cases is the time, but not ordinary time, as we are at the mercy of the scheduler. Bug 825268 suggests to clean-up the way we are using the use-count to make it a reliable & *determinist* source of time based on the execution of scripts.

- Adding profile guided optimization, the idea would be to profile which
branches are used and to prune branches which are unused, either while
generating the MIR Graph, or as a second optimization phase working on the
graph.  A trivial way of doing so can just look at the jump target which
have been visited so far.  A more clever one might register the transition
and use Markov chain to determine relations between branches, and duplicate
a sub-part of the graph to improve constant propagation and the range
analysis.  Before doing anything fancy like the more-clever case we still
need to check that this is worth it and see the benefit of other
optimizations if we fold the graph.

I'm interested in this idea and I'm willing to implement it. A simple and
fast algorithm may be a good choice for GSoC, while the advanced methods
require more investigation. I haven't found any branch profiling code in
the interpreter so the instrumenting mechanism must be finished first, or
we can leverage baseline compiler to generate self-instrumented jit code.
Profiling data can be saved separately or in MIR nodes as an annotation. In
the second case more other passes may benefit from it easily.

Indeed, there is no code for profiling yet. The most easiest way to think about it is to do like the write barrier of the incremental GC, i-e having a buffer (potentially circular, as the opposite of the write barrier) which is filled with jump targets. Thus, every time we jump to another location in the code we push the location of the offset in this buffer, and later we reuse this buffer to re-organize basic blocks in IonMonkey and to duplicate / prune basic blocks if needed.

Two bugs (#410994 <https://bugzilla.mozilla.org/show_bug.cgi?id=410994>, #
419344 <https://bugzilla.mozilla.org/show_bug.cgi?id=419344> ) have
mentioned PGO but may be irrelevant with this idea.

Indeed, they are irrelevant for doing PGO on JavaScript.

Other case of smaller projects might be:

- Improving our Alias Analysis to take advantage of the type set (this
might help a lot kraken benchmarks, by factoring out array accesses).

My knowledge about Alias Analysis is limited; I know the principles and
have read [4] <http://dl.acm.org/citation.cfm?id=277670> one year ago. I am
not sure how type sets can help subsequent optimizations. Would you please
provide me some more information(wiki, blogs, or papers) about it?
[4]: http://dl.acm.org/citation.cfm?id=277670

JavaScript is an untyped language, which means that any variable access can alias (reference) the same value as variable. Currently IonMonkey alias analysis is not clever and it does not use the type of object to know if one write might alias another. If two variables are holding objects with different types, then we have the guarantee that their value cannot alias each others.

One JavaScript function which looks like that:

function f(a, arr) {
  for (var i = 0; i < arr.length; i++) {
    arr[i].b = 1;
    a.b = 0;
  }
}

Can be written in a well-typed language as:

void f(Foo *a, Vector<Bar> *arr) {
  for (int i = 0; i < arr->length(); i++) {
    (*arr)[i].b = 1;
    a->b = 0;
  }
}

where Foo is a different type than Bar. Which implies that both b are part of different object, which means that the previous JavaScript function can be transformed to:

function f(a, arr) {
  a.b = 0; /* Assume shape of a is different than the shape of arr[i] */
  for (var i = 0; i < arr.length; i++) {
    arr[i].b = 1;
  }
}

In the case of kraken, the problem is that the alias analysis does not consider the type of Arrays and consider that the value they contain can be anything, including them-self. If the alias analysis were using the type information we should be able to figure out that M[i] does not alias M[i][j], and thus factor M[i] access across setters of M[i][j].


- Improve dummy functions used for asm.js boundaries.  Asm.js needs to
communicate with the DOM, and to do so it need some trampoline functions
which are used as an interface with the DOM API.  Such trampolines might
transform typed arrays into strings or objects and serialize the result
back into typed arrays.

I haven't read OdinMonkey's code yet, but I will look into it next week.
Speeding up asm.js on ARM is one of my interests.

I do not know much of the status of OdinMonkey on ARM, a few weeks ago the concern was just to be able to compile it, as the ahead-of-time compilation was taking a lot of memory at the statr-up of the application, I wonder if this is still a concern as we removed the generation of the bytecode.

What I was referring to are mostly bugs similar to Bug 860574 [1].

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=860574

--
Nicolas B. Pierron

_______________________________________________
dev-tech-js-engine-internals mailing list
[email protected]
https://lists.mozilla.org/listinfo/dev-tech-js-engine-internals

Reply via email to