wasm gc: basic implementation of WeakReference

This commit is contained in:
Alexey Andreev 2024-09-20 15:03:43 +02:00
parent 28c64ef7d8
commit 8ed8322b17
10 changed files with 230 additions and 18 deletions

View File

@ -35,6 +35,7 @@ public class WasmGCDependencies {
contributeMathUtils(); contributeMathUtils();
contributeExceptionUtils(); contributeExceptionUtils();
contributeInitializerUtils(); contributeInitializerUtils();
analyzer.addDependencyListener(new WasmGCReferenceQueueDependency());
} }
public void contributeStandardExports() { public void contributeStandardExports() {

View File

@ -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 "<init>":
method.getVariable(1).connect(valueNode);
break;
case "get":
valueNode.connect(method.getResult());
break;
}
}
}
}

View File

@ -1118,20 +1118,25 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
if (classReader.getParent() != null) { if (classReader.getParent() != null) {
fillClassFields(fields, classReader.getParent()); fillClassFields(fields, classReader.getParent());
} }
for (var field : classReader.getFields()) { if (className.equals("java.lang.ref.WeakReference")) {
if (className.equals("java.lang.Object") && field.getName().equals("monitor")) { var field = new WasmField(WasmType.Reference.EXTERN.asStorage(), "nativeRef");
continue; 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")) { if (className.equals("java.lang.Class")) {
var cls = classSource.get("java.lang.Class"); var cls = classSource.get("java.lang.Class");

View File

@ -26,6 +26,7 @@ public interface WasmGCClassInfoProvider {
int MONITOR_FIELD_OFFSET = 1; int MONITOR_FIELD_OFFSET = 1;
int CUSTOM_FIELD_OFFSETS = 2; int CUSTOM_FIELD_OFFSETS = 2;
int ARRAY_DATA_FIELD_OFFSET = 2; int ARRAY_DATA_FIELD_OFFSET = 2;
int WEAK_REFERENCE_OFFSET = 2;
WasmGCClassInfo getClassInfo(ValueType type); WasmGCClassInfo getClassInfo(ValueType type);

View File

@ -15,6 +15,8 @@
*/ */
package org.teavm.backend.wasm.generators.gc; package org.teavm.backend.wasm.generators.gc;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -41,6 +43,7 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider {
fillStringPool(); fillStringPool();
fillSystem(); fillSystem();
fillArray(); fillArray();
fillWeakReference();
for (var entry : generators.entrySet()) { for (var entry : generators.entrySet()) {
add(entry.getKey(), entry.getValue()); 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); 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, "<init>", 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 @Override
public WasmGCCustomGenerator get(MethodReference method) { public WasmGCCustomGenerator get(MethodReference method) {
var result = generators.get(method); var result = generators.get(method);

View File

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

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.backend.wasm.transformation.gc; package org.teavm.backend.wasm.transformation.gc;
import java.lang.ref.ReferenceQueue;
import org.teavm.interop.Import; import org.teavm.interop.Import;
import org.teavm.model.AccessLevel; import org.teavm.model.AccessLevel;
import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationHolder;
@ -24,6 +25,7 @@ import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.Program; 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("<init>", 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);
} }
} }

View File

@ -16,10 +16,15 @@
var TeaVM = TeaVM || {}; var TeaVM = TeaVM || {};
TeaVM.wasm = function() { TeaVM.wasm = function() {
let exports;
function defaults(imports) { function defaults(imports) {
let stderr = ""; let stderr = "";
let stdout = ""; let stdout = "";
let exports; let finalizationRegistry = new FinalizationRegistry(heldValue => {
if (typeof exports.reportGarbageCollectedValue === "function") {
exports.reportGarbageCollectedValue(heldValue)
}
});
imports.teavm = { imports.teavm = {
putcharStderr(c) { putcharStderr(c) {
if (c === 10) { if (c === 10) {
@ -40,8 +45,18 @@ TeaVM.wasm = function() {
currentTimeMillis() { currentTimeMillis() {
return new Date().getTime(); return new Date().getTime();
}, },
dateToString(timestamp, controller) { dateToString(timestamp) {
return stringToJava(new Date(timestamp).toString()); 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; imports.teavmMath = Math;
@ -83,7 +98,6 @@ TeaVM.wasm = function() {
exports = instance.exports; exports = instance.exports;
let javaArgs = exports.createStringArray(args.length); let javaArgs = exports.createStringArray(args.length);
for (let i = 0; i < args.length; ++i) { for (let i = 0; i < args.length; ++i) {
let arg = args[i];
exports.setToStringArray(javaArgs, i, stringToJava(args[i])); exports.setToStringArray(javaArgs, i, stringToJava(args[i]));
} }
try { try {

View File

@ -322,7 +322,7 @@ public class StringTest {
private String turkish = "istanbul"; private String turkish = "istanbul";
@Test @Test
@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) @SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI, TestPlatform.WEBASSEMBLY_GC })
public void convertsCaseLocaled() { public void convertsCaseLocaled() {
assertEquals(turkish, "İstanbul".toLowerCase(new Locale("tr", "TR"))); assertEquals(turkish, "İstanbul".toLowerCase(new Locale("tr", "TR")));
assertEquals(common, "İstanbul".toLowerCase(Locale.US)); assertEquals(common, "İstanbul".toLowerCase(Locale.US));

View File

@ -51,7 +51,7 @@ import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.TestPlatform; import org.teavm.junit.TestPlatform;
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
@SkipPlatform({TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) @SkipPlatform({TestPlatform.WEBASSEMBLY, TestPlatform.WASI, TestPlatform.WEBASSEMBLY_GC})
public class WeakHashMapTest { public class WeakHashMapTest {
static class MockMap<K, V> extends AbstractMap<K, V> { static class MockMap<K, V> extends AbstractMap<K, V> {
@Override @Override