From 4b76396332b881e842453c9ac988afb798c2ca34 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 1 Oct 2024 14:43:53 +0200 Subject: [PATCH] wasm gc: basic support for functors in JSO --- .../org/teavm/backend/wasm/wasm-gc-runtime.js | 14 ++++++++++- .../src/main/java/org/teavm/jso/impl/JS.java | 1 + .../jso/impl/JSObjectClassTransformer.java | 23 +++++++++++++++---- .../java/org/teavm/jso/test/FunctorTest.java | 5 +++- 4 files changed, 37 insertions(+), 6 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 d1445fb70..7e71d7bcf 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 @@ -22,6 +22,7 @@ TeaVM.wasm = function() { } let javaObjectSymbol = Symbol("javaObject"); let functionsSymbol = Symbol("functions"); + let functionOriginSymbol = Symbol("functionOrigin"); let javaWrappers = new WeakMap(); function defaults(imports) { let stderr = ""; @@ -135,7 +136,7 @@ TeaVM.wasm = function() { this[functionsSymbol] = null; };` ); - return fn(javaObjectSymbol, functionsSymbol); + return fn(javaObjectSymbol, functionsSymbol, functionOriginSymbol); }, defineMethod(cls, name, fn) { cls.prototype[name] = function(...args) { @@ -181,9 +182,20 @@ TeaVM.wasm = function() { result = function() { return instance[propertyName].apply(instance, arguments); } + result[functionOriginSymbol] = instance; functions[propertyName] = result; } return result; + }, + functionAsObject(fn, property) { + let origin = fn[functionOriginSymbol]; + if (typeof origin !== 'undefined') { + let functions = origin[functionsSymbol]; + if (functions !== void 0 && functions[property] === fn) { + return origin; + } + } + return { [property]: fn }; } }; for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte", diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index 4b5e77fc2..20f49aef7 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -743,6 +743,7 @@ public final class JS { @GeneratedBy(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "functionAsObject", module = "teavmJso") public static native JSObject functionAsObject(JSObject instance, JSObject property); @InjectedBy(JSNativeInjector.class) diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index f2d194332..fe83f0b05 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -87,7 +87,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { processor.setClassFilter(classFilter); } processor.processClass(cls); - if (typeHelper.isJavaScriptClass(cls.getName())) { + if (isJavaScriptClass(cls)) { processor.processMemberMethods(cls); } @@ -122,17 +122,17 @@ class JSObjectClassTransformer implements ClassHolderTransformer { exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod); exportStaticMethods(cls, context.getDiagnostics()); - if (typeHelper.isJavaScriptImplementation(cls.getName()) || !exposedClass.methods.isEmpty()) { + if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) { cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName())); } - if (wasmGC && (!exposedClass.methods.isEmpty() || typeHelper.isJavaScriptClass(cls.getName()))) { + if (wasmGC && (!exposedClass.methods.isEmpty() || isJavaScriptClass(cls))) { var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS); createWrapperMethod.setLevel(AccessLevel.PUBLIC); createWrapperMethod.getModifiers().add(ElementModifier.NATIVE); cls.addMethod(createWrapperMethod); - if (typeHelper.isJavaScriptImplementation(cls.getName())) { + if (isJavaScriptImplementation(cls)) { cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE); } } @@ -387,6 +387,21 @@ class JSObjectClassTransformer implements ClassHolderTransformer { return false; } + private boolean isJavaScriptImplementation(ClassReader cls) { + if (typeHelper.isJavaScriptImplementation(cls.getName())) { + return true; + } + if (cls.getAnnotations().get(JSClass.class.getName()) != null) { + return false; + } + if (cls.getParent() != null) { + if (typeHelper.isJavaScriptClass(cls.getParent())) { + return true; + } + } + return cls.getInterfaces().stream().anyMatch(typeHelper::isJavaScriptClass); + } + private boolean addInterfaces(ExposedClass exposedCls, ClassReader cls) { boolean added = false; for (String ifaceName : cls.getInterfaces()) { diff --git a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java index c3fd76a93..4b59fe9eb 100644 --- a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java +++ b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java @@ -26,12 +26,13 @@ import org.teavm.jso.JSProperty; import org.teavm.junit.EachTestCompiledSeparately; import org.teavm.junit.OnlyPlatform; import org.teavm.junit.SkipJVM; +import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class FunctorTest { @Test @@ -45,6 +46,7 @@ public class FunctorTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void functorIdentityPreserved() { JSBiFunction javaFunction = (a, b) -> a + b; JSObject firstRef = getFunction(javaFunction); @@ -89,6 +91,7 @@ public class FunctorTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void castToFunctor() { JSBiFunction f = getBiFunctionAsObject().cast(); assertEquals(23042, f.foo(23, 42));