From 8e01638bfc392685eea7222cb495d62530a54ba7 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Fri, 23 Oct 2026 07:51:42 -0700 Subject: [PATCH] Delete deprecated RCTProfile systrace feature (#59323) Summary: RCTProfile hooked bridge modules for systrace capture, driven only by the deleted legacy bridge. With all call sites migrated or removed, delete the header, implementation, trampolines, and remaining hooks in the display link, module methods, performance logger, and downstream systrace callers. Changelog: [iOS][Removed] - Remove deprecated RCTProfile systrace API Reviewed By: shwanton Differential Revision: D126581734 --- packages/react-native/Package.swift | 1 - packages/react-native/React/Base/RCTBridge.mm | 1 - .../react-native/React/Base/RCTDisplayLink.m | 7 - .../React/Base/RCTModuleMethod.mm | 6 - .../React/Base/RCTPerformanceLogger.mm | 14 - .../react-native/React/Base/RCTRootView.m | 1 - .../RCTSurfaceHostingProxyRootView.mm | 1 - .../React/CoreModules/RCTEventDispatcher.mm | 1 - .../React/Modules/RCTUIManager.mm | 1 - .../react-native/React/Profiler/RCTMacros.h | 16 - .../react-native/React/Profiler/RCTProfile.h | 268 ------ .../react-native/React/Profiler/RCTProfile.m | 811 ------------------ .../React/Profiler/RCTProfileTrampoline-arm.S | 96 --- .../Profiler/RCTProfileTrampoline-arm64.S | 125 --- .../Profiler/RCTProfileTrampoline-i386.S | 97 --- .../Profiler/RCTProfileTrampoline-x86_64.S | 190 ---- .../ios-prebuild/__tests__/setup-test.js | 12 + .../scripts/ios-prebuild/setup.js | 18 +- 18 files changed, 28 insertions(+), 1638 deletions(-) delete mode 100644 packages/react-native/React/Profiler/RCTMacros.h delete mode 100644 packages/react-native/React/Profiler/RCTProfile.h delete mode 100644 packages/react-native/React/Profiler/RCTProfile.m delete mode 100644 packages/react-native/React/Profiler/RCTProfileTrampoline-arm.S delete mode 100644 packages/react-native/React/Profiler/RCTProfileTrampoline-arm64.S delete mode 100644 packages/react-native/React/Profiler/RCTProfileTrampoline-i386.S delete mode 100644 packages/react-native/React/Profiler/RCTProfileTrampoline-x86_64.S diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index bc51dadab45c..ac1611c59c07 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -454,7 +454,6 @@ let reactCore = RNTarget( path: "React", searchPaths: [ "React/I18n", - "React/Profiler", "ReactCommon/react/runtime/platform/ios", // explicit header search path to break circular dependency. RCTHost imports `RCTDefines.h` in ReactCore, ReacCore needs to import RCTHost ], linkedFrameworks: ["CoreServices"], diff --git a/packages/react-native/React/Base/RCTBridge.mm b/packages/react-native/React/Base/RCTBridge.mm index b2258a62c0b9..f46ded1db8fd 100644 --- a/packages/react-native/React/Base/RCTBridge.mm +++ b/packages/react-native/React/Base/RCTBridge.mm @@ -27,7 +27,6 @@ #import "RCTModuleData.h" #import "RCTPausedInDebuggerOverlayController.h" #import "RCTPerformanceLogger.h" -#import "RCTProfile.h" #import "RCTReloadCommand.h" #import "RCTUtils.h" diff --git a/packages/react-native/React/Base/RCTDisplayLink.m b/packages/react-native/React/Base/RCTDisplayLink.m index ebb430f42d93..afb1b4da566c 100644 --- a/packages/react-native/React/Base/RCTDisplayLink.m +++ b/packages/react-native/React/Base/RCTDisplayLink.m @@ -12,7 +12,6 @@ #import "RCTAssert.h" #import "RCTFrameUpdate.h" -#import "RCTProfile.h" #define RCTAssertRunLoop() \ RCTAssert(_runLoop == [NSRunLoop currentRunLoop], @"This method must be called on the CADisplayLink run loop") @@ -81,8 +80,6 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { RCTAssertRunLoop(); - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTDisplayLink _jsThreadUpdate:]", nil); - // This always runs on the JS thread run loop, which is the queue the frame // update observer expects its callbacks on, so dispatch inline. RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; @@ -91,10 +88,6 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink } [self updateJSDisplayLinkState]; - - RCTProfileImmediateEvent(RCTProfileTagAlways, @"JS Thread Tick", displayLink.timestamp, 'g'); - - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call"); } - (void)updateJSDisplayLinkState diff --git a/packages/react-native/React/Base/RCTModuleMethod.mm b/packages/react-native/React/Base/RCTModuleMethod.mm index 4dda9e0ff7ce..b853608e5f60 100644 --- a/packages/react-native/React/Base/RCTModuleMethod.mm +++ b/packages/react-native/React/Base/RCTModuleMethod.mm @@ -17,7 +17,6 @@ #import "RCTLog.h" #import "RCTManagedPointer.h" #import "RCTParserUtils.h" -#import "RCTProfile.h" #import "RCTUtils.h" typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); @@ -471,12 +470,7 @@ - (void)processMethodSignature - (SEL)selector { if (_selector == NULL) { - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, - @"", - (@{@"module" : NSStringFromClass(_moduleClass), @"method" : @(_methodInfo->objcName)})); [self processMethodSignature]; - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } return _selector; diff --git a/packages/react-native/React/Base/RCTPerformanceLogger.mm b/packages/react-native/React/Base/RCTPerformanceLogger.mm index e1c40ff51064..f31d1df4e6c1 100644 --- a/packages/react-native/React/Base/RCTPerformanceLogger.mm +++ b/packages/react-native/React/Base/RCTPerformanceLogger.mm @@ -12,14 +12,12 @@ #import "RCTLog.h" #import "RCTPerformanceLogger.h" #import "RCTPerformanceLoggerLabels.h" -#import "RCTProfile.h" #import "RCTRootView.h" using namespace facebook::react; @interface RCTPerformanceLogger () { int64_t _data[RCTPLSize][2]; - NSInteger _cookies[RCTPLSize]; } @end @@ -46,12 +44,6 @@ @implementation RCTPerformanceLogger - (void)markStartForTag:(RCTPLTag)tag { -#if RCT_PROFILE - if (RCTProfileIsProfiling()) { - NSString *label = RCTPLLabelForTag(tag); - _cookies[tag] = RCTProfileBeginAsyncEvent(RCTProfileTagAlways, label, nil); - } -#endif const NSTimeInterval currentTime = CACurrentMediaTime() * 1000; _data[tag][0] = currentTime; _data[tag][1] = 0; @@ -65,12 +57,6 @@ - (void)markStartForTag:(RCTPLTag)tag - (void)markStopForTag:(RCTPLTag)tag { -#if RCT_PROFILE - if (RCTProfileIsProfiling()) { - NSString *label = RCTPLLabelForTag(tag); - RCTProfileEndAsyncEvent(RCTProfileTagAlways, @"native", _cookies[tag], label, @"RCTPerformanceLogger"); - } -#endif const NSTimeInterval currentTime = CACurrentMediaTime() * 1000; if (_data[tag][0] != 0 && _data[tag][1] == 0) { _data[tag][1] = currentTime; diff --git a/packages/react-native/React/Base/RCTRootView.m b/packages/react-native/React/Base/RCTRootView.m index 976be1e92312..178fa0f76f3d 100644 --- a/packages/react-native/React/Base/RCTRootView.m +++ b/packages/react-native/React/Base/RCTRootView.m @@ -15,7 +15,6 @@ #import "RCTKeyCommands.h" #import "RCTLog.h" #import "RCTPerformanceLogger.h" -#import "RCTProfile.h" #import "RCTTouchHandler.h" #import "RCTUIManager.h" #import "RCTUIManagerUtils.h" diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm index 4e4101a6c6ed..bddc3b863912 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm @@ -13,7 +13,6 @@ #import "RCTBridge.h" #import "RCTLog.h" #import "RCTPerformanceLogger.h" -#import "RCTProfile.h" #import "RCTRootViewDelegate.h" #import "RCTSurface.h" #import "UIView+React.h" diff --git a/packages/react-native/React/CoreModules/RCTEventDispatcher.mm b/packages/react-native/React/CoreModules/RCTEventDispatcher.mm index 6bf4508067b8..5a64e1d8622b 100644 --- a/packages/react-native/React/CoreModules/RCTEventDispatcher.mm +++ b/packages/react-native/React/CoreModules/RCTEventDispatcher.mm @@ -9,7 +9,6 @@ #import #import -#import #import #import diff --git a/packages/react-native/React/Modules/RCTUIManager.mm b/packages/react-native/React/Modules/RCTUIManager.mm index 3764fffef7c4..3e7eba4aae4a 100644 --- a/packages/react-native/React/Modules/RCTUIManager.mm +++ b/packages/react-native/React/Modules/RCTUIManager.mm @@ -24,7 +24,6 @@ #import "RCTLog.h" #import "RCTModuleData.h" #import "RCTModuleMethod.h" -#import "RCTProfile.h" #import "RCTScrollableProtocol.h" #import "RCTShadowView.h" #import "RCTSurfaceRootShadowView.h" diff --git a/packages/react-native/React/Profiler/RCTMacros.h b/packages/react-native/React/Profiler/RCTMacros.h deleted file mode 100644 index b636f9a831d4..000000000000 --- a/packages/react-native/React/Profiler/RCTMacros.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#define _CONCAT(A, B) A##B -#define CONCAT(A, B) _CONCAT(A, B) - -#if !defined(PIC_MODIFIER) -#define PIC_MODIFIER -#endif - -#define SYMBOL_NAME(name) CONCAT(__USER_LABEL_PREFIX__, name) -#define SYMBOL_NAME_PIC(name) CONCAT(SYMBOL_NAME(name), PIC_MODIFIER) diff --git a/packages/react-native/React/Profiler/RCTProfile.h b/packages/react-native/React/Profiler/RCTProfile.h deleted file mode 100644 index a2780535de62..000000000000 --- a/packages/react-native/React/Profiler/RCTProfile.h +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#import -#import - -/** - * RCTProfile - * - * This file provides a set of functions and macros for performance profiling - * - * NOTE: This API is a work in progress, please consider carefully before - * using it. - */ - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated" - -RCT_EXTERN __deprecated_msg("This API will be removed along with the legacy architecture.") NSString *const - RCTProfileDidStartProfiling; -RCT_EXTERN __deprecated_msg("This API will be removed along with the legacy architecture.") NSString *const - RCTProfileDidEndProfiling; - -RCT_EXTERN __deprecated_msg("This API will be removed along with the legacy architecture.") const uint64_t - RCTProfileTagAlways; - -#if RCT_PROFILE - -@class RCTBridge; - -#define RCTProfileBeginFlowEvent() \ - _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wshadow\"") NSUInteger __rct_profile_flow_id = \ - _RCTProfileBeginFlowEvent(); \ - _Pragma("clang diagnostic pop") - -#define RCTProfileEndFlowEvent() _RCTProfileEndFlowEvent(__rct_profile_flow_id) - -RCT_EXTERN dispatch_queue_t RCTProfileGetQueue(void) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -RCT_EXTERN NSUInteger _RCTProfileBeginFlowEvent(void) - __deprecated_msg("This API will be removed along with the legacy architecture."); -RCT_EXTERN void _RCTProfileEndFlowEvent(NSUInteger /*cookie*/) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Returns YES if the profiling information is currently being collected - */ -RCT_EXTERN BOOL RCTProfileIsProfiling(void) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Start collecting profiling information - */ -RCT_EXTERN void RCTProfileInit(RCTBridge * /*bridge*/) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Stop profiling and return a JSON string of the collected data - The data - * returned is compliant with google's trace event format - the format used - * as input to trace-viewer - */ -RCT_EXTERN void RCTProfileEnd(RCTBridge * /*bridge*/, void (^/*callback*/)(NSString *)) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Route the RCT_PROFILE_BEGIN_EVENT hooks to our loom tracing. - */ -#ifdef WITH_LOOM_TRACE -RCT_EXTERN BOOL _RCTLoomIsProfiling(void); -RCT_EXTERN void -_RCTLoomBeginEvent(NSString *name, const char *file, size_t line, NSDictionary *args); -RCT_EXTERN void _RCTLoomEndEvent(); -#else -#define _RCTLoomIsProfiling(...) NO -#define _RCTLoomBeginEvent(...) -#define _RCTLoomEndEvent(...) -#endif - -/** - * Collects the initial event information for the event and returns a reference ID - */ -RCT_EXTERN void _RCTProfileBeginEvent( - NSThread *calleeThread, - NSTimeInterval time, - uint64_t tag, - NSString *name, - NSDictionary *args) - __deprecated_msg("This API will be removed along with the legacy architecture."); -#define RCT_PROFILE_BEGIN_EVENT(tag, name, args) \ - do { \ - if (_RCTLoomIsProfiling()) { \ - _RCTLoomBeginEvent(name, __FILE__, __LINE__, args); \ - } \ - if (RCTProfileIsProfiling()) { \ - NSThread *__calleeThread = [NSThread currentThread]; \ - NSTimeInterval __time = CACurrentMediaTime(); \ - _RCTProfileBeginEvent(__calleeThread, __time, tag, name, args); \ - } \ - } while (0) - -/** - * The ID returned by BeginEvent should then be passed into EndEvent, with the - * rest of the event information. Just at this point the event will actually be - * registered - */ -RCT_EXTERN void -_RCTProfileEndEvent(NSThread *calleeThread, NSString *threadName, NSTimeInterval time, uint64_t tag, NSString *category) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -#define RCT_PROFILE_END_EVENT(tag, category) \ - do { \ - if (_RCTLoomIsProfiling()) { \ - _RCTLoomEndEvent(); \ - } \ - if (RCTProfileIsProfiling()) { \ - NSThread *__calleeThread = [NSThread currentThread]; \ - NSString *__threadName = RCTCurrentThreadName(); \ - NSTimeInterval __time = CACurrentMediaTime(); \ - _RCTProfileEndEvent(__calleeThread, __threadName, __time, tag, category); \ - } \ - } while (0) - -/** - * Collects the initial event information for the event and returns a reference ID - */ -RCT_EXTERN NSUInteger -RCTProfileBeginAsyncEvent(uint64_t tag, NSString *name, NSDictionary *args) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * The ID returned by BeginEvent should then be passed into EndEvent, with the - * rest of the event information. Just at this point the event will actually be - * registered - */ -RCT_EXTERN void -RCTProfileEndAsyncEvent(uint64_t tag, NSString *category, NSUInteger cookie, NSString *name, NSString *threadName) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * An event that doesn't have a duration (i.e. Notification, VSync, etc) - */ -RCT_EXTERN void RCTProfileImmediateEvent(uint64_t tag, NSString *name, NSTimeInterval time, char scope) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Helper to profile the duration of the execution of a block. This method uses - * self and _cmd to name this event for simplicity sake. - * - * NOTE: The block can't expect any argument - * - * DEPRECATED: this approach breaks debugging and stepping through instrumented block functions - */ -#define RCTProfileBlock(block, tag, category, arguments) \ - ^{ \ - RCT_PROFILE_BEGIN_EVENT(tag, @(__PRETTY_FUNCTION__), nil); \ - block(); \ - RCT_PROFILE_END_EVENT(tag, category, arguments); \ - } - -/** - * Hook into a bridge instance to log all bridge module's method calls - */ -RCT_EXTERN void RCTProfileHookModules(RCTBridge * /*bridge*/) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Unhook from a given bridge instance's modules - */ -RCT_EXTERN void RCTProfileUnhookModules(RCTBridge * /*bridge*/) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Hook into all of a module's methods - */ -RCT_EXTERN void RCTProfileHookInstance(id instance) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Send systrace or cpu profiling information to the packager - * to present to the user - */ -RCT_EXTERN void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *profileData) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Systrace gluecode - * - * allow to use systrace to back RCTProfile - */ - -typedef struct { - const char *key; - unsigned long key_len; - const char *value; - unsigned long value_len; -} systrace_arg_t __deprecated_msg("This API will be removed along with the legacy architecture."); - -typedef struct { - char *(*start)(void); - void (*stop)(void); - - void (*begin_section)(uint64_t tag, const char *name, size_t numArgs, systrace_arg_t *args); - void (*end_section)(uint64_t tag, size_t numArgs, systrace_arg_t *args); - - void (*begin_async_section)(uint64_t tag, const char *name, int cookie, size_t numArgs, systrace_arg_t *args); - void (*end_async_section)(uint64_t tag, const char *name, int cookie, size_t numArgs, systrace_arg_t *args); - - void (*instant_section)(uint64_t tag, const char *name, char scope); - - void (*begin_async_flow)(uint64_t tag, const char *name, int cookie); - void (*end_async_flow)(uint64_t tag, const char *name, int cookie); -} RCTProfileCallbacks __deprecated_msg("This API will be removed along with the legacy architecture."); - -RCT_EXTERN void RCTProfileRegisterCallbacks(RCTProfileCallbacks * /*cb*/) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -/** - * Systrace control window - */ -RCT_EXTERN void RCTProfileShowControls(void) - __deprecated_msg("This API will be removed along with the legacy architecture."); -RCT_EXTERN void RCTProfileHideControls(void) - __deprecated_msg("This API will be removed along with the legacy architecture."); - -#else - -#define RCTProfileBeginFlowEvent() -#define _RCTProfileBeginFlowEvent() @0 - -#define RCTProfileEndFlowEvent() -#define _RCTProfileEndFlowEvent(...) - -#define RCTProfileIsProfiling(...) NO -#define RCTProfileInit(...) -#define RCTProfileEnd(...) @"" - -#define _RCTProfileBeginEvent(...) -#define _RCTProfileEndEvent(...) - -#define RCT_PROFILE_BEGIN_EVENT(...) -#define RCT_PROFILE_END_EVENT(...) - -#define RCTProfileBeginAsyncEvent(...) 0 -#define RCTProfileEndAsyncEvent(...) - -#define RCTProfileImmediateEvent(...) - -#define RCTProfileBlock(block, ...) block - -#define RCTProfileHookModules(...) -#define RCTProfileHookInstance(...) -#define RCTProfileUnhookModules(...) - -#define RCTProfileSendResult(...) - -#define RCTProfileShowControls(...) -#define RCTProfileHideControls(...) - -#endif - -#pragma clang diagnostic pop diff --git a/packages/react-native/React/Profiler/RCTProfile.m b/packages/react-native/React/Profiler/RCTProfile.m deleted file mode 100644 index 47e2c86ac6f3..000000000000 --- a/packages/react-native/React/Profiler/RCTProfile.m +++ /dev/null @@ -1,811 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTProfile.h" - -#import -#import -#import -#import -#import - -#import -#import - -#import "RCTAssert.h" -#import "RCTBridge+Private.h" -#import "RCTBridge.h" -#import "RCTComponentData.h" -#import "RCTLog.h" -#import "RCTModuleData.h" -#import "RCTReloadCommand.h" -#import "RCTUIManager.h" -#import "RCTUIManagerUtils.h" -#import "RCTUtils.h" - -NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling"; -NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; - -const uint64_t RCTProfileTagAlways = 1L << 0; - -#if RCT_PROFILE - -#pragma mark - Constants - -static NSString *const kProfileTraceEvents = @"traceEvents"; -static NSString *const kProfileSamples = @"samples"; -static NSString *const kProfilePrefix = @"rct_profile_"; - -#pragma mark - Variables - -static atomic_bool RCTProfileProfiling = NO; - -static NSDictionary *RCTProfileInfo; -static NSMutableDictionary *RCTProfileOngoingEvents; -static NSTimeInterval RCTProfileStartTime; -static NSUInteger RCTProfileEventID = 0; -static CADisplayLink *RCTProfileDisplayLink; -static __weak RCTBridge *_RCTProfilingBridge; -static UIWindow *RCTProfileControlsWindow; - -#pragma mark - Macros - -#define RCTProfileAddEvent(type, props...) \ - [RCTProfileInfo[type] addObject:@{@"pid" : @([[NSProcessInfo processInfo] processIdentifier]), props}]; - -#define CHECK(...) \ - if (!RCTProfileIsProfiling()) { \ - return __VA_ARGS__; \ - } - -#pragma mark - systrace glue code - -static RCTProfileCallbacks *callbacks; -static char *systrace_buffer; - -static systrace_arg_t *newSystraceArgsFromDictionary(NSDictionary *args) -{ - if (args.count == 0) { - return NULL; - } - - systrace_arg_t *systrace_args = malloc(sizeof(systrace_arg_t) * args.count); - if (systrace_args) { - __block size_t i = 0; - [args enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) { - systrace_args[i].key = [key UTF8String]; - systrace_args[i].key_len = [key length]; - systrace_args[i].value = [value UTF8String]; - systrace_args[i].value_len = [value length]; - i++; - }]; - } - return systrace_args; -} - -void RCTProfileRegisterCallbacks(RCTProfileCallbacks *cb) -{ - callbacks = cb; -} - -#pragma mark - Private Helpers - -static RCTBridge *RCTProfilingBridge(void) -{ - return _RCTProfilingBridge ?: [RCTBridge currentBridge]; -} - -static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) -{ - return @((timestamp - RCTProfileStartTime) * 1e6); -} - -static NSString *RCTProfileMemory(vm_size_t memory) -{ - double mem = ((double)memory) / 1024 / 1024; - return [NSString stringWithFormat:@"%.2lfmb", mem]; -} - -static NSDictionary *RCTProfileGetMemoryUsage(void) -{ - struct task_basic_info info; - mach_msg_type_number_t size = sizeof(info); - kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size); - if (kerr == KERN_SUCCESS) { - return @{ - @"suspend_count" : @(info.suspend_count), - @"virtual_size" : RCTProfileMemory(info.virtual_size), - @"resident_size" : RCTProfileMemory(info.resident_size), - }; - } else { - return @{}; - } -} - -#pragma mark - Module hooks - -static const char *RCTProfileProxyClassName(Class class) -{ - return [kProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String; -} - -static dispatch_group_t RCTProfileGetUnhookGroup(void) -{ - static dispatch_group_t unhookGroup; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - unhookGroup = dispatch_group_create(); - }); - - return unhookGroup; -} - -// Used by RCTProfileTrampoline assembly file to call libc`malloc -RCT_EXTERN void *RCTProfileMalloc(size_t size); -void *RCTProfileMalloc(size_t size) -{ - return malloc(size); -} - -// Used by RCTProfileTrampoline assembly file to call libc`free -RCT_EXTERN void RCTProfileFree(void *buf); -void RCTProfileFree(void *buf) -{ - free(buf); -} - -RCT_EXTERN IMP RCTProfileGetImplementation(id obj, SEL cmd); -IMP RCTProfileGetImplementation(id obj, SEL cmd) -{ - return class_getMethodImplementation([obj class], cmd); -} - -/** - * For the profiling we have to execute some code before and after every - * function being profiled, the only way of doing that with pure Objective-C is - * by using `-forwardInvocation:`, which is slow and could skew the profile - * results. - * - * The alternative in assembly is much simpler, we just need to store all the - * state at the beginning of the function, start the profiler, restore all the - * state, call the actual function we want to profile and stop the profiler. - * - * The implementation can be found in RCTProfileTrampoline-.s where arch - * is one of: i386, x86_64, arm, arm64. - */ -#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__arm64__) - -RCT_EXTERN void RCTProfileTrampoline(void); -#else -static void *RCTProfileTrampoline = NULL; -#endif - -RCT_EXTERN void RCTProfileTrampolineStart(id /*self*/, SEL /*cmd*/); -void RCTProfileTrampolineStart(id self, SEL cmd) -{ - /** - * This call might be during dealloc, so we shouldn't retain the object in the - * block. - */ - Class klass = [self class]; - RCT_PROFILE_BEGIN_EVENT( - RCTProfileTagAlways, ([NSString stringWithFormat:@"-[%s %s]", class_getName(klass), sel_getName(cmd)]), nil); -} - -RCT_EXTERN void RCTProfileTrampolineEnd(void); -void RCTProfileTrampolineEnd(void) -{ - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"objc_call,modules,auto"); -} - -static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *, NSNumber *); -static UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag, NSNumber *rootTag) -{ - UIView *view = originalCreateView(self, _cmd, tag, rootTag); - RCTProfileHookInstance(view); - return view; -} - -static void RCTProfileHookUIManager(RCTUIManager *uiManager) -{ - dispatch_async(dispatch_get_main_queue(), ^{ - for (id view in [uiManager valueForKey:@"viewRegistry"]) { - RCTProfileHookInstance([uiManager viewForReactTag:view]); - } - - Method createView = class_getInstanceMethod([RCTComponentData class], @selector(createViewWithTag:rootTag:)); - - if (method_getImplementation(createView) != (IMP)RCTProfileCreateView) { - originalCreateView = (typeof(originalCreateView))method_getImplementation(createView); - method_setImplementation(createView, (IMP)RCTProfileCreateView); - } - }); -} - -void RCTProfileHookInstance(id instance) -{ - Class moduleClass = object_getClass(instance); - - /** - * We swizzle the instance -class method to return the original class, but - * object_getClass will return the actual class. - * - * If they are different, it means that the object is returning the original - * class, but it's actual class is the proxy subclass we created. - */ - if ([instance class] != moduleClass) { - return; - } - - Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); - - if (!proxyClass) { - proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); - if (proxyClass) { - object_setClass(instance, proxyClass); - } - return; - } - - unsigned int methodCount; - Method *methods = class_copyMethodList(moduleClass, &methodCount); - for (NSUInteger i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - - /** - * Bail out on struct returns (except arm64) - we don't use it enough - * to justify writing a stret version - */ -#ifdef __arm64__ - BOOL returnsStruct = NO; -#else - const char *typeEncoding = method_getTypeEncoding(method); - // bail out on structs and unions (since they might contain structs) - BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; -#endif - - /** - * Avoid hooking into NSObject methods, methods generated by React Native - * and special methods that start `.` (e.g. .cxx_destruct) - */ - if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || - sel_getName(selector)[0] == '.' || returnsStruct) { - continue; - } - - const char *types = method_getTypeEncoding(method); - class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types); - } - free(methods); - - class_replaceMethod( - object_getClass(proxyClass), - @selector(initialize), - imp_implementationWithBlock(^{ - }), - "v@:"); - - for (Class cls in @[ proxyClass, object_getClass(proxyClass) ]) { - Method oldImp = class_getInstanceMethod(cls, @selector(class)); - class_replaceMethod( - cls, - @selector(class), - imp_implementationWithBlock(^{ - return moduleClass; - }), - method_getTypeEncoding(oldImp)); - } - - objc_registerClassPair(proxyClass); - object_setClass(instance, proxyClass); - - if (moduleClass == [RCTUIManager class]) { - RCTProfileHookUIManager((RCTUIManager *)instance); - } -} - -void RCTProfileHookModules(RCTBridge *bridge) -{ - _RCTProfilingBridge = bridge; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-pointer-compare" -#pragma clang diagnostic ignored "-Wunreachable-code-return" - if (RCTProfileTrampoline == NULL) { - return; - } -#pragma clang diagnostic pop - - RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"RCTProfileHookModules", nil); - for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { - // Only hook modules with an instance, to prevent initializing everything - if ([moduleData hasInstance]) { - [bridge - dispatchBlock:^{ - RCTProfileHookInstance(moduleData.instance); - } - queue:moduleData.methodQueue]; - } - } - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); -} - -static void RCTProfileUnhookInstance(id instance) -{ - if ([instance class] != object_getClass(instance)) { - object_setClass(instance, [instance class]); - } -} - -void RCTProfileUnhookModules(RCTBridge *bridge) -{ - _RCTProfilingBridge = nil; - - dispatch_group_enter(RCTProfileGetUnhookGroup()); - - NSDictionary *moduleDataByID = [bridge valueForKey:@"moduleDataByID"]; - for (RCTModuleData *moduleData in moduleDataByID) { - if ([moduleData hasInstance]) { - RCTProfileUnhookInstance(moduleData.instance); - } - } - - if ([bridge moduleIsInitialized:[RCTUIManager class]]) { - dispatch_async(dispatch_get_main_queue(), ^{ - for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) { - RCTProfileUnhookInstance([bridge.uiManager viewForReactTag:view]); - } - - dispatch_group_leave(RCTProfileGetUnhookGroup()); - }); - } -} - -#pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target - -@interface RCTProfile : NSObject -@end - -@implementation RCTProfile - -+ (void)vsync:(CADisplayLink *)displayLink -{ - RCTProfileImmediateEvent(RCTProfileTagAlways, @"VSYNC", displayLink.timestamp, 'g'); -} - -+ (void)reload -{ - RCTTriggerReloadCommandListeners(@"Profiling controls"); -} - -+ (void)toggle:(UIButton *)target -{ - BOOL isProfiling = RCTProfileIsProfiling(); - - // Start and Stop are switched here, since we're going to toggle isProfiling - [target setTitle:isProfiling ? @"Start" : @"Stop" forState:UIControlStateNormal]; - - if (isProfiling) { - RCTProfileEnd(RCTProfilingBridge(), ^(NSString *result) { - NSString *outFile = [NSTemporaryDirectory() stringByAppendingString:@"tmp_trace.json"]; - [result writeToFile:outFile atomically:YES encoding:NSUTF8StringEncoding error:nil]; -#if !TARGET_OS_TV - UIActivityViewController *activityViewController = - [[UIActivityViewController alloc] initWithActivityItems:@[ [NSURL fileURLWithPath:outFile] ] - applicationActivities:nil]; - activityViewController.completionWithItemsHandler = - ^(__unused UIActivityType activityType, - __unused BOOL completed, - __unused NSArray *items, - __unused NSError *error) { - RCTProfileControlsWindow.hidden = NO; - }; - RCTProfileControlsWindow.hidden = YES; - dispatch_async(dispatch_get_main_queue(), ^{ - [[RCTKeyWindow() rootViewController] presentViewController:activityViewController animated:YES completion:nil]; - }); -#else - RCTProfileControlsWindow.hidden = NO; -#endif - }); - } else { - RCTProfileInit(RCTProfilingBridge()); - } -} - -+ (void)drag:(UIPanGestureRecognizer *)gestureRecognizer -{ - CGPoint translation = [gestureRecognizer translationInView:RCTProfileControlsWindow]; - RCTProfileControlsWindow.center = - CGPointMake(RCTProfileControlsWindow.center.x + translation.x, RCTProfileControlsWindow.center.y + translation.y); - [gestureRecognizer setTranslation:CGPointMake(0, 0) inView:RCTProfileControlsWindow]; -} - -@end - -#pragma mark - Public Functions - -dispatch_queue_t RCTProfileGetQueue(void) -{ - static dispatch_queue_t queue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("com.facebook.react.Profiler", DISPATCH_QUEUE_SERIAL); - }); - return queue; -} - -BOOL RCTProfileIsProfiling(void) -{ - return atomic_load(&RCTProfileProfiling); -} - -void RCTProfileInit(RCTBridge *bridge) -{ - // TODO: enable assert JS thread from any file (and assert here) - BOOL wasProfiling = atomic_fetch_or(&RCTProfileProfiling, 1); - if (wasProfiling) { - return; - } - - if (callbacks != NULL) { - systrace_buffer = callbacks->start(); - } else { - NSTimeInterval time = CACurrentMediaTime(); - dispatch_async(RCTProfileGetQueue(), ^{ - RCTProfileStartTime = time; - RCTProfileOngoingEvents = [NSMutableDictionary new]; - RCTProfileInfo = @{ - kProfileTraceEvents : [NSMutableArray new], - kProfileSamples : [NSMutableArray new], - }; - }); - } - - // Set up thread ordering - dispatch_async(RCTProfileGetQueue(), ^{ - NSArray *orderedThreads = - @[ @"JS async", @"RCTPerformanceLogger", @"com.facebook.react.JavaScript", @(RCTUIManagerQueueName), @"main" ]; - [orderedThreads enumerateObjectsUsingBlock:^(NSString *thread, NSUInteger idx, __unused BOOL *stop) { - RCTProfileAddEvent( - kProfileTraceEvents, - @"ph" : @"M", // metadata event - @"name" : @"thread_sort_index", - @"tid" : thread, - @"args" : - @{@"sort_index" : @(-1000 + (NSInteger)idx)}); - }]; - }); - - RCTProfileHookModules(bridge); - - RCTProfileDisplayLink = [CADisplayLink displayLinkWithTarget:[RCTProfile class] selector:@selector(vsync:)]; - [RCTProfileDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling object:bridge]; -} - -void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *)) -{ - // assert JavaScript thread here again - BOOL wasProfiling = atomic_fetch_and(&RCTProfileProfiling, 0); - if (!wasProfiling) { - return; - } - - [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling object:bridge]; - - [RCTProfileDisplayLink invalidate]; - RCTProfileDisplayLink = nil; - - RCTProfileUnhookModules(bridge); - - if (callbacks != NULL) { - if (systrace_buffer) { - callbacks->stop(); - callback(@(systrace_buffer)); - } - } else { - dispatch_async(RCTProfileGetQueue(), ^{ - NSString *log = RCTJSONStringify(RCTProfileInfo, NULL); - RCTProfileEventID = 0; - RCTProfileInfo = nil; - RCTProfileOngoingEvents = nil; - - callback(log); - }); - } -} - -static NSMutableArray *RCTProfileGetThreadEvents(NSThread *thread) -{ - static NSString *const RCTProfileThreadEventsKey = @"RCTProfileThreadEventsKey"; - NSMutableArray *threadEvents = thread.threadDictionary[RCTProfileThreadEventsKey]; - if (!threadEvents) { - threadEvents = [NSMutableArray new]; - thread.threadDictionary[RCTProfileThreadEventsKey] = threadEvents; - } - return threadEvents; -} - -void _RCTProfileBeginEvent( - NSThread *calleeThread, - NSTimeInterval time, - uint64_t tag, - NSString *name, - NSDictionary *args) -{ - CHECK(); - - if (callbacks != NULL) { - systrace_arg_t *systraceArgs = newSystraceArgsFromDictionary(args); - callbacks->begin_section(tag, name.UTF8String, args.count, systraceArgs); - free(systraceArgs); - return; - } - - dispatch_async(RCTProfileGetQueue(), ^{ - NSMutableArray *events = RCTProfileGetThreadEvents(calleeThread); - [events addObject:@[ - RCTProfileTimestamp(time), - name, - RCTNullIfNil(args), - ]]; - }); -} - -void _RCTProfileEndEvent( - NSThread *calleeThread, - NSString *threadName, - NSTimeInterval time, - uint64_t tag, - NSString *category) -{ - CHECK(); - - if (callbacks != NULL) { - callbacks->end_section(tag, 0, nil); - return; - } - - dispatch_async(RCTProfileGetQueue(), ^{ - NSMutableArray *events = RCTProfileGetThreadEvents(calleeThread); - NSArray *event = events.lastObject; - [events removeLastObject]; - - if (!event) { - return; - } - - NSNumber *start = event[0]; - RCTProfileAddEvent( - kProfileTraceEvents, - @"tid" : threadName, - @"name" : event[1], - @"cat" : category, - @"ph" : @"X", - @"ts" : start, - @"dur" : @(RCTProfileTimestamp(time).doubleValue - start.doubleValue), - @"args" : event[2], ); - }); -} - -NSUInteger RCTProfileBeginAsyncEvent(uint64_t tag, NSString *name, NSDictionary *args) -{ - CHECK(0); - - static NSUInteger eventID = 0; - - NSTimeInterval time = CACurrentMediaTime(); - NSUInteger currentEventID = ++eventID; - - if (callbacks != NULL) { - systrace_arg_t *systraceArgs = newSystraceArgsFromDictionary(args); - callbacks->begin_async_section(tag, name.UTF8String, (int)(currentEventID % INT_MAX), args.count, systraceArgs); - free(systraceArgs); - } else { - dispatch_async(RCTProfileGetQueue(), ^{ - RCTProfileOngoingEvents[@(currentEventID)] = @[ - RCTProfileTimestamp(time), - name, - RCTNullIfNil(args), - ]; - }); - } - - return currentEventID; -} - -void RCTProfileEndAsyncEvent(uint64_t tag, NSString *category, NSUInteger cookie, NSString *name, NSString *threadName) -{ - CHECK(); - - if (callbacks != NULL) { - callbacks->end_async_section(tag, name.UTF8String, (int)(cookie % INT_MAX), 0, nil); - return; - } - - NSTimeInterval time = CACurrentMediaTime(); - - dispatch_async(RCTProfileGetQueue(), ^{ - NSArray *event = RCTProfileOngoingEvents[@(cookie)]; - - if (event) { - NSNumber *endTimestamp = RCTProfileTimestamp(time); - - RCTProfileAddEvent( - kProfileTraceEvents, - @"tid" : threadName, - @"name" : event[1], - @"cat" : category, - @"ph" : @"X", - @"ts" : event[0], - @"dur" : @(endTimestamp.doubleValue - [event[0] doubleValue]), - @"args" : event[2], ); - [RCTProfileOngoingEvents removeObjectForKey:@(cookie)]; - } - }); -} - -void RCTProfileImmediateEvent(uint64_t tag, NSString *name, NSTimeInterval time, char scope) -{ - CHECK(); - - if (callbacks != NULL) { - callbacks->instant_section(tag, name.UTF8String, scope); - return; - } - - NSString *threadName = RCTCurrentThreadName(); - - dispatch_async(RCTProfileGetQueue(), ^{ - RCTProfileAddEvent( - kProfileTraceEvents, - @"tid" : threadName, - @"name" : name, - @"ts" : RCTProfileTimestamp(time), - @"scope" : @(scope), - @"ph" : @"i", - @"args" : RCTProfileGetMemoryUsage(), ); - }); -} - -NSUInteger _RCTProfileBeginFlowEvent(void) -{ - static NSUInteger flowID = 0; - - CHECK(0); - - NSUInteger cookie = ++flowID; - if (callbacks != NULL) { - callbacks->begin_async_flow(1, "flow", (int)cookie); - return cookie; - } - - NSTimeInterval time = CACurrentMediaTime(); - NSString *threadName = RCTCurrentThreadName(); - - dispatch_async(RCTProfileGetQueue(), ^{ - RCTProfileAddEvent( - kProfileTraceEvents, - @"tid" : threadName, - @"name" : @"flow", - @"id" : @(cookie), - @"cat" : @"flow", - @"ph" : @"s", - @"ts" : RCTProfileTimestamp(time), ); - }); - - return cookie; -} - -void _RCTProfileEndFlowEvent(NSUInteger cookie) -{ - CHECK(); - - if (callbacks != NULL) { - callbacks->end_async_flow(1, "flow", (int)cookie); - return; - } - - NSTimeInterval time = CACurrentMediaTime(); - NSString *threadName = RCTCurrentThreadName(); - - dispatch_async(RCTProfileGetQueue(), ^{ - RCTProfileAddEvent( - kProfileTraceEvents, - @"tid" : threadName, - @"name" : @"flow", - @"id" : @(cookie), - @"cat" : @"flow", - @"ph" : @"f", - @"ts" : RCTProfileTimestamp(time), ); - }); -} - -void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data) -{ - if (![bridge.bundleURL.scheme hasPrefix:@"http"]) { - RCTLogWarn( - @"Cannot upload profile information because you're not connected to the packager. The profiling data is still saved in the app container."); - return; - } - - NSURL *URL = [NSURL URLWithString:[@"/" stringByAppendingString:route] relativeToURL:bridge.bundleURL]; - - NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; - URLRequest.HTTPMethod = @"POST"; - [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - NSURLSessionTask *task = [[NSURLSession sharedSession] - uploadTaskWithRequest:URLRequest - fromData:data - completionHandler:^(NSData *responseData, __unused NSURLResponse *response, NSError *error) { - if (error) { - RCTLogError(@"%@", error.localizedDescription); - } else { - NSString *message = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; - - if (message.length) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:@"Profile" - message:message - preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:@"OK" - style:UIAlertActionStyleCancel - handler:nil]]; - [RCTPresentedViewController() presentViewController:alertController animated:YES completion:nil]; - }); - } - } - }]; - - [task resume]; -} - -void RCTProfileShowControls(void) -{ - static const CGFloat height = 30; - static const CGFloat width = 60; - - UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(20, 80, width * 2, height)]; - window.windowLevel = UIWindowLevelAlert + 1000; - window.hidden = NO; - window.backgroundColor = [UIColor lightGrayColor]; - window.layer.borderColor = [UIColor grayColor].CGColor; - window.layer.borderWidth = 1; - window.alpha = 0.8; - - UIButton *startOrStop = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, width, height)]; - [startOrStop setTitle:RCTProfileIsProfiling() ? @"Stop" : @"Start" forState:UIControlStateNormal]; - [startOrStop addTarget:[RCTProfile class] action:@selector(toggle:) forControlEvents:UIControlEventTouchUpInside]; - startOrStop.titleLabel.font = [UIFont systemFontOfSize:12]; - - UIButton *reload = [[UIButton alloc] initWithFrame:CGRectMake(width, 0, width, height)]; - [reload setTitle:@"Reload" forState:UIControlStateNormal]; - [reload addTarget:[RCTProfile class] action:@selector(reload) forControlEvents:UIControlEventTouchUpInside]; - reload.titleLabel.font = [UIFont systemFontOfSize:12]; - - [window addSubview:startOrStop]; - [window addSubview:reload]; - - UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:[RCTProfile class] - action:@selector(drag:)]; - [window addGestureRecognizer:gestureRecognizer]; - - RCTProfileControlsWindow = window; -} - -void RCTProfileHideControls(void) -{ - RCTProfileControlsWindow.hidden = YES; - RCTProfileControlsWindow = nil; -} - -#endif diff --git a/packages/react-native/React/Profiler/RCTProfileTrampoline-arm.S b/packages/react-native/React/Profiler/RCTProfileTrampoline-arm.S deleted file mode 100644 index c7b8e414c1d0..000000000000 --- a/packages/react-native/React/Profiler/RCTProfileTrampoline-arm.S +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include "RCTMacros.h" - -#if RCT_PROFILE && defined(__arm__) - - .thumb - .thumb_func - .globl SYMBOL_NAME(RCTProfileTrampoline) -SYMBOL_NAME(RCTProfileTrampoline): - /** - * The explanation here is shorter, refer to the x86_64 implementation to a - * richer explanation - */ - - /** - * Save the parameter registers (r0-r3), r7 (frame pointer) and lr (link - * register (contains the address of the caller of RCTProfileTrampoline) - */ - push {r0-r3, r7, lr} - - /** - * Allocate memory to store values across function calls: 12-bytes are - * allocated to store 3 values: the previous value of the callee saved - * register used to save the pointer to the allocated memory, the caller of - * RCTProfileTrampoline and the address of the actual function we want to - * profile - */ - mov r0, #0xc - bl SYMBOL_NAME(RCTProfileMalloc) - /** - * r4 is the callee saved register we'll use to refer to the allocated memory, - * store its initial value, so we can restore it later - */ - str r4, [r0] - mov r4, r0 - - /** - * void RCTProfileGetImplementation(id object, SEL selector) in RCTProfile.m - * - * Load the first 2 argumenters (self and _cmd) used to call - * RCTProfileTrampoline from the stack and put them on the appropriate registers. - */ - ldr r0, [sp] - ldr r1, [sp, #0x4] - bl SYMBOL_NAME(RCTProfileGetImplementation) - // store the actual function address in the allocated memory - str r0, [r4, #0x4] - - /** - * void RCTProfileGetImplementation(id object, SEL selector) in RCTProfile.m - * - * Load the first 2 arguments again to start the profiler - */ - ldr r0, [sp] - ldr r1, [sp, #0x4] - bl SYMBOL_NAME(RCTProfileTrampolineStart) - - /** - * Restore the state to call the actual function we want to profile: pop - * all the registers - */ - pop {r0-r3, r7, lr} - - // store lr (the caller) since it'll be overridden by `blx` (call) - str lr, [r4, #0x8] - ldr r12, [r4, #0x4] // load the function address - blx r12 // call it - push {r0} // save return value - - // void RCTProfileTrampolineEnd(void) in RCTProfile.m - just ends this profile - bl SYMBOL_NAME(RCTProfileTrampolineEnd) - - /** - * Save the value we still need from the allocated memory (caller address), - * restore r4 and free the allocated memory (put its address in r0) - */ - mov r0, r4 - ldr r1, [r4, #0x8] - ldr r4, [r4] - push {r1} // save the caller on the stack - bl SYMBOL_NAME(RCTProfileFree) - - pop {lr} // pop the caller - pop {r0} // pop the return value - bx lr // jump to the caller - - trap - -#endif diff --git a/packages/react-native/React/Profiler/RCTProfileTrampoline-arm64.S b/packages/react-native/React/Profiler/RCTProfileTrampoline-arm64.S deleted file mode 100644 index 1e04fb00801f..000000000000 --- a/packages/react-native/React/Profiler/RCTProfileTrampoline-arm64.S +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include "RCTMacros.h" - -#if RCT_PROFILE && defined(__arm64__) - - .align 5 - .globl SYMBOL_NAME(RCTProfileTrampoline) -SYMBOL_NAME(RCTProfileTrampoline): - /** - * The explanation here is shorter, refer to the x86_64 implementation to a - * richer explanation - */ - - // Basic prolog: save the frame pointer and the link register (caller address) - stp fp, lr, [sp, #-16]! - mov fp, sp - - /** - * Store the value of all the parameter registers (x0-x8, q0-q7) so we can - * restore everything to the initial state at the time of the actual function - * call - */ - sub sp, sp, #(10*8 + 8*16) - stp q0, q1, [sp, #(0*16)] - stp q2, q3, [sp, #(2*16)] - stp q4, q5, [sp, #(4*16)] - stp q6, q7, [sp, #(6*16)] - stp x0, x1, [sp, #(8*16+0*8)] - stp x2, x3, [sp, #(8*16+2*8)] - stp x4, x5, [sp, #(8*16+4*8)] - stp x6, x7, [sp, #(8*16+6*8)] - str x8, [sp, #(8*16+8*8)] - - /** - * Allocate 16-bytes for the values that have to be preserved across the call - * to the actual function, since the stack has to be in the exact initial - * state. During its lifetime we use it to store the initial value of the - * callee saved registers we use to point the memory, the actual address of - * the implementation and the caller address. - */ - mov x0, #0x10 - bl SYMBOL_NAME(RCTProfileMalloc) - // store the initial value of r19, the callee saved register we'll use - str x19, [x0] - mov x19, x0 - - /** - * void RCTProfileGetImplementation(id object, SEL selector) - * - * Load the 2 first arguments from the stack, they are the same used to call - * this function - */ - ldp x0, x1, [sp, #(8*16+0*8)] - bl SYMBOL_NAME(RCTProfileGetImplementation) - str x0, [x19, #0x8] // store the actual function address - - /** - * void RCTProfileTrampolineStart(id, SEL) in RCTProfile.m - * - * start the profile, it takes the same first 2 arguments as above. - */ - ldp x0, x1, [sp, #(8*16+0*8)] - bl SYMBOL_NAME(RCTProfileTrampolineStart) - - // Restore all the parameter registers to the initial state. - ldp q0, q1, [sp, #(0*16)] - ldp q2, q3, [sp, #(2*16)] - ldp q4, q5, [sp, #(4*16)] - ldp q6, q7, [sp, #(6*16)] - ldp x0, x1, [sp, #(8*16+0*8)] - ldp x2, x3, [sp, #(8*16+2*8)] - ldp x4, x5, [sp, #(8*16+4*8)] - ldp x6, x7, [sp, #(8*16+6*8)] - ldr x8, [sp, #(8*16+8*8)] - - // Restore the stack pointer, frame pointer and link register - mov sp, fp - ldp fp, lr, [sp], #16 - - - ldr x9, [x19, #0x8] // Load the function - str lr, [x19, #0x8] // store the address of the caller - - blr x9 // call the actual function - - /** - * allocate 32-bytes on the stack, for the 2 return values + the caller - * address that has to preserved across the call to `free` - */ - sub sp, sp, #0x20 - str q0, [sp, #0x0] // 16-byte return value - str x0, [sp, #0x10] // 8-byte return value - - // void RCTProfileTrampolineEnd(void) in RCTProfile.m - just ends this profile - bl SYMBOL_NAME(RCTProfileTrampolineEnd) - - /** - * restore the callee saved registers, move the values we still need to the - * stack and free the allocated memory - */ - mov x0, x19 // move the address of the memory to x0, first argument - ldr x10, [x19, #0x8] // load the caller address - ldr x19, [x19] // restore x19 - str x10, [sp, #0x18] // store x10 on the stack space allocated above - bl SYMBOL_NAME(RCTProfileFree) - - // Load both return values and link register from the stack - ldr q0, [sp, #0x0] - ldr x0, [sp, #0x10] - ldr lr, [sp, #0x18] - - // restore the stack pointer - add sp, sp, #0x20 - - // jump to the caller, without a link - br lr - -#endif diff --git a/packages/react-native/React/Profiler/RCTProfileTrampoline-i386.S b/packages/react-native/React/Profiler/RCTProfileTrampoline-i386.S deleted file mode 100644 index 8d3fdce28f7f..000000000000 --- a/packages/react-native/React/Profiler/RCTProfileTrampoline-i386.S +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include "RCTMacros.h" - -#if RCT_PROFILE && defined(__i386__) - - .globl SYMBOL_NAME(RCTProfileTrampoline) -SYMBOL_NAME(RCTProfileTrampoline): - /** - * The x86 version is much simpler, since all the arguments are passed in the - * stack, so we just have to preserve the stack pointer (%esp) and the callee - * saved register used to keep the memory allocated - * - * The explanation here is also shorter, refer to the x86_64 implementation to - * a richer explanation - */ - - /** - * Allocate memory to save the caller of RCTProfileTrampoline (used afterwards - * to return at the end of the function) and the initial value for the callee - * saved register (%edi) that will be used to point to the memory allocated. - */ - subl $0x8, %esp // stack padding (16-byte alignment for function calls) - pushl $0xc // allocate 12-bytes - calll SYMBOL_NAME(RCTProfileMalloc) - addl $0xc, %esp // restore stack (8-byte padding + 4-byte argument) - - /** - * actually store the values in the memory allocated - */ - movl %edi, 0x0(%eax) // previous value of edi - popl 0x4(%eax) // caller of RCTProfileTrampoline - - // save the pointer to the allocated memory in %edi - movl %eax, %edi - - /** - * void RCTProfileGetImplementation(id object, SEL selector) in RCTProfile.m - * - * Get the address of the actual C function we have to profile - */ - calll SYMBOL_NAME(RCTProfileGetImplementation) - movl %eax, 0x8(%edi) // Save it in the allocated memory - - /** - * void RCTProfileTrampolineStart(id, SEL) in RCTProfile.m - * - * start profile - the arguments are already in the right position in the - * stack since it takes the same first 2 arguments as the any ObjC function - - * "self" and "_cmd" - */ - calll SYMBOL_NAME(RCTProfileTrampolineStart) - - /** - * Call the actual function and save it's return value, since it should be the - * return value of RCTProfileTrampoline - */ - calll *0x8(%edi) - pushl %eax - - // Align stack and end profile - subl $0xc, %esp - calll SYMBOL_NAME(RCTProfileTrampolineEnd) - addl $0xc, %esp // restore the stack - - /** - * Move the values from the allocated memory to the stack, restore the - * value of %edi, and prepare to free the allocated memory. - */ - pushl 0x4(%edi) // caller of RCTProfileTrampoline - subl $0x4, %esp // Stack padding - pushl %edi // push the memory address - movl 0x0(%edi), %edi // restore the value of %edi - - /** - * Actually free the memory used to store the values across function calls, - * the stack has already been padded and the first and only argument, the - * memory address, is already in the bottom of the stack. - */ - calll SYMBOL_NAME(RCTProfileFree) - addl $0x8, %esp - - /** - * pop the caller address to %ecx and the actual function return value to - * %eax, so it's the return value of RCTProfileTrampoline - */ - popl %ecx - popl %eax - jmpl *%ecx - -#endif diff --git a/packages/react-native/React/Profiler/RCTProfileTrampoline-x86_64.S b/packages/react-native/React/Profiler/RCTProfileTrampoline-x86_64.S deleted file mode 100644 index d5bcb50d4e6e..000000000000 --- a/packages/react-native/React/Profiler/RCTProfileTrampoline-x86_64.S +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include "RCTMacros.h" - -#if RCT_PROFILE && defined(__x86_64__) - - .globl SYMBOL_NAME(RCTProfileTrampoline) -SYMBOL_NAME(RCTProfileTrampoline): - - /** - * Saves all the state so we can restore it before calling the functions being - * profiled. Registers have the same value at the point of the function call, - * the only thing we can change is the return value, so we return to - * `RCTProfileTrampoline` rather than to its caller. - * - * Save all the parameters registers (%rdi, %rsi, %rdx, %rcx, %r8, %r9), they - * have the 6 first arguments of the function call, and %rax which in special - * cases might be a pointer used for struct returns. - * - * We have to save %r12 since its value should be preserved across function - * calls and we'll use it to keep the stack pointer - */ - subq $0x80+8, %rsp // 8 x 16-bytes xmm registers + 8-bytes alignment - movdqa %xmm0, 0x70(%rsp) - movdqa %xmm1, 0x60(%rsp) - movdqa %xmm2, 0x50(%rsp) - movdqa %xmm3, 0x40(%rsp) - movdqa %xmm4, 0x30(%rsp) - movdqa %xmm5, 0x20(%rsp) - movdqa %xmm6, 0x10(%rsp) - movdqa %xmm7, 0x00(%rsp) - pushq %rdi - pushq %rsi - pushq %rdx - pushq %rcx - pushq %r8 - pushq %r9 - pushq %rax - pushq %r12 - - /** - * Store the stack pointer in the callee saved register %r12 and align the - * stack - it has to 16-byte aligned at the point of the function call - */ - movq %rsp, %r12 - andq $-0x10, %rsp - - /** - * void RCTProfileGetImplementation(id object, SEL selector) - * - * This is a C function defined in `RCTProfile.m`, the object and the selector - * already have to be on %rdi and %rsi respectively, as in any ObjC call. - */ - callq SYMBOL_NAME_PIC(RCTProfileGetImplementation) - - // Restore/unalign the stack pointer, so we can access the registers we stored - movq %r12, %rsp - - /** - * pop %r12 before pushing %rax, which contains the address of the actual - * function we have to call, than we keep %r12 at the bottom of the stack to - * reference the stack pointer - */ - popq %r12 - pushq %rax - pushq %r12 - - // align stack - movq %rsp, %r12 - andq $-0x10, %rsp - - /** - * Allocate memory to save parent before start profiling: the address is put - * at the bottom of the stack at the function call, so ret can actually return - * to the caller. In this case it has the address of RCTProfileTrampoline's - * caller where we'll have to return to after we're finished. - * - * We can't store it on the stack or in any register, since we have to be in - * the exact same state we were at the moment we were called, so the solution - * is to allocate a tiny bit of memory to save this address - */ - - // allocate 16 bytes - movq $0x10, %rdi - callq SYMBOL_NAME_PIC(RCTProfileMalloc) - - // store the initial value of caller saved registers %r13 and %r14 - movq %r13, 0x0(%rax) - movq %r14, 0x8(%rax) - - // mov the pointers we need to the callee saved registers - movq 0xd8(%rsp), %r13 // caller of RCTProfileTrampoline (0xd8 is stack top) - movq %rax, %r14 // allocated memory's address - - /** - * Move self and cmd back to the registers and call start profile: it uses - * the object and the selector to label the call in the profile. - */ - movq 0x40(%r12), %rdi // object - movq 0x38(%r12), %rsi // selector - - // void RCTProfileTrampolineStart(id, SEL) in RCTProfile.m - callq SYMBOL_NAME_PIC(RCTProfileTrampolineStart) - - // unalign the stack and restore %r12 - movq %r12, %rsp - popq %r12 - - // Restore registers for actual function call - popq %r11 - popq %rax - popq %r9 - popq %r8 - popq %rcx - popq %rdx - popq %rsi - popq %rdi - movdqa 0x00(%rsp), %xmm7 - movdqa 0x10(%rsp), %xmm6 - movdqa 0x20(%rsp), %xmm5 - movdqa 0x30(%rsp), %xmm4 - movdqa 0x40(%rsp), %xmm3 - movdqa 0x50(%rsp), %xmm2 - movdqa 0x60(%rsp), %xmm1 - movdqa 0x70(%rsp), %xmm0 - addq $0x80+8, %rsp - - /** - * delete parent caller (saved in %r13) `call` will add the new address so - * we return to RCTProfileTrampoline rather than to its caller - */ - addq $0x8, %rsp - - // call the actual function and save the return value - callq *%r11 - pushq %rax - pushq %rdx - subq $0x20, %rsp // 2 16-bytes xmm register - movdqa %xmm0, 0x00(%rsp) - movdqa %xmm1, 0x10(%rsp) - - // void RCTProfileTrampolineEnd(void) in RCTProfile.m - just ends this profile - callq SYMBOL_NAME_PIC(RCTProfileTrampolineEnd) - - /** - * Restore the initial value of the callee saved registers, saved in the - * memory allocated. - */ - movq %r13, %rcx - movq %r14, %rdi - movq 0x0(%r14), %r13 - movq 0x8(%r14), %r14 - - /** - * save caller address and actual function return (previously in the allocated - * memory) and align the stack - */ - pushq %rcx - pushq %r12 - movq %rsp, %r12 - andq $-0x10, %rsp - - // Free the memory allocated to stash callee saved registers - callq SYMBOL_NAME_PIC(RCTProfileFree) - - // unalign stack and restore %r12 - movq %r12, %rsp - popq %r12 - - /** - * pop the caller address to %rcx and the actual function return value(s) - * so it's the return value of RCTProfileTrampoline - */ - popq %rcx - movdqa 0x00(%rsp), %xmm0 - movdqa 0x10(%rsp), %xmm1 - addq $0x20, %rsp - popq %rdx - popq %rax - - // jump to caller - jmpq *%rcx - -#endif diff --git a/packages/react-native/scripts/ios-prebuild/__tests__/setup-test.js b/packages/react-native/scripts/ios-prebuild/__tests__/setup-test.js index a0a66f27721d..ae77c15cbed1 100644 --- a/packages/react-native/scripts/ios-prebuild/__tests__/setup-test.js +++ b/packages/react-native/scripts/ios-prebuild/__tests__/setup-test.js @@ -282,6 +282,18 @@ describe('createHeaderLinker', () => { ]); }); + it('warns and skips a source folder that no longer exists', () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + expect(() => stage('React/Profiler', 'React')).not.toThrow(); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('React/Profiler'), + ); + expect(log).not.toHaveBeenCalled(); + expect(fs.existsSync(staged('React'))).toBe(false); + }); + it.each(['__tests__', 'tests', 'platform'])( 'does not stage headers from a %s subfolder', folder => { diff --git a/packages/react-native/scripts/ios-prebuild/setup.js b/packages/react-native/scripts/ios-prebuild/setup.js index c8d6ef4a5324..84e054e04c96 100644 --- a/packages/react-native/scripts/ios-prebuild/setup.js +++ b/packages/react-native/scripts/ios-prebuild/setup.js @@ -67,7 +67,6 @@ async function setup( link('React/CoreModules', 'React'); link('React/Modules', 'React'); link('React/Fabric', 'React'); - link('React/Profiler', 'React'); link('React/CoreModules', 'React'); link('React/Runtime', 'React'); link('React/Views/ScrollView', 'React'); @@ -186,11 +185,26 @@ function createHeaderLinker( const source = path.resolve(root, fromPath); const target = path.resolve(linksFolder, includePath ?? fromPath); + let entries; + try { + entries = fs.readdirSync(source, {withFileTypes: true}); + } catch (e) { + if (e.code === 'ENOENT') { + // A source folder was deleted without updating the link() list below. + // Warn instead of crashing the whole prebuild (and OSS builds with it). + console.warn( + `Skipping missing source folder ${path.relative(root, source)} ` + + `— remove its link() entry from setup.js if the deletion was intentional.`, + ); + return; + } + throw e; + } + createFolderIfNotExists(target); let linkedFiles = 0; - const entries = fs.readdirSync(source, {withFileTypes: true}); if ( entries.some( dirent => dirent.isFile() && /\.(h|hpp)$/.test(String(dirent.name)),