diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 54b30b729..76e7c5dff 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -16,6 +16,8 @@ package org.teavm.backend.wasm; import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -55,6 +57,7 @@ import org.teavm.backend.wasm.runtime.StringInternPool; import org.teavm.backend.wasm.transformation.gc.BaseClassesTransformation; import org.teavm.backend.wasm.transformation.gc.ClassLoaderResourceTransformation; import org.teavm.backend.wasm.transformation.gc.EntryPointTransformation; +import org.teavm.backend.wasm.transformation.gc.ReferenceQueueTransformation; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; import org.teavm.interop.Platforms; @@ -176,6 +179,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { return List.of( new BaseClassesTransformation(), new ClassLoaderResourceTransformation(), + new ReferenceQueueTransformation(), entryPointTransformation ); } @@ -267,6 +271,12 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { exceptionMessageFunction.setExportName("teavm.exceptionMessage"); } + var refQueueSupplyRef = new MethodReference(ReferenceQueue.class, "supply", Reference.class, void.class); + if (controller.getDependencyInfo().getMethod(refQueueSupplyRef) != null) { + var refQueueSupplyFunction = declarationsGenerator.functions().forInstanceMethod(refQueueSupplyRef); + refQueueSupplyFunction.setExportName("teavm.reportGarbageCollectedValue"); + } + moduleGenerator.generate(); customGenerators.contributeToModule(module); adjustModuleMemory(module); 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 index aea8c1cd3..3ef661fa3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCReferenceQueueDependency.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCReferenceQueueDependency.java @@ -15,13 +15,19 @@ */ package org.teavm.backend.wasm.gc; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; +import org.teavm.model.MethodReference; public class WasmGCReferenceQueueDependency extends AbstractDependencyListener { private DependencyNode valueNode; + private boolean refQueuePassedToRef; + private boolean refQueuePoll; @Override public void started(DependencyAgent agent) { @@ -33,12 +39,30 @@ public class WasmGCReferenceQueueDependency extends AbstractDependencyListener { if (method.getMethod().getOwnerName().equals("java.lang.ref.WeakReference")) { switch (method.getMethod().getName()) { case "": + if (method.getMethod().parameterCount() == 2) { + refQueuePassedToRef = true; + checkRefQueue(agent); + } method.getVariable(1).connect(valueNode); break; case "get": valueNode.connect(method.getResult()); break; } + } else if (method.getMethod().getOwnerName().equals(ReferenceQueue.class.getName())) { + if (method.getMethod().getName().equals("poll")) { + refQueuePoll = true; + checkRefQueue(agent); + } + } + } + + private void checkRefQueue(DependencyAgent agent) { + if (refQueuePassedToRef && refQueuePoll) { + agent.linkMethod(new MethodReference(ReferenceQueue.class, "supply", Reference.class, void.class)) + .propagate(0, ReferenceQueue.class) + .propagate(1, WeakReference.class) + .use(); } } } 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 index 4de5b22ab..cad44d5de 100644 --- 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 @@ -61,7 +61,8 @@ public class WeakReferenceGenerator implements WasmGCCustomGenerator { function.add(queueLocal); var weakRefConstructor = getCreateWeakReferenceFunction(context); - var weakRef = new WasmCall(weakRefConstructor, new WasmGetLocal(valueLocal), new WasmGetLocal(thisLocal)); + var weakRef = new WasmCall(weakRefConstructor, new WasmGetLocal(valueLocal), new WasmGetLocal(thisLocal), + new WasmGetLocal(queueLocal)); function.getBody().add(new WasmStructSet(weakRefStruct, new WasmGetLocal(thisLocal), WasmGCClassInfoProvider.WEAK_REFERENCE_OFFSET, weakRef)); } @@ -97,7 +98,8 @@ public class WeakReferenceGenerator implements WasmGCCustomGenerator { var function = new WasmFunction(context.functionTypes().of( WasmType.Reference.EXTERN, context.typeMapper().mapType(ValueType.parse(Object.class)), - context.typeMapper().mapType(ValueType.parse(WeakReference.class)) + context.typeMapper().mapType(ValueType.parse(WeakReference.class)), + context.typeMapper().mapType(ValueType.parse(ReferenceQueue.class)) )); function.setName(context.names().topLevel("teavm@createWeakReference")); function.setImportName("createWeakRef"); diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueEntry.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueEntry.java new file mode 100644 index 000000000..e1c0f9b6b --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueEntry.java @@ -0,0 +1,27 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * 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.transformation.gc; + +import java.lang.ref.Reference; + +class ReferenceQueueEntry { + final Reference reference; + ReferenceQueueEntry next; + + ReferenceQueueEntry(Reference reference) { + this.reference = reference; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueTemplate.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueTemplate.java new file mode 100644 index 000000000..2a912ae24 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueTemplate.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * 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.transformation.gc; + +import java.lang.ref.Reference; + +class ReferenceQueueTemplate { + private ReferenceQueueEntry start; + private ReferenceQueueEntry end; + + public Reference poll() { + var result = start; + if (result == null) { + return null; + } + start = result.next; + if (start == null) { + end = null; + } + return result.reference; + } + + public void supply(Reference reference) { + var entry = new ReferenceQueueEntry<>(reference); + if (start == null) { + start = entry; + } else { + end.next = entry; + } + end = entry; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueTransformation.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueTransformation.java new file mode 100644 index 000000000..7e01ac95c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ReferenceQueueTransformation.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * 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.transformation.gc; + +import java.lang.ref.ReferenceQueue; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.util.ModelUtils; +import org.teavm.model.util.ProgramUtils; + +public class ReferenceQueueTransformation implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (cls.getName().equals(ReferenceQueue.class.getName())) { + transformReferenceQueue(cls, context); + } + } + + private void transformReferenceQueue(ClassHolder cls, ClassHolderTransformerContext context) { + var templateClass = context.getHierarchy().getClassSource().get(ReferenceQueueTemplate.class.getName()); + for (var method : templateClass.getMethods()) { + if (!method.getName().equals("")) { + copyMethod(cls, method); + } + } + for (var field : templateClass.getFields()) { + cls.addField(ModelUtils.copyField(field)); + } + } + + private void copyMethod(ClassHolder cls, MethodReader method) { + var targetMethod = cls.getMethod(method.getDescriptor()); + if (targetMethod == null) { + targetMethod = new MethodHolder(method.getDescriptor()); + cls.addMethod(targetMethod); + targetMethod.getModifiers().addAll(method.readModifiers()); + targetMethod.setLevel(method.getLevel()); + } + + var targetProgram = ProgramUtils.copy(method.getProgram()); + targetMethod.setProgram(targetProgram); + for (var block : targetProgram.getBasicBlocks()) { + for (var instruction : block) { + if (instruction instanceof GetFieldInstruction) { + var getField = (GetFieldInstruction) instruction; + getField.setField(mapField(getField.getField())); + } else if (instruction instanceof PutFieldInstruction) { + var putField = (PutFieldInstruction) instruction; + putField.setField(mapField(putField.getField())); + } + } + } + } + + private FieldReference mapField(FieldReference field) { + if (field.getClassName().equals(ReferenceQueueTemplate.class.getName())) { + return new FieldReference(ReferenceQueue.class.getName(), field.getFieldName()); + } + return field; + } +} diff --git a/core/src/main/js/wasm-gc-runtime/runtime.js b/core/src/main/js/wasm-gc-runtime/runtime.js index 40f287f4c..398a0c95c 100644 --- a/core/src/main/js/wasm-gc-runtime/runtime.js +++ b/core/src/main/js/wasm-gc-runtime/runtime.js @@ -124,8 +124,8 @@ function consoleImports(imports) { function coreImports(imports, context) { let finalizationRegistry = new FinalizationRegistry(heldValue => { let report = context.exports["teavm.reportGarbageCollectedValue"]; - if (typeof report === "function") { - report(heldValue) + if (typeof report !== "undefined") { + report(heldValue.queue, heldValue.ref); } }); let stringFinalizationRegistry = new FinalizationRegistry(heldValue => { @@ -135,18 +135,16 @@ function coreImports(imports, context) { } }); imports.teavm = { - createWeakRef(value, heldValue) { - let weakRef = new WeakRef(value); - if (heldValue !== null) { - finalizationRegistry.register(value, heldValue) + createWeakRef(value, ref, queue) { + if (queue !== null) { + finalizationRegistry.register(value, { ref: ref, queue: queue }); } - return weakRef; + return new WeakRef(value); }, deref: weakRef => weakRef.deref(), createStringWeakRef(value, heldValue) { - let weakRef = new WeakRef(value); stringFinalizationRegistry.register(value, heldValue) - return weakRef; + return new WeakRef(value); }, stringDeref: weakRef => weakRef.deref(), takeStackTrace() {