Recognise and run BC bundles

Reviewed By: javache

Differential Revision: D4067425

fbshipit-source-id: fade9adebfa8a59dc49aeadfd01a782f7b686082
This commit is contained in:
Ashok Menon 2016-11-01 10:14:00 -07:00 committed by Facebook Github Bot
parent 94711bfb06
commit 16290851aa
5 changed files with 147 additions and 51 deletions

View File

@ -9,7 +9,40 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
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; extern NSString *const RCTJavaScriptLoaderErrorDomain;
@ -42,7 +75,7 @@ typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source, int64_t sourc
* @experimental * @experimental
* Attempts to synchronously load the script at the given URL. The following two conditions must be met: * Attempts to synchronously load the script at the given URL. The following two conditions must be met:
* 1. It must be a file URL. * 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 * 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. * RCTJavaScriptLoaderErrorDomain and the code RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously.
*/ */

View File

@ -18,10 +18,24 @@
#include <sys/stat.h> #include <sys/stat.h>
uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5; static uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5;
static uint64_t const RCTBCBundleMagicNumber = 0xFF4865726D657300;
NSString *const RCTJavaScriptLoaderErrorDomain = @"RCTJavaScriptLoaderErrorDomain"; 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 @implementation RCTLoadingProgress
- (NSString *)description - (NSString *)description
@ -112,7 +126,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return nil; return nil;
} }
uint32_t magicNumber; RCTMagicNumber magicNumber = {.allBytes = 0};
size_t readResult = fread(&magicNumber, sizeof(magicNumber), 1, bundle); size_t readResult = fread(&magicNumber, sizeof(magicNumber), 1, bundle);
fclose(bundle); fclose(bundle);
if (readResult != 1) { if (readResult != 1) {
@ -125,13 +139,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return nil; return nil;
} }
magicNumber = NSSwapLittleIntToHost(magicNumber); RCTScriptTag tag = RCTParseMagicNumber(magicNumber);
if (magicNumber != RCTRAMBundleMagicNumber) { if (tag == RCTScriptString) {
if (error) { if (error) {
*error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain *error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
userInfo:@{NSLocalizedDescriptionKey: userInfo:@{NSLocalizedDescriptionKey:
@"Cannot load non-RAM bundled files synchronously"}]; @"Cannot load text/javascript files synchronously"}];
} }
return nil; return nil;
} }

View File

@ -66,6 +66,11 @@ struct RandomAccessBundleStartupCode {
} }
}; };
struct TaggedScript {
const RCTScriptTag tag;
const NSData *script;
};
#if RCT_PROFILE #if RCT_PROFILE
@interface RCTCookieMap : NSObject @interface RCTCookieMap : NSObject
{ {
@ -280,15 +285,20 @@ static NSThread *newJavaScriptThread(void)
JSContext:(JSContext *)context JSContext:(JSContext *)context
error:(NSError **)error error:(NSError **)error
{ {
BOOL isRAMBundle = NO; TaggedScript taggedScript = loadTaggedScript(script, sourceURL, _performanceLogger, _randomAccessBundle, error);
script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, error);
if (!script) { if (!taggedScript.script) {
return NO; return NO;
} }
if (isRAMBundle) {
if (taggedScript.tag == RCTScriptRAMBundle) {
registerNativeRequire(context, self); 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 (returnedError) {
if (error) { if (error) {
*error = returnedError; *error = returnedError;
@ -652,16 +662,16 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
RCTAssertParam(script); RCTAssertParam(script);
RCTAssertParam(sourceURL); RCTAssertParam(sourceURL);
BOOL isRAMBundle = NO; NSError *loadError;
{ TaggedScript taggedScript = loadTaggedScript(script, sourceURL,
NSError *error; _performanceLogger,
script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, &error); _randomAccessBundle,
if (script == nil) { &loadError);
if (onComplete) { if (!taggedScript.script) {
onComplete(error); if (onComplete) {
} onComplete(loadError);
return;
} }
return;
} }
RCTProfileBeginFlowEvent(); RCTProfileBeginFlowEvent();
@ -671,11 +681,13 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
return; return;
} }
if (isRAMBundle) { if (taggedScript.tag == RCTScriptRAMBundle) {
registerNativeRequire(self.context.context, self); 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); self->_context.context.JSGlobalContextRef);
if (onComplete) { if (onComplete) {
onComplete(error); onComplete(error);
@ -683,33 +695,42 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
}]; }];
} }
static NSData *loadPossiblyBundledApplicationScript(NSData *script, NSURL *sourceURL, static TaggedScript loadTaggedScript(NSData *script,
RCTPerformanceLogger *performanceLogger, NSURL *sourceURL,
BOOL &isRAMBundle, RandomAccessBundleData &randomAccessBundle, RCTPerformanceLogger *performanceLogger,
NSError **error) RandomAccessBundleData &randomAccessBundle,
NSError **error)
{ {
RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / prepare bundle", nil); RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / prepare bundle", nil);
// The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`. RCTMagicNumber magicNumber = {.allBytes = 0};
uint32_t magicNumber = 0;
[script getBytes:&magicNumber length:sizeof(magicNumber)]; [script getBytes:&magicNumber length:sizeof(magicNumber)];
isRAMBundle = NSSwapLittleIntToHost(magicNumber) == RCTRAMBundleMagicNumber; RCTScriptTag tag = RCTParseMagicNumber(magicNumber);
if (isRAMBundle) {
[performanceLogger markStartForTag:RCTPLRAMBundleLoad]; NSData *loadedScript = NULL;
script = loadRAMBundle(sourceURL, error, randomAccessBundle); switch (tag) {
[performanceLogger markStopForTag:RCTPLRAMBundleLoad]; case RCTScriptRAMBundle:
[performanceLogger setValue:script.length forTag:RCTPLRAMStartupCodeSize]; [performanceLogger markStartForTag:RCTPLRAMBundleLoad];
} else {
// JSStringCreateWithUTF8CString expects a null terminated C string. loadedScript = loadRAMBundle(sourceURL, error, randomAccessBundle);
// RAM Bundling already provides a null terminated one.
NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1]; [performanceLogger markStopForTag:RCTPLRAMBundleLoad];
[nullTerminatedScript appendData:script]; [performanceLogger setValue:loadedScript.length forTag:RCTPLRAMStartupCodeSize];
[nullTerminatedScript appendBytes:"" length:1]; break;
script = nullTerminatedScript;
case RCTScriptBCBundle:
loadedScript = script;
break;
case RCTScriptString: {
NSMutableData *nullTerminatedScript = [NSMutableData dataWithData:script];
[nullTerminatedScript appendBytes:"" length:1];
loadedScript = nullTerminatedScript;
}
} }
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return script; return { .tag = tag, .script = loadedScript };
} }
static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor) 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]; }; context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakExecutor _nativeRequire:moduleID]; };
} }
static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJSCWrapper *jscWrapper, static NSError *executeApplicationScript(TaggedScript taggedScript,
RCTPerformanceLogger *performanceLogger, JSGlobalContextRef ctx) NSURL *sourceURL,
RCTJSCWrapper *jscWrapper,
RCTPerformanceLogger *performanceLogger,
JSGlobalContextRef ctx)
{ {
RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", (@{ RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", (@{
@"url": sourceURL.absoluteString, @"size": @(script.length) @"url": sourceURL.absoluteString, @"size": @(taggedScript.script.length)
})); }));
[performanceLogger markStartForTag:RCTPLScriptExecution]; [performanceLogger markStartForTag:RCTPLScriptExecution];
JSValueRef jsError = NULL; JSValueRef jsError = NULL;
JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes);
JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String); 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(bundleURL);
jscWrapper->JSStringRelease(execJSString);
[performanceLogger markStopForTag:RCTPLScriptExecution]; [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"); RCT_PROFILE_END_EVENT(0, @"js_call");
return error; return error;
} }

View File

@ -25,6 +25,7 @@ typedef JSStringRef (*JSValueCreateJSONStringFuncType)(JSContextRef, JSValueRef,
typedef bool (*JSValueIsUndefinedFuncType)(JSContextRef, JSValueRef); typedef bool (*JSValueIsUndefinedFuncType)(JSContextRef, JSValueRef);
typedef bool (*JSValueIsNullFuncType)(JSContextRef, JSValueRef); typedef bool (*JSValueIsNullFuncType)(JSContextRef, JSValueRef);
typedef JSValueRef (*JSEvaluateScriptFuncType)(JSContextRef, JSStringRef, JSObjectRef, JSStringRef, int, JSValueRef *); typedef JSValueRef (*JSEvaluateScriptFuncType)(JSContextRef, JSStringRef, JSObjectRef, JSStringRef, int, JSValueRef *);
typedef JSValueRef (*JSEvaluateBytecodeBundleFuncType)(JSContextRef, JSObjectRef, int, JSStringRef, JSValueRef *);
typedef struct RCTJSCWrapper { typedef struct RCTJSCWrapper {
JSStringCreateWithCFStringFuncType JSStringCreateWithCFString; JSStringCreateWithCFStringFuncType JSStringCreateWithCFString;
@ -41,6 +42,7 @@ typedef struct RCTJSCWrapper {
JSValueIsUndefinedFuncType JSValueIsUndefined; JSValueIsUndefinedFuncType JSValueIsUndefined;
JSValueIsNullFuncType JSValueIsNull; JSValueIsNullFuncType JSValueIsNull;
JSEvaluateScriptFuncType JSEvaluateScript; JSEvaluateScriptFuncType JSEvaluateScript;
JSEvaluateBytecodeBundleFuncType JSEvaluateBytecodeBundle;
Class JSContext; Class JSContext;
Class JSValue; Class JSValue;
} RCTJSCWrapper; } RCTJSCWrapper;

View File

@ -26,7 +26,7 @@ static void *RCTCustomLibraryHandler(void)
static dispatch_once_t token; static dispatch_once_t token;
static void *handler; static void *handler;
dispatch_once(&token, ^{ 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) { if (!handler) {
const char *err = dlerror(); const char *err = dlerror();
@ -57,6 +57,7 @@ static void RCTSetUpSystemLibraryPointers(RCTJSCWrapper *wrapper)
wrapper->JSValueIsUndefined = JSValueIsUndefined; wrapper->JSValueIsUndefined = JSValueIsUndefined;
wrapper->JSValueIsNull = JSValueIsNull; wrapper->JSValueIsNull = JSValueIsNull;
wrapper->JSEvaluateScript = JSEvaluateScript; wrapper->JSEvaluateScript = JSEvaluateScript;
wrapper->JSEvaluateBytecodeBundle = NULL;
wrapper->JSContext = [JSContext class]; wrapper->JSContext = [JSContext class];
wrapper->JSValue = [JSValue class]; wrapper->JSValue = [JSValue class];
} }
@ -83,6 +84,7 @@ static void RCTSetUpCustomLibraryPointers(RCTJSCWrapper *wrapper)
wrapper->JSValueIsUndefined = (JSValueIsUndefinedFuncType)dlsym(libraryHandle, "JSValueIsUndefined"); wrapper->JSValueIsUndefined = (JSValueIsUndefinedFuncType)dlsym(libraryHandle, "JSValueIsUndefined");
wrapper->JSValueIsNull = (JSValueIsNullFuncType)dlsym(libraryHandle, "JSValueIsNull"); wrapper->JSValueIsNull = (JSValueIsNullFuncType)dlsym(libraryHandle, "JSValueIsNull");
wrapper->JSEvaluateScript = (JSEvaluateScriptFuncType)dlsym(libraryHandle, "JSEvaluateScript"); wrapper->JSEvaluateScript = (JSEvaluateScriptFuncType)dlsym(libraryHandle, "JSEvaluateScript");
wrapper->JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)dlsym(libraryHandle, "JSEvaluateBytecodeBundle");
wrapper->JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext"); wrapper->JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext");
wrapper->JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue"); wrapper->JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue");