JWebAssembly将Java编译为wasm的研究

前言

目前将Java转成wasm的相关文档比较少,对于teavm、Jwebassembly之类的使用都得通过阅读源码获得,以下对Jwebassembly进行了一定的研究。
下载源码:
git clone https://github.com/i-net-software/JWebAssembly.git

目录结构如下:
在这里插入图片描述
该项目为gradle项目,我安装的版本是gradle6.5,配置完gradle项目加载需要一定的时间。另外对idea版本有一定的要求,我之前一直用的2018,build一直会失败,后来下了2021才build成功的。
test下为测试代码,解读源码可通过\de\inetsoftware\jwebassembly\runtime下的测试类作为突破口,该测试类的目的在于测试SpiderMonkey、NodeJS等引擎验证编译的正确性。测试类运行会生成wasm文件及其依赖的js文件,测试文件及映射map文件。结合测试类可对其做修改后,得到下面的编译方式

1. 实现方式

在该项目test包下新建Main.Java用于转换,以及目标函数WasmConvert.java
WasmConvert.java:

public class WasmConvert {
    @Export
    public static DOMString hello() {
        return JSObject.domString( "Hello World!" );
    }

    @Export
    public static int intdata() {
        return 123;
    }
}

Main.java

  public class Main {
  	private final WasmRule     wasm;

  	private final ScriptEngine script;

  	private final String       method;

  	private final Object[]     params;

  	public Main( WasmRule wasm, ScriptEngine script, String method, Object[] params ) {
          this.wasm = wasm;
          this.script = script;
          this.method = method;
          this.params = params;
 	 }

      protected static void addParam(ArrayList<Object[]> list, ScriptEngine script, String method, Object ...params ) {
          list.add( new Object[]{script, method, params} );
      }

      public static void main(String[] args) {
          try {
              WasmRule rule = new WasmRule( WasmConvert.class );  //目标类
              for (Method m : WasmConvert.class.getMethods()) {  //目标类中的方法遍历
                  Main main = new Main(rule, ScriptEngine.SpiderMonkey, m.getName(), new Object[0]);
                  main.wasm.before(ScriptEngine.SpiderMonkey);  //选择一个引擎
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }

运行Main.java,在C:\Users\tangminyan\AppData\Local\Temp目录下生成目标文件,该目录地址可在WasmRule类中重写addFile方法替换。
简单加减法可直接生成后实现,包含非原生方法的需要借助js或编写自定义方法。

2. 手动添加非原生函数

@WasmTextCode注解添加非原生方法

例如:

public class ReplacementForMath {
    
    // i32.max没有定义,要添加涉及到转为二进制文件的编码定义,如下:
    // static final int F32_MAX   = 0x97;
    // 固无法直接添加
    public static int int_max( int obj1, int obj2 ) {
        float f = float_max(obj1, obj2);
        return (int) f;
    }

/**
* 用WasmTextCode注解写入wasm语法的计算方法,该方法前要加native 
*/
    @WasmTextCode( "local.get 0 " //
            + "local.get 1 " //
            + "f32.max " //
            + "return" )
    public static native float float_max( float a, float b);
}

目标类

public class TestClass {
    @Export
    static int intCall() {
        return ReplacementForMath.int_max(4, 5);
    }

    @Export
    static float nativeCall() {
        return ReplacementForMath.float_max( 4.5F,5.5F);
    }
}

通过第一节所述方式生成wasm文件,用wasmedge运行:
在这里插入图片描述

@Import注解引入js

Import注解将js代码引入wasm文件中使用,下面为Import注解源码部分

/**
     * The module/object name of the import. If not set then the simple class name is used.
     * 
     * @return the module name
     * js文件中按模块分类,模块的名称
     */
    String module() default "";

    /**
     * The function name in the scope of the module. If not set then the method name is used.
     * @return the name
     * js文件中方法的名字
     */
    String name() default "";

    /**
     * The JavaScript replacement. If empty then there must be a same naming object in JavaScript or another import declaration like WASI.
     * @return JavaScript replacement. This is the body of the function.
     * js代码,可在代码中写明,也可在js文件中编写
     */
    String js() default "";

    /**
     * Signatures of required callback methods. The callback methods will only exports if the this function is needed. This is different to the @Export annotation.
     * @see Export
     * @return the full Java method signature like "com/foo/Bar.method()V"
     */
    String[] callbacks() default "";

如:在Jwebassembly-api包下有一系列Replacement方法(可下载Jwebassembly-api进行研究:https://github.com/i-net-software/JWebAssembly-API.git),@Import注解下的方法(包括Jwebassembly-api下的方法)会被记录到JavaScriptWriter的modules中和FunctionManager的states中,生成wasm.js文件时会把modules中的方法都写入到该文件中。
使用方式如下:
方式一

@Export
static int intCall() {
	 return abc(4, 5);
}
@Import( module = "Math", name = "max" )
static int abc( int a, int b) {
    return Math.max( a, b );
 }

以上方法在用第一节所述方式生成过程中,会生成js文件,文件内容类似为:

'use strict';var wasmImports = {
Math:Math
};
if (typeof module !== 'undefined') module.exports = wasmImports;

方式二

@Import( js = "(o) => {console.log(o)}" )
public native static void print( DOMString o);

则js中会出现以下内容:

……
Math:Math,
CallFunctions$TestClass:{
print:(o) => {console.log(o)}
},
……

@Replace注解

其参数用jvm描述符标识,如:

@Replace( "java/lang/Math.pow(DD)D" )
@Import( module = "Math", name = "pow" )
static native double pow( double a, double b );

value=方法(入参)返回值,native表示无法用Java代码实现,需要借助其他代码实现,此处用Import注解借助js代码。

@Replace可实现重写,如以下代码将Math.max(int, int)方法改为求和(举例子而已,主要用于借助js实现复杂代码)

@Replace("java/lang/Math.max(II)I")
public static int int_max( int obj1, int obj2 ) {
    return obj1 + obj2;
}

总之采用JAVA程序转换为WASM语言,需要大量的JS代码支撑,运行过程中需要JS引擎,而非全部的可执行二进制文件,没有GO\C等合适。对于Java与wasm之间的转换还有很长的路要走。

以上文章还有很多不足之处,仍在学习中,望指点,共同进步~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值