On 2010-05-07, at 12:52 AM, Maxime Chevalier-Boisvert wrote:
please explain why you propose using a C++/Java style OOP style. Why
not use the procedural variant?
For one, I believe the OOP style is the one we agreed on when we discussed this earlier today in your office. The OOP style is the one you've already been using in the parser code you've showed us.
The code I showed you was my first attempt to explore how to express an OOP style in Javascript. There is more than one way, in fact you mentioned yesterday having a preference for defining the methods outside the constructor. In my message I mention the x.meth() and meth(x) variants, which are both OOP styles based on slightly different syntax. It is important to make our design decisions in a reasoned way, which is why I am questioning the choice of using the x.meth() variant. We should analyze our options before we commit to a particular variant. If we do adopt the x.meth() variant we should understand why and accept the consequences. I raise the issue that the x.meth() variant will very likely be less efficient than the meth(x) variant, at least until our compiler is "sufficiently intelligent". I did a simple speed test with V8 and the meth(x) variant is 10% faster than the x.meth() variant. I think the difference could be larger if the compiler did some simple inlining optimization. This can easily be done by having a compiler switch (or annotation) to tell the compiler it can assume toplevel functions will not be redefined (which I hope will be the case for the source code of the Tachyon compiler), and doing a "whole program" compilation (by basically concatenating all the source code of the compiler's modules).
By the way, the argument that "a sufficiently intelligent compiler should optimize this away" is a dangerous one that has caused much grief in the past in the field of programming languages and compilers. It is better to plan for the worst (i.e. what we know is doable) and hope for the best (i.e. what we are trying to demonstrate).
Another reason is that the OOP style is clearer, and allows us to use things like the prototype chain for inheritance, as well as other JavaScript facilities. The whole point of programming this VM in JavaScript is to simplify the implementation and make us more productive.
I'm not against an OOP style, but as I say there is more than one way to express OOP in JS.
There is also the issue of name collision. Your method won't be called meth(). It will need to be called FooMeth. Or, in a more realistic example BasicBlockGetParents(), or something of the sort. Longer, uglier method names everywhere.
I'm not sure this is as big an issue as you say because the Tachyon compiler will in any case have to have a mechanism to separate the namespace of the compiler's code and the code of the application. In other words, there has to be more than one toplevel environment otherwise the compiler and the application will interfere. The namespace could be implemented by having the compiler automatically append a prefix to all the global names it sees, with a prefix that is a command-line option of the compiler. So all the compiler's code would be in the compiler's namespace. Note that the prefix could be something like "%tachyon_" (which is illegal in pure JS), so that no interference with the application is possible.
We also still would need to use local functions in the case where virtual method calls are actually what we want, thereby forcing us to use x.meth1() in some cases, and FooMeth2(x) in other cases.
This is not necessarily bad, because it highlights the use of virtual methods. Moreover, this can still be hidden in a toplevel function if that's desired, i.e.
function FooMeth1(self) { return self.meth1(); }
Let's not adopt coding standards that complicate our lives and essentially turn JavaScript into C, and make our lives harder, because then, we might as well use a *proven* approach, such as implementing this all in C++. That would perform well. I can guarantee it. It has type annotations, and the compilers already do inlining for you. We wouldn't even need to worry about all this bootstrapping business...
This "bootstrapping business" is part of our research objectives and is (probably) an important part of Erick's MS thesis!
I can guarantee that using C/C++ will cause problems (as we have discussed previously).
It will be hard for a compiler to determine that the call x.meth()
is actually calling the method "meth" in Foo (not only does the compiler have to determine that x is always an instance of the constructor Foo, but you have to ensure that there have been no mutations of the field "meth" in the instance bound to x). So it is hard to inline that method in place of the call x.meth().
Being able to know this kind of thing is the whole point of my Ph.D. thesis, as far as I'm concerned. A pretty simple flow-insensitive analysis could deduce those facts, and I intend to do better than that. You also have to realize. Knowing the type of x in a call to x.foo() is essential to gaining high performance levels. Inlining foo(x) won't gain you that much if your foo() function is naively compiled and full of dynamic dispatch. Furthermore, I believe it's possible for us to set some fields as read-only, thereby avoiding the problem of the field being modified.
I understand you are trying to demonstrate this in your Ph.D. I think it is much harder than you think ("simple flow-insensitive analysis"). Perhaps you should look into this soon to explore how it can be done for JS. This will shed some light on the issues specific to JS. Unfortunately, you can only prove this for sure by building a compiler and implementing your ideas. I guess we have a chicken and egg problem!
By the way, how can fields be marked as "read-only" in Javascript?
Moreover, space has to be reserved in all instances of Foo for all
the methods in the non-optimized case, and it is not clear how these fields can be eliminated.
Not if the fields are read only. Not if the methods belong to a class we inherit from in the prototype hierarchy. Furthermore, is space really that much of an issue at this point?
Imagine a 2D point "class" with 2 fields (x and y) and 20 methods (add points, compare points, print points, magnitude, etc). That would entail a 1000% space overhead! That is significant. A similar situation could happen for Tachyon, think of an AST node class where AST nodes have few fields but a large number of methods. In a fancy compiler, it is not unusual to see very high memory usage in the analysis and optimization phases (for doing various flow analyses). GCC for example can use gigabytes of memory for compiling some programs. If we use 10 times more memory than we need to this will hinder the kinds of analyses the compiler can perform and prevent us from achieving our objectives.
Marc