Work on kide-som is is at an end. "All" tests green. And lessons learned.
Kide-som , ie a "version" of kide that compiles SOM, works by transpiling SOM (a stripped down Smalltalk) to go.
This works by transpiling methods to functions, having a general object, using go anonymous functions as blocks and vargs for calling.
After initially implementing method resolve, i made a cache for that, implemented some type inferrence and for known case implemented inlining of some methods (while etc)
Finishing off i implemented a basic Interpreter, to compare numers (in the repo)
Not having known if the approach is even possible (especially not knowing go or Smalltalk beforehand), i am pleased with the result. It is possible.
In numbers i got 139 of 240 tests to work. The main goal was to get to the AreWeFastYet benchmarks and i did so. At least the simple ones in the top directory all work.
So i started with this feeling that it should be possible to compile a dynamic language to a static one. Off course without being so super dynamic, but that is really more down to the build system than the language. I benefitted a lot from go's vargs, closures and gc, but the basics are probably transferrable to other static languages (not that i want to)
Compared to the interpreter compiling proved 10 times faster. Faster also than the original java implementation. But ..
The surprising fact of the benchmarks was that they were quite slow, slower than ruby and much slower than Java. Though these are not som, just AreWeFastYet implementations of the same algorithms, it is still a blow. This is to some degree due to the way Smalltalk just wraps everything again and again, in soo many blocks.
The profiling revealed that the go code spends over 90% of it's time in memory management, both creating and collecting. This is due to go not allowing casts (rather converting) and boxing/unboxing and related gc costs.
Which brings me to the first main point. Total control of the memory is a must. Go was a bad choice for that, C would have been better. Go checks all casts which takes a lot of time for a lot of casts. And interface instantiation and gc takes more than 90% of the time. Since we build a type system this is quite extra, but did lead to the realisation that one should keep values and objects very distinct. Passing values around and keeping track of their types is one of the main tasks.
Another thing one would need better control over is calling or maybe one should say jumping. Type invariants i noticed get broken quite deep inside call stacks and one would need a way to switch to different code that are just not possible with normal call semantics. Tbc.
The other main lesson here is that optimisation needs to be done at a completely different level. While caching and inlining both brought their 30 or 50%, they are not automatic. Also with small methods (as prevalent in oo), even these basic methods would have to be applied over several levels.
So what is really needed is a way to auomatically (automagically?) optimise to a deep level. I am thinking in the lines of partial evaluation (or tracing, or is that the same). Pulling out the constants / assumptions from the code over several levels of method involation and completely optimising many things awway. Ie object creation, object type checks, method lookups.
This requires a way to detect assumption (maybe explicitly flag them?), and then, as hinted above, to exit to code that is either more general (ie has all the checks in, eg an interpreter), or to a different branch (or instantiation?) of the code with the "right" kind of assumptions.
Sooo, some deep thinking is in order :-)