From 8ed8322b17c5ec6cb958f07d12d55f537b78fc03 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 20 Sep 2024 15:03:43 +0200 Subject: [PATCH] wasm gc: basic implementation of WeakReference --- .../backend/wasm/gc/WasmGCDependencies.java | 1 + .../gc/WasmGCReferenceQueueDependency.java | 44 +++++++ .../gc/classes/WasmGCClassGenerator.java | 31 +++-- .../gc/classes/WasmGCClassInfoProvider.java | 1 + .../generators/gc/WasmGCCustomGenerators.java | 11 ++ .../generators/gc/WeakReferenceGenerator.java | 121 ++++++++++++++++++ .../gc/BaseClassesTransformation.java | 15 +++ .../org/teavm/backend/wasm/wasm-gc-runtime.js | 20 ++- .../teavm/classlib/java/lang/StringTest.java | 2 +- .../classlib/java/util/WeakHashMapTest.java | 2 +- 10 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/WasmGCReferenceQueueDependency.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/generators/gc/WeakReferenceGenerator.java diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java index 68ae28a47..16af962af 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java @@ -35,6 +35,7 @@ public class WasmGCDependencies { contributeMathUtils(); contributeExceptionUtils(); contributeInitializerUtils(); + analyzer.addDependencyListener(new WasmGCReferenceQueueDependency()); } public void contributeStandardExports() { diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCReferenceQueueDependency.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCReferenceQueueDependency.java new file mode 100644 index 000000000..aea8c1cd3 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCReferenceQueueDependency.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 konsoletyper. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.gc; + +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; +import org.teavm.dependency.MethodDependency; + +public class WasmGCReferenceQueueDependency extends AbstractDependencyListener { + private DependencyNode valueNode; + + @Override + public void started(DependencyAgent agent) { + valueNode = agent.createNode(); + } + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getMethod().getOwnerName().equals("java.lang.ref.WeakReference")) { + switch (method.getMethod().getName()) { + case "": + method.getVariable(1).connect(valueNode); + break; + case "get": + valueNode.connect(method.getResult()); + break; + } + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index 1c5b59384..95e238f02 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -1118,20 +1118,25 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit if (classReader.getParent() != null) { fillClassFields(fields, classReader.getParent()); } - for (var field : classReader.getFields()) { - if (className.equals("java.lang.Object") && field.getName().equals("monitor")) { - continue; + if (className.equals("java.lang.ref.WeakReference")) { + var field = new WasmField(WasmType.Reference.EXTERN.asStorage(), "nativeRef"); + fields.add(field); + } else { + for (var field : classReader.getFields()) { + if (className.equals("java.lang.Object") && field.getName().equals("monitor")) { + continue; + } + if (className.equals("java.lang.Class") && field.getName().equals("platformClass")) { + continue; + } + if (field.hasModifier(ElementModifier.STATIC)) { + continue; + } + fieldIndexes.putIfAbsent(field.getReference(), fields.size()); + var wasmField = new WasmField(typeMapper.mapStorageType(field.getType()), + names.forMemberField(field.getReference())); + fields.add(wasmField); } - if (className.equals("java.lang.Class") && field.getName().equals("platformClass")) { - continue; - } - if (field.hasModifier(ElementModifier.STATIC)) { - continue; - } - fieldIndexes.putIfAbsent(field.getReference(), fields.size()); - var wasmField = new WasmField(typeMapper.mapStorageType(field.getType()), - names.forMemberField(field.getReference())); - fields.add(wasmField); } if (className.equals("java.lang.Class")) { var cls = classSource.get("java.lang.Class"); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java index c3b4257e1..2e4dbd676 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java @@ -26,6 +26,7 @@ public interface WasmGCClassInfoProvider { int MONITOR_FIELD_OFFSET = 1; int CUSTOM_FIELD_OFFSETS = 2; int ARRAY_DATA_FIELD_OFFSET = 2; + int WEAK_REFERENCE_OFFSET = 2; WasmGCClassInfo getClassInfo(ValueType type); diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java index 8ef4bb8d1..1a6785dc8 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java @@ -15,6 +15,8 @@ */ package org.teavm.backend.wasm.generators.gc; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.util.HashMap; import java.util.List; @@ -41,6 +43,7 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider { fillStringPool(); fillSystem(); fillArray(); + fillWeakReference(); for (var entry : generators.entrySet()) { add(entry.getKey(), entry.getValue()); } @@ -68,6 +71,14 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider { add(new MethodReference(Array.class, "newInstanceImpl", Class.class, int.class, Object.class), arrayGenerator); } + private void fillWeakReference() { + var generator = new WeakReferenceGenerator(); + add(new MethodReference(WeakReference.class, "", Object.class, ReferenceQueue.class, + void.class), generator); + add(new MethodReference(WeakReference.class, "get", Object.class), generator); + add(new MethodReference(WeakReference.class, "clear", void.class), generator); + } + @Override public WasmGCCustomGenerator get(MethodReference method) { var result = generators.get(method); diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WeakReferenceGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WeakReferenceGenerator.java new file mode 100644 index 000000000..4de5b22ab --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WeakReferenceGenerator.java @@ -0,0 +1,121 @@ +/* + * Copyright 2024 konsoletyper. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.generators.gc; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmBlock; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmGetLocal; +import org.teavm.backend.wasm.model.expression.WasmNullBranch; +import org.teavm.backend.wasm.model.expression.WasmNullCondition; +import org.teavm.backend.wasm.model.expression.WasmNullConstant; +import org.teavm.backend.wasm.model.expression.WasmReturn; +import org.teavm.backend.wasm.model.expression.WasmStructGet; +import org.teavm.backend.wasm.model.expression.WasmStructSet; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public class WeakReferenceGenerator implements WasmGCCustomGenerator { + private WasmFunction createWeakReferenceFunction; + + @Override + public void apply(MethodReference method, WasmFunction function, WasmGCCustomGeneratorContext context) { + switch (method.getName()) { + case "": + generateConstructor(context, function); + break; + case "get": + generateDeref(context, function); + break; + case "clear": + generateClear(context, function); + break; + } + } + + private void generateConstructor(WasmGCCustomGeneratorContext context, WasmFunction function) { + var weakRefStruct = context.classInfoProvider().getClassInfo(WeakReference.class.getName()).getStructure(); + var thisLocal = new WasmLocal(weakRefStruct.getReference(), "this"); + var valueLocal = new WasmLocal(context.typeMapper().mapType(ValueType.parse(Object.class)), "value"); + var queueLocal = new WasmLocal(context.typeMapper().mapType(ValueType.parse(ReferenceQueue.class)), "queue"); + function.add(thisLocal); + function.add(valueLocal); + function.add(queueLocal); + + var weakRefConstructor = getCreateWeakReferenceFunction(context); + var weakRef = new WasmCall(weakRefConstructor, new WasmGetLocal(valueLocal), new WasmGetLocal(thisLocal)); + function.getBody().add(new WasmStructSet(weakRefStruct, new WasmGetLocal(thisLocal), + WasmGCClassInfoProvider.WEAK_REFERENCE_OFFSET, weakRef)); + } + + private void generateDeref(WasmGCCustomGeneratorContext context, WasmFunction function) { + var weakRefStruct = context.classInfoProvider().getClassInfo(WeakReference.class.getName()).getStructure(); + var objectType = context.classInfoProvider().getClassInfo("java.lang.Object").getType(); + var thisLocal = new WasmLocal(weakRefStruct.getReference(), "this"); + function.add(thisLocal); + + var block = new WasmBlock(false); + block.setType(WasmType.Reference.EXTERN); + var weakRef = new WasmStructGet(weakRefStruct, new WasmGetLocal(thisLocal), + WasmGCClassInfoProvider.WEAK_REFERENCE_OFFSET); + var br = new WasmNullBranch(WasmNullCondition.NOT_NULL, weakRef, block); + block.getBody().add(br); + block.getBody().add(new WasmReturn(new WasmNullConstant(objectType))); + + function.getBody().add(new WasmCall(createDerefFunction(context), block)); + } + + private void generateClear(WasmGCCustomGeneratorContext context, WasmFunction function) { + var weakRefStruct = context.classInfoProvider().getClassInfo(WeakReference.class.getName()).getStructure(); + var thisLocal = new WasmLocal(weakRefStruct.getReference(), "this"); + function.add(thisLocal); + + function.getBody().add(new WasmStructSet(weakRefStruct, new WasmGetLocal(thisLocal), + WasmGCClassInfoProvider.WEAK_REFERENCE_OFFSET, new WasmNullConstant(WasmType.Reference.EXTERN))); + } + + private WasmFunction getCreateWeakReferenceFunction(WasmGCCustomGeneratorContext context) { + if (createWeakReferenceFunction == null) { + var function = new WasmFunction(context.functionTypes().of( + WasmType.Reference.EXTERN, + context.typeMapper().mapType(ValueType.parse(Object.class)), + context.typeMapper().mapType(ValueType.parse(WeakReference.class)) + )); + function.setName(context.names().topLevel("teavm@createWeakReference")); + function.setImportName("createWeakRef"); + function.setImportModule("teavm"); + context.module().functions.add(function); + createWeakReferenceFunction = function; + } + return createWeakReferenceFunction; + } + + private WasmFunction createDerefFunction(WasmGCCustomGeneratorContext context) { + var function = new WasmFunction(context.functionTypes().of( + context.typeMapper().mapType(ValueType.parse(Object.class)), + WasmType.Reference.EXTERN)); + function.setName(context.names().topLevel("teavm@deref")); + function.setImportName("deref"); + function.setImportModule("teavm"); + context.module().functions.add(function); + return function; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java index 0a7b48bd8..1a9fd4e05 100644 --- a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/BaseClassesTransformation.java @@ -15,6 +15,7 @@ */ package org.teavm.backend.wasm.transformation.gc; +import java.lang.ref.ReferenceQueue; import org.teavm.interop.Import; import org.teavm.model.AccessLevel; import org.teavm.model.AnnotationHolder; @@ -24,6 +25,7 @@ import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ElementModifier; import org.teavm.model.FieldReference; +import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReference; import org.teavm.model.Program; @@ -95,6 +97,19 @@ public class BaseClassesTransformation implements ClassHolderTransformer { } } } + } else if (cls.getName().equals("java.lang.ref.WeakReference")) { + var constructor = cls.getMethod(new MethodDescriptor("", Object.class, ReferenceQueue.class, + void.class)); + constructor.getModifiers().add(ElementModifier.NATIVE); + constructor.setProgram(null); + + var get = cls.getMethod(new MethodDescriptor("get", Object.class)); + get.getModifiers().add(ElementModifier.NATIVE); + get.setProgram(null); + + var clear = cls.getMethod(new MethodDescriptor("clear", void.class)); + clear.getModifiers().add(ElementModifier.NATIVE); + clear.setProgram(null); } } 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 bea922bd8..c39c36334 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 @@ -16,10 +16,15 @@ var TeaVM = TeaVM || {}; TeaVM.wasm = function() { + let exports; function defaults(imports) { let stderr = ""; let stdout = ""; - let exports; + let finalizationRegistry = new FinalizationRegistry(heldValue => { + if (typeof exports.reportGarbageCollectedValue === "function") { + exports.reportGarbageCollectedValue(heldValue) + } + }); imports.teavm = { putcharStderr(c) { if (c === 10) { @@ -40,8 +45,18 @@ TeaVM.wasm = function() { currentTimeMillis() { return new Date().getTime(); }, - dateToString(timestamp, controller) { + dateToString(timestamp) { return stringToJava(new Date(timestamp).toString()); + }, + createWeakRef(value, heldValue) { + let weakRef = new WeakRef(value); + if (heldValue !== null) { + finalizationRegistry.register(value, heldValue) + } + return weakRef; + }, + deref(weakRef) { + return weakRef.deref(); } }; imports.teavmMath = Math; @@ -83,7 +98,6 @@ TeaVM.wasm = function() { exports = instance.exports; let javaArgs = exports.createStringArray(args.length); for (let i = 0; i < args.length; ++i) { - let arg = args[i]; exports.setToStringArray(javaArgs, i, stringToJava(args[i])); } try { diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/StringTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/StringTest.java index 045821b7d..a797bbbf6 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/StringTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/StringTest.java @@ -322,7 +322,7 @@ public class StringTest { private String turkish = "istanbul"; @Test - @SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) + @SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI, TestPlatform.WEBASSEMBLY_GC }) public void convertsCaseLocaled() { assertEquals(turkish, "İstanbul".toLowerCase(new Locale("tr", "TR"))); assertEquals(common, "İstanbul".toLowerCase(Locale.US)); diff --git a/tests/src/test/java/org/teavm/classlib/java/util/WeakHashMapTest.java b/tests/src/test/java/org/teavm/classlib/java/util/WeakHashMapTest.java index bbe8c09af..7b724b055 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/WeakHashMapTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/WeakHashMapTest.java @@ -51,7 +51,7 @@ import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) -@SkipPlatform({TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) +@SkipPlatform({TestPlatform.WEBASSEMBLY, TestPlatform.WASI, TestPlatform.WEBASSEMBLY_GC}) public class WeakHashMapTest { static class MockMap extends AbstractMap { @Override