From 753a028fc9111c93ee87181d5c284e0f6be14704 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 4 Oct 2024 15:38:14 +0200 Subject: [PATCH] wasm gc: improve performance of JS interop --- .../org/teavm/backend/wasm/wasm-gc-runtime.js | 74 +++++++++++++------ .../jso/impl/wasmgc/WasmGCJSIntrinsic.java | 27 +++++-- .../org/teavm/jso/impl/wasmgc/WasmGCJso.java | 2 +- .../impl/wasmgc/WasmGCJsoCommonGenerator.java | 24 ++++++ 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js index 38300a970..798cd57f4 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js @@ -17,8 +17,14 @@ var TeaVM = TeaVM || {}; TeaVM.wasm = function() { let exports; + let globalsCache = new Map(); 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) { new Function("value", name + " = value;")(value); @@ -237,13 +243,20 @@ TeaVM.wasm = function() { return fn(javaObjectSymbol, functionsSymbol, functionOriginSymbol); }, defineMethod(cls, name, fn) { - cls.prototype[name] = function(...args) { - try { - return fn(this, ...args); - } catch (e) { - rethrowJavaAsJs(e); - } + let params = []; + for (let i = 1; i < fn.length; ++i) { + params.push("p" + i); } + 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) { let descriptor = { @@ -390,30 +403,47 @@ TeaVM.wasm = function() { "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { imports.teavmJso[name] = identity; } + function wrapCallFromJavaToJs(call) { + try { + return call(); + } catch (e) { + rethrowJsAsJava(e); + } + } + let argumentList = []; for (let i = 0; i < 32; ++i) { - imports.teavmJso["createFunction" + i] = (...args) => new Function(...args); - imports.teavmJso["callFunction" + i] = (fn, ...args) => { + let args = argumentList.length === 0 ? "" : argumentList.join(", "); + 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 { - return fn(...args); + return fn(${args}); } catch (e) { rethrowJsAsJava(e); } - }; - imports.teavmJso["callMethod" + i] = (instance, method, ...args) => { + `).bind(null, rethrowJsAsJava); + imports.teavmJso["callMethod" + i] = new Function("rethrowJsAsJava", "getGlobalName", "instance", + "method", ...argumentList, ` 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) { rethrowJsAsJava(e); } - } - imports.teavmJso["construct" + i] = (constructor, ...args) => { - try { - return new constructor(...args); - } catch (e) { - rethrowJsAsJava(e); - } - } - imports.teavmJso["arrayOf" + i] = (...args) => args + `).bind(null, rethrowJsAsJava); + imports.teavmJso["arrayOf" + i] = new Function(...argumentList, "return [" + args + "]"); + + let param = "p" + (i + 1); + argumentList.push(param); } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java index 26b78f5d0..8c91fa433 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java @@ -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.STRING_TO_JS; +import org.teavm.ast.ConstantExpr; +import org.teavm.ast.Expr; import org.teavm.ast.InvocationExpr; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; @@ -37,21 +39,23 @@ import org.teavm.model.ValueType; class WasmGCJSIntrinsic implements WasmGCIntrinsic { private WasmFunction globalFunction; + private WasmGCJsoCommonGenerator commonGen; + + WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen) { + this.commonGen = commonGen; + } @Override public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { switch (invocation.getMethod().getName()) { - case "wrap": { - var function = context.functions().forStaticMethod(STRING_TO_JS); - return new WasmCall(function, context.generate(invocation.getArguments().get(0))); - } + case "wrap": + return wrapString(invocation.getArguments().get(0), context); case "unwrapString": { var function = context.functions().forStaticMethod(JS_TO_STRING); return new WasmCall(function, context.generate(invocation.getArguments().get(0))); } case "global": { - var stringToJs = context.functions().forStaticMethod(STRING_TO_JS); - var name = new WasmCall(stringToJs, context.generate(invocation.getArguments().get(0))); + var name = wrapString(invocation.getArguments().get(0), context); return new WasmCall(getGlobalFunction(context), name); } 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) { if (globalFunction == null) { globalFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java index 4e721d70c..8b1ff49bc 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java @@ -36,7 +36,7 @@ public final class WasmGCJso { wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, 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, "unwrapString", JSObject.class, String.class), jsIntrinsic); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java index e6fd4b064..421ecf8c9 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java @@ -17,9 +17,12 @@ package org.teavm.jso.impl.wasmgc; import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; 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.WasmGlobal; import org.teavm.backend.wasm.model.WasmLocal; @@ -46,6 +49,7 @@ class WasmGCJsoCommonGenerator { private boolean initialized; private List> initializerParts = new ArrayList<>(); private boolean rethrowExported; + private Map stringsConstants = new HashMap<>(); WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) { this.jsFunctions = jsFunctions; @@ -159,4 +163,24 @@ class WasmGCJsoCommonGenerator { throwExpr.getArguments().add(asThrowable); 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); + } }