wasm gc: support ReferenceQueue

This commit is contained in:
Alexey Andreev 2024-10-18 19:48:48 +02:00
parent 797ceb9cd7
commit ce862b9eaa
7 changed files with 197 additions and 11 deletions

View File

@ -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);

View File

@ -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 "<init>":
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();
}
}
}

View File

@ -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");

View File

@ -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<T> {
final Reference<T> reference;
ReferenceQueueEntry<T> next;
ReferenceQueueEntry(Reference<T> reference) {
this.reference = reference;
}
}

View File

@ -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<T> {
private ReferenceQueueEntry<T> start;
private ReferenceQueueEntry<T> end;
public Reference<T> poll() {
var result = start;
if (result == null) {
return null;
}
start = result.next;
if (start == null) {
end = null;
}
return result.reference;
}
public void supply(Reference<T> reference) {
var entry = new ReferenceQueueEntry<>(reference);
if (start == null) {
start = entry;
} else {
end.next = entry;
}
end = entry;
}
}

View File

@ -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("<init>")) {
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;
}
}

View File

@ -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() {