Skip to content

Commit

Permalink
VA method supports arbitrary number of arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
ke.xu committed Apr 12, 2019
1 parent e80a557 commit 3b9eeff
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 90 deletions.
13 changes: 9 additions & 4 deletions Demo/iOSDemo/JSPatchDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -557,9 +557,11 @@
TargetAttributes = {
DE94AE0A1AF246BF00E461D4 = {
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = W7PRP2Z9TY;
};
DE94AE231AF246C000E461D4 = {
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = W7PRP2Z9TY;
TestTargetID = DE94AE0A1AF246BF00E461D4;
};
};
Expand All @@ -569,6 +571,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
Expand Down Expand Up @@ -772,7 +775,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = W7PRP2Z9TY;
GCC_C_LANGUAGE_STANDARD = gnu99;
INFOPLIST_FILE = "$(SRCROOT)/JSPatchDemo/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
Expand All @@ -785,6 +788,7 @@
);
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.husor.beibei.dev;
PRODUCT_NAME = JSPatchDemo;
};
name = Debug;
Expand All @@ -794,7 +798,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = W7PRP2Z9TY;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
INFOPLIST_FILE = "$(SRCROOT)/JSPatchDemo/Supporting Files/Info.plist";
Expand All @@ -808,6 +812,7 @@
);
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.husor.beibei.dev;
PRODUCT_NAME = JSPatchDemo;
};
name = Release;
Expand All @@ -816,7 +821,7 @@
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = W7PRP2Z9TY;
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
Expand All @@ -836,7 +841,7 @@
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = W7PRP2Z9TY;
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
Expand Down
2 changes: 1 addition & 1 deletion Demo/iOSDemo/JSPatchDemo/Supporting Files/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>net.cnbang.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
Expand Down
139 changes: 55 additions & 84 deletions JSPatch/JPEngine.m
Original file line number Diff line number Diff line change
Expand Up @@ -1108,16 +1108,64 @@ static id callSelector(NSString *className, NSString *selectorName, JSValue *arg

NSUInteger numberOfArguments = methodSignature.numberOfArguments;
NSInteger inputArguments = [(NSArray *)argumentsObj count];
int argRegNum = 0;
if (inputArguments > numberOfArguments - 2) {
// calling variable argument method, only support parameter type `id` and return type `id`
id sender = instance != nil ? instance : cls;
id result = invokeVariableParameterMethod(argumentsObj, methodSignature, sender, selector);
return formatOCToJS(result);
#ifdef __arm64__
// https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
// The iOS ABI for functions that take a variable number of arguments is entirely different from the generic version.
// Stages A and B of the generic procedure call standard are performed as usual—in particular, even variadic aggregates larger than 16 bytes are passed via a reference to temporary memory allocated by the caller. After that, the fixed arguments are allocated to registers and stack slots as usual in iOS.
// The NSRN is then rounded up to the next multiple of 8 bytes, and each variadic argument is assigned to the appropriate number of 8-byte stack slots.
// The C language requires arguments smaller than int to be promoted before a call, but beyond that, unused bytes on the stack are not specified by this ABI.
// As a result of this change, the type va_list is an alias for char * rather than for the struct type specified in the generic PCS. It is also not in the std namespace when compiling C++ code.
argRegNum = 8;

#else
// https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
// Some otherwise portable C programs depend on the argument passing scheme,
// implicitly assuming that all arguments are passed on the stack, and arguments
// appear in increasing order on the stack. Programs that make these assumptions
// never have been portable, but they have worked on many implementations. However, they do not work on the AMD64 architecture because some arguments are
// passed in registers. Portable C programs must use the header file <stdarg.h>
// in order to handle variable argument lists.
argRegNum = 0;

#endif

size_t objCTypesLen = 1 /* return type */
+ 2 /* self and _cmd */
+ inputArguments
+ MAX(argRegNum - (int)numberOfArguments, 0) /* placeholders to fill register */
+ 1 /* '\0' */;
char * methodObjCTypes = (char *)calloc(sizeof(char), objCTypesLen);
methodObjCTypes[0] = [methodSignature methodReturnType][0]; // TODO: support complex return type.
size_t i = 0;
for (i = 0; i < numberOfArguments; i++) {
methodObjCTypes[i + 1] = [methodSignature getArgumentTypeAtIndex:i][0];
}
for (i = 1 + numberOfArguments; i < objCTypesLen; i++) {
methodObjCTypes[i] = '@';
}
methodObjCTypes[objCTypesLen - 1] = '\0';
methodSignature = [NSMethodSignature signatureWithObjCTypes:methodObjCTypes];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature];
[inv setTarget:invocation.target];
[inv setSelector:invocation.selector];
invocation = inv;
}

for (NSUInteger i = 2; i < numberOfArguments; i++) {
for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
id valObj = argumentsObj[i-2];
id valObj = _nilObj;
BOOL isAppendedArgument = NO;
if (i - 2 < inputArguments && (argRegNum == 0 || i < argRegNum)) {
valObj = argumentsObj[i - 2];
} else if (argRegNum) {
int idx = (int)i - argRegNum + (int)numberOfArguments;
if (idx >= 2 && idx - 2 < inputArguments) {
valObj = argumentsObj[idx - 2];
isAppendedArgument = YES;
}
}
switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {

#define JP_CALL_ARG_CASE(_typeString, _type, _selector) \
Expand Down Expand Up @@ -1215,7 +1263,7 @@ static id callSelector(NSString *className, NSString *selectorName, JSValue *arg
[invocation setArgument:&valObj atIndex:i];
break;
}
if ([(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) {
if (!isAppendedArgument && [(JSValue *)arguments[i-2] hasProperty:@"__isBlock"]) {
JSValue *blkJSVal = arguments[i-2];
Class JPBlockClass = NSClassFromString(@"JPBlock");
if (JPBlockClass && ![blkJSVal[@"blockObj"] isUndefined]) {
Expand Down Expand Up @@ -1351,90 +1399,13 @@ static id callSelector(NSString *className, NSString *selectorName, JSValue *arg
return nil;
}

static id (*new_msgSend1)(id, SEL, id,...) = (id (*)(id, SEL, id,...)) objc_msgSend;
static id (*new_msgSend2)(id, SEL, id, id,...) = (id (*)(id, SEL, id, id,...)) objc_msgSend;
static id (*new_msgSend3)(id, SEL, id, id, id,...) = (id (*)(id, SEL, id, id, id,...)) objc_msgSend;
static id (*new_msgSend4)(id, SEL, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id,...)) objc_msgSend;
static id (*new_msgSend5)(id, SEL, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id,...)) objc_msgSend;
static id (*new_msgSend6)(id, SEL, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id,...)) objc_msgSend;
static id (*new_msgSend7)(id, SEL, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id,id,...)) objc_msgSend;
static id (*new_msgSend8)(id, SEL, id, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id, id, id,...)) objc_msgSend;
static id (*new_msgSend9)(id, SEL, id, id, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id, id, id, id, ...)) objc_msgSend;
static id (*new_msgSend10)(id, SEL, id, id, id, id, id, id, id, id, id, id,...) = (id (*)(id, SEL, id, id, id, id, id, id, id, id, id, id,...)) objc_msgSend;

static id invokeVariableParameterMethod(NSMutableArray *origArgumentsList, NSMethodSignature *methodSignature, id sender, SEL selector) {

NSInteger inputArguments = [(NSArray *)origArgumentsList count];
NSUInteger numberOfArguments = methodSignature.numberOfArguments;

NSMutableArray *argumentsList = [[NSMutableArray alloc] init];
for (NSUInteger j = 0; j < inputArguments; j++) {
NSInteger index = MIN(j + 2, numberOfArguments - 1);
const char *argumentType = [methodSignature getArgumentTypeAtIndex:index];
id valObj = origArgumentsList[j];
char argumentTypeChar = argumentType[0] == 'r' ? argumentType[1] : argumentType[0];
if (argumentTypeChar == '@') {
[argumentsList addObject:valObj];
} else {
return nil;
}
}

id results = nil;
numberOfArguments = numberOfArguments - 2;

//If you want to debug the macro code below, replace it to the expanded code:
//https://gist.github.com/bang590/ca3720ae1da594252a2e
#define JP_G_ARG(_idx) getArgument(argumentsList[_idx])
#define JP_CALL_MSGSEND_ARG1(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0));
#define JP_CALL_MSGSEND_ARG2(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1));
#define JP_CALL_MSGSEND_ARG3(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2));
#define JP_CALL_MSGSEND_ARG4(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3));
#define JP_CALL_MSGSEND_ARG5(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4));
#define JP_CALL_MSGSEND_ARG6(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5));
#define JP_CALL_MSGSEND_ARG7(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6));
#define JP_CALL_MSGSEND_ARG8(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7));
#define JP_CALL_MSGSEND_ARG9(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7), JP_G_ARG(8));
#define JP_CALL_MSGSEND_ARG10(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7), JP_G_ARG(8), JP_G_ARG(9));
#define JP_CALL_MSGSEND_ARG11(_num) results = new_msgSend##_num(sender, selector, JP_G_ARG(0), JP_G_ARG(1), JP_G_ARG(2), JP_G_ARG(3), JP_G_ARG(4), JP_G_ARG(5), JP_G_ARG(6), JP_G_ARG(7), JP_G_ARG(8), JP_G_ARG(9), JP_G_ARG(10));

#define JP_IF_REAL_ARG_COUNT(_num) if([argumentsList count] == _num)

#define JP_DEAL_MSGSEND(_realArgCount, _defineArgCount) \
if(numberOfArguments == _defineArgCount) { \
JP_CALL_MSGSEND_ARG##_realArgCount(_defineArgCount) \
}

JP_IF_REAL_ARG_COUNT(1) { JP_CALL_MSGSEND_ARG1(1) }
JP_IF_REAL_ARG_COUNT(2) { JP_DEAL_MSGSEND(2, 1) JP_DEAL_MSGSEND(2, 2) }
JP_IF_REAL_ARG_COUNT(3) { JP_DEAL_MSGSEND(3, 1) JP_DEAL_MSGSEND(3, 2) JP_DEAL_MSGSEND(3, 3) }
JP_IF_REAL_ARG_COUNT(4) { JP_DEAL_MSGSEND(4, 1) JP_DEAL_MSGSEND(4, 2) JP_DEAL_MSGSEND(4, 3) JP_DEAL_MSGSEND(4, 4) }
JP_IF_REAL_ARG_COUNT(5) { JP_DEAL_MSGSEND(5, 1) JP_DEAL_MSGSEND(5, 2) JP_DEAL_MSGSEND(5, 3) JP_DEAL_MSGSEND(5, 4) JP_DEAL_MSGSEND(5, 5) }
JP_IF_REAL_ARG_COUNT(6) { JP_DEAL_MSGSEND(6, 1) JP_DEAL_MSGSEND(6, 2) JP_DEAL_MSGSEND(6, 3) JP_DEAL_MSGSEND(6, 4) JP_DEAL_MSGSEND(6, 5) JP_DEAL_MSGSEND(6, 6) }
JP_IF_REAL_ARG_COUNT(7) { JP_DEAL_MSGSEND(7, 1) JP_DEAL_MSGSEND(7, 2) JP_DEAL_MSGSEND(7, 3) JP_DEAL_MSGSEND(7, 4) JP_DEAL_MSGSEND(7, 5) JP_DEAL_MSGSEND(7, 6) JP_DEAL_MSGSEND(7, 7) }
JP_IF_REAL_ARG_COUNT(8) { JP_DEAL_MSGSEND(8, 1) JP_DEAL_MSGSEND(8, 2) JP_DEAL_MSGSEND(8, 3) JP_DEAL_MSGSEND(8, 4) JP_DEAL_MSGSEND(8, 5) JP_DEAL_MSGSEND(8, 6) JP_DEAL_MSGSEND(8, 7) JP_DEAL_MSGSEND(8, 8) }
JP_IF_REAL_ARG_COUNT(9) { JP_DEAL_MSGSEND(9, 1) JP_DEAL_MSGSEND(9, 2) JP_DEAL_MSGSEND(9, 3) JP_DEAL_MSGSEND(9, 4) JP_DEAL_MSGSEND(9, 5) JP_DEAL_MSGSEND(9, 6) JP_DEAL_MSGSEND(9, 7) JP_DEAL_MSGSEND(9, 8) JP_DEAL_MSGSEND(9, 9) }
JP_IF_REAL_ARG_COUNT(10) { JP_DEAL_MSGSEND(10, 1) JP_DEAL_MSGSEND(10, 2) JP_DEAL_MSGSEND(10, 3) JP_DEAL_MSGSEND(10, 4) JP_DEAL_MSGSEND(10, 5) JP_DEAL_MSGSEND(10, 6) JP_DEAL_MSGSEND(10, 7) JP_DEAL_MSGSEND(10, 8) JP_DEAL_MSGSEND(10, 9) JP_DEAL_MSGSEND(10, 10) }

return results;
}

NSMethodSignature *block_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector) {
uint8_t *p = (uint8_t *)((__bridge void *)self);
p += sizeof(void *) * 2 + sizeof(int32_t) *2 + sizeof(uintptr_t) * 2;
const char **signature = (const char **)p;
return [NSMethodSignature signatureWithObjCTypes:*signature];
}


static id getArgument(id valObj){
if (valObj == _nilObj ||
([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) {
return nil;
}
return valObj;
}

#pragma mark -

static id genCallbackBlock(JSValue *jsVal)
Expand Down

0 comments on commit 3b9eeff

Please sign in to comment.