使用JS實現JS編譯器,並將目標js生成二進制

上一篇文章 利用LLVM實現JS的編譯器,創造屬於本身的語言 中講到使用llvm用C實現JS編譯器,本片文章將使用JS來實現JS編譯器,仍是應了《Atwood定律》可以使用JavaScript實現的,必將使用JavaScript實現。本片文章C代碼不超過10行,即便徹底不會C也能夠相對容易的閱讀。node

本文代碼倉庫地址:點這裏c++

本次使用npm庫

@babel/core

直接使用@babel/core來進行詞法分析和生成ast。git

llvm-node

使用本庫將ast樹綁定生成IR(中間代碼)和編譯成二進制文件,雖然這個庫看起來沒什麼文檔,但好用api基本寫在了llvm-node.d.ts裏面。題外話:講真我挺喜歡用ts寫的庫,但我我的不喜歡寫ts,固然這並不矛盾。github

使用@babel/core進行解析

講真這個挺好用的,解析JS就幾行代碼就能生成ast,爽歪歪,具體結構不展開,本身能夠嘗試一下。代碼也就幾行,應該一看就明白了。express

//parse.js
const babel_core = require('@babel/core');
const fs  = require('fs');
module.exports = function (js_path){
    let js_conent = fs.readFileSync(js_path);
    let js_ast = babel_core.parse(js_conent);
    return js_ast;
}
複製代碼

將解析的的AST綁定IR,實現編譯器

語法解析開始,針對不通的類型進入不通的入口進行解析

//jsvm.js
class JSVM{
...
    handler(node,parent_node = null) {
        switch(node.type) {
            case 'Program': // 這是主程序入口
                return this.programHandler(node);
            case 'FunctionDeclaration': // 當是方法的類型走這裏
                return this.functionHandler(node);
            case 'BlockStatement': // 代碼塊類型走這裏
                return this.blockHandler(node);
            case 'IfStatement': // IF塊類型走這裏
                return this.ifHandler(node);
            case 'BinaryExpression': // 二進制表達式類型走這裏
                return this.binaryHandler(node);
            case 'ReturnStatement': // 解析返回
                return this.returnHandler(node);
            case 'Identifier': // 變量或函數調用,須要經過父節點判斷,因此傳入
                return this.identifierHandler(node,parent_node);
            case 'NumericLiteral': //數字類型走這
                return this.numberHandler(node);
            case 'StringLiteral': //文本類型走這
                return this.stringHandler(node);
            case 'CallExpression': // 函數調用走着
                return this.callHandler(node);
            case 'ExpressionStatement': // 表達式類型走這
                return this.expressionHandler(node);
            default: // 目前不支持的類型直接拋錯
                throw new Error('not support grammar type');
        }
    }
    // 入口文件
    gen() {
        // 初始化變量和方法包括C擴展
        this.init();
        // 將ast進行解析和綁定
        this.handler(this.js_ast.program);
    }
    // 對程序主題不斷解析下一個語法節點就好
    programHandler(node) {
        for(let i=0;i<node.body.length;i++)
        {
            this.handler(node.body[i]);
        }
    }
...    
}
複製代碼

以函數綁定舉例

//jsvm.js
    functionHandler(node) {
        // 拿到函數節點的函數名
        let func_name = node.id.name;
        // 判斷模塊中函數是否存在
        the_function = the_module.getFunction(func_name);
        if (the_function) {
           throw new Error('function is exist');
        }
        // 設置返回值,目前先定死爲double類型
        let double_type = llvm.Type.getDoubleTy(the_context);
        // 設置參數,目前先定死爲double類型
        let params_list = [];
        for(let i=0;i<node.params.length;i++)
        {
            params_list.push(double_type);
        }
        // 把參數注入,生成函數類型
        let the_function_type = llvm.FunctionType.get(
            double_type,params_list,false
        );
        // 創造出一個函數
        the_function = llvm.Function.create(
            the_function_type,
            llvm.LinkageTypes.ExternalLinkage,
            func_name,the_module
        );
        // 將參數的名稱插入
        let the_args = the_function.getArguments();
        for(let i=0;i<the_args.length;i++)
        {
            the_args[i].name=node.params[i].name;
        }
        // 建立函數主運行節點
        let basic_block = llvm.BasicBlock.create(the_context,"entry",the_function);
        // 設置代碼插入位置,這個basic_block就是entry節點
        builder.setInsertionPoint(basic_block);
        // 這裏是爲了註冊變量,使得在函數做用域內變量可用
        variable_map = {};
        for(let i=0;i<the_args.length;i++)
        {
            variable_map[the_args[i].name]=the_args[i];
        }
        // 判斷函數是不是塊表達式,不是則踢出
        if (node.body.type!='BlockStatement')
        {
            throw new Error('function body only support BlockStatement');
        }
        // 調用解析塊表達式的方法
        this.blockHandler(node.body);
        // 校驗函數是否正確,不正確這個函數會直接報錯
        llvm.verifyFunction(the_function);
        return the_function;
    }
複製代碼

塊表達式解析的實現

其實這異步就是遍歷節點進行解析,是否是很簡單npm

//jsvm.js
    blockHandler(node)
    {
        let expr_list = [];
        for(let i=0;i<node.body.length;i++)
        {
            expr_list.push(this.handler(node.body[i]));
        }
        return expr_list;
    }
複製代碼

以IF的解析實現來說代碼塊的跳躍

//jsvm.js
    ifHandler(node) {
        //判斷條件的類型是不是二進制表達式
        if (node.test.type!='BinaryExpression') {
            throw new Error('if conds only support binary expression');
        }
        // 解析二進制表達式做爲條件
        let cond = this.binaryHandler(node.test);
        // 生成數字0
        let zero = llvm.ConstantFP.get(the_context,0);
        // 若是cond不是bool類型的指,將它轉換爲bool類型的值
        let cond_v = builder.createFCmpONE(cond,zero,"ifcond");
        // 建立then和else和ifcont代碼塊,實際就是代碼塊標籤
        let then_bb = llvm.BasicBlock.create(the_context,"then",the_function);
        let else_bb = llvm.BasicBlock.create(the_context,"else",the_function);
        let phi_bb = llvm.BasicBlock.create(the_context, "ifcont",the_function);
        // 創造條件判斷
        // 若是cond_v是真就跳躍到then_bb代碼塊,不然跳躍到else_bb代碼塊
        builder.createCondBr(cond_v,then_bb,else_bb);
        // 設定往then_bb代碼塊寫入內容
        builder.setInsertionPoint(then_bb);
        if (!node.consequent) {throw new Error('then not extist');}
        if (node.consequent.type!='BlockStatement')
        {
            throw new Error('then body only support BlockStatement');
        }
        // 解析代碼塊
        let then_value_list = this.blockHandler(node.consequent);
        // 若是代碼塊沒內容就就跳躍到phi_bb代碼塊
        if (then_value_list.length==0)
        {
            builder.createBr(phi_bb);
        }
        // 設定往else_bb代碼塊寫入內容,和then_else差很少
        // 不一樣點:else容許沒有
        builder.setInsertionPoint(else_bb);
        let else_value_list =  [];
        if (node.alternate) {
            if (node.alternate.type!='BlockStatement')
            {
                throw new Error('else body only support BlockStatement');
            }
            else_value_list = this.blockHandler(node.alternate);
        }
        if (else_value_list.length==0)
        {
            builder.createBr(phi_bb);
        }
        // 由於不管是then或else若是不中斷必定會往phi_bb代碼塊
        // 因此後續的代碼直接在phi_bb裏面寫就好
        builder.setInsertionPoint(phi_bb);
    }
複製代碼

支持C擴展的實現

首先先定義存在值api

//jsvm.js
    // 定義一個C函數printDouble用於打印二進制變量
    getPrintDouble()
    {
        // 獲取返回值類型
        let double_type = llvm.Type.getDoubleTy(the_context)
        // 設置參數列表
        let params_list = [double_type];
        // 獲取函數類型
        let the_function_type = llvm.FunctionType.get(
            double_type,params_list,false
        );
        // 建立函數定義
        the_function = llvm.Function.create(
            the_function_type,
            llvm.LinkageTypes.ExternalLinkage,
            'printDouble',the_module
        );
        // 設置參數名稱
        let the_args = the_function.getArguments();
        the_args[0].name = "double_name";
        return the_function;
    }
    // 初始化方法值講須要預置的方法放入
    init()
    {
        init_function_map.printDouble = this.getPrintDouble();
    }
複製代碼

C代碼的實現printDouble方法babel

// printDouble.cpp
#include <stdio.h>
// 問什麼要要加extern "C" ,由於c++編譯的時候會自動進行函數簽名
// 若是沒有extern "C" ,彙編裏的方法名就會是Z11printDoubled
// 其中籤名前部分由返回值和命名空間名字中間是方法名,後面是參數縮寫
extern "C" {
    // 設定返回值和參數都是double類型
    double printDouble(double double_num) {
        // 打印double類型
        printf("double_num is: %f\r\n",double_num);
        // 返回double類型
        return double_num;
    }
}
複製代碼

看看實現效果

要被編譯的代碼異步

// fibo.js 這是斐波納切數
function fibo(num) {
    if (num<=2) {return 1;}
    return fibo(num-1)+fibo(num-2);
}
// 講main做爲主函數運行
function main() {
    return printDouble(fibo(9));
}
複製代碼

開始編譯,並生成中間代碼和bitcode代碼,以下ide

# index.js是編譯器入口,fibo.js是要被編譯的函數
node index.js fibo.js
複製代碼

tobc.png
將bitcode代碼生成彙編代碼

llc fibo.js.bc -o fibo.s
複製代碼

將彙編代碼和咱們要注入的C代碼一塊兒編譯

固然除了C只要能被gcc編譯成彙編的也都支持做爲擴展語言,本文舉例C代碼容易讓人理解

gcc printDouble.cpp fibo.s -o fibo
複製代碼

最後運行看看

./fibo
複製代碼

run.png

總結

此次實現是用純JS就能實現,若是後續這個JSVM能編譯覆蓋全部的編譯器自身全部的代碼功能,理論上來講能夠用JSVM編譯JSVM實現自舉,固然這個是一個浩大的工程,方法是有了缺的只是時間而已。

相關文章
相關標籤/搜索