31 comments

[ 3.6 ms ] story [ 79.0 ms ] thread
This looks a lot like typescript. Is there any particular reason for not either 1) adding a wasm backend to tsc (which tsc may or may not allow), or 2) implementing a strict subset of typescript? It just seems like piggybacking off of a larger language would help this sort of idea gain traction.
TypeScript is garbage collected, like JavaScript, while WebAssembly is not. So directly compiling TypeScript to WebAssembly isn't possible, without also compiling in a userspace GC implementation.

I'm not sure what Thinscript does for this. edit: README says nothing is deleted, so I guess that each new() allocates but nothing is freed?

I'm planning on experimenting with either pure reference counting (Swift), reference counting + cycle collection (Python), or garbage collection. I'd love to hear about what people are doing for emscripten actually. I've read that Unity uses IL2CPP which uses the Boehm GC but I couldn't find anywhere that described how that actually works in emscripten. Local variables correspond to "registers" in machine code, but how does the garbage collector read them so it can scan them during a collection?
Yeah, a bunch of projects use Boehm (or their own GC, like the Lua VM). For large projects the extra size of including a GC implementation isn't so bad (but for small ones it can be). And it can be fairly fast.

A problem is that you can't have GC cycles with the outside world, so you can't interact with the DOM in a normal way. In particular, your objects can't be automatically GC'd if they can be held on to by anything outside of the compiled linear memory. That's why emscripten's C++ <-> JS binding tool has a .destroy() method on each wrapped C++ object, as they need to be manually destroyed by the user.

For local variables, they aren't scannable by themselves. You need to emit special inline code that writes them to linear memory. In C++, you can do this with an RAII class for a reference, for example.

One trick you can use (which Chromium+Oilpan uses) is to only GC in between turns of the event loop. In that case, your set of roots is exactly equal to the accessible global variables plus everything accessible from the DOM (including event handlers). In particular, nothing is on the stack, so there is no need to introspect stack frames.
I considered that but I'm worried that won't work with WebAssembly because it's a more memory-constrained environment. Your app may generate a ton of data in a frame and run off the end of the address space. I guess I'm probably just thinking back to asm.js where you sometimes only have 256mb to work with. I'll try this first at least since it makes stuff easier.
Just go with pure reference counting. It'll be faster and easier.
That's a really clever idea. Thanks for sharing.
It does say that it is inspired by Typescript.
Yes. TypeScript doesn't have a sound type system and it comes with a lot of baggage:

* Using hashtables in TypeScript relies on the representation of JavaScript objects with strings as properties. Emulating JavaScript's in-memory representation in an ahead-of-time compiled language isn't a good idea for performance reasons.

* TypeScript only has a single number type (double) which is pretty slow if you actually need to work with integers. JavaScript VMs do a lot of work to figure out which numbers are integers using information that's only available at runtime. Using explicit integer types means the generated code is a lot tighter.

* A lot of existing TypeScript code uses structural typing to model dynamic type contracts present in today's JavaScript code, but this is usually a loosely-typed approximation and not something that a compiler can rely on. It's more just to improve tooling for developers.

* TypeScript uses exceptions which are hard to emulate efficiently in native code.

* I want to be able to add certain things and extend the language, so I don't want to limit myself to TypeScript. For example, I'm considering a preprocessor for conditional compilation and something similar to C#'s unsafe syntax for pointer manipulation.

All these reasons in Typescript are intentionally designed so that it's easy to work with the rest of the JS ecosystem. Good tooling + rapid prototyping is a strength not a weakness.
But they are weaknesses when designing a languages to compile to wasm. I love TypeScript and the Microsoft team has done a fantastic job with it, but that doesn't mean it's the right tool for this particular job.
I don't consider the design of TypeScript a weakness at all. TypeScript is really well designed and is great at what it does. The type system they came up with is pretty optimal for modeling real-world JavaScript code. But the tuning they did around the JavaScript ecosystem means it's not a great language to use for generating efficient native code. I started ThinScript in part for exactly that reason. I want good tooling and rapid prototyping, just for native development instead of web development. That requires a different set of trade-offs and design decisions.
It sounds like your issue here is that TypeScript is a superset of JavaScript and comes with JavaScript's complications/inefficiencies. Do you agree with that assessment?
Yes. As the parent comment said, you could try to to choose a subset of TypeScript that's relatively efficient to help avoid some of the issues with JavaScript. The problem is that the TypeScript language itself lacks certain primitives required for efficient ahead-of-time compiled code (integers, for example).
Thinscript looks interesting, aside from coding in it directly it could prove to be a useful transpiler target, allowing non-web-languages to make better use of WebAssembly as both WebAssembly and Thinscript mature.
Yes! I considered this and it's an interesting idea. ThinScript may end up being pretty opinionated about memory allocation though (I'm not sure yet if I'm doing pure reference counting, pure garbage collection, or some combination of both) so it could be a useful higher-level target language.
I wonder if there's a plan for generics support?
I plan to do it eventually. I've implemented generics before so that's not the issue. It's just hard to get the design of generics right when the compiler has multiple targets. You want to emit compact JavaScript code and at the same time support the generation of efficient machine code. C++ does generic code through template expansion but that ends up generating a ton of code, which is bad for code delivered in a network environment. It may make sense to make different tradeoffs given the context of this project. I'm planning on putting off generics for a while though until more pieces fall into place.
Oh wow I did not expect this to be up here this soon. Warning: two-week-old project. I've got a lot of plans for this but it'll take at least a few months for this to be anywhere near useful. Right now it's just an experiment!
^ constexpr is the author
I'm glad it did though—this is awesome! Good luck with it, and perhaps I'll contribute something (do you plan for it to be garbage collected?).
Do you plan to support return type inference? Will classes be accessible before they are declared? Why "int" (opposed to supporting the wasm type names)? (I'm interested because I was planning to add a typescript parsing demo for an expression parser (https://github.com/stefanhaustein/expressionparser), but typescript turned out to be a bit more tricky than anticipated originally)
I'm not planning on supporting return type inference since I actually don't like that feature of TypeScript, but I could be convinced otherwise. It really harmed API readability for me when I tried to use it.

All declarations in ThinScript are order-independent right now so classes are accessible everywhere simultaneously. I'm getting away with that because I'm currently requiring all global initializers to be constant. One idea I had that would allow for non-constant global initializers without really complex static analysis to determine initialization order was to initialize non-constant global variables on first access instead of on startup.

I chose "int" because I was aiming for familiarity with existing languages. I'm also planning for WebAssembly to be just one target among many targets so I don't want to align too closely with WebAssembly. Some other targets I'm considering are C, x86, Swift, and C#, for example.

Are parameter types mandatory or do omitted types imply "any"?

Do you support "let" (the example uses "var"?)

There is no "any" type since ThinScript is an ahead-of-time compiled language and emulating dynamic types is a non-goal. Types are required and omitting them is a syntax error. Right now "let" is an alias for "var" and "var" behaves like "let".
What about implicit conversions to boolean? Is "if (1)" legal? Does "==" require equal types (or compatible types) on both sides?
Right now the only implicit conversions are null to nullable types and smaller integers to larger ones. There is no "===" right now and "==" requires equal types. I may consider aligning this more with JavaScript in the future. There's a live compiler demo at http://evanw.github.io/thinscript/ where you can try out all of this yourself :)
Please don't add type inference. Coming from Scala it seems such a beautiful concept and a productivity booster when you are writing code but then you come back a few months later / see someone else's code and quickly figure out what a time waste and a productivity drain it really is.
If I may, that just means that either 1) your tooling is bad 2) Scala's type system is too complex.

I don't know scala much, but it's not a problem in other languages with full inference (particularly OCaml, but it's not a huge problem in Haskell either).

Just because type inference exists doesn't mean that you have to use in every case. Better to establish best practices when to use type annotations.