[HN Gopher] Porffor: A from-scratch experimental ahead-of-time J...
___________________________________________________________________
Porffor: A from-scratch experimental ahead-of-time JS engine
Author : bpierre
Score : 217 points
Date : 2024-07-30 18:55 UTC (4 hours ago)
(HTM) web link (porffor.dev)
(TXT) w3m dump (porffor.dev)
| rubenfiszel wrote:
| At windmill.dev, when users deploy their code, we use Bun build
| (which is similar to esbuild) to bundle their scripts and all
| their dependencies into a single js file to load which improve
| cold start and memory usage. We store the bundle on s3 because of
| the size of the bundles.
|
| If we could bundle everything to native that would completely
| change the game since as good as bun's cold start is, you can't
| beat running straight native with a small binary.
| solumos wrote:
| Just out of curiosity, how does the performance (compilation +
| runtime) compare to something like bun[0]?
|
| [0] https://bun.sh/
| ijustlovemath wrote:
| I'd love to know if there's a way to compile NodeJS to native
| libraries with this! I have a process [0], but it's a bit hacky
| and error prone
|
| [0] - https://github.com/ijustlovemath/jescx
| awesomekling wrote:
| Oliver (the main developer) just announced that they're going to
| work full time on Porffor:
| https://x.com/canadahonk/status/1818347311417938237
| simlevesque wrote:
| Financed by defunkt[1], GitHub cofounder and ex CEO, for an
| undisclosed future project.
|
| [1] https://news.ycombinator.com/user?id=defunkt
| pityJuke wrote:
| Also funded the Ladybird browser recently. Seems to like his
| web.
| obviouslynotme wrote:
| 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 wrote:
| Somewhat related to this idea is AssemblyScript
| https://www.assemblyscript.org
| com2kid wrote:
| Ecmascript 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 wrote:
| Number 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 wrote:
| Yeah, 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 wrote:
| > 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 wrote:
| With 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
| wk_end wrote:
| At 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 wrote:
| Outside 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 wrote:
| You'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 wrote:
| I 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.
| munificent wrote:
| I think the even bigger elephant in the room is that
| TypeScript's type system is unsound. You can have a function
| whose parameter type is annotated to be String and there's
| absolutely no guarantee that every call to that function will
| pass it a string.
|
| This isn't because of `any` either. The type system itself
| deliberately has holes in it. So any language that uses
| TypeScript type annotations to generate faster/smaller code
| is opening itself to miscompiling code and segfaults, etc.
| wk_end wrote:
| So - I know this in theory, but avoided mentioning it
| because I couldn't immediately think of any persuasive
| examples (whereas subtype polymorphism is a core, widely
| used, wholly unrestricted property of the language) that
| didn't involve casts or any/unknown or other things that
| people might make excuses for.
|
| Do you have any examples off the top of your head?
| neongreen wrote:
| https://counterexamples.org/ is a good collection of
| unsoundness examples in various languages.
|
| For TypeScript, they list an example with `instanceof`:
|
| https://counterexamples.org/polymorphic-union-
| refinement.htm...
|
| In the playground:
|
| https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9m
| ABA...
| curtisblaine wrote:
| It might be useful for an interpreter though. I believe
| that in V8 you have this probabilistic mechanism in which
| if the interpreter "learns" that an array contains e.g.
| numbers consistently, it will optimize for numbers and
| start accessing the array in a more performance way.
| Typescript could be used to inform the interpreter even
| before execution. (My supposition, I'm not an interpreter
| expert)
| pjmlp wrote:
| Such Typescript already exists, Static Typescript,
|
| https://makecode.com/language
|
| Microsoft's AOT compiler for MakeCode, via C++.
| cprecioso wrote:
| > A TypeScript designed to aid static compilation likely
| would have that distinction.
|
| AssemblyScript (https://www.assemblyscript.org/) is a
| TypeScript dialect with that distinction
| refulgentis wrote:
| You 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? :)
| syrusakbary wrote:
| It's awesome to see how more JS runtimes try to approach Wasm.
| This project reminds me to Static Hermes (the JS engine from
| Facebook to improve the speed of React Native projects on iOS and
| Android).
|
| I've spent a bit of time trying to review each, so hopefully this
| analysis will be useful for some readers. What are the main
| commonalities and differences between Static Hermes and Porffor?
| * They both aim for JS test262 conformance [1] * Porffor
| supports both Native and Wasm outputs while Static Hermes is
| mainly focused on Native outputs for now * Porffor is self-
| hosted (Porffor is written in pure JS and can compile itself),
| while Static Hermes relies on LLVM * Porffor currently
| doesn't support async/promise/await while Static Hermes does
| (with some limitations) * Static Hermes is written in C++
| while Porffor is mainly JS * They both support TypeScript
| (although Static Hermes does it through transpiling the TS AST to
| Flow, while Porffor supports it natively) * Static Hermes
| has a fallback interpreter (to support `eval` and other hard-to-
| compile JS scenarios), while Porffor only supports AOT compiling
| (although, as I commented in other thread here, it maybe be
| possible to support `eval` in Porffor as well)
|
| In general, I'm excited to see if this project can gain some
| traction so we can speed-up Javascript engines one the Edge!
| Context: I'm Syrus, from Wasmer [3]
|
| [1] https://github.com/facebook/hermes/discussions/1137
|
| [2] https://github.com/tc39/test262
|
| [3] https://wasmer.io
| jonathanyc wrote:
| Just wanted to say I really appreciated the high-quality
| comparison. How something compares to existing work is my #1
| question whenever I read an announcement like this.
| syrusakbary wrote:
| Thanks!
| saagarjha wrote:
| What happens when someone calls eval?
| syrusakbary wrote:
| Since Porffor can compile itself (you can run the compiler
| inside of Porffor), any calls to eval could be compiled to Wasm
| (via executing the Porffor compiler in Porffor JS engine) and
| executed performantly on the same JS context *
|
| *or at least, in theory
| tasty_freeze wrote:
| I haven't used it, but reading their landing page, Porffor
| says their runtime is vastly smaller because it is AOT. If
| the compiler had to be bundled with the executable, then the
| size of the executable would grow much larger.
| david2ndaccount wrote:
| With a string literal it works, with a dynamic string it just
| gives an undefined reference error to eval.
| canadahonk wrote:
| For now, unless it is given a literal string (eg `eval('42')`)
| eval just won't work.
| THBC wrote:
| This seems like an opaque supply chain attack waiting to happen.
| nick_g wrote:
| I'm a bit suspicious of the versioning scheme described here[0]
|
| If some change were required which introduced a regression on
| some Test262 tests, it could cause the version number to regress
| as well. This means Porffor cannot have both a version number
| which increases monotonically and the ability to introduce
| necessary changes which cause Test262 regressions
|
| [0] https://github.com/CanadaHonk/porffor?tab=readme-ov-
| file#ver...
| derdi wrote:
| Presumably the idea is that any work that causes Test262
| regressions is temporary, takes place in a separate branch, and
| is only merged to main once the branch also contains all the
| necessary fixes to make the regressions go away again. A new
| version number would only be used once that merge happens.
| rvnx wrote:
| Seems like the same idea that Facebook had with PHP which was to
| transpile PHP to C.
|
| It was called hiphop-php, then they eventually gave up, before
| creating hhvm on a complete new concept.
| pjmlp wrote:
| Historically the sequence is a bit different.
|
| After HHVM proved that its JIT compilation engine was faster
| than their HipHop AOT attempt, did they decided to focus only
| on HHVM going forward.
| Borkdude wrote:
| I got "TodoError: no generation for ImportDeclaration!" for this
| script:
|
| import * as squint_core from 'squint-cljs/core.js';
| console.log("hello");
| mproud wrote:
| "Purple" in Welsh
| zem wrote:
| with etymology from the greek for purple; the english
| "porphyry" (a purple mineral) is probably the commonest word
| with the same root.
___________________________________________________________________
(page generated 2024-07-30 23:00 UTC)