as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
AWS
Documentation
Support
Contact Us
My Cases
Get Started
Design and Develop
Publish
Reference
Support

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:

  1. 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.
  2. 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 and target_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.

Copied to clipboard.

## 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:

  1. Review your current CMakeLists.txt to understand how your TurboModule is configured
  2. Make sure that you have the find_package(turbomoduleAPI CONFIG REQUIRED) line in your CMakeLists.txt
  3. Replace your add_library call and all subsequent configuration with a single kepler_add_turbo_module_library call
  4. Delete the version-script (.map file) previously used for managing the autoLinkVegaTurboModulesV1 export
  5. Test your build to ensure everything works correctly

The following examples show the CMake configuration before and after migration.

Before (Manual Configuration)

Copied to clipboard.

## 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)

Copied to clipboard.

## 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:

Copied to clipboard.

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.

  1. 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.

    Copied to clipboard.

     # ./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.

  2. Update CMakeLists.txt to use symbol map file in cmake linker options.

    Copied to clipboard.

     # ./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 the tmapp 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

Copied to clipboard.

❯ 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.

Copied to clipboard.

❯ ~/.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.

Copied to clipboard.

❯ ~/.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.

Copied to clipboard.

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.

Copied to clipboard.

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.

Create a Turbo Module


Last updated: Sep 30, 2025