Dear AssemblyScript contributors. This is a fascinating project, well done. I've read a great deal of your documentation, tickets and source code and I'm still getting a handle on Binaryen but wondered if I could ask for a little more detail on what "Classes are still wonky when it comes to the details" means at a lower level? I appreciate this comment comes from the "High Level Summary". I've attempted to compile a simple TS class and it fails so I wanted to troubleshoot it a bit. If someone could point me to some existing work on this that would be a great help Just a "start here" would be helpful... I will most certainly share anything that I learn with the community. Many thanks and kind regards.
/W
To support working with classes as you would in TS it is necessary to support instance creation with the new keyword. The current implementation of new, if working at all, is unfinished and simply allocates a region of memory for the instantiated class instance to utilize (see). A memory allocator must be included in the project for this to work, i.e. through import "allocator/tlsf"; in the entry file. Since garbage collection isn't yet implemented, its memory also isn't automatically deallocated but at this point requires manually 'free'ing it with free_memory(changetype<usize>(classInstance)).
Additionally, some details haven't been implemented or sufficiently tested, yet. The implements keyword, interfaces in general or abstract methods are not implemented. These will require indirect function calls on fields that store the respective implementing function's table index. The extends keyword is somewhat implemented but hasn't been extensively tested when it comes to specializing (overriding) existing fields or their types.
Interally, memory allocators and standard library implementations use a special sort of unmanaged classes (see). One can think of them as structs with methods, basically a lower level form of classes without automatic memory management, but these don't support some things a C struct does, for example static inclusion of other struct-like elements because all structures in AssemblyScript are referenced and not passed by value. Hence, these implementations use some tricks to mimic the behaviour by managing offsets manually (see).
In short, full class support still requires some sort of GC, maybe some sort of finalizers (think arrays that also have to deallocate their backing memory), posssibly some sort of classId stored alongside each instance (think instanceof checks) and compiler-level support of virtual members by utilizing the function table.
Hope this helps :)
@dcodeIO That helps a great deal and just after I wrote the above I found a ticket related to
import "allocator/tlsf"
Thank you for providing so much information. I have enough to keep me busy for a while I think. I attempted to do some work on the Kotlin Native project around the implementation of their JS / Dom interop for WASM but they seemed uninterested (closed my ticket without saying a word) even though I solved an issue they didn't believe they had... anyway you are already a million times more helpful already. Thank you.
Kind Regards
/W
The following works just fine:
WASM Side
import "allocator/arena";
export { allocate_memory, free_memory };
namespace console {
export declare function log(int: f64): void;
}
class Point {
x: f64;
y: f64;
constructor(x: f64, y: f64) {
this.x = x;
this.y = y;
}
add(point: Point): Point {
return new Point(this.x + point.x, this.y + point.y);
}
}
var _point = new Point(100, 100);
console.log(_point.x);
var _newPoint = new Point(100, 100);
var result = _point.add(_newPoint);
console.log(result.x);
JS Side
function main(){
/** Fetch the .wasm */
fetch("/webgl.wasm").then(response => response.arrayBuffer()).then(binary => {
/** Instantiate the module */
let module = new WebAssembly.Module(binary);
window.instance = new WebAssembly.Instance(module, {
console: {
log: function(value){
console.log(value);
}
}
});
}).catch(err => { console.log(err); })
}
result

Here, using the arena allocator also has the potential to work around some of the current limitations in that it provides a reset_memory function that can be called to reset the entire memory to its initial state so one doesn't have to free manually. Special care must be taken where globals might still reference something afterwards. It simply just accumulates things and throws them all away at once. Use cases are somewhat limited of course because it might not always be applicable depending on the application, but it's also the smallest and fastest allocator for the same reasons. You probably know that already, just pinning it here so others can find it as well :)
Excellent, good tip thanks. I updated the example above (I just got that from the TS By Example site.. I don't actually write TypeScript on a regular basis my interest is in exploring WASM from a higher level language than the C's / Rust etc. I do use Kotlin daily which is why I found that project interesting but TS is fine, the concepts are all the same.... I'm primarily interested in playing with JS / DOM interop related to WebGL... I understand the limitations regarding access to the dom and the need for proxying using ints to reference JS side objects...
Also you can overload operators that not support in TS/JS:
class Point {
x: f64;
y: f64;
constructor(x: f64, y: f64) {
this.x = x;
this.y = y;
}
@operator('+')
add(point: Point): Point {
return new Point(this.x + point.x, this.y + point.y);
}
}
var p1 = new Point(100, 100);
var p2 = new Point(100, 100);
var result = p1 + p2;
That is very helpful! What about overloaded constructors? I found the TS implementation a bit limiting.
Syntactially those things walk on a thin line between staying compatible to TS and implementing what's necessary. Method overloading (if that's applicable here as well) is something that can be done in TS, but somewhat limited to falling back to the dynamic nature of JS through utilizing definitions for JS code or using union types. At this point in time my feeling is that we shouldn't split too much from TS and rather leave it up to the developer to find appropriate alternatives, syntax-wise. Here, this might be a single common, possibly private, constructor with multiple static createXY methods using it in different ways. But super should be supported, if it isn't broken.
@dcodeIO I can totally appreciate and respect that... I forked Assembly Script with a view to adding some dom interop based on the work I started on the Kotlin Native project (which they weren't interested in).. I get the feeling that you guys would be receptive to this kind of thing... Like I said my primary interest is WebGL but if I'm going to do dom interop I may as well deal with the whole thing... Most of my initial work / prototyping will be around WebGL however :-)
Sure, go ahead. Just note that host-bindings and reference types have the potential to change the interop game entirely. There's this old parser test with my initial thoughts.
@dcodeIO .. nice thanks. Who knows how long it will take for host-bindings to make it to the dev channel release builds for the four major browsers... I'm very curious to know how WASM performs now, even if there is a JS step in-between. @MaxGraey I tried your operator overloading and I'm sorry to report that it doesn't appear to be working. It compiles and runs but returns the original value. I've added the example with both a direct call to add and the operator overload example to my fork in examples/jsinterop
https://github.com/WolfieWerewolf/assemblyscript
@dcodeIO It seems operator overloading working only for specific values yet.
Yeah, operator overloading is largely unfinished. I believe indexed get/set on arrays is the only thing that is somewhat checked.
@WolfieWerewolf
Try this:
@operator("+")
static add(a: Point, b: Point): Point {
...
}
PS Nope, this doesn't work as well unfortunately
@MaxGraey thanks... that panics V8 in some cool ways but doesn't compile unfortunately.
@operator("+")
static add(point: Point): Point {
return new Point(this.x + point.x, this.y + point.y);
}
compiles but also doesn't work... Now I'm curious so I will see if I can find where you defined this...
https://github.com/AssemblyScript/assemblyscript/blob/master/src/compiler.ts#L2771 :)
@WolfieWerewolf
Actually you write different.
static add(point: Point): Point
Istead
static add(a: Point, b: Point): Point
But nevermind this non-workable in current stage
@dcodeIO this is great! I really only expressed interest in this because @MaxGraey mentioned it. I'll have more time over the weekend to review this but you can be sure that I will.
full class support still requires some sort of GC
Why is that? Why not just have a something like a destroy method that users can define, which can free stuff, and finally behind the scene free the class? I think being able to control memory is the nice part about AS Wasm. :)
We've settled for ARC meanwhile. Took a while to get right. Instead of destroy, there is a traversal mechanism now that integrates with the runtime by iterating all reference-typed children of a class with custom memory management. Array, Set and Map use this for example, and "normal" classes use minimal compiler-generated code. This makes AS a fully garbage collected language with a mechanism somewhat similar to Swift's. Of course everyone is free to include their own stdlib classes and use this mechanism (makes the most sense for collection-like classes).
Wow, That's some serious stuff. Amazing.
How does that affect runtime behavior? Is it GC'ing continuously? Or in chunks, like JS?
One thing I'm interested in is, as far as Wasm, the possibility that even if we're running in Wasm without much performance benefit for the use case, that we may still be able to have consistent predictable behavior (in contrast to JS causing pauses every second due to GC pauses).
How does AS GC compare to JS GC?
How does that affect runtime behavior? Is it GC'ing continuously? Or in chunks, like JS?
The mechanism used here is described in this paper, with modifications to what the compiler considers inherently acyclic objects, explained a bit in the readme of that repo.
My expectation is that ARC will be relatively predictable in that it does most of its work concurrently with the program, on assignments and exiting scopes, with most objects being known to be inherently acyclic due to static evaluation the compiler performs, but to know for sure we'll need some real world testing. Future interface could have a gc.collect, gc.pause and gc.resume or something to give a user more control (currently one has to call __collect manually to collect cycles). The compiler could technically even emit diagnostics to hint a programmer on possibly cyclic objects so these could be avoided altogether.
concurrently with the program, on assignments and exiting scopes
_Nice._
how to compiler it
Most helpful comment
Also you can overload operators that not support in TS/JS: