• 关于我对js运行时的一些浅薄的理解:(1,实现简单的原生编译)


    运行时是什么?

    runtime(运行时)在计算机世界中并不少见,或者说基本上所有的语言都需要一个运行时。计算机的能力,比如操作系统的网络,io操作,文件系统等能力,单纯的使用js是无法运用的。js本声就是一门很简单的解释型脚本而已。他并没有能力触碰操作系统。这也就是我们常常把js比作一把无比华丽的宝剑,但是却找不到剑柄。

    而js目前的主要运行时有两个,浏览器和nodejs

    怎么去实现简单的运行时

    这里有一个著名的轮子叫quickjs
    怎么样可以触碰到底层呢?这个问题和怎么把大象装到冰箱里一样。其实很简单,js的解释器,也就是我们所说的引擎,对于c++(c)开发者来说和其他的库没有什么不同

    简单的说就是

    • 将引擎的源码编译成库文件
    • 编到头文件include一下
    • 编译自己的c源码,经过编译,连接,装载。。。(开始背书)
      那么quickjs就是做的这件事,只需要一行make&&sudo make install就可以生成可执行的c代码,就这么简单,而quickjs做的事也很简单,就是我们上面说的,把js代码转换成机器码喂给js引擎而已,然后把他们的文件结构整理成unix规范

    比如我们用quickjs把下面的js代码转换成c
    js console.log("Hello World");

    #include <quickjs/quickjs-libc.h>
    
    const uint32_t qjsc_hello_size = 87;
    
    const uint8_t qjsc_hello[87] = {
     0x02, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
     0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x48,
     0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72,
     0x6c, 0x64, 0x22, 0x65, 0x78, 0x61, 0x6d, 0x70,
     0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c,
     0x6f, 0x2e, 0x6a, 0x73, 0x0e, 0x00, 0x06, 0x00,
     0x9e, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00,
     0x14, 0x01, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x39,
     0xf1, 0x00, 0x00, 0x00, 0x43, 0xf2, 0x00, 0x00,
     0x00, 0x04, 0xf3, 0x00, 0x00, 0x00, 0x24, 0x01,
     0x00, 0xd1, 0x28, 0xe8, 0x03, 0x01, 0x00,
    };
    
    int main(int argc, char **argv)
    {
      JSRuntime *rt;
      JSContext *ctx;
      rt = JS_NewRuntime();
      ctx = JS_NewContextRaw(rt);
      JS_AddIntrinsicBaseObjects(ctx);
      js_std_add_helpers(ctx, argc, argv);
      js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0);
      js_std_loop(ctx);
      JS_FreeContext(ctx);
      JS_FreeRuntime(rt);
      return 0;
    }
    

    所以并没有什么黑魔法,就只是拿胶水站在一起而已。。。。

    现在我们已经实现了一个可以在原生环境运行js的这么一个东西了。当然他现在只认识原生js,任何触摸到操作系统的事情都做不了,settimeout也不行(要用到时钟)。

    我们把上面生成的c代码编译运行之后就可以在控制台看到“hello world”
    有了这个工具我们可以做什么呢?比如说一些计算密集型的任务,就可以直接交给c去做
    要想在 QuickJS 引擎中使用上面这个 C 函数,大致要做这么几件事:

    把 C 函数包一层,处理它与 JS 引擎之间的类型转换。
    将包好的函数挂载到 JS 模块下。
    将整个原生模块对外提供出来。
    这一共只要约 30 行胶水代码就够了,相应的 fib.c 源码如下所示:

    #include <quickjs/quickjs.h>
    #define countof(x) (sizeof(x) / sizeof((x)[0]))
    
    // 原始的 C 函数
    static int fib(int n) {
        if (n <= 0) return 0;
        else if (n == 1) return 1;
        else return fib(n - 1) + fib(n - 2);
    }
    
    // 包一层,处理类型转换
    static JSValue js_fib(JSContext *ctx, JSValueConst this_val,
                          int argc, JSValueConst *argv) {
        int n, res;
        if (JS_ToInt32(ctx, &n, argv[0])) return JS_EXCEPTION;
        res = fib(n);
        return JS_NewInt32(ctx, res);
    }
    
    // 将包好的函数定义为 JS 模块下的 fib 方法
    static const JSCFunctionListEntry js_fib_funcs[] = {
        JS_CFUNC_DEF("fib", 1, js_fib ),
    };
    
    // 模块初始化时的回调
    static int js_fib_init(JSContext *ctx, JSModuleDef *m) {
        return JS_SetModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs));
    }
    
    // 最终对外的 JS 模块定义
    JSModuleDef *js_init_module_fib(JSContext *ctx, const char *module_name) {
        JSModuleDef *m;
        m = JS_NewCModule(ctx, module_name, js_fib_init);
        if (!m) return NULL;
        JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs));
        return m;
    }
    上面这个 fib.c 文件只要加入 CMakeLists.txt 中的 add_executable 项中,就可以被编译进来使用了。这样在原本的 main.c 入口里,只要在 eval JS 代码前多加两行初始化代码,就能准备好带有原生模块的 JS 引擎环境了:
    
    // ...
    int main(int argc, char **argv)
    {
      // ...
      // 在 eval 前注册上名为 fib.so 的原生模块
      extern JSModuleDef *js_init_module_fib(JSContext *ctx, const char *name);
      js_init_module_fib(ctx, "fib.so");
    
      // eval JS 字节码
      js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0);
      // ...
    }
    

    这样,我们就能用这种方式在 JS 中使用 C 模块了:

    import { fib } from "fib.so";
    
    fib(42);
    

    运行时间:

    js c v8
    42ms 2 ms 3.5ms
    (这个实验我没做,看的别人的

    所以jit是一个很伟大的事,他大幅提高了js的效率。甚至可以和原生媲美

  • 相关阅读:
    smarty模板中如何嵌入javascript脚本
    正则表达式(一)
    c#获取凌晨时间
    启动VUE项目报错:Error: Cannot find module 'node-sass'
    安装VUE过程记录
    Jenkins自动化工具完整介绍
    使用VS开发C#的常用快捷键
    获取枚举中的描述值
    PDF链接转字节流输出
    MSSQL执行计划的优化建议
  • 原文地址:https://www.cnblogs.com/geck/p/13664334.html
Copyright © 2020-2023  润新知