Advanced Turbo Module topics
This topic provides additional information that is useful in when creating complex Turbo Modules.
Turbo Module CMAKE Helper
The kepler_add_turbo_module_library
function is a CMake helper that simplifies the process of defining a Turbo Module library target for use with the React Native for Vega runtime. It handles common configuration and optimization settings required for Turbo Module libraries, streamlining the process by automatically applying the necessary settings and optimizations.
This helper function provides the following benefits:
- Simplified Configuration: Reduces boilerplate CMake code with a single function call
- Consistent Best Practices: Makes sure that all Turbo Modules follow the same configuration patterns and provides a central place to roll out common settings in the future
- Improved Application Binary Interface (ABI) Stability: Applies proper symbol visibility settings automatically
- Optimized Performance: Makes sure that performance optimizations such as link-time and size optimizations are applied
Key symbol contracts for Turbo Module DSO
All TurboModules on Vega platforms must adhere to two critical requirements:
- Export EntryPoint function: The Turbo Module library must export a single symbol named
autoLinkVegaTurboModulesV1
. This symbol serves as the entry point that the Vega runtime uses to automatically link and load your Turbo Module. - Hidden Symbol Visibility: All other symbols in the Turbo Module library should be hidden. This prevents symbol conflicts with other libraries, ensures a stable ABI, improves TurboModule loading time, and can reduce the size of the library in many cases.
The kepler_add_turbo_module_library
helper automatically configures these requirements for you, eliminating the need for manual symbol visibility management.
Functionality provided
The kepler_add_turbo_module_library
function provides several key features that simplify the creation of Turbo Module libraries. These features are organized into three main categories.
Shared library creation
The helper function provides support for shared libraries as follows:
- Creates a shared library with the specified name using the standard CMake
add_library
command. - Links the
turbomoduleAPI
static library, which provides the C++ methods to develop Turbo Modules, and the necessary platform-level functionality, to the library. - Handles the proper setup of the library target, similar to what you would do manually with
add_library
andtarget_link_libraries
.
Symbol visibility control
The helper function automatically configures symbol visibility to ensure ABI stability:
- Sets default visibility to "hidden" for all symbols
- Applies a version script that explicitly exports only the
autoLinkVegaTurboModulesV1
symbol - Disables symbol interposition with
-fno-semantic-interposition
and applies local binding of global references with-Bsymbolic
to prevent symbol conflicts
Performance optimizations
The helper function applies several optimizations to improve the performance and size of your Turbo Module:
- Enables link-time optimization (LTO) for better performance and smaller binary size
- Applies size optimizations by applying garbage collection of unused sections
Usage
To use the kepler_add_turbo_module_library
function, add the following to your CMakeLists.txt.
## TMAPI package required to use `kepler_add_turbo_module_library`
find_package(turbomoduleAPI CONFIG REQUIRED)
## Define source files
set(SOURCES
AutoLinkInit.cpp
MyTurboModule.cpp
)
## Create the TurboModule library
kepler_add_turbo_module_library(MyTurboModule ${SOURCES})
Frequently Asked Questions
Q: Should I migrate my existing TurboModule to use this helper function?
A: Yes. If you have an existing Turbo Module, you are strongly recommended to migrate to this helper function. The current approach of propagating optimization flags as INTERFACE flags via the turbomoduleAPI library will be removed in the future. This helper function is part of a broader strategy to provide more explicit and maintainable configuration for Turbo Modules while avoiding implicit propagation of build settings that can lead to surprising behavior.
Q: How do I migrate my existing TurboModule to use this helper function?
A: Follow these steps to migrate:
- Review your current CMakeLists.txt to understand how your TurboModule is configured
- Make sure that you have the
find_package(turbomoduleAPI CONFIG REQUIRED)
line in your CMakeLists.txt - Replace your
add_library
call and all subsequent configuration with a singlekepler_add_turbo_module_library
call - Delete the version-script (.map file) previously used for managing the
autoLinkVegaTurboModulesV1
export - Test your build to ensure everything works correctly
The following examples show the CMake configuration before and after migration.
Before (Manual Configuration)
## Find the turbomoduleAPI package
find_package(turbomoduleAPI CONFIG REQUIRED)
## Define source files
set(SOURCES
AutoLinkInit.cpp
MyTurboModule.cpp
)
## Create the shared library
add_library(MyTurboModule ${SOURCES})
## Link to the turbomoduleAPI library
target_link_libraries(MyTurboModule PRIVATE turbomoduleAPI::turbomoduleAPI)
## Apply version script to allow export of stable symbols only.
target_link_options(MyTurboModule PRIVATE -Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/MyTurboModule_symbols.map)
After (Using the Helper)
## Find the turbomoduleAPI package
find_package(turbomoduleAPI CONFIG REQUIRED)
## Define source files
set(SOURCES
AutoLinkInit.cpp
MyTurboModule.cpp
)
## Create the TurboModule library with all optimizations applied
kepler_add_turbo_module_library(MyTurboModule ${SOURCES})
Q: How do I handle version scripts with this helper function? Can I export additional symbols beyond autoLinkVegaTurboModulesV1
?
A: The helper function automatically applies a version script that exports only the autoLinkVegaTurboModulesV1
symbol. If you're already using a version script, remove your existing version script applications from your CMake configuration to avoid conflicts. If you need to export additional symbols beyond autoLinkVegaTurboModulesV1
, you can apply another version script after calling the helper function, but ensure there's no overlap between the symbols exported by each script. This approach should be used sparingly, as exporting additional symbols may compromise ABI stability.
Q: How do I debug issues with the kepler_add_turbo_module_library function?
A: You can check the generated compilation database (compile_commands.json found in build directory) to inspect the compiler and linker flags being used. The helper function enables EXPORT_COMPILE_COMMANDS ON
by default, which creates this database. You can open the database with IDEs to see the exact flags applied.
Q: How can I add additional target behavior?
A: The helper function already handles platform-specific behavior. If you need additional settings, you can add them after calling the helper function:
kepler_add_turbo_module_library(MyTurboModule ${SOURCES})
target_compile_definitions(MyTurboModule PRIVATE MY_CUSTOM_DEFINE=1)
Symbol Hiding
The Vega Turbo Module API (TMAPI) is a stable API that provide backwards-compatibility which guarantees that you don't need to constantly update your Turbo Module to keep up with API changes. This stability ensures that the communication is ABI-safe, and the compiled shared libraries of your Turbo Modules are binary compatible with any version of the Vega OS.
Compiled native binaries include symbols, which are named entities such as variables, functions, class, and namespaces. Occasionally, symbols in the turbomodule library may clash with symbols generated from other libraries in your app or from the OS sysroot, and this unintentional exposure of symbols may potentially lead to ABI issues. To ensure overall stability of your Turbo Modules, Amazon recommends using symbol hiding. This ensures that internal symbols from your library are hidden by default, and avoid any leaks or clashes.
Hiding the internal symbols in your Turbo Module library is important for the following reasons:
- Dynamic overriding of symbols: When internal symbols are exposed, it may be possible for symbols from different binaries to override each other depending on the order they are loaded into the process. This might lead to unintended behaviors, compatibility issues, and potential security vulnerabilities.
- Different versions of standard C++ library: Binaries compiled with different C++ standard libraries (e.g. libstdc++ or libcpp) can introduced compatibility issues when symbols are exposed.
Hiding symbols in your TurboModule projects
To hide symbols in your Vega TurboModule projects created with Vega SDK v0.9 and before, make these changes in your sources. Starting with V0.10, the Vega templates include these improvements and symbols are hidden by default for all new projects.
-
Add a symbol map.
A symbol map file specifies the version node(s) exported symbols are labeled with, and also used to hide local symbols. Vega selectively exposes symbols from the auto-link function used to register the Turbo Module, and hide all other symbols. In the case of Turbo Module projects created with Vega templates, the function used is
autoLinkVegaTurboModulesV1
.# ./sampleturbomodule_symbols.map { global: autoLinkVegaTurboModulesV1; local: *; };
NOTE: Read more about Symbol Versioning and symbol map files on the Symbol Versioning page of the GNU wiki.
-
Update
CMakeLists.txt
to use symbol map file in cmake linker options.# ./CMakeLists.txt ... add_library(SampleTurboModule SHARED ${HEADERS} ${SOURCES}) ... # Apply version script to allow export of stable symbols only. target_link_options( SampleTurboModule PRIVATE -Wl, --version-script,${CMAKE_CURRENT_SOURCE_DIR}/sampleturbomodule_symbols.map )
The
target_link_options
command applies the--version-script
linker option to thetmapp
target, which uses the specified symbol map file during the linking process.
Validate that the symbols are correctly hidden
To verify the effectiveness of symbol hiding, you can inspect the symbols exposed by the Turbo Module binary before and after applying the symbol hiding changes. Use the llvm-nm
tool, which is already available in the Vega SDK, to view exposed symbols from your libraries.
Get the path for llvm-nm tool from kepler sdk cache folder
❯ find ~/.kepler -name llvm-nm
~/.kepler/conan/p/keple5543e70392195/p/bin/llvm-nm
List symbols without the updates
Without symbol hiding, the Turbo Module binary exposes a large number of internal symbols, including those related to the implementation of the Turbo Module's functionality.
❯ ~/.kepler/conan/p/keple5543e70392195/p/bin/llvm-nm -C -D --defined-only build/vega-tv2023-armv7-release/lib/libtmapp.so | grep "tmapp:"
000f55a4 T tmappTurboModule::tmapp::getConstants()
000f57b4 T tmappTurboModule::tmapp::getArrayBuffer(com::amazon::kepler::turbomodule::ArrayBuffer)
000f55f8 T tmappTurboModule::tmapp::getMajorVersion()
000f5600 T tmappTurboModule::tmapp::getMinorVersion()
000f5608 T tmappTurboModule::tmapp::getPatchVersion()
000f5904 T tmappTurboModule::tmapp::getValueWithPromise(bool)
000f58b0 T tmappTurboModule::tmapp::getValueWithCallback(com::amazon::kepler::turbomodule::Callback)
000f5664 T tmappTurboModule::tmapp::getBool(bool)
000f5760 T tmappTurboModule::tmapp::getArray(com::amazon::kepler::turbomodule::JSArray)
000f585c T tmappTurboModule::tmapp::getValue(double, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, com::amazon::kepler::turbomodule::JSObject)
000f5610 T tmappTurboModule::tmapp::voidFunc()
000f56b8 T tmappTurboModule::tmapp::getNumber(double)
000f5808 T tmappTurboModule::tmapp::getObject(com::amazon::kepler::turbomodule::JSObject)
000f570c T tmappTurboModule::tmapp::getString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>)
000f5568 T tmappTurboModule::tmapp::tmapp()
000f5568 T tmappTurboModule::tmapp::tmapp()
000f558c T tmappTurboModule::tmapp::~tmapp()
000f5588 T tmappTurboModule::tmapp::~tmapp()
000f5588 T tmappTurboModule::tmapp::~tmapp()
List symbols after symbol hiding updates
After implementing symbol hiding, the Turbo Module binary only exposes the intended, stable public API symbol.
❯ ~/.kepler/conan/p/keple5543e70392195/p/bin/llvm-nm -C -D --defined-only build/vega-tv2023-armv7-release/lib/libtmapp.so
000757e8 T autoLinkVegaTurboModulesV1
Threading
All Turbo Module methods are invoked on the JSThread. To maintain good performance, it is crucial to avoid blocking the JSThread, by using a separate non-JSThread wherever it is beneficial, such as for Promise
or Callback
usages.
You can use a thread utility library or even std::thread
directly to create and manage a separate non-JSThread for your Turboo Module, depending on your specific requirements.
Event Handling
Turbo Modules can send events from C++ to JavaScript, but the @amzn/keplerscript-turbomodule-api
package currently does not include support for handling these events. To listen for events, you can use NativeEventEmitter
from react-native
.
Turbo Modules can signal events to JavaScript by using the built-in emit
method, which accepts most Turbo Module C++ types as a payload. When on JSThread (i.e., no new thread was created for emitting events), you should use emitSync
instead.
void SampleTurboModule::testEmit(std::string eventName) {
std::thread([self = this, eventName]() {
JSObject payload{};
payload["StringValue"] = "stringMessage with JSObject";
payload["intValue"] = 123;
payload["doubleValue"] = 45.67;
self->emit(eventName, payload);
}).detach();
}
The JavaScript side can register to receive events using NativeEventEmitter
. The following example builds on the instructions found in Implement JavaScript Layer.
import { NativeEventEmitter } from 'react-native';
import PingerModule from './NativePinger';
class Pinger {
__eventEmitter: NativeEventEmitter = null;
constructor() {
this.__eventEmitter = new NativeEventEmitter();
}
testEmit: (key: string) => {
const eventName = 'SampleEvent';
const subscription = this.__eventEmitter.addListener(
eventName,
(args) => {
console.log('From JS: addListener ', args);
}
);
SampleTurboModule.testEmit(eventName); // This emits a 'SampleEvent', which triggers above subscription
subscription.remove(); // Make sure to clean up your subscriptions
}
...
}
export default new Pinger();
Error Handling
Many of the exceptions which might be encountered when running a Turbo Module are thrown into the JavaScript layer. If you have a JavaScript implementation which is wrapped over the native module, you can use a try/catch
to address these errors there.
Related topics
Last updated: Sep 30, 2025