Disable C++ RTTI by default - #1817
Merged
bkaradzic-microsoft merged 4 commits intoAug 7, 2026
Merged
Conversation
Babylon Native contains no `dynamic_cast` or `typeid` expressions in Core, Plugins, Polyfills, Embedding or Apps, yet MSVC, GCC and Clang all emit RTTI records for every polymorphic class by default. That metadata is dead weight in the shipped binary. Adds an `ENABLE_RTTI` option (default OFF) that passes `/GR-` on MSVC and `-fno-rtti` elsewhere. The flag is guarded on `COMPILE_LANGUAGE:CXX` so it is never handed to C sources. `ENABLE_RTTI` is the same variable the sanitizer block already forces ON, so `-DENABLE_SANITIZERS=ON` keeps RTTI and UBSan's vptr check keeps working. Dependencies that genuinely need RTTI opt back in per target -- DirectXTK already does exactly this for its Model class. Measured on Windows x64 RelWithDebInfo (Playground.exe): | | bytes | | --- | ---: | | before | 11,116,032 | | after | 10,927,616 | | delta | -188,416 (-1.70%) | RTTI content dropped from 202,000 bytes (1.85% of the image) to 20,632 (0.19%). The remainder is exception-handling type descriptors emitted for `throw` / `catch` under `/EHsc`, which cannot be removed while exceptions are enabled. Validated with the Playground validation suite on D3D11: 301 enabled tests ran, 301 passed, 0 failed, across eight chunked runs. The build produces zero C4541 (`dynamic_cast` used with /GR-) and zero C4530 warnings. Only the Windows/MSVC path was built and tested locally; Apple, Android and Linux rely on CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f28021aa-1036-4de0-b8ad-ca88efa3429e
bkaradzic-microsoft
requested review from
bghgary
and
a lite review from Copilot
August 5, 2026 17:45
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The current COMPILE_LANGUAGE:CXX gating likely misses Objective-C++ (.mm) compilation units (typically OBJCXX), so RTTI may remain enabled for Apple builds despite the new default.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds a top-level CMake option to disable C++ RTTI by default (while keeping sanitizer builds able to force RTTI back on), to reduce shipped binary size across Babylon Native and its in-tree dependencies.
Changes:
- Introduces
ENABLE_RTTICMake option (defaultOFF) near other top-level build options. - Applies
/GR-(MSVC) or-fno-rtti(non-MSVC) globally viaadd_compile_options, scoped to C++ compilation units viaCOMPILE_LANGUAGE.
File summaries
| File | Description |
|---|---|
| CMakeLists.txt | Adds ENABLE_RTTI and injects compiler flags to disable RTTI by default (with sanitizer compatibility). |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
CI showed two dependency targets cannot be compiled without RTTI:
* `v8inspector` pulls in asio, whose headers call `typeid()`
unconditionally (`any_executor.hpp`, `service_registry.hpp`,
`handler_work.hpp`). Clang and GCC reject that outright; MSVC only warns,
which is why the Windows V8 job passed and the Android ones did not.
* `jsi` defines `typeinfo for facebook::jsi::*`. Hermes builds its
`API/hermes` targets with RTTI and their vtables reference those symbols,
so compiling jsi without RTTI breaks the link of libhermesvmlean.so.
Adds a `babylon_native_force_rtti()` helper and applies it to both targets
right after JsRuntimeHost is made available -- Hermes is fetched from inside
JsRuntimeHost, so both exist at that point. The same target name `jsi` is used
by Node-API-JSI for V8JSI, so one call covers both engines.
Also extends the language guard from `COMPILE_LANGUAGE:CXX` to
`COMPILE_LANGUAGE:CXX,OBJCXX` so Objective-C++ sources are covered on Apple
regardless of how CMake classifies `.mm` files.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f28021aa-1036-4de0-b8ad-ca88efa3429e
bghgary
approved these changes
Aug 7, 2026
ENABLE_RTTI was an option() whose only documented reason to be ON was UBSan's vptr check, which ENABLE_SANITIZERS already forced. The option therefore added nothing, and the cache entry it created made the FORCE sticky: a build directory configured once with -DENABLE_SANITIZERS=ON kept ENABLE_RTTI:BOOL=ON afterwards, so later configures of the same directory silently lost the size win. Deriving a plain variable from ENABLE_SANITIZERS drops the option and the stale-cache bug together. babylon_native_force_rtti() silently returned for an unknown target, which is what let Dependencies/CMakeLists.txt call it unconditionally for v8inspector and jsi even though neither exists in, say, a QuickJS build. It also meant an upstream rename would surface much later as a missing-typeinfo link error. Gate each call on the engine that provides the target and make an unresolved name a configure error. Verified by configuring with NAPI_JAVASCRIPT_ENGINE=Chakra (neither target defined, both calls skipped), with V8 (v8inspector generated with RuntimeTypeInfo=true while Graphics stays false), and by toggling ENABLE_SANITIZERS ON then OFF in one build directory, which now returns to RuntimeTypeInfo=false instead of staying enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 88569c10-a7ff-4373-9a58-afa9c68b8c09
target_compile_options already rejects an unknown target and names it, so with both call sites gated the explicit check only restated the error less precisely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 88569c10-a7ff-4373-9a58-afa9c68b8c09
bkaradzic-microsoft
enabled auto-merge (squash)
August 7, 2026 23:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Babylon Native contains no
dynamic_castortypeidexpressions inCore,Plugins,Polyfills,EmbeddingorApps, yet MSVC, GCC and Clang all emit RTTI records (type_infoobjects and the vtable slots that reach them) for every polymorphic class by default. That metadata is dead weight in the shipped binary.Change
A single
ENABLE_RTTIoption, defaulting toOFF, in the top-levelCMakeLists.txt:Notes on the details:
ENABLE_RTTIis not a new name. It is the exact variable the sanitizer block already sets viaset(ENABLE_RTTI ON CACHE BOOL "" FORCE). Because thatFORCEruns after theoption(),-DENABLE_SANITIZERS=ONstill turns RTTI back on and UBSan'svptrcheck keeps working. Declaring the option just makes the knob discoverable and gives it a default.COMPILE_LANGUAGE:CXX,OBJCXXguard. Neither/GR-nor-fno-rttiis valid for C sources, so the guard keeps them off the C dependencies built in-tree.OBJCXXis listed explicitly so Objective-C++ (.mm) sources are covered on Apple regardless of how CMake classifies them.add_compile_optionsat this point reaches every target added afterwards, which is all ofDependencies,Core,Plugins,Polyfills,EmbeddingandApps.target_compile_options(${t} PRIVATE /Wall /EHsc /GR), commented "Model uses dynamic_cast, so we need /GR (Enable RTTI)". A directory-scoped flag cannot break it.Anyone who wants the old behaviour can configure with
-DENABLE_RTTI=ON.Dependencies that need RTTI
Two JsRuntimeHost targets do not build without it. They get it back through a
babylon_native_force_rtti()helper, called right afterFetchContent_MakeAvailable_With_Message(JsRuntimeHost)— Hermes is fetched from inside JsRuntimeHost, so both targets already exist at that point.v8inspectortypeid()unconditionally (any_executor.hpp,service_registry.hpp,handler_work.hpp). Clang and GCC reject that outright; MSVC only warns, which is why the Windows V8 job passed and the Android ones did not.jsijsi.cppis the only translation unit that definestypeinfo for facebook::jsi::*. Hermes builds itsAPI/hermestargets with RTTI and their vtables reference those symbols, so compilingjsiwithout RTTI breaks the link oflibhermesvmlean.so. Node-API-JSI uses the same target name for V8JSI, so one call covers both engines.This is the same escape hatch DirectXTK uses, just applied from the consumer side because these targets live in another repo.
Measurements
Windows x64,
RelWithDebInfo,Apps/Playground/Playground.exe:RTTI content in the image, from the linker map (
??_R0…??_R4symbols):Before, that RTTI was spread across the whole tree — Babylon Native itself 144,280 B, DirectXTK/UrlLib/arcana 31,392 B, glslang/SPIRV 14,056 B, bgfx/bimg/bx 12,096 B — which is why a global flag is the right scope rather than a per-target one.
The 20,632 B that remain are
??_R0type descriptors MSVC emits forthrow/catchunder/EHsc. That is exception-handling metadata rather than RTTI proper, and cannot be removed while exceptions are enabled.Validation
Full build of
Playgroundand all dependencies produced zero C4541 ('dynamic_cast' used on polymorphic type with /GR-) and zero C4530 warnings. C4541 is only a warning and not an error, so this is the check that actually proves nodynamic_castwas silently broken.Playground validation suite on D3D11, run in eight chunks of 100 test indices:
Only the Windows/MSVC path was built and measured locally. Every other platform, engine and toolchain is covered by CI, which is green across all 34 checks — including Android, Apple, Linux and UWP on JSC, QuickJS, V8, Hermes and JSI, plus the Windows and macOS sanitizer jobs that exercise the
ENABLE_SANITIZERSpath where RTTI stays on.