8 ms·
I have thought about doing this and I just can't get around the fact that you can't get much better performance in JS. The best you could probably do is transpi
by obviouslynotme 2y ago
I have thought about doing this and I just can't get around the fact that you can't get much better performance in JS. The best you could probably do is transpile the JS into V8 C++ calls.
The really cool optimizations come from compiling TypeScript, or something close to it. You could use types to get enormous gains. Anything without typing gets the default slow JS calls. Interfaces can get reduced to vtables or maybe even straight calls, possibly on structs instead of maps. You could have an Int and Float type that degrade into Number that just sit inside registers.
The main problem is that both TS and V8 are fast-moving, non-standard targets. You could only really do such a project with a big team. Maintaining compatibility would be a job by itself.
- ch_sm 2y agoSomewhat related to this idea is AssemblyScript https://www.assemblyscript.org https://www.assemblyscript.org
- lerpgame 2y agoYea I came here to say this, actually I was able to transpile a few typescript files from my project into assembly using GPT just for fun and it actually worked pretty well. If someone simply implements a strict typescript-like linter that is a subset of javascript and typescript that transpiles into assemblyscript, I think that would work better for AOT because then you can have more critical portions of the application in AOT and other parts that are non-critical in JIT and you get best of both worlds or something like that. making js backwards compatible and AOT sounds way too complicated.
- com2kid 2y agoEcmascript 4 was an attempt to add better types to the language, which sadly failed a long time ago. It'd be nice of TS at least allowed for specifying types like integer, allowing some of the newer TS aware runtimes could take advantage of the additional info, even if the main TS->JS compilation just treated `const val: int` the same as `const val: number`. I wonder if a syntax like const counter: Number<int> would be acceptable.
- THBC 2y agoNumber is not semantically compatible with raw 64-bit integer, so you might as well wish for a native const counter = UInt64(42); The current state of the art is const counter = BigInt.asUintN(64, 42);
- obviouslynotme 2y agoYeah, that is why I said TS (or something similar). TS made some decisions that make sense at the time, but do not help compilation. The complexity of its typing system is another problem. I'm pretty sure that it is Turing-complete. That doesn't remove feasibility, but it increases the complexity of compiling it by a whole lot. When you add onto this the fact that "the compiler is the spec," you really get bogged down. It would be much easier to recognize a sensible subset of TS. You could probably even have the type checker throw a WTFisThisGuyDoing flag and just immediately downgrade it to an any.
- com2kid 2y ago> I'm pretty sure that it is Turing-complete. Because JS code can arbitrarily modify a type, any language trying to specify what the outputs of a function can be also has to be Turing complete. There are of course still plenty of types that TS doesn't bother trying to model, but it does try to cover even funny cases like field names going from kebab-case to camelCase.
- the_imp 2y agoWith Extractors [1] (currently at Stage 1), you could define something like this to work: const Integer = { [Symbol.customMatcher]: (value) => [Number.parseInt(value)] } const Integer(counter) = 42.56; // counter === 42 [1] https://github.com/tc39/proposal-extractors https://github.com/tc39/proposal-extractors
- wk_end 2y agoAt least without additional extensions, TypeScript would help less than you think. It just wasn’t designed for the job. As a simple example - TypeScript doesn’t distinguish between integers and floats; they’re all just numbers. So all array accesses need casting. A TypeScript designed to aid static compilation likely would have that distinction. But the big elephant in the room is TypeScript’s structural subtyping. The nature of this makes it effectively impossible for the compiler to statically determine the physical structure of any non-primitive argument passed into a function. This gives you worse-than-JIT performance on all field access, since JITs can perform dynamic shape analysis.
- obviouslynotme 2y agoOutside of really funky code, especially code originally written in TS, you can assume the interface is the actual underlying object. You could easily flag non-recognized-member accesses to interfaces and then degrade them back to object accesses.
- wk_end 2y agoYou’re misunderstanding me, I think. Suppose you have some interface with fields a and c. If your function takes in an object with that interface and operates on the c field, what you want is to be able to do is compile that function to access c at “the address pointed to by the pointer to the object, plus 8” (assuming 64-bit fields). Your CPU supports such addressing directly. Because of structural subtyping, you can’t do that. It’s not unrecognized member. But your caller might pass in an object with fields a, b, and c. This is entirely idiomatic. Now c is at offset 16, not 8. Because the physical layout of the object is different, you no longer have a statically known offset to the known field.
- obviouslynotme 2y agoI would bet that, especially outside of library code, 95+% of the typed objects are only interacted with using a single interface. These could be turned into structs with direct calls. Outside of this, you can unify the types. You would take every interface used to access the object and create a new type that has all of the members of both. You can then either create vtables or monomorphize where it is used in calls. At any point that analysis cannot determine the actual underlying shape, you drop to the default any.
- refulgentis 2y agoYou say you have "thought about doing this"..."[but] you can't get much better performance", then describe the approach requiring things that are described first-thing, above the fold, on the site. Did the site change? Or am I missing something? :)
- bobvarioa 2y agoContributor to Porffor here! I actually disagree, there's quite a lot that can be improved in JS during compile time. There's been a lot of work creating static type analysis tools for JS, that can do very very thorough analysis, an example that comes to mind is [TAJS](https://www.brics.dk/TAJS/ https://www.brics.dk/TAJS/) although its somewhat old.
- attractivechaos 2y ago> there's quite a lot that can be improved in JS during compile time I wonder how much performance gain you expect to achieve. For simple CPU-bounded tasks, C/Rust/etc is roughly three times as fast as v8 and Julia, which compiles full scripts and has good type analysis, is about twice as fast. There is not much room left. C/Rust/etc can be much faster with SIMD, multi-threading and fine control of memory layout but an AOT JS compiler might not gain much from these.
- itsTyrion 2y agoHonestly, I’m fine with only some speed up compared to V8, it’s already pretty fast… My issue with desktop/mobile apps using web tech (JS) is mostly the install size and RAM hunger.
- mst 2y ago[raises hand] I'd be fine with no speedup at all if I can get more reasonable RAM usage and an easily linkable .so out of the deal.
- attractivechaos 2y agoThe "node" binary on my laptop is 45MB in size. I guess the browser component may take more disk space than JS runtime. Similarly, I am not sure whether JS runtime or webpage rendering takes more RAM. If it is the latter, an AOT compiler won't help much.
- jitl 2y agoIn my mind, the big room for improvement is eliminating the cost to call from JS into other native languages. In node/V8 you pay a memcopy when you pass or return a string from C++ land. If an ahead of time compiler for JS can use escape analysis or other lifetime analysis for string or byte array data, you could make I/0 or at least writes from JavaScript to, for example, sqlite, about twice as fast.
- myko 2y agoDart, maybe, but it lost
- singpolyma3 2y agoYou can do inference and only fall back to Dynamic/any when something more specific can't be globally inferred in the program. For an optimization pass this is an option.