Migrate to emscripten #1177

Closed
jeetiss wants to merge 49 commits from emscripten into main
jeetiss commented 2022-11-22 04:11:59 -08:00 (Migrated from github.com)
No description provided.
jacobp100 (Migrated from github.com) reviewed 2022-11-24 07:18:47 -08:00
jacobp100 (Migrated from github.com) commented 2022-11-24 07:18:47 -08:00

I left these as YGValues in the hope that in the future, we add support for percentages. I just couldn't figure it out at the time

I left these as `YGValues` in the hope that in the future, we add support for percentages. I just couldn't figure it out at the time
jeetiss commented 2022-12-07 02:06:49 -08:00 (Migrated from github.com)

@NickGerleman hello, I'm back

The bindings are ready so let's discuss the migration and the next steps.

  • I just replaced nbind with embind and removed all nbind code. Is it okay or it would be better to maintain both versions while migrating?

  • I wanna provide separate dev (with some additional assertions and checks) and prod (minified and with optimizations) bundles, so I need some bundler (rollup). What do you think about that? maybe you have suggestions here.

  • I don't know what to do with CI. Can I setup GitHub action for testing like this or it is not a big deal rn?

@NickGerleman hello, I'm back The bindings are ready so let's discuss the migration and the next steps. - I just replaced `nbind` with `embind` and removed all `nbind` code. Is it okay or it would be better to maintain both versions while migrating? - I wanna provide separate `dev` (with some additional assertions and checks) and `prod` (minified and with optimizations) bundles, so I need some bundler (rollup). What do you think about that? maybe you have suggestions here. - I don't know what to do with CI. Can I setup GitHub action for testing like [this](https://github.com/jeetiss/exp-emscripten/blob/main/.github/workflows/test.yaml) or it is not a big deal rn?
jeetiss (Migrated from github.com) reviewed 2022-12-07 02:09:03 -08:00
jeetiss (Migrated from github.com) commented 2022-12-07 02:09:03 -08:00

this makes sense now, thanks for the clarification

but I have problems with types YGNodeStyleGetGap returns float that can't be converted with Value::fromYGValue

this makes sense now, thanks for the clarification but I have problems with types `YGNodeStyleGetGap` returns `float` that can't be converted with `Value::fromYGValue`
jeetiss (Migrated from github.com) reviewed 2022-12-07 02:48:02 -08:00
jeetiss (Migrated from github.com) commented 2022-12-07 02:48:02 -08:00

I think that is better to fix YGNodeStyleGetGap first, it should return YGValue

I can't pick the unit for this type now (should it be undefined or auto or digit?)

I think that is better to fix `YGNodeStyleGetGap` first, it should return `YGValue` I can't pick the unit for this type now (should it be undefined or auto or digit?)
jacobp100 (Migrated from github.com) reviewed 2022-12-07 03:01:55 -08:00
jacobp100 (Migrated from github.com) commented 2022-12-07 03:01:55 -08:00

Ah ok - didn't realise the these functions were inconsistent. The unit is YGUnitPoint

Ah ok - didn't realise the these functions were inconsistent. The unit is `YGUnitPoint`
NickGerleman commented 2022-12-08 01:38:51 -08:00 (Migrated from github.com)

This is incredible! I will take a close look soon.

I think it is okay to fully replace nbind. It has been a long time since folks used it for Node native module versions of Yoga compared to packages like yoga-layout-prebuilt, so it is better imo to have something working, compared to full parity.

Re dev vs prod, I know Emscripten has options for what it emits (it will already minify), so I’m guessing the extra bits would be for the JavaScript frontend to it? If we can avoid the complexity, it might be better to distribute the package without running through another bundler, then the consuming project could bundle using the bundler they are already using.

I’d love it if we could add GitHub Actions validation, but I know it isn’t present right now. There are some workflows I added recently that could be pattern matched, and it would document how we are running tests against this. The website build is currently using the last published version of yoga-layout instead of the one in the repo too, so we don’t get validation from the website build job (tried to move this to workspaces recently but the website setup needs some serious renovation to support it).

This is incredible! I will take a close look soon. I think it is okay to fully replace nbind. It has been a long time since folks used it for Node native module versions of Yoga compared to packages like yoga-layout-prebuilt, so it is better imo to have something working, compared to full parity. Re dev vs prod, I know Emscripten has options for what it emits (it will already minify), so I’m guessing the extra bits would be for the JavaScript frontend to it? If we can avoid the complexity, it might be better to distribute the package without running through another bundler, then the consuming project could bundle using the bundler they are already using. I’d love it if we could add GitHub Actions validation, but I know it isn’t present right now. There are some workflows I added recently that could be pattern matched, and it would document how we are running tests against this. The website build is currently using the last published version of yoga-layout instead of the one in the repo too, so we don’t get validation from the website build job (tried to move this to workspaces recently but the website setup needs some serious renovation to support it).
jeetiss commented 2022-12-10 13:16:21 -08:00 (Migrated from github.com)

I got the warning

warning: undefined symbol: _ZN8facebook4yoga24LayoutPassReasonToStringENS0_16LayoutPassReasonE (referenced by top-level compiled C/C++ code)

do you have any idea why it is here and maybe it can be fixed

I got the warning ``` warning: undefined symbol: _ZN8facebook4yoga24LayoutPassReasonToStringENS0_16LayoutPassReasonE (referenced by top-level compiled C/C++ code) ``` do you have any idea why it is here and maybe it can be fixed
NickGerleman commented 2022-12-11 20:48:14 -08:00 (Migrated from github.com)

I got the warning

warning: undefined symbol: _ZN8facebook4yoga24LayoutPassReasonToStringENS0_16LayoutPassReasonE (referenced by top-level compiled C/C++ code)

do you have any idea why it is here and maybe it can be fixed

I think this means the build isn’t picking up yoga/event/event.cpp.

> I got the warning > > ``` > warning: undefined symbol: _ZN8facebook4yoga24LayoutPassReasonToStringENS0_16LayoutPassReasonE (referenced by top-level compiled C/C++ code) > ``` > > do you have any idea why it is here and maybe it can be fixed I think this means the build isn’t picking up [yoga/event/event.cpp](https://github.com/facebook/yoga/blob/acbffc8485bc230087ce23b2cb86551e1fa322d7/yoga/event/event.cpp).
nicoburns (Migrated from github.com) reviewed 2022-12-16 08:33:15 -08:00
nicoburns (Migrated from github.com) commented 2022-12-16 08:33:15 -08:00

I left these as YGValues in the hope that in the future, we add support for percentages. I just couldn't figure it out at the time

@jacobp100 I added in support for percentage gaps when I ported your gap changes to Taffy (a rust flexbox library originally based off of Yoga). Diff is here if it helps you figure things out. There are also test fixtures for percentage based gaps in that PR (same format as yoga, except we have each fixture in a separate file).

> I left these as YGValues in the hope that in the future, we add support for percentages. I just couldn't figure it out at the time @jacobp100 I added in support for percentage gaps when I ported your gap changes to [Taffy](https://github.com/DioxusLabs/taffy) (a rust flexbox library originally based off of Yoga). Diff is [here](https://github.com/DioxusLabs/taffy/pull/248/files#diff-78a705bd422b6ec7ece906ca29973427ec10834766ca5bf1609459f0e4f539d5) if it helps you figure things out. There are also test fixtures for percentage based gaps in that PR (same format as yoga, except we have each fixture in a separate file).
NickGerleman (Migrated from github.com) reviewed 2022-12-16 09:21:16 -08:00
NickGerleman (Migrated from github.com) left a comment

I'm super grateful for this work. Given how the nbind version was long-term broken, we should be aggressive in taking improvements here.

If you don't mind, I'm going to try to push a commit to this PR adding the yarn test script you wrote to GitHub workflows. The previous lack of coverage for bindings let a lot of bad code slip into them, so if we have a working test script, it would help to both protect the change and demonstrate it is working when I import it 🙂.

I'm super grateful for this work. Given how the nbind version was long-term broken, we should be aggressive in taking improvements here. If you don't mind, I'm going to try to push a commit to this PR adding the `yarn test` script you wrote to GitHub workflows. The previous lack of coverage for bindings let a lot of bad code slip into them, so if we have a working test script, it would help to both protect the change and demonstrate it is working when I import it 🙂.
NickGerleman (Migrated from github.com) commented 2022-12-12 07:49:06 -08:00

Reading, this is smaller than the default dlmalloc, but may perform worse. Do you know if it has a performance impact for Yoga?

Reading, this is smaller than the default dlmalloc, but may perform worse. Do you know if it has a performance impact for Yoga?
NickGerleman (Migrated from github.com) commented 2022-12-12 07:51:37 -08:00

We should add a -std=c++xx here somewhere (was previously 14, but other bits build as 11)

We should add a `-std=c++xx` here somewhere (was previously 14, but other bits build as 11)
NickGerleman (Migrated from github.com) commented 2022-12-12 07:53:00 -08:00

This will effectively break C++ exceptions, so we should also probably go ahead and add -fno-exceptions (and -fno-rtti would probably add some more savings)

This will effectively break [C++ exceptions](https://emscripten.org/docs/optimizing/Optimizing-Code.html#c-exceptions), so we should also probably go ahead and add `-fno-exceptions` (and `-fno-rtti` would probably add some more savings)
NickGerleman (Migrated from github.com) commented 2022-12-12 07:55:38 -08:00

One more, we should consider adding -flto which could make things a bit smaller/faster.

One more, we should consider adding `-flto` which could make things a bit smaller/faster.
NickGerleman (Migrated from github.com) commented 2022-12-16 09:16:08 -08:00

That seems like a great help 🙂.

During H1 2023 I have some work on my plate to try to port some of the layout examples in WPT to Yoga compatible fixtures as well. Seems like a great resource for validating correctness of new layout props outside the context of browsers.

That seems like a great help 🙂. During H1 2023 I have some work on my plate to try to port some of the layout examples in WPT to Yoga compatible fixtures as well. Seems like a great resource for validating correctness of new layout props outside the context of browsers.
nicoburns (Migrated from github.com) reviewed 2022-12-16 09:38:10 -08:00
nicoburns (Migrated from github.com) commented 2022-12-16 09:38:10 -08:00

Ah, thanks for putting me onto Web Platform Tests I knew there must be something like this, but I hadn't been able to find it. I'm about to create a test suite for a CSS Grid implementation, and that looks like a great source of test cases, or at least inspiration.

Ah, thanks for putting me onto [Web Platform Tests](https://github.com/web-platform-tests) I knew there must be something like this, but I hadn't been able to find it. I'm about to create a test suite for a CSS Grid implementation, and that looks like a great source of test cases, or at least inspiration.
NickGerleman (Migrated from github.com) reviewed 2022-12-22 00:13:04 -08:00
NickGerleman (Migrated from github.com) commented 2022-12-22 00:13:04 -08:00

Testing locally:
emmalloc: 165kb
dlmalloc: 173kb

Testing locally: emmalloc: 165kb dlmalloc: 173kb
NickGerleman (Migrated from github.com) reviewed 2022-12-22 00:24:39 -08:00
NickGerleman (Migrated from github.com) commented 2022-12-22 00:24:39 -08:00

Disabling exceptions and RTTI saves 10kb. LTCG does not lead to size savings, but is best practice for performance so I am tempted to think we should enable it (despite impact to build perf of the Yoga repo).

Disabling exceptions and RTTI saves 10kb. LTCG does not lead to size savings, but is best practice for performance so I am tempted to think we should enable it (despite impact to build perf of the Yoga repo).
jeetiss (Migrated from github.com) reviewed 2022-12-22 01:48:27 -08:00
jeetiss (Migrated from github.com) commented 2022-12-22 01:48:27 -08:00

Yes, I tested it too and it doesn't affect the perf

Yes, I tested it too and it doesn't affect the perf
jeetiss (Migrated from github.com) reviewed 2022-12-22 01:54:56 -08:00
jeetiss (Migrated from github.com) commented 2022-12-22 01:54:55 -08:00

yep, sure emcc build takes 2 times longer but it definitely makes sense

thanks for suggestion

yep, sure `emcc` build takes 2 times longer but it definitely makes sense thanks for suggestion
jeetiss (Migrated from github.com) reviewed 2022-12-22 02:09:21 -08:00
jeetiss (Migrated from github.com) commented 2022-12-22 02:09:21 -08:00

🚀

Снимок экрана 2022-12-22 в 13 08 02

🚀 [<img width="633" alt="Снимок экрана 2022-12-22 в 13 08 02" src="https://user-images.githubusercontent.com/6726016/209110602-9a736bb6-6649-49de-bdc6-ce8a306e260b.png">](https://github.com/jeetiss/yet-another-yoga-layout-fork/pull/10)
NickGerleman commented 2022-12-22 03:11:25 -08:00 (Migrated from github.com)

I pushed a couple of changes here:

  1. We have passing tests in GitHub Actions now 🎉
  2. We build separate targets for asm.js and wasm.js, defaulting to asm.js for compat, but allowing platforms supporting wasm to use the wasm entrypoint from the export map (if using a modern bundler or Node). Anecdotally, the WASM build of tests ran in about half the time of the asm.js version.
  3. Updated the babel versions, transforms. The old version didn't recognize Flow syntax to reexport all types, which I wanted to add to avoid duplication in the multiple entrypoints.
I pushed a couple of changes here: 1. We have passing tests in GitHub Actions now 🎉 2. We build separate targets for asm.js and wasm.js, defaulting to asm.js for compat, but allowing platforms supporting wasm to use the wasm entrypoint from the export map (if using a modern bundler or Node). Anecdotally, the WASM build of tests ran in about half the time of the asm.js version. 3. Updated the babel versions, transforms. The old version didn't recognize Flow syntax to reexport all types, which I wanted to add to avoid duplication in the multiple entrypoints.
NickGerleman commented 2022-12-22 03:18:58 -08:00 (Migrated from github.com)

Here is the performance difference between asm.js and wasm being run by Node from the scenarios in the run-bench script.
image

Here is the performance difference between asm.js and wasm being run by Node from the scenarios in the run-bench script. <img width="641" alt="image" src="https://user-images.githubusercontent.com/835219/209123195-2a4eeb26-d610-438a-b92b-e0a42c8cb3c0.png">
nicoburns commented 2022-12-22 03:37:19 -08:00 (Migrated from github.com)

Here is the performance difference between asm.js and wasm being run by Node from the scenarios in the run-bench script.

Interesting. I'm not sure how representative these benchmarks are, but ~13ms for the "huge nested layout" was also what I was getting by using the yoga-layout-prebuilt package from NPM (although I was only measuring layout computation time and not including the tree setup time there).

I can't understand why "Align stretch in undefined axis" is so slow though. That only has 10 nodes (compared to 10,000 for the huge nested layout)! Admittedly it's not the same benchmark, but I was able to run a variant of the "huge nested layout" with depth of 1 instead of depth of 4 (so 10^1 = 10 rather than 10^4 = 10,000 nodes) in ~45 micro seconds.

One thing I did notice when benchmarking yoga-layout-prebuilt was that there seemed to be a fixed ~30ms penalty the first time I called calculate_layout() (can't remember exact name in yoga) in a given run (this happened even if other functions to setup nodes had already been called. So in order to get accurate results I had to run the benchmark once and throw away the result before collecting measurements. I wonder if a similar penalty might be skewing the results here. I would say that the results of the 4 benchmarks are suspiciously similar.

> Here is the performance difference between asm.js and wasm being run by Node from the scenarios in the run-bench script. Interesting. I'm not sure how representative these benchmarks are, but ~13ms for the "huge nested layout" was also what I was getting by using the `yoga-layout-prebuilt` package from NPM (although I was only measuring layout computation time and not including the tree setup time there). I can't understand why "Align stretch in undefined axis" is so slow though. That only has 10 nodes (compared to 10,000 for the huge nested layout)! Admittedly it's not the same benchmark, but I was able to run a variant of the "huge nested layout" with depth of 1 instead of depth of 4 (so 10^1 = 10 rather than 10^4 = 10,000 nodes) in ~45 *micro* seconds. One thing I did notice when benchmarking `yoga-layout-prebuilt` was that there seemed to be a fixed ~30ms penalty the first time I called `calculate_layout()` (can't remember exact name in yoga) in a given run (this happened even if other functions to setup nodes had already been called. So in order to get accurate results I had to run the benchmark once and throw away the result before collecting measurements. I wonder if a similar penalty might be skewing the results here. I would say that the results of the 4 benchmarks are suspiciously similar.
NickGerleman commented 2022-12-22 03:46:18 -08:00 (Migrated from github.com)

I haven't tried to debug it to see if it is working as expected, but there is some mention in the script on skipping the first few iterations to avoid one-time costs, then averaging the rest.

Some of the tests I can see like "Align stretch in undefined axis" add 2000 child nodes under the root node, so there still is a lot there. That 2000 node count is the same for other tests, but "huge nested layout" is triply nested of 6 children it looks like.

I haven't tried to debug it to see if it is working as expected, but there is some mention in the script on skipping the first few iterations to avoid one-time costs, then averaging the rest. Some of the tests I can see like "Align stretch in undefined axis" add 2000 child nodes under the root node, so there still is a lot there. That 2000 node count is the same for other tests, but "huge nested layout" is triply nested of 6 children it looks like.
nicoburns commented 2022-12-22 03:51:22 -08:00 (Migrated from github.com)

Some of the tests I can see like "Align stretch in undefined axis" add 2000 child nodes under the root node, so there still is a lot there.

Ah, I can what I've done here. I'm reading the C benchmarks and assuming the JavaScript benchmarks are the same! The C benchmarks with the same name is only using 10 child nodes. These numbers make a lot more sense at a count of 2000 nodes.

> Some of the tests I can see like "Align stretch in undefined axis" add 2000 child nodes under the root node, so there still is a lot there. Ah, I can what I've done here. I'm reading the C benchmarks and assuming the JavaScript benchmarks are the same! The C benchmarks with the same name is only using 10 child nodes. These numbers make a lot more sense at a count of 2000 nodes.
NickGerleman commented 2022-12-22 05:04:38 -08:00 (Migrated from github.com)

Looking at the detail of Chrome not allowing synchronous compilation of WebAssembly files> 4KB, we should bite the bullet and change the bindings to require async initialization I think.

Looking at the detail of Chrome not allowing synchronous compilation of WebAssembly files> 4KB, we should bite the bullet and change the bindings to require async initialization I think.
jeetiss commented 2022-12-22 05:19:04 -08:00 (Migrated from github.com)

Looking at the detail of Chrome not allowing synchronous compilation of WebAssembly files> 4KB

I like async initialization on default, but it is better to have a sync version too (for asm) Because it is a pain to migrate Yoga from sync to async version on platforms that don't support top-level await

> Looking at the detail of Chrome not allowing synchronous compilation of WebAssembly files> 4KB I like async initialization on default, but it is better to have a sync version too (for asm) Because it is a pain to migrate `Yoga` from sync to async version on platforms that don't support top-level await
NickGerleman commented 2022-12-22 06:11:12 -08:00 (Migrated from github.com)

I think it would be reasonable to have a yoga-layout/sync documented in the readme which points to a synchronously obtained asmjs build. Technically, we could go further and have both sync and async asmjs and wasm builds, since Node doesn't have the same sync compilation limitation that Chrome does. So we could have a default async variant, then the sync one would be the same export map as the async one, minus browsers getting the asmjs version.

I think it would be reasonable to have a `yoga-layout/sync` documented in the readme which points to a synchronously obtained asmjs build. Technically, we could go further and have both sync and async asmjs and wasm builds, since Node doesn't have the same sync compilation limitation that Chrome does. So we could have a default async variant, then the sync one would be the same export map as the async one, minus browsers getting the asmjs version.
NickGerleman commented 2022-12-22 17:25:14 -08:00 (Migrated from github.com)

@jeetiss What do you think of a design where we:

  1. Package 4x entrypoints, for the matrix of sync/async and asm/wasm
  2. The default entrypoint provides a version where the user needs to call await Yoga.load() or similar to allow for async wasm compilation. Default to wasm for node, browser, but default to asm elsewhere or for old clients that don't understand export maps.
  3. A /sync entrypoint provides the legacy sync API, but falls back to asm.js on browsers to allow it (at a size/performance cost), but we still let Node use sync wasm compilation

This change in general is a breaking API change compared to the last package, and means semver wise we ideally ought to do a yoga@2.0.0 (hoping it doesn't create confusion as Yoga 2 is a bit overloaded lol). I still don't have a publishing pipeline set up, and might end up just starting with Maven/npm since the Apple/YogaKit build is not in a great state and I haven't built the context to really critically understand the set of problems and fixes for it.

After this change, but before we publish that new breaking version, it might make sense to make the change to YGNodeStyleGetGap to move to YGValue (@jacobp100).

Also want to apologize for commandeering this PR a bit 😅. Original goal was just to add GitHub actions/apply the flag changes, but I got a little carried away hacking at this and trying to understand everything here.

@jeetiss What do you think of a design where we: 1. Package 4x entrypoints, for the matrix of sync/async and asm/wasm 2. The default entrypoint provides a version where the user needs to call `await Yoga.load()` or similar to allow for async wasm compilation. Default to wasm for node, browser, but default to asm elsewhere or for old clients that don't understand export maps. 3. A `/sync` entrypoint provides the legacy sync API, but falls back to asm.js on browsers to allow it (at a size/performance cost), but we still let Node use sync wasm compilation This change in general is a breaking API change compared to the last package, and means semver wise we ideally ought to do a `yoga@2.0.0` (hoping it doesn't create confusion as Yoga 2 is a bit overloaded lol). I still don't have a publishing pipeline set up, and might end up just starting with Maven/npm since the Apple/YogaKit build is not in a great state and I haven't built the context to really critically understand the set of problems and fixes for it. After this change, but before we publish that new breaking version, it might make sense to make the change to `YGNodeStyleGetGap` to move to `YGValue` (@jacobp100). Also want to apologize for commandeering this PR a bit 😅. Original goal was just to add GitHub actions/apply the flag changes, but I got a little carried away hacking at this and trying to understand everything here.
jeetiss commented 2022-12-23 03:41:17 -08:00 (Migrated from github.com)

@NickGerleman sounds awesome, I am glad that you are pushing this forward 🚀

@NickGerleman sounds awesome, I am glad that you are pushing this forward 🚀
facebook-github-bot commented 2022-12-25 02:33:31 -08:00 (Migrated from github.com)

@NickGerleman has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@NickGerleman has imported this pull request. If you are a Meta employee, you can view this diff [on Phabricator](https://www.internalfb.com/diff/D42207782).
facebook-github-bot commented 2022-12-28 01:29:28 -08:00 (Migrated from github.com)

@NickGerleman merged this pull request in facebook/yoga@1813748eaa.

@NickGerleman merged this pull request in facebook/yoga@1813748eaa60e6129c1e1c00c08322b5687b1702.

Pull request closed

Sign in to join this conversation.
No description provided.