Skip to content

Remove embind from Yoga JavaScript bindings#55864

Closed
NickGerleman wants to merge 1 commit intofacebook:mainfrom
NickGerleman:export-D95011356
Closed

Remove embind from Yoga JavaScript bindings#55864
NickGerleman wants to merge 1 commit intofacebook:mainfrom
NickGerleman:export-D95011356

Conversation

@NickGerleman
Copy link
Copy Markdown
Contributor

Summary:
X-link: facebook/yoga#1906

Yoga's JavaScript bindings previously used Emscripten embind to bridge
C++ wrapper classes to JavaScript. This added unnecessary overhead and
complexity: JS calls went through embind → C++ wrapper classes (Node.cpp,
Config.cpp) → Yoga C API, requiring the embind runtime to be bundled into
the WASM output.

This diff removes embind entirely. JavaScript now calls Yoga's C API
directly, with a thin C bridge (wasm_bridge.c, ~140 lines) handling only
what raw C exports cannot:

  • Struct returns (YGValue) via a static buffer read through HEAPF32/HEAP32
  • Measure/dirtied callbacks via EM_JS dispatching to JS-side Maps

The JS side (wrapAssembly.ts) is rewritten to use classes (NodeImpl,
ConfigImpl) that store a WASM pointer and call C functions directly via
lib._YGFunctionName(). Tree hierarchy (children/parent) is tracked
entirely in JS, matching the pattern used by the Java JNI bindings.

To avoid maintaining a hardcoded EXPORTED_FUNCTIONS list, YG_EXPORT is
extended on Emscripten to include __attribute__((used)) (matching
EMSCRIPTEN_KEEPALIVE). Combined with -fvisibility=hidden and
--export-dynamic, this ensures all public Yoga C API functions are
automatically exported from WASM — no manual list needed. The closure
compiler is retained for JS glue minification.

The public API shape (Yoga, Node, Config types) remains identical.

Bundle size (SINGLE_FILE base64-embedded WASM):

Metric Before (embind) After (direct C) Change
Raw 126,288 B 115,370 B -8.6%
Gzip 51,042 B 42,764 B -16.2%

Benchmark results (median of 3 runs):

Benchmark Before After Change
Stack with flex 20ms 8ms 2.5x faster
Align stretch in undefined axis 19ms 5ms 3.8x faster
Nested flex 17ms 4ms 4.3x faster
Huge nested layout 18ms 5ms 3.6x faster

Fixes facebook/yoga#1545

Differential Revision: D95011356

@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented Mar 3, 2026

@NickGerleman has exported this pull request. If you are a Meta employee, you can view the originating Diff in D95011356.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Mar 3, 2026
NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 3, 2026
Summary:
X-link: facebook/react-native#55864


Yoga's JavaScript bindings previously used Emscripten embind to bridge
C++ wrapper classes to JavaScript. This added unnecessary overhead and
complexity: JS calls went through embind → C++ wrapper classes (Node.cpp,
Config.cpp) → Yoga C API, requiring the embind runtime to be bundled into
the WASM output.

This diff removes embind entirely. JavaScript now calls Yoga's C API
directly, with a thin C bridge (wasm_bridge.c, ~140 lines) handling only
what raw C exports cannot:
- Struct returns (YGValue) via a static buffer read through HEAPF32/HEAP32
- Measure/dirtied callbacks via EM_JS dispatching to JS-side Maps

The JS side (wrapAssembly.ts) is rewritten to use classes (NodeImpl,
ConfigImpl) that store a WASM pointer and call C functions directly via
`lib._YGFunctionName()`. Tree hierarchy (children/parent) is tracked
entirely in JS, matching the pattern used by the Java JNI bindings.

The public API shape (Yoga, Node, Config types) remains identical.

**Bundle size (SINGLE_FILE base64-embedded WASM):**

| Metric | Before (embind) | After (direct C) | Change |
|---|---|---|---|
| Raw | 126,288 B | 115,370 B | -8.6% |
| Gzip | 51,042 B | 42,764 B | -16.2% |

**Benchmark results (median of 3 runs):**

| Benchmark | Before | After | Change |
|---|---|---|---|
| Stack with flex | 20ms | 8ms | 2.5x faster |
| Align stretch in undefined axis | 19ms | 5ms | 3.8x faster |
| Nested flex | 17ms | 4ms | 4.3x faster |
| Huge nested layout | 18ms | 5ms | 3.6x faster |

Fixes facebook#1545

Differential Revision: D95011356
Summary:

X-link: facebook/yoga#1906

Yoga's JavaScript bindings previously used Emscripten embind to bridge
C++ wrapper classes to JavaScript. This added unnecessary overhead and
complexity: JS calls went through embind → C++ wrapper classes (Node.cpp,
Config.cpp) → Yoga C API, requiring the embind runtime to be bundled into
the WASM output.

This diff removes embind entirely. JavaScript now calls Yoga's C API
directly, with a thin C bridge (wasm_bridge.c, ~140 lines) handling only
what raw C exports cannot:
- Struct returns (YGValue) via a static buffer read through HEAPF32/HEAP32
- Measure/dirtied callbacks via EM_JS dispatching to JS-side Maps

The JS side (wrapAssembly.ts) is rewritten to use classes (NodeImpl,
ConfigImpl) that store a WASM pointer and call C functions directly via
`lib._YGFunctionName()`. Tree hierarchy (children/parent) is tracked
entirely in JS, matching the pattern used by the Java JNI bindings.

The public API shape (Yoga, Node, Config types) remains identical.

**Bundle size (SINGLE_FILE base64-embedded WASM):**

| Metric | Before (embind) | After (direct C) | Change |
|---|---|---|---|
| Raw | 126,288 B | 115,370 B | -8.6% |
| Gzip | 51,042 B | 42,764 B | -16.2% |

**Benchmark results (median of 3 runs):**

| Benchmark | Before | After | Change |
|---|---|---|---|
| Stack with flex | 20ms | 8ms | 2.5x faster |
| Align stretch in undefined axis | 19ms | 5ms | 3.8x faster |
| Nested flex | 17ms | 4ms | 4.3x faster |
| Huge nested layout | 18ms | 5ms | 3.6x faster |

Fixes facebook/yoga#1545

Changelog: [Internal]

Reviewed By: elicwhite

Differential Revision: D95011356
NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 5, 2026
Summary:
X-link: facebook/react-native#55864


Yoga's JavaScript bindings previously used Emscripten embind to bridge
C++ wrapper classes to JavaScript. This added unnecessary overhead and
complexity: JS calls went through embind → C++ wrapper classes (Node.cpp,
Config.cpp) → Yoga C API, requiring the embind runtime to be bundled into
the WASM output.

This diff removes embind entirely. JavaScript now calls Yoga's C API
directly, with a thin C bridge (wasm_bridge.c, ~140 lines) handling only
what raw C exports cannot:
- Struct returns (YGValue) via a static buffer read through HEAPF32/HEAP32
- Measure/dirtied callbacks via EM_JS dispatching to JS-side Maps

The JS side (wrapAssembly.ts) is rewritten to use classes (NodeImpl,
ConfigImpl) that store a WASM pointer and call C functions directly via
`lib._YGFunctionName()`. Tree hierarchy (children/parent) is tracked
entirely in JS, matching the pattern used by the Java JNI bindings.

The public API shape (Yoga, Node, Config types) remains identical.

**Bundle size (SINGLE_FILE base64-embedded WASM):**

| Metric | Before (embind) | After (direct C) | Change |
|---|---|---|---|
| Raw | 126,288 B | 115,370 B | -8.6% |
| Gzip | 51,042 B | 42,764 B | -16.2% |

**Benchmark results (median of 3 runs):**

| Benchmark | Before | After | Change |
|---|---|---|---|
| Stack with flex | 20ms | 8ms | 2.5x faster |
| Align stretch in undefined axis | 19ms | 5ms | 3.8x faster |
| Nested flex | 17ms | 4ms | 4.3x faster |
| Huge nested layout | 18ms | 5ms | 3.6x faster |

Fixes facebook#1545

Changelog: [Internal]

Reviewed By: elicwhite

Differential Revision: D95011356
meta-codesync Bot pushed a commit to facebook/yoga that referenced this pull request Mar 5, 2026
Summary:
X-link: facebook/react-native#55864

Pull Request resolved: #1906

Yoga's JavaScript bindings previously used Emscripten embind to bridge
C++ wrapper classes to JavaScript. This added unnecessary overhead and
complexity: JS calls went through embind → C++ wrapper classes (Node.cpp,
Config.cpp) → Yoga C API, requiring the embind runtime to be bundled into
the WASM output.

This diff removes embind entirely. JavaScript now calls Yoga's C API
directly, with a thin C bridge (wasm_bridge.c, ~140 lines) handling only
what raw C exports cannot:
- Struct returns (YGValue) via a static buffer read through HEAPF32/HEAP32
- Measure/dirtied callbacks via EM_JS dispatching to JS-side Maps

The JS side (wrapAssembly.ts) is rewritten to use classes (NodeImpl,
ConfigImpl) that store a WASM pointer and call C functions directly via
`lib._YGFunctionName()`. Tree hierarchy (children/parent) is tracked
entirely in JS, matching the pattern used by the Java JNI bindings.

The public API shape (Yoga, Node, Config types) remains identical.

**Bundle size (SINGLE_FILE base64-embedded WASM):**

| Metric | Before (embind) | After (direct C) | Change |
|---|---|---|---|
| Raw | 126,288 B | 115,370 B | -8.6% |
| Gzip | 51,042 B | 42,764 B | -16.2% |

**Benchmark results (median of 3 runs):**

| Benchmark | Before | After | Change |
|---|---|---|---|
| Stack with flex | 20ms | 8ms | 2.5x faster |
| Align stretch in undefined axis | 19ms | 5ms | 3.8x faster |
| Nested flex | 17ms | 4ms | 4.3x faster |
| Huge nested layout | 18ms | 5ms | 3.6x faster |

Fixes #1545

Changelog: [Internal]

Reviewed By: elicwhite

Differential Revision: D95011356

fbshipit-source-id: a936a9e6705aebe66bf7515ccd67cdc0f55ccae2
@meta-codesync meta-codesync Bot closed this in 3cca0d4 Mar 5, 2026
@react-native-bot
Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @NickGerleman in 3cca0d4

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Mar 5, 2026
@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Mar 5, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented Mar 5, 2026

This pull request has been merged in 3cca0d4.

zoontek pushed a commit to zoontek/react-native that referenced this pull request Mar 9, 2026
Summary:
Pull Request resolved: facebook#55864

X-link: facebook/yoga#1906

Yoga's JavaScript bindings previously used Emscripten embind to bridge
C++ wrapper classes to JavaScript. This added unnecessary overhead and
complexity: JS calls went through embind → C++ wrapper classes (Node.cpp,
Config.cpp) → Yoga C API, requiring the embind runtime to be bundled into
the WASM output.

This diff removes embind entirely. JavaScript now calls Yoga's C API
directly, with a thin C bridge (wasm_bridge.c, ~140 lines) handling only
what raw C exports cannot:
- Struct returns (YGValue) via a static buffer read through HEAPF32/HEAP32
- Measure/dirtied callbacks via EM_JS dispatching to JS-side Maps

The JS side (wrapAssembly.ts) is rewritten to use classes (NodeImpl,
ConfigImpl) that store a WASM pointer and call C functions directly via
`lib._YGFunctionName()`. Tree hierarchy (children/parent) is tracked
entirely in JS, matching the pattern used by the Java JNI bindings.

The public API shape (Yoga, Node, Config types) remains identical.

**Bundle size (SINGLE_FILE base64-embedded WASM):**

| Metric | Before (embind) | After (direct C) | Change |
|---|---|---|---|
| Raw | 126,288 B | 115,370 B | -8.6% |
| Gzip | 51,042 B | 42,764 B | -16.2% |

**Benchmark results (median of 3 runs):**

| Benchmark | Before | After | Change |
|---|---|---|---|
| Stack with flex | 20ms | 8ms | 2.5x faster |
| Align stretch in undefined axis | 19ms | 5ms | 3.8x faster |
| Nested flex | 17ms | 4ms | 4.3x faster |
| Huge nested layout | 18ms | 5ms | 3.6x faster |

Fixes facebook/yoga#1545

Changelog: [Internal]

Reviewed By: elicwhite

Differential Revision: D95011356

fbshipit-source-id: a936a9e6705aebe66bf7515ccd67cdc0f55ccae2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported Merged This PR has been merged. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[JS] Reduce WASM call overhead

3 participants