From 16290851aa842d2527b2d1c515ae14e51130d6dd Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Tue, 1 Nov 2016 10:14:00 -0700 Subject: [PATCH] Recognise and run BC bundles Reviewed By: javache Differential Revision: D4067425 fbshipit-source-id: fade9adebfa8a59dc49aeadfd01a782f7b686082 --- React/Base/RCTJavaScriptLoader.h | 37 ++++++++- React/Base/RCTJavaScriptLoader.m | 24 ++++-- React/Executors/RCTJSCExecutor.mm | 131 ++++++++++++++++++++---------- React/Executors/RCTJSCWrapper.h | 2 + React/Executors/RCTJSCWrapper.mm | 4 +- 5 files changed, 147 insertions(+), 51 deletions(-) diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index 4c5b962adf..8d09f07220 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -9,7 +9,40 @@ #import -extern uint32_t const RCTRAMBundleMagicNumber; +#import "RCTDefines.h" + +/** + * RCTScriptTag + * + * Scripts given to the JS Executors to run could be in any of the following + * formats. They are tagged so the executor knows how to run them. + */ +typedef NS_ENUM(NSInteger) { + RCTScriptString = 0, + RCTScriptRAMBundle, + RCTScriptBCBundle, +} RCTScriptTag; + +/** + * RCTMagicNumber + * + * RAM bundles and BC bundles begin with magic numbers. For RAM bundles this is + * 4 bytes, for BC bundles this is 8 bytes. This structure holds the first 8 + * bytes from a bundle in a way that gives access to that information. + */ +typedef union { + uint64_t allBytes; + uint32_t first4; + uint64_t first8; +} RCTMagicNumber; + +/** + * RCTParseMagicNumber + * + * Takes the first 8 bytes of a bundle, and returns a tag describing the + * bundle's format. + */ +RCT_EXTERN RCTScriptTag RCTParseMagicNumber(RCTMagicNumber magic); extern NSString *const RCTJavaScriptLoaderErrorDomain; @@ -42,7 +75,7 @@ typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source, int64_t sourc * @experimental * Attempts to synchronously load the script at the given URL. The following two conditions must be met: * 1. It must be a file URL. - * 2. It must point to a RAM bundle. + * 2. It must not point to a text/javascript file. * If the URL does not meet those conditions, this method will return nil and supply an error with the domain * RCTJavaScriptLoaderErrorDomain and the code RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously. */ diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 58988920af..dedd936b6a 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -18,10 +18,24 @@ #include -uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5; +static uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5; +static uint64_t const RCTBCBundleMagicNumber = 0xFF4865726D657300; NSString *const RCTJavaScriptLoaderErrorDomain = @"RCTJavaScriptLoaderErrorDomain"; +RCTScriptTag RCTParseMagicNumber(RCTMagicNumber magic) +{ + if (NSSwapLittleIntToHost(magic.first4) == RCTRAMBundleMagicNumber) { + return RCTScriptRAMBundle; + } + + if (NSSwapLittleLongLongToHost(magic.first8) == RCTBCBundleMagicNumber) { + return RCTScriptBCBundle; + } + + return RCTScriptString; +} + @implementation RCTLoadingProgress - (NSString *)description @@ -112,7 +126,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) return nil; } - uint32_t magicNumber; + RCTMagicNumber magicNumber = {.allBytes = 0}; size_t readResult = fread(&magicNumber, sizeof(magicNumber), 1, bundle); fclose(bundle); if (readResult != 1) { @@ -125,13 +139,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) return nil; } - magicNumber = NSSwapLittleIntToHost(magicNumber); - if (magicNumber != RCTRAMBundleMagicNumber) { + RCTScriptTag tag = RCTParseMagicNumber(magicNumber); + if (tag == RCTScriptString) { if (error) { *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously userInfo:@{NSLocalizedDescriptionKey: - @"Cannot load non-RAM bundled files synchronously"}]; + @"Cannot load text/javascript files synchronously"}]; } return nil; } diff --git a/React/Executors/RCTJSCExecutor.mm b/React/Executors/RCTJSCExecutor.mm index 34089ae3d2..2c96b5a39d 100644 --- a/React/Executors/RCTJSCExecutor.mm +++ b/React/Executors/RCTJSCExecutor.mm @@ -66,6 +66,11 @@ struct RandomAccessBundleStartupCode { } }; +struct TaggedScript { + const RCTScriptTag tag; + const NSData *script; +}; + #if RCT_PROFILE @interface RCTCookieMap : NSObject { @@ -280,15 +285,20 @@ static NSThread *newJavaScriptThread(void) JSContext:(JSContext *)context error:(NSError **)error { - BOOL isRAMBundle = NO; - script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, error); - if (!script) { + TaggedScript taggedScript = loadTaggedScript(script, sourceURL, _performanceLogger, _randomAccessBundle, error); + + if (!taggedScript.script) { return NO; } - if (isRAMBundle) { + + if (taggedScript.tag == RCTScriptRAMBundle) { registerNativeRequire(context, self); } - NSError *returnedError = executeApplicationScript(script, sourceURL, _jscWrapper, _performanceLogger, _context.context.JSGlobalContextRef); + + NSError *returnedError = executeApplicationScript(taggedScript, sourceURL, + _jscWrapper, + _performanceLogger, + _context.context.JSGlobalContextRef); if (returnedError) { if (error) { *error = returnedError; @@ -652,16 +662,16 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName) RCTAssertParam(script); RCTAssertParam(sourceURL); - BOOL isRAMBundle = NO; - { - NSError *error; - script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, &error); - if (script == nil) { - if (onComplete) { - onComplete(error); - } - return; + NSError *loadError; + TaggedScript taggedScript = loadTaggedScript(script, sourceURL, + _performanceLogger, + _randomAccessBundle, + &loadError); + if (!taggedScript.script) { + if (onComplete) { + onComplete(loadError); } + return; } RCTProfileBeginFlowEvent(); @@ -671,11 +681,13 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName) return; } - if (isRAMBundle) { + if (taggedScript.tag == RCTScriptRAMBundle) { registerNativeRequire(self.context.context, self); } - NSError *error = executeApplicationScript(script, sourceURL, self->_jscWrapper, self->_performanceLogger, + NSError *error = executeApplicationScript(taggedScript, sourceURL, + self->_jscWrapper, + self->_performanceLogger, self->_context.context.JSGlobalContextRef); if (onComplete) { onComplete(error); @@ -683,33 +695,42 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName) }]; } -static NSData *loadPossiblyBundledApplicationScript(NSData *script, NSURL *sourceURL, - RCTPerformanceLogger *performanceLogger, - BOOL &isRAMBundle, RandomAccessBundleData &randomAccessBundle, - NSError **error) +static TaggedScript loadTaggedScript(NSData *script, + NSURL *sourceURL, + RCTPerformanceLogger *performanceLogger, + RandomAccessBundleData &randomAccessBundle, + NSError **error) { RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / prepare bundle", nil); - // The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`. - uint32_t magicNumber = 0; + RCTMagicNumber magicNumber = {.allBytes = 0}; [script getBytes:&magicNumber length:sizeof(magicNumber)]; - isRAMBundle = NSSwapLittleIntToHost(magicNumber) == RCTRAMBundleMagicNumber; - if (isRAMBundle) { - [performanceLogger markStartForTag:RCTPLRAMBundleLoad]; - script = loadRAMBundle(sourceURL, error, randomAccessBundle); - [performanceLogger markStopForTag:RCTPLRAMBundleLoad]; - [performanceLogger setValue:script.length forTag:RCTPLRAMStartupCodeSize]; - } else { - // JSStringCreateWithUTF8CString expects a null terminated C string. - // RAM Bundling already provides a null terminated one. - NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1]; - [nullTerminatedScript appendData:script]; - [nullTerminatedScript appendBytes:"" length:1]; - script = nullTerminatedScript; + RCTScriptTag tag = RCTParseMagicNumber(magicNumber); + + NSData *loadedScript = NULL; + switch (tag) { + case RCTScriptRAMBundle: + [performanceLogger markStartForTag:RCTPLRAMBundleLoad]; + + loadedScript = loadRAMBundle(sourceURL, error, randomAccessBundle); + + [performanceLogger markStopForTag:RCTPLRAMBundleLoad]; + [performanceLogger setValue:loadedScript.length forTag:RCTPLRAMStartupCodeSize]; + break; + + case RCTScriptBCBundle: + loadedScript = script; + break; + + case RCTScriptString: { + NSMutableData *nullTerminatedScript = [NSMutableData dataWithData:script]; + [nullTerminatedScript appendBytes:"" length:1]; + loadedScript = nullTerminatedScript; + } } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - return script; + return { .tag = tag, .script = loadedScript }; } static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor) @@ -718,22 +739,46 @@ static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor) context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakExecutor _nativeRequire:moduleID]; }; } -static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJSCWrapper *jscWrapper, - RCTPerformanceLogger *performanceLogger, JSGlobalContextRef ctx) +static NSError *executeApplicationScript(TaggedScript taggedScript, + NSURL *sourceURL, + RCTJSCWrapper *jscWrapper, + RCTPerformanceLogger *performanceLogger, + JSGlobalContextRef ctx) { RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", (@{ - @"url": sourceURL.absoluteString, @"size": @(script.length) + @"url": sourceURL.absoluteString, @"size": @(taggedScript.script.length) })); + [performanceLogger markStartForTag:RCTPLScriptExecution]; JSValueRef jsError = NULL; - JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes); JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String); - jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError); + + switch (taggedScript.tag) { + case RCTScriptRAMBundle: + /* fallthrough */ + case RCTScriptString: { + JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)taggedScript.script.bytes); + jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError); + jscWrapper->JSStringRelease(execJSString); + break; + } + + case RCTScriptBCBundle: { + file_ptr source(fopen(sourceURL.path.UTF8String, "r"), fclose); + int sourceFD = fileno(source.get()); + + jscWrapper->JSEvaluateBytecodeBundle(ctx, NULL, sourceFD, bundleURL, &jsError); + break; + } + } + jscWrapper->JSStringRelease(bundleURL); - jscWrapper->JSStringRelease(execJSString); [performanceLogger markStopForTag:RCTPLScriptExecution]; - NSError *error = jsError ? RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper) : nil; + NSError *error = jsError + ? RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper) + : nil; + RCT_PROFILE_END_EVENT(0, @"js_call"); return error; } diff --git a/React/Executors/RCTJSCWrapper.h b/React/Executors/RCTJSCWrapper.h index 51c1faeb4a..68ddb46c17 100644 --- a/React/Executors/RCTJSCWrapper.h +++ b/React/Executors/RCTJSCWrapper.h @@ -25,6 +25,7 @@ typedef JSStringRef (*JSValueCreateJSONStringFuncType)(JSContextRef, JSValueRef, typedef bool (*JSValueIsUndefinedFuncType)(JSContextRef, JSValueRef); typedef bool (*JSValueIsNullFuncType)(JSContextRef, JSValueRef); typedef JSValueRef (*JSEvaluateScriptFuncType)(JSContextRef, JSStringRef, JSObjectRef, JSStringRef, int, JSValueRef *); +typedef JSValueRef (*JSEvaluateBytecodeBundleFuncType)(JSContextRef, JSObjectRef, int, JSStringRef, JSValueRef *); typedef struct RCTJSCWrapper { JSStringCreateWithCFStringFuncType JSStringCreateWithCFString; @@ -41,6 +42,7 @@ typedef struct RCTJSCWrapper { JSValueIsUndefinedFuncType JSValueIsUndefined; JSValueIsNullFuncType JSValueIsNull; JSEvaluateScriptFuncType JSEvaluateScript; + JSEvaluateBytecodeBundleFuncType JSEvaluateBytecodeBundle; Class JSContext; Class JSValue; } RCTJSCWrapper; diff --git a/React/Executors/RCTJSCWrapper.mm b/React/Executors/RCTJSCWrapper.mm index 2c012c04d7..b2a9ea0166 100644 --- a/React/Executors/RCTJSCWrapper.mm +++ b/React/Executors/RCTJSCWrapper.mm @@ -26,7 +26,7 @@ static void *RCTCustomLibraryHandler(void) static dispatch_once_t token; static void *handler; dispatch_once(&token, ^{ - handler = dlopen("@executable_path/Frameworks/JSC.framework/JSC", RTLD_LAZY | RTLD_LOCAL); + handler = dlopen("@loader_path/Frameworks/JSC.framework/JSC", RTLD_LAZY | RTLD_LOCAL); if (!handler) { const char *err = dlerror(); @@ -57,6 +57,7 @@ static void RCTSetUpSystemLibraryPointers(RCTJSCWrapper *wrapper) wrapper->JSValueIsUndefined = JSValueIsUndefined; wrapper->JSValueIsNull = JSValueIsNull; wrapper->JSEvaluateScript = JSEvaluateScript; + wrapper->JSEvaluateBytecodeBundle = NULL; wrapper->JSContext = [JSContext class]; wrapper->JSValue = [JSValue class]; } @@ -83,6 +84,7 @@ static void RCTSetUpCustomLibraryPointers(RCTJSCWrapper *wrapper) wrapper->JSValueIsUndefined = (JSValueIsUndefinedFuncType)dlsym(libraryHandle, "JSValueIsUndefined"); wrapper->JSValueIsNull = (JSValueIsNullFuncType)dlsym(libraryHandle, "JSValueIsNull"); wrapper->JSEvaluateScript = (JSEvaluateScriptFuncType)dlsym(libraryHandle, "JSEvaluateScript"); + wrapper->JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)dlsym(libraryHandle, "JSEvaluateBytecodeBundle"); wrapper->JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext"); wrapper->JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue");