前言
目前将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之间的转换还有很长的路要走。
以上文章还有很多不足之处,仍在学习中,望指点,共同进步~