美文网首页手机移动程序开发
朱雀-基于LLVM的Objective-C解释器的基本实现

朱雀-基于LLVM的Objective-C解释器的基本实现

作者: 08a2d4bd26c9 | 来源:发表于2020-01-21 00:35 被阅读0次

    0 前言

    朱雀是中国古代神兽之一,以此来命名这个项目,希望大家可以在闲暇之余可以多了解一些古代的神话故事,会有别样的乐趣和收获。

    这应该是一篇比较长的文章,来尝试描述清楚这个项目的实现方案。不过受限于作者本身的技术和理解能力,本文可能会存在很多值得商榷甚至是完全错误的地方,希望尽量不会给读者带来误区,也十分欢迎相关专家批评指正。当然,阅读这篇文章也需要一定的OC语言和iOS开发基础,一些基础的概念没有做过多的赘述。
    本文也不讨论任何有关开发模式,应用审核,以及实际应用等问题,希望只可以单独的作为一个技术项目来进行研究和拓展。

    目前朱雀对于OC的语言特性以及数据类型的支持并不完整,文中的代码目前支持部分OC代码和数据类型,供阐述原理以及开发测试使用,后续会列出目前暂不支持的用法和数据类型。同时代码中可能有一些很明显的内存泄漏,有些是有意为之,在下面的多线程部分会有说明。

    1 概述

    众所周知OC是一门所谓编译型的语言,是C语言的一个超集。朱雀项目的目的是支持在iOS模拟器和真机的环境下解释执行OC代码。熟悉Flutter的朋友们应该对Dart语言有一些了解,Dart可以支持AOT和JIT等不同的执行模式。但在iOS环境下,受限于iOS对于可写可执行内存的管控,除去JSCore等苹果自己的框架之外,JIT也是无法实现的。朱雀项目实现了一个简单的解释器,以纯解释执行的模式来执行对应的OC代码。

    之所以想实现一个解释器来解释执行OC代码,主要的好处在于两点。一是大幅降低了开发成本和对技术栈的要求,也免去了不同语言转换成OC的复杂度。第二点比较关键,在于保持了内存管理的语义一致性。众所周知OC采用了引用计数的方式来管理内存,而IR代码中编译器引入的ARC相关的指令可以被朱雀完美支持,会在执行时使用libffi调用runtime中对应的函数,下面的 2.5 ARC 一节也会对此做具体的说明。

    在思考朱雀的技术方案时,首先想到的就是LLVM,因为一个人从0到1实现一个完整的OC解释器不太现实,肯定要借助于一些已有的框架。
    Mac/iOS的编译链使用了Clang&LLVM,LLVM是Low Level Virtual Machine的缩写。LLVM有一个中间语言的概念,也就是IR,是编译过程的中间产物。大家听起来会熟悉一些的Bitcode是同一个概念。所以问题来了,既然LLVM本身就是一个“VM”,而这个VM所使用的IR又是OC代码编译过程中的产物,那么可不可以使用LLVM作为一个解释器来解释执行IR代码,从而达到动态执行一段逻辑的目的呢?答案是肯定的,但问题也有很多。

    LLVM本身有一个简单的解释器的实现,可以解释执行C代码生成的部分IR代码,并且可以链接外部的动态库,使用libffi来进行外部函数的调用。但这个解释器对于OC来说是一个不完整的实现,IR代码中涉及到OC Runtime的部分都没有支持,这些缺失的部分需要我们补齐实现。朱雀的实现可以理解为是在LLVM的基础上打了很多补丁,来支持OC的解释执行。

    2 应用

    朱雀目前对外暴露了一个函数用于调用。参数中的filePath是要加载的IR文件的路径,invocation可以是runtime在消息转发过程中构造的,当然也可以手动构造并传入。invocation中包含了target,selector以及参数等信息。IR文件中对应的Function执行结束之后会设置invocation的return value,用于处理返回值。

    void runIRCode(NSString *filePath, NSInvocation *invocation)
    {
      StringRef InputFile = StringRef([filePath cStringUsingEncoding:NSUTF8StringEncoding]);
      static LLVMContext Context;
      SMDiagnostic Err;
      std::unique_ptr<Module> Owner = parseIRFile(InputFile, Err, Context);
      if (!Owner.get()){
        reportError(Err, "create module");
      }
      EngineBuilder builder(std::move(Owner));
      builder.setEngineKind(EngineKind::Interpreter).setErrorStr(NULL);
      ExecutionEngine *engine = builder.create();
      engine->currentModuleFilePath = std::string([filePath cStringUsingEncoding:NSUTF8StringEncoding]);
    
      NSString *methodName = NSStringFromSelector(invocation.selector);
      NSString *className = NSStringFromClass([invocation.target class]);
    
      NSString *functionName = [NSString stringWithFormat:@"%@%@%@%@%@", @"�-[", className, @" ", methodName ,@"]"];
      Function *function = engine->FindFunctionNamed(StringRef([functionName cStringUsingEncoding:NSUTF8StringEncoding]));
    
      std::vector<GenericValue> params;
      NSMethodSignature *signature = invocation.methodSignature;
      int argCount = (int)signature.numberOfArguments;
      for (int i = 0;i < signature.numberOfArguments;i++){
        GenericValue value;
        const char *type = [signature getArgumentTypeAtIndex:i];
        NSString *typeString = [[NSString alloc] initWithUTF8String:type];
        void *argument = nil;
        [invocation getArgument:&argument atIndex:i];
        if ([typeString containsString:@"i"]){
          int intResult;
          memcpy(&intResult, &argument, sizeof(int));
          value.IntVal = APInt(32, intResult);
        } else if ([typeString containsString:@"i"]){
          int intResult;
          memcpy(&intResult, &argument, sizeof(int));
          value.IntVal = APInt(32, intResult);
        } else {
          value.PointerVal = argument;
        }
        params.push_back(value);
      }
    
      GenericValue GV = engine->runFunction(function, params);
      const char *type = signature.methodReturnType;
      NSString *typeString = [[NSString alloc] initWithUTF8String:type];
      if ([typeString isEqualToString:@"i"] || [typeString isEqualToString:@"I"]){
        int intResult = GV.IntVal.getZExtValue();
        [invocation setReturnValue:&intResult];     
      } else if ([typeString isEqualToString:@"l"] || [typeString isEqualToString:@"L"]){
        long longResult = GV.IntVal.getZExtValue();
        [invocation setReturnValue:&longResult];     
      } else if ([typeString isEqualToString:@"f"]){
        float floatResult = GV.FloatVal;
        [invocation setReturnValue:&floatResult];     
      } else if ([typeString isEqualToString:@"d"]){
        double doubleResult = GV.DoubleVal;
        [invocation setReturnValue:&doubleResult];     
      } else if ([typeString isEqualToString:@"B"]){
        BOOL boolResult = GV.IntVal.getBoolValue() ? YES : NO;
        [invocation setReturnValue:&boolResult];     
      } else if ([typeString containsString:@"*"]){
        void *result = GV.PointerVal;
        [invocation setReturnValue:&result];
      } else if ([typeString containsString:@"@"]){
        void *result = GV.PointerVal;
        [invocation setReturnValue:&result];
      } else if ([typeString containsString:@":"]){
        void *result = GV.PointerVal;
        [invocation setReturnValue:&result];
      } else if ([typeString containsString:@"#"]){
        void *result = GV.PointerVal;
        [invocation setReturnValue:&result];
      } else if ([typeString containsString:@"v"]){
        // do nothing
      } else {
        NSLog(@"typeString:%@", typeString);
        report_fatal_error("Current not support type");
      }
    }
    

    下面是开发过程用于测试的代码,calculateWithA:andB:方法可以被朱雀解释执行并且完成同样的逻辑。

    #import "TestClass2.h"
    #import <objc/message.h>
    #import "TestView.h"
    
    @interface TestClass2()
    @property (nonatomic) int testInt;
    @property (nonatomic, strong) UIImageView *view;
    @end
    
    @implementation TestClass2
    
    - (void)tap
    {
        printf("tap called\n");
    }
    
    - (void)testAddMethod
    {
        printf(":::testAddMethod called\n");
    }
    
    + (void)testClassMethod
    {
        printf("testClassMethod called\n");
    }
    
    - (int)calculateWithA:(int)a andB:(int)b
    {
        printf("%d\n", self.testInt);
        self.testInt = 10;
        printf("%d\n", self.testInt);
    
        self.view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 300, 200, 200)];
        self.view.image = [UIImage imageNamed:@"1024"];
        [[UIApplication sharedApplication].keyWindow addSubview:self.view];
        self.view.userInteractionEnabled = YES;
    
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
        [self.view addGestureRecognizer:tap];
    
        int __block h = 3333;
        for (int i = 0;i < 1;i++){
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                h ++;
                printf(":::change a in block:%d\n", 808);
            });
        }
    
        int __block m = 10;
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            m = 88;
            printf(":::change a in block:%d\n", m);
        });
    
        __block NSString * str = @"hello1111111111111111";
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            str = @"change hello11111111111111111111111";
            //str = [NSString stringWithFormat:@"change hello11111111111111111111111"]
            printf(":::change a in block:%s\n", [str cStringUsingEncoding:NSUTF8StringEncoding]);
        });
        __block NSNumber * num = [NSNumber numberWithInt:77];
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            num = [NSNumber numberWithFloat:50.0f];
            printf(":::change a in block:%f\n", num.floatValue);
        });
    
        for (int i = 0;i < 2;i++){
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                printf(":::change a in block:%d\n", 666);
            });
    
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                printf(":::change a in block:%d\n", 888);
            });
        }
    
        NSString *s = @"hello";
        s = [s performSelector:@selector(uppercaseString)];
        printf("uppercaseString:%s\n", [s cStringUsingEncoding:NSUTF8StringEncoding]);
    
        [self performSelector:@selector(testPerform) withObject:s afterDelay:5];
    
        TestView *view = [[TestView alloc] initWithFrame:CGRectMake(100.0f, 100.0f, 200.0f, 200.0f)];
        view.backgroundColor = [UIColor redColor];
        view.frame = CGRectMake(100.0f, 100.0f, 300.0f, 100.0f);
        [[UIApplication sharedApplication].keyWindow addSubview:view];
        [view testClass:[NSString class] b:YES c:'f' cp:"hello cp"];
    
        [view testBlock:^(char input) {
            printf("testArgument called with input:%c\n", input);
        }];
    
        __weak id weakSelf = self;
        [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://www.mi.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            printf(":::nsurlsession callback called\n");
            NSString *rStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            printf(":::url session response:%s\n", rStr.UTF8String);
            NSLog(@"weakSelf:%@", weakSelf);
        }] resume];
    
        NSTimer *timer = [NSTimer timerWithTimeInterval:30.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
            printf("test timer\n");
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
        int o = 300;
        void(^testArgument)(int) = ^(int input){
            printf("testArgument called with input:%d\n", input + o);
        };
        testArgument(456);
    
        void(^testArgument1)(double) = ^(double input){
            printf("testArgument called with input:%lf\n", input);
        };
        testArgument1(456.789f);
    
        void(^testArgument2)(char) = ^(char input){
            printf("testArgument called with input:%c\n", input);
        };
        testArgument2('e');
        
        [self testAddMethod];
        [[self class] testClassMethod];
        return [NSNumber numberWithInt:33].intValue + 88 + [super calculateWithA:a andB:b];
    }
    
    @end
    

    目前朱雀可以作为一个动态库被嵌入到iOS的工程中。
    假设我们之前自己创建了一个类叫做AClass,这个类有一个实例方法叫做calculate。我们之后修改了calculate的实现,使用clang生成了对应的IR文件。在应用启动后,可以获取到这个IR文件,通过runtime可以替换掉原有calculate方法的实现,在其新的实现中可以通过朱雀解释执行IR文件中的calculate方法,从而执行我们修改后的逻辑。

    3 实现

    2.1 selector和class

    需要注意的是OC的selector,其本质可以理解为在runtime中注册过的字符串,表示一个特定的方法名。然而LLVM解释器中使用的selector虽然字符串是等价的,但并没有在runtime中注册过,所以直接使用的话会报错,需要调用sel_registerName/sel_getUid来进行注册后使用。这两个函数的返回值是同名的但在runtime中注册过的selector。

    与此同理的还有class对象,在日常的开发过程中,一般一个m文件会包含一个类的实现,然后会被编译为一个IR文件。LLVM解释器加载这个文件后会生成一个Module,在这个Module内部使用的该类的class对象只是LLVM自己生成的一个数据结构,虽然和runtime中OC类的数据结构是一样的,但这个特殊的class并没有在runtime中注册过,所以没办法使用。此处需要做一个处理,就是通过NSStringFromClass函数拿到这个class的名称,然后通过NSClassFromString函数,使用类名称拿到runtime中实际注册过的同名的类。

    2.2 objc_msgSend

    关于IR,有一篇很长的文档,描述了IR的各种语法和细节。
    下面是截取的部分代码段:

    store %1* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to %1*), %1** %16, align 8
    %73 = load %1*, %1** %16, align 8
    %74 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.11, align 8, !invariant.load !10
    %75 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.13, align 8, !invariant.load !10
    %76 = bitcast %1* %73 to i8*
    %77 = invoke i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*, i8*)*)(i8* %76, i8* %75, i8* %74) to label %78 unwind label %177
    

    可以看到,每一个OC方法的调用,都会被转换成objc_msgSend/objc_msgSend_stret等函数调用,下面暂时都用objc_msgSend表示此类函数。需要注意的是,不管编译时OBJC_OLD_DISPATCH_PROTOTYPES这个编译选项如何设置,生成的IR中,objc_msgSend都被定义成了可变参数的函数,只是在调用的时候做了一个cast,如上面的第6行代码所示。为了解决调用外部函数的问题,LLVM现有的解释器引入了libffi,可以动态调用外部的c函数。然而由于其解释器模块目前的设计,目前并没有在调用外部可变参函数时,将可变部分的参数类型传递过来。所以为了解决这个问题,我们有两个方案可以选择,一是通过runtime拿到可变部分的参数类型,通过libffi来进行调用;二是可以在LLVM解释器模块中内置一个objc_msgSend函数,使得所有的objc_msgSend调用都可以进入到我们自己定义的objc_msgSend函数中来,在其实现中,根据传入的参数,构造出NSInvocation对象,达到模拟方法调用的目的。这个objc_msgSend并不是需要我们实现一个等价于OC runtime的objc_msgSend,只是提供一个c函数给LLVM解释器使用,使得LLVM解释器认为这即是OC runtime的objc_msgSend,从而可以将需要真正传递给objc_msgSend参数传递进来,我们在自己的实现中可以使用这些参数完成消息发送的任务即可。为了减少复杂度,朱雀选择了第二种方案来实现。

    下面是其大概的实现逻辑:

    static GenericValue lle_X_objc_msgSend(FunctionType *FT,
                                           ArrayRef<GenericValue> Args) {
      id object = (__bridge id)GVTOP(Args[0]);
      SEL sel = sel_registerName((char *)GVTOP(Args[1]));
    
      /*
      如果object本身是一个类对象,那么调用class方法会返回自身。所以可以确保拿到正确的类名。
      */
      NSString *className = NSStringFromClass([object class]);
      NSString *methodName = NSStringFromSelector(sel);
    
      /*
      首先需要区分是实例方法还是类方法,从而构造出不同的方法名在IR文件中查找。
      通过object_isClass来判断一个对象是普通的对象还是一个类对象。
      如果是类对象的话说明本次调用的方法是一个类方法。
      */
      NSString *methodType = object_isClass(object)?@"+":@"-";
      NSString *result = [NSString stringWithFormat:@"\01%@[%@ %@]", methodType, className, methodName];
    
      /*
      查找当前IR文件中是否包含该函数,如果包含则直接执行。
      */
      Function *f = mainEngine->FindFunctionNamed(StringRef([result cStringUsingEncoding:NSUTF8StringEncoding]));
      if (f){
        StringRef InputFile = StringRef(mainEngine->currentModuleFilePath.c_str());
        LLVMContext *Context = new LLVMContext();
        SMDiagnostic Err;
        std::unique_ptr<Module> Owner = parseIRFile(InputFile, Err, *Context);
        std::string Error;
        EngineBuilder builder(std::move(Owner));
        builder.setEngineKind(EngineKind::Interpreter).setErrorStr(&Error);
        ExecutionEngine *newEngine = builder.create();
        f = newEngine->FindFunctionNamed(StringRef([result cStringUsingEncoding:NSUTF8StringEncoding]));
        return newEngine->runFunction(f, Args);
      }
    
      NSMethodSignature *signature = [object methodSignatureForSelector:sel];
      NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
      [invocation setTarget:object];
      [invocation setSelector:sel];
    
      buildInvocation(invocation, Args);
      [invocation invoke];
      
      return buildReturnGenericValue(invocation);
    }
    

    objc_msgSend_fpret和objc_msgSend_fp2ret在arm64架构下不会使用,在x86架构下适用范围极小,暂时不作考虑。使用上述实现可以兼容objc_msgSend_stret的逻辑,不再需要额外处理。

    2.3 super和objc_msgSendSuper

    还需要考虑super的支持。虽然super和self的用法比较类似,但是两者并不是一个概念,反而有巨大的差距。self通常可以当作指向当前对象的一个指针来使用,而super则是一个编译器关键字,而不是一个指针指向了某个地方。类似于[super init]的方法调用会被实际转换成objc_msgSendSuper的函数调用。objc_msgSendSuper的第一个参数是名为objc_super的结构体,这个结构体也是编译器来生成的。OC runtime中objc_super的定义如下:

    /// Specifies the superclass of an instance. 
    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained _Nonnull id receiver;
    
        /// Specifies the particular superclass of the instance to message. 
    #if !defined(__cplusplus)  &&  !__OBJC2__
        /* For compatibility with old objc-runtime.h header */
        __unsafe_unretained _Nonnull Class class;
    #else
        __unsafe_unretained _Nonnull Class super_class;
    #endif
        /* super_class is the first class to search */
    };
    

    在自定义的objc_msgSendSuper实现中,我们要做的就是调用目标对象的父类的目标方法。为了用最简单直接的方式来实现,我们还是使用了NSInvocation,但在设置invocation对象的target的时候遇到做一个tricky的处理。在调用invocation对象的invoke方法之前,首先需要将其target对象的class设置为其class的父类,然后在invoke方法调用结束后再设置回来,达到“欺骗”runtime的目的,使得父类的方法得以调用。因为在OC的消息调用或者invocation的invoke过程中,runtime 是会根据对象的isa指针,找到其所属的类,在类对象的方法列表中找到对应的方法来进行调用的。通过object_setClass改变对象的类至其父类之后,查找方法列表的过程就会从其父类开始,达到调用其父类方法的目的。

    static GenericValue lle_X_objc_msgSendSuper(FunctionType *FT,
                                                ArrayRef<GenericValue> Args) {
      struct objc_super *s = (struct objc_super *)GVTOP(Args[0]);
      Class sc = s->super_class;
      NSString *name = NSStringFromClass(sc);
      s->super_class = NSClassFromString(name);
      s->super_class = class_getSuperclass(s->super_class);
    
      Class originalClass = object_getClass(s->receiver);
      object_setClass(s->receiver, s->super_class);
      
      id object = s->receiver;
      SEL sel = sel_registerName((char *)GVTOP(Args[1]));
    
      NSMethodSignature *signature = [object methodSignatureForSelector:sel];
      NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
            
      [invocation setTarget:object];
      [invocation setSelector:sel];
    
      buildInvocation(invocation, Args);
      [invocation invoke];
      object_setClass(s->receiver, originalClass);
    
      return buildReturnGenericValue(invocation);
    }
    

    当然,更为合理的方式还是通过libffi直接调用实际的objc_msgSendSuper函数。但由于实现成本比较大,目前采用了上述的实现方式。

    2.4 Block

    关于OC的block可以参照这篇文档
    大概的数据结构摘抄如下:

    struct Block_literal_1 {
        void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor_1 {
            unsigned long int reserved;         // NULL
            unsigned long int size;         // sizeof(struct Block_literal_1)
            // optional helper functions
            void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
            void (*dispose_helper)(void *src);             // IFF (1<<25)
            // required ABI.2010.3.16
            const char *signature;                         // IFF (1<<30)
        } *descriptor;
        // imported variables
    };
    

    整个过程最难支持的就是block。因为系统的某些API也需要传入block类型的参数作为回调,所以我们要构造出合法的block对象来使用。LLVM的解释器其实可以生成block对象,其数据结构是合法的,但对象里所有的函数指针都是不可用的,因为其指向的是LLVM Module内的Function对象,而不是实际可以调用的函数指针。所以我们也需要使用libffi,根据Function对象中保存的参数个数和类型,来构造出合法的函数指针进行替换,从而构造出合法的block对象来使用。这样当block得到执行时,我们使用libffi生成的用于替换的函数就会得到调用,在其实现内,就可以执行其真正对应的LLVM Function,使得原有的逻辑得到执行。同理,可能会出现的copy_helper函数和dispose_helper函数也需要做同样的处理。所以需要一些全局的字典,来保存block对象和其对应的LLVM Function,以及对应的copy_helper和dispose_helper,这样才能在block被调用的时候找到实际要执行的LLVM函数。同理还需要处理__block变量会生成的byref结构体。

    首先定义几个全局字典备用:

    /*
    用于保存block对象和其invoke函数对应的LLVM Function名称。
    */
    static CFMutableDictionaryRef invokeDic;
    
    /*
    用于保存block对象和其descriptor的copy_helper函数对应的LLVM Function名称。
    */
    static CFMutableDictionaryRef copyDic;
    
    /*
    用于保存block对象和其descriptor的dispose_helper函数对应的LLVM Function名称。
    */
    static CFMutableDictionaryRef disposeDic;
    
    /*
    用于保存通过libffi构造出来的block的invoke函数和其对应的LLVM Function名称。
    */
    static CFMutableDictionaryRef functionDic;
    

    根据block的ABI定义这几个结构体备用:

    struct Block_layout 
    {
      void *isa;
      volatile int32_t flags;
      int32_t reserved; 
      void (*invoke)(void *);
      struct Block_desc *desc;
    };
    
    struct Block_desc {
      unsigned long int reserved;
      unsigned long int size;
      void (*copy_helper)(void *dst, void *src);
      void (*dispose_helper)(void *src);
      const char *signature;
    };
    
    struct Block_byref {
        void *isa;
        struct Block_byref *forwarding;
        int flags;
        int size;
        void (*byref_keep)(void *dst, void *src);
        void (*byref_dispose)(void *);
    };
    

    下面是根据block对应的LLVM Module内的Function,使用libffi构造实际可用的函数指针的过程,同时将block和其对应的LLVM Function的映射关系保存在了上面提到的全局字典中备用。

    ExecutionContext &SF = ECStack.back();
    GenericValue src = getOperandValue(I.getOperand(0), SF);
    struct Block_layout *block = (struct Block_layout *)src.PointerVal;
    Value *v = (Value *)block->invoke;
    Function *function = this->FindFunctionNamed(v->getName());
    
    ffi_cif *cif = (ffi_cif *)malloc(sizeof(ffi_cif));
    FunctionType *FTy = function->getFunctionType();
    const unsigned NumArgs = function->arg_size();
    
    ffi_type **args = (ffi_type **)malloc(sizeof(ffi_type *)*NumArgs);
    for (Function::const_arg_iterator A = function->arg_begin(), E = function->arg_end();A != E; ++A) {
      const unsigned ArgNo = A->getArgNo();
      Type *ArgTy = FTy->getParamType(ArgNo);
      args[ArgNo] = ffiTypeFor(ArgTy);
    }
    
    Type *RetTy = FTy->getReturnType();
    ffi_type *rtype = ffiTypeFor(RetTy);
    void (*globalForward)(void); 
    ffi_closure *closure = (ffi_closure *)ffi_closure_alloc(sizeof(ffi_closure), (void **)&globalForward);
    if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, NumArgs, rtype, args) == FFI_OK) {
      if (ffi_prep_closure_loc(closure, cif, ffi_function, NULL, (void *)globalForward) != FFI_OK) {
        //...handle error...
      }
    } else {
      //...handle error...
    }
    block->invoke = (void (*)(void *))globalForward;
    
    CFDictionaryAddValue(functionDic, (void *)globalForward, (void *)function);
    
    struct Block_desc *desc = block->desc;
    char *copyName = (char *)CFDictionaryGetValue(copyDic, desc);
    if (copyName == nullptr){
      Value *copy = (Value *)desc->copy_helper;
      Value *destroy = (Value *)desc->dispose_helper;
    
      CFDictionaryAddValue(copyDic, block, copy->getName().data());
      CFDictionaryAddValue(disposeDic, block, destroy->getName().data());
    
      desc->copy_helper = copy_helper;
      desc->dispose_helper = dispose_helper;
    
      CFDictionaryAddValue(copyDic, desc, copy->getName().data());
    }
    
    CFDictionaryAddValue(invokeDict, src.PointerVal, v->getName().data());
    

    下面是全局的ffi_function,copy_helper和dispose_helper的实现,所有的block对象被外部invoke时都会调用到ffi_function函数。在这个函数的实现内,会通过block对象的地址,在invokeDic字典中查到其对应的LLVM Function,然后做一些必要的参数转换进行调用。copy_helper和dispose_helper的实现同理,也是在block对象做对应的操作时被调用。

    static void ffi_function(ffi_cif *cif, void *ret, void **args, void *userdata) {
      void *temp = args[0];
      void *block;
      memcpy(&block, temp, sizeof(void *));
    
      StringRef InputFile = StringRef(mainEngine->currentModuleFilePath.c_str());
      LLVMContext *Context = new LLVMContext();
      SMDiagnostic Err;
      std::unique_ptr<Module> Owner = parseIRFile(InputFile, Err, *Context);
      std::string Error;
      EngineBuilder builder(std::move(Owner));
      builder.setEngineKind(EngineKind::Interpreter).setErrorStr(&Error);
      ExecutionEngine *engine = builder.create();
    
      char *funcName = (char *)CFDictionaryGetValue(invokeDic, block);
      Function *function = engine->FindFunctionNamed(StringRef(funcName));
      FunctionType *type = function->getFunctionType();
    
      std::vector<GenericValue> Args;
      for (Function::const_arg_iterator A = function->arg_begin(), E = function->arg_end(); A != E; ++A) {
        FunctionType *FTy = function->getFunctionType();
        const unsigned ArgNo = A->getArgNo();
        Type *ArgTy = FTy->getParamType(ArgNo);
        switch (ArgTy->getTypeID()) {
        case Type::IntegerTyID:
          switch (cast<IntegerType>(ArgTy)->getBitWidth()) {
            case 8: {
              int8_t number;
              memcpy(&number, args[ArgNo], sizeof(int8_t));
              GenericValue value;
              value.IntVal = APInt(8, number);
              Args.push_back(value);
            }
            case 16: {
              int16_t number;
              memcpy(&number, args[ArgNo], sizeof(int16_t));
              GenericValue value;
              value.IntVal = APInt(16, number);
              Args.push_back(value);
            }
            case 32: {
              int32_t number;
              memcpy(&number, args[ArgNo], sizeof(int32_t));
              GenericValue value;
              value.IntVal = APInt(32, number);
              Args.push_back(value);
            }
            case 64: {
              long number;
              memcpy(&number, args[ArgNo], sizeof(long));
              GenericValue value;
              value.IntVal = APInt(64, number);
              Args.push_back(value);
            }
          }
          case Type::FloatTyID: {
              float number;
              memcpy(&number, args[ArgNo], sizeof(float));
              GenericValue value;
              value.FloatVal = number;
              Args.push_back(value);
            }
          case Type::DoubleTyID: {
              double number;
              memcpy(&number, args[ArgNo], sizeof(double));
              GenericValue value;
              value.DoubleVal = number;
              Args.push_back(value);
            }
          case Type::PointerTyID: {
              void* pointer = args[ArgNo];
              void *param;
              memcpy(&param, pointer, sizeof(void *));
              GenericValue value;
              value.PointerVal = param;
              Args.push_back(value);
            }
          default: break;
        }
      }
      engine->runFunction(function, Args);
    }
    
    void copy_helper(void *dst, void *src)
    {
      StringRef InputFile = StringRef(mainEngine->currentModuleFilePath.c_str());
      LLVMContext *Context = new LLVMContext();
      SMDiagnostic Err;
      std::unique_ptr<Module> Owner = parseIRFile(InputFile, Err, *Context);
      std::string Error;
      EngineBuilder builder(std::move(Owner));
      builder.setEngineKind(EngineKind::Interpreter).setErrorStr(&Error);
      ExecutionEngine *engine = builder.create();
    
      char *funcName = (char *)CFDictionaryGetValue(copyDic, src);
      Function *function = engine->FindFunctionNamed(StringRef(funcName));
      FunctionType *type = function->getFunctionType();
    
      std::vector<GenericValue> Args;
      GenericValue value1;
      value1.PointerVal = dst;
      Args.push_back(value1);
      GenericValue value2;
      value2.PointerVal = src;
      Args.push_back(value2);
      engine->runFunction(function, Args);
      
      funcName = (char *)CFDictionaryGetValue(invokeDic, src);
      CFDictionaryAddValue(invokeDic, dst, funcName);
    
      funcName = (char *)CFDictionaryGetValue(copyDic, src);
      CFDictionaryAddValue(copyDic, dst, funcName);
    
      funcName = (char *)CFDictionaryGetValue(disposeDic, src);
      CFDictionaryAddValue(disposeDic, dst, funcName);
    }
    
    void dispose_helper(void *block)
    {
      StringRef InputFile = StringRef(mainEngine->currentModuleFilePath.c_str());
      LLVMContext *Context = new LLVMContext();
      SMDiagnostic Err;
      std::unique_ptr<Module> Owner = parseIRFile(InputFile, Err, *Context);
      std::string Error;
      EngineBuilder builder(std::move(Owner));
      builder.setEngineKind(EngineKind::Interpreter).setErrorStr(&Error);
      ExecutionEngine *engine = builder.create();
    
      char *funcName = (char *)CFDictionaryGetValue(disposeDic, block);
      Function *function = engine->FindFunctionNamed(StringRef(funcName));
      FunctionType *type = function->getFunctionType();
    
      std::vector<GenericValue> Args;
      GenericValue value;
      value.PointerVal = block;
      Args.push_back(value);
      engine->runFunction(function, Args);
    }
    

    下面是处理__block变量的逻辑,处理方法和copy/dispose类似。根据OC block的ABI,需要检测byref->flags&1<<25来判断是否需要处理byref_keep和byref_dispose。byref_keep可以复用copy_helper的实现,同理byref_dispose可以复用dispose_helper的实现。

    ExecutionContext &SF = ECStack.back();
    GenericValue src = getOperandValue(I.getOperand(0), SF);
    struct Block_byref *byref = (struct Block_byref *)src.PointerVal;
    if (byref->flags & 1<<25){
      char *copyName = (char *)CFDictionaryGetValue(copyDic, byref);
      if (copyName == nullptr){
        Value *keep = (Value *)byref->byref_keep;
        Value *dispose = (Value *)byref->byref_dispose;
    
        CFDictionaryAddValue(copyDic, byref, keep->getName().data());
        CFDictionaryAddValue(disposeDic, byref, dispose->getName().data());
    
        byref->byref_keep = copy_helper;
        byref->byref_dispose = dispose_helper;
      }
    }
    

    此外还需要考虑直接调用一个block的情况,比如:

    void(^testArgument)(int) = ^(int input){
      printf("testArgument called with input:%d\n", input);
    };
    testArgument(456);
    

    这个时候调用会被转换为IR内部的一个Call指令。所以需要在Call指令的实现内做一个检测,如果当前block的invoke函数已经被上述的操作替换了,那么需要将其替换回原本对应的LLVM Function以供直接调用。

    Function *f = (Function *)CFDictionaryGetValue(functionDic, GVTOP(SRC));
    if (f){
      callFunction(f, ArgVals);
      return;
    }
    

    2.5 ARC

    还需要考虑arc的支持。在生成IR文件的过程中,使用-fobjc-arc会生成类似于llvm.objc.retainAutoreleasedReturnValue这种调用,然而这并不是实际的函数名称,实际的函数名应该是objc_retainAutoreleasedReturnValue这种形式。所以在解释执行IR的时机,我们要对这些调用做一个替换处理,arc相关的这些函数是一个有限集,逐个判断替换即可。

    if (F->getName() == StringRef("llvm.objc.retain")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_retain"));
    } else if (F->getName() == StringRef("llvm.objc.release")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_release"));
    } else if (F->getName() == StringRef("llvm.objc.retainAutoreleasedReturnValue")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_retainAutoreleasedReturnValue"));
    } else if (F->getName() == StringRef("llvm.objc.retainAutorelease")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_retainAutorelease"));
    } else if (F->getName() == StringRef("llvm.objc.retainAutoreleaseRetureValue")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_retainAutoreleaseRetureValue"));
    } else if (F->getName() == StringRef("llvm.objc.autorelease")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_autorelease"));
    } else if (F->getName() == StringRef("llvm.objc.storeStrong")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_storeStrong"));
    } else if (F->getName() == StringRef("llvm.objc.storeWeak")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_storeWeak"));
    } else if (F->getName() == StringRef("llvm.objc.copyWeak")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_copyWeak"));
    } else if (F->getName() == StringRef("llvm.objc.initWeak")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_initWeak"));
    } else if (F->getName() == StringRef("llvm.objc.loadWeak")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_loadWeak"));
    } else if (F->getName() == StringRef("llvm.objc.loadWeakRetained")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_loadWeakRetained"));
    } else if (F->getName() == StringRef("llvm.objc.destroyWeak")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_destroyWeak"));
    } else if (F->getName() == StringRef("llvm.objc.moveWeak")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_moveWeak"));
    } else if (F->getName() == StringRef("llvm.objc.retainBlock")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_retainBlock"));
    } else if (F->getName() == StringRef("llvm.objc.autoreleaseReturnValue")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_autoreleaseReturnValue"));
    } else if (F->getName() == StringRef("llvm.objc.autoreleasePoolPop")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_autoreleasePoolPop"));
    } else if (F->getName() == StringRef("llvm.objc.autoreleasePoolPush")){
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(StringRef("objc_autoreleasePoolPush"));
    } else {
      RawFn = (RawFunc)(intptr_t)sys::DynamicLibrary::SearchForAddressOfSymbol(F->getName());  
    }
    

    2.6 多线程

    还需要考虑多线程的支持。日常的开发中dispatch_sync/dispatch_async类的函数会使用很多,涉及到block的处理和多线程的处理两部分。block的处理上面已经简单介绍过,多线程的方案目前处理的比较简单,为了避免线程安全的问题,每次需要在异步线程执行IR代码时,都会创建新的LLVM Context,Mudule,和ExecutionEngine来处理。但坏处在于性能会比较差,以及会有内存泄漏等问题,还有很大的优化空间。内存泄漏的问题也主要来自于此,比如像一些NSConcreteGlobalBlock类型的block对象是LLVM创建并保存的,这些block也可能会当作参数传递给系统的API,并且这些block对象的copy等操作都是空逻辑。所以如果对应的LLVM对象的内存被释放掉的话,等外部的方法再调用这些block的时候,就会出现崩溃。

    后续可以针对性的做一些改进。一个比较理想的方案是给每个用到的线程,创建一个解释器并保存一份LLVM相关的对象,在某个线程需要执行代码的时候,获取到其对应的解释器和对象进行处理。

    此外,如果解释器在执行某个函数的时候,调用到了我们自定义的函数,并且这个自定义的函数也需要解释器去执行另外的函数,那么也会出现一个问题,就是会破坏掉解释器现有的栈结构,这个时候也是需要创建一个新的解释器来执行另外的函数,避免对原有解释器的栈产生影响。

    2.7 Struct

    此外还有类似于CGRect这种数据结构的处理。比如CGRectMake这样的函数,其实际上是一个内联函数。但由于x86和arm64 ABI的差异,针对不同架构生成的IR也有不同的处理。由于现有的LLVM解释器的实现不完整,无法支持直接load一个struct类型的变量到其寄存器中,所以无法完全支持arm64架构下IR的解释执行。目前暂时没有发现x86和arm64下的IR有实际影响逻辑的差异性,所以可以暂时使用针对x86架构生成的IR文件来处理真机环境下的执行。

    4 后续

    朱雀的实现目前并不完整,还停留在原型阶段。等进一步完善后会开放源码以供大家共同探讨。

    下面的工作计划大概如下:

    a. 支持添加或者删除类的property

    目前并不支持修改AClass的属性。比如AClass有两个property,view1和view2,我们添加了一个新的property,UIView类型,名字为view3。那么通常情况下,编译器会自动帮我们生成getter和setter,这两个方法也同样会存在于IR中。但是,当代码中真正使用view3时,这个getter方法是没办法正常运行的,原因在于,外部创建出来的AClass的对象AObject实际的内存空间其实并不包含view3,只有view1和view2。而getter会假定AObject的内存空间中是含有view3的,那么直接执行getter方法就会产生内存问题。众所周知OC的runtime可以支持给一个类新增一个方法,但并不会支持新增property,基本也是同样的原因。

    实际上这个问题也是有办法在一定程度上支持的。需要在IR文件中获取到当前类的所有property,然后从runtime中获取到现有的property,对于那些新增的部分,在需要调用其getter和setter的时候,并不执行IR中的Function,而是使用自定义的实现,在自定义的实现中可以通过associated object等手段去处理。

    b. 支持同时加载处理多个IR文件

    目前只支持单个IR文件的加载,支持多个IR文件会允许解释执行更多的逻辑,甚至可以执行一个比较完整的代码模块。

    c. 支持新增一个新的OC类

    目前只支持runtime中已有类的IR文件解释执行,并不能通过处理一个新类的IR文件,向runtime中注册一个新的类。这会是一个比较复杂的工作。

    5 闲谈

    理论上来说,是可以做到基本的解释执行一个iOS App的所有代码,从AppDelegate开始,就完全进入到我们的世界了。当然像+load,kvc/kvo,以及各种hook等等可能并不能完全支持或者有很大难度。不过到了这个程度,壳App就会越来越像一个浏览器,而OC代码则等同于JS,绕了一圈又回到了所谓大前端的概念上。

    在我们这儿的大环境下,单一原生客户端的开发感觉会越来越被忽视,一些跨端的,有一些动态化能力的解决方案可能更受欢迎。虽然是一个老生常谈的话题,但个人感觉跨端的开发能力确实应该重点关注,在很大程度上会缩减多端开发的时间和成本。所以趁着还算年轻,抓紧进入到Flutter和Dart的世界,这更像是移动开发的未来。

    相关文章

      网友评论

        本文标题:朱雀-基于LLVM的Objective-C解释器的基本实现

        本文链接:https://www.haomeiwen.com/subject/tbwlnctx.html