美文网首页
WAVM源码解析 —— 实例化

WAVM源码解析 —— 实例化

作者: 蟹蟹宁 | 来源:发表于2021-08-18 01:26 被阅读0次

    前言

    在本系列的第三篇文章中,有一段关于实例化的论述:

    以Function为例,实例化的过程就是:

    1. 将导入函数的地址写到指定的位置,这样在执行是就能找到对应的函数
    2. 同时提供一个接口,可以让链接器找到本实例导出函数的地址。

    以Memory为例,实例化的过程就是:

    1. 分配内存空间、初始化内存的数据
    2. 将创建的内存地址写入到指定位置
    3. 提供一个接口,可以让链接器找到本实例导出的内存

    这段话基本上就概括了实例化的工作,因为这部分的代码涉及了llvm的很多东西,因此我们将着重理解上述过程是怎么实现的。

    函数声明

    Instance* Runtime::instantiateModuleInternal(Compartment* compartment,
                                                 ModuleConstRefParam module,
                                                 std::vector<FunctionImportBinding>&& functionImports,
                                                 std::vector<Table*>&& tables,
                                                 std::vector<Memory*>&& memories,
                                                 std::vector<Global*>&& globals,
                                                 std::vector<ExceptionType*>&& exceptionTypes,
                                                 std::string&& moduleDebugName,
                                                 ResourceQuotaRefParam resourceQuota)
    

    中间5项,就囊括了Function、Table、Memory、Global等需要实例化的对象,其中对于内部实例,仅仅需要提供Function,即FunctionImportBinding的信息即可!

    我们看一下FunctionImportBinding的定义:

    struct FunctionImportBinding
    {
        union
        {
            Function* wasmFunction;
            const void* nativeFunction;
        };
        FunctionImportBinding(Function* inWASMFunction) : wasmFunction(inWASMFunction) {}
        FunctionImportBinding(void* inNativeFunction) : nativeFunction(inNativeFunction) {}
    };
    

    只是一个联合体,对于内部函数,其函数地址是直接给出的,而对于普通实例,其函数地址目前是Runtime::Function*类型,这里需要知道,最终程序是一JIT模式运行的,也就是函数调用是遵循CPP的规则的,因此我们需要通过Runtime::Function*来获取其地址,这个地址方式应该是非常的类似于动态链接库的中函数获取。

    我们看一下Runtime::Function的结构:

    struct Function
    {
        Object object;
        FunctionMutableData* mutableData;
        const Uptr instanceId;
        const IR::FunctionType::Encoding encodedType;
        const U8 code[1];
    };
    

    其中code字段是一个字节数组,内容正式函数的二进制代码,因此code就是函数的入口地址,但是奇怪的是这个数组仅仅有1个字节,这是为啥呢?

    显然我们要先确定Function对象是由谁来创建的?

    答案是实例化的过程中创建的,Function对象是用于导出的,因此显然应该是由实例化的函数创建。

    那么为啥code[1]仅仅有一个字节呢?其实他利用了和linux内核中好象是PCB和栈类似的方法,也就是在将wasm编译成二进制代码的过程中,预留Function的空间结构,形成类似于如下图的存储方式:

    执行过程

    1. 创建Table、Memory等结构,并记录其地址
    2. 记录要导入的表、内存、函数等的地址信息记录
    3. 为每一个Funtion对象创建FunctionMutableData,为其申请空间并记录其地址
      FunctionMutableData中存放了其所在Function对象的地址,以便我们获取
    4. 利用前三步骤中获取的各类地址,来填充编译好的二进制代码,这个过程简直等同于运行一个可执行文件时,将可执行文件中各个符号(symbol)的地址替换为确定的物理内存地址
    5. 生成函数名和函数地址的映射,已供调用接口使用
    6. 生成导出项影射,以供链接过程使用,
    7. 填充数据段、元素段的内容到我们申请的内存、表等空间中

    上述过程最难理解的就是第4部分,其实现是调用了:

    std::shared_ptr<LLVMJIT::Module> jitModule
        = LLVMJIT::loadModule(module->objectCode,
                              std::move(wavmIntrinsicsExportMap),
                              std::move(jitTypes),
                              std::move(jitFunctionImports),
                              std::move(jitTables),
                              std::move(jitMemories),
                              std::move(jitGlobals),
                              std::move(jitExceptionTypes),
                              {id},
                              reinterpret_cast<Uptr>(getOutOfBoundsElement()),
                              functionDefMutableDatas,
                              std::string(moduleDebugName));
    

    其中jitTypesjitFunctionImportsjitTablesjitMemories等都是要填充的地址,以及functionDefMutableDatas,也是需要我们填充到我上面画得图的位置。

    LLVMJIT::loadModule的主要工作就是将这些地址进行整理,融合到importedSymbolMap中,然后调用一下函数,创建一个LLVMJIT::Module对象

    std::make_shared<Module>(objectFileBytes, importedSymbolMap, true, std::move(debugName));
    

    LLVMJIT::Module中存放了函数名到函数对象的Map映射:

    struct Module
    {
        HashMap<std::string, Runtime::Function*> nameToFunctionMap;
    

    最终在LLVMJIT::Module的构造函数中,将实现4、5步骤,我对这部分得代码进行了极大的简化,符号地址填充的部分我是根据代码的注释猜测的,应该错不了,而生成Function的代码中,使用了:

    Runtime::Function* function
                = (Runtime::Function*)(loadedAddress - offsetof(Runtime::Function, code));
    

    这也证实了我前面的图

    Module::Module(const std::vector<U8>& objectBytes,
                   const HashMap<std::string, Uptr>& importedSymbolMap,
                   bool shouldLogMetrics,
                   std::string&& inDebugName)
        : debugName(std::move(inDebugName))
        , memoryManager(new ModuleMemoryManager())
        , globalModuleState(GlobalModuleState::get())
    {
        // 完成对符号(导入的函数,创建的内存、表,function->mutableData等等)的地址填充
        std::unique_ptr<llvm::object::ObjectFile> object;
        object = cantFail(llvm::object::ObjectFile::createObjectFile(llvm::MemoryBufferRef(
            llvm::StringRef((const char*)objectBytes.data(), objectBytes.size()), "memory")));
        struct SymbolResolver : llvm::JITSymbolResolver
        {
           ...
        };
        SymbolResolver symbolResolver(importedSymbolMap);
        llvm::RuntimeDyld loader(*memoryManager, symbolResolver);
        loader.setProcessAllSections(true);
        std::unique_ptr<llvm::RuntimeDyld::LoadedObjectInfo> loadedObject = loader.loadObject(*object);
        loader.finalizeWithMemoryManagerLocking();
    
        
        // 此循环用于解析loaded的object中的函数,算出其地址,并构建Runtime::Function结构
        for(std::pair<llvm::object::SymbolRef, U64> symbolSizePair :
            llvm::object::computeSymbolSizes(*object))
        {
            // 获取函数的地址
            llvm::object::SymbolRef symbol = symbolSizePair.first;
            llvm::Expected<U64> address = symbol.getAddress();
            Uptr loadedAddress = Uptr(*address);
    
            // 创建Function对象,配置nameToFunctionMap
            Runtime::Function* function
                = (Runtime::Function*)(loadedAddress - offsetof(Runtime::Function, code));// offsetof(Runtime::Function, code)==32
            nameToFunctionMap.addOrFail(std::string(*name), function);
            addressToFunctionMap.emplace(Uptr(loadedAddress + symbolSizePair.second), function);
    
            // 初始化mutableData
            function->mutableData->jitModule = this;
            function->mutableData->function = function;
            function->mutableData->numCodeBytes = Uptr(symbolSizePair.second);
            function->mutableData->offsetToOpIndexMap = std::move(offsetToOpIndexMap);
        }
    }
    

    相关文章

      网友评论

          本文标题:WAVM源码解析 —— 实例化

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