wasm gc: improve performance of JS interop

This commit is contained in:
Alexey Andreev 2024-10-04 15:38:14 +02:00
parent 3218a00eb9
commit 753a028fc9
4 changed files with 98 additions and 29 deletions

View File

@ -17,8 +17,14 @@
var TeaVM = TeaVM || {}; var TeaVM = TeaVM || {};
TeaVM.wasm = function() { TeaVM.wasm = function() {
let exports; let exports;
let globalsCache = new Map();
let getGlobalName = function(name) { let getGlobalName = function(name) {
return eval(name); let result = globalsCache.get(name);
if (typeof result === "undefined") {
result = new Function("return " + name + ";");
globalsCache.set(name, result);
}
return result();
} }
let setGlobalName = function(name, value) { let setGlobalName = function(name, value) {
new Function("value", name + " = value;")(value); new Function("value", name + " = value;")(value);
@ -237,13 +243,20 @@ TeaVM.wasm = function() {
return fn(javaObjectSymbol, functionsSymbol, functionOriginSymbol); return fn(javaObjectSymbol, functionsSymbol, functionOriginSymbol);
}, },
defineMethod(cls, name, fn) { defineMethod(cls, name, fn) {
cls.prototype[name] = function(...args) { let params = [];
try { for (let i = 1; i < fn.length; ++i) {
return fn(this, ...args); params.push("p" + i);
} catch (e) {
rethrowJavaAsJs(e);
}
} }
let paramsAsString = params.length === 0 ? "" : params.join(", ");
cls.prototype[name] = new Function("rethrowJavaAsJs", "fn", `
return function(${paramsAsString}) {
try {
return fn(${['this', params].join(", ")});
} catch (e) {
rethrowJavaAsJs(e);
}
};
`)(rethrowJavaAsJs, fn);
}, },
defineProperty(cls, name, getFn, setFn) { defineProperty(cls, name, getFn, setFn) {
let descriptor = { let descriptor = {
@ -390,30 +403,47 @@ TeaVM.wasm = function() {
"unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) {
imports.teavmJso[name] = identity; imports.teavmJso[name] = identity;
} }
function wrapCallFromJavaToJs(call) {
try {
return call();
} catch (e) {
rethrowJsAsJava(e);
}
}
let argumentList = [];
for (let i = 0; i < 32; ++i) { for (let i = 0; i < 32; ++i) {
imports.teavmJso["createFunction" + i] = (...args) => new Function(...args); let args = argumentList.length === 0 ? "" : argumentList.join(", ");
imports.teavmJso["callFunction" + i] = (fn, ...args) => { let argsAndBody = [...argumentList, "body"].join(", ");
imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body", `
return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);
`).bind(null, wrapCallFromJavaToJs);
imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList, `
try { try {
return fn(...args); return fn(${args});
} catch (e) { } catch (e) {
rethrowJsAsJava(e); rethrowJsAsJava(e);
} }
}; `).bind(null, rethrowJsAsJava);
imports.teavmJso["callMethod" + i] = (instance, method, ...args) => { imports.teavmJso["callMethod" + i] = new Function("rethrowJsAsJava", "getGlobalName", "instance",
"method", ...argumentList, `
try { try {
return instance !== null ? instance[method](...args) : getGlobalName(method)(...args); return instance !== null
? instance[method](${args})
: getGlobalName(method)(${args});
} catch (e) {
rethrowJsAsJava(e);
}`).bind(null, rethrowJsAsJava, getGlobalName);
imports.teavmJso["construct" + i] = new Function("rethrowJsAsJava", "constructor", ...argumentList, `
try {
return new constructor(${args});
} catch (e) { } catch (e) {
rethrowJsAsJava(e); rethrowJsAsJava(e);
} }
} `).bind(null, rethrowJsAsJava);
imports.teavmJso["construct" + i] = (constructor, ...args) => { imports.teavmJso["arrayOf" + i] = new Function(...argumentList, "return [" + args + "]");
try {
return new constructor(...args); let param = "p" + (i + 1);
} catch (e) { argumentList.push(param);
rethrowJsAsJava(e);
}
}
imports.teavmJso["arrayOf" + i] = (...args) => args
} }
} }

View File

@ -17,6 +17,8 @@ package org.teavm.jso.impl.wasmgc;
import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.JS_TO_STRING; import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.JS_TO_STRING;
import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS; import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS;
import org.teavm.ast.ConstantExpr;
import org.teavm.ast.Expr;
import org.teavm.ast.InvocationExpr; import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext;
@ -37,21 +39,23 @@ import org.teavm.model.ValueType;
class WasmGCJSIntrinsic implements WasmGCIntrinsic { class WasmGCJSIntrinsic implements WasmGCIntrinsic {
private WasmFunction globalFunction; private WasmFunction globalFunction;
private WasmGCJsoCommonGenerator commonGen;
WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen) {
this.commonGen = commonGen;
}
@Override @Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
switch (invocation.getMethod().getName()) { switch (invocation.getMethod().getName()) {
case "wrap": { case "wrap":
var function = context.functions().forStaticMethod(STRING_TO_JS); return wrapString(invocation.getArguments().get(0), context);
return new WasmCall(function, context.generate(invocation.getArguments().get(0)));
}
case "unwrapString": { case "unwrapString": {
var function = context.functions().forStaticMethod(JS_TO_STRING); var function = context.functions().forStaticMethod(JS_TO_STRING);
return new WasmCall(function, context.generate(invocation.getArguments().get(0))); return new WasmCall(function, context.generate(invocation.getArguments().get(0)));
} }
case "global": { case "global": {
var stringToJs = context.functions().forStaticMethod(STRING_TO_JS); var name = wrapString(invocation.getArguments().get(0), context);
var name = new WasmCall(stringToJs, context.generate(invocation.getArguments().get(0)));
return new WasmCall(getGlobalFunction(context), name); return new WasmCall(getGlobalFunction(context), name);
} }
case "throwCCEIfFalse": case "throwCCEIfFalse":
@ -65,6 +69,17 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic {
} }
} }
private WasmExpression wrapString(Expr stringExpr, WasmGCIntrinsicContext context) {
if (stringExpr instanceof ConstantExpr) {
var constantExpr = (ConstantExpr) stringExpr;
if (constantExpr.getValue() instanceof String) {
return commonGen.jsStringConstant(WasmGCJsoContext.wrap(context), (String) constantExpr.getValue());
}
}
var function = context.functions().forStaticMethod(STRING_TO_JS);
return new WasmCall(function, context.generate(stringExpr));
}
private WasmFunction getGlobalFunction(WasmGCIntrinsicContext context) { private WasmFunction getGlobalFunction(WasmGCIntrinsicContext context) {
if (globalFunction == null) { if (globalFunction == null) {
globalFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, globalFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN,

View File

@ -36,7 +36,7 @@ public final class WasmGCJso {
wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen)); wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen));
wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(commonGen)); wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(commonGen));
var jsIntrinsic = new WasmGCJSIntrinsic(); var jsIntrinsic = new WasmGCJSIntrinsic(commonGen);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class), wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class),
jsIntrinsic); jsIntrinsic);

View File

@ -17,9 +17,12 @@ package org.teavm.jso.impl.wasmgc;
import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS; import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.teavm.backend.javascript.rendering.AstWriter; import org.teavm.backend.javascript.rendering.AstWriter;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmGlobal; import org.teavm.backend.wasm.model.WasmGlobal;
import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmLocal;
@ -46,6 +49,7 @@ class WasmGCJsoCommonGenerator {
private boolean initialized; private boolean initialized;
private List<Consumer<WasmFunction>> initializerParts = new ArrayList<>(); private List<Consumer<WasmFunction>> initializerParts = new ArrayList<>();
private boolean rethrowExported; private boolean rethrowExported;
private Map<String, WasmGlobal> stringsConstants = new HashMap<>();
WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) { WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) {
this.jsFunctions = jsFunctions; this.jsFunctions = jsFunctions;
@ -159,4 +163,24 @@ class WasmGCJsoCommonGenerator {
throwExpr.getArguments().add(asThrowable); throwExpr.getArguments().add(asThrowable);
fn.getBody().add(throwExpr); fn.getBody().add(throwExpr);
} }
WasmExpression jsStringConstant(WasmGCJsoContext context, String str) {
var global = stringsConstants.computeIfAbsent(str, s -> {
var javaGlobal = context.strings().getStringConstant(s).global;
var function = context.functions().forStaticMethod(STRING_TO_JS);
var index = stringsConstants.size();
var brief = str.length() > 16 ? str.substring(0, 16) : str;
var name = context.names().topLevel("teavm.js.strings<" + index + ">:"
+ WasmGCNameProvider.sanitize(brief));
var jsGlobal = new WasmGlobal(name, WasmType.Reference.EXTERN,
new WasmNullConstant(WasmType.Reference.EXTERN));
context.module().globals.add(jsGlobal);
addInitializerPart(context, initializer -> {
var call = new WasmCall(function, new WasmGetGlobal(javaGlobal));
initializer.getBody().add(new WasmSetGlobal(jsGlobal, call));
});
return jsGlobal;
});
return new WasmGetGlobal(global);
}
} }