js: support WeakReference

This commit is contained in:
Alexey Andreev 2023-08-07 16:51:02 +02:00
parent cd38447057
commit 22864c88f2
9 changed files with 454 additions and 2 deletions

View File

@ -19,7 +19,7 @@ public class TWeakReference<T> extends TReference<T> {
private T value;
public TWeakReference(T value) {
this.value = value;
this(value, null);
}
public TWeakReference(T value, @SuppressWarnings("unused") TReferenceQueue<T> queue) {

View File

@ -21,6 +21,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
@ -50,6 +53,11 @@ import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.codegen.SourceWriterBuilder;
import org.teavm.backend.javascript.decompile.PreparedClass;
import org.teavm.backend.javascript.decompile.PreparedMethod;
import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueGenerator;
import org.teavm.backend.javascript.intrinsics.ref.ReferenceQueueTransformer;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceDependencyListener;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceGenerator;
import org.teavm.backend.javascript.intrinsics.ref.WeakReferenceTransformer;
import org.teavm.backend.javascript.rendering.Renderer;
import org.teavm.backend.javascript.rendering.RenderingContext;
import org.teavm.backend.javascript.rendering.RenderingUtil;
@ -143,7 +151,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
@Override
public List<ClassHolderTransformer> getTransformers() {
return Collections.emptyList();
return List.of(
new WeakReferenceTransformer(),
new ReferenceQueueTransformer()
);
}
@Override
@ -154,6 +165,16 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
@Override
public void setController(TeaVMTargetController controller) {
this.controller = controller;
var weakRefGenerator = new WeakReferenceGenerator();
methodGenerators.put(new MethodReference(WeakReference.class, "<init>", Object.class,
ReferenceQueue.class, void.class), weakRefGenerator);
methodGenerators.put(new MethodReference(WeakReference.class, "get", Object.class), weakRefGenerator);
methodGenerators.put(new MethodReference(WeakReference.class, "clear", void.class), weakRefGenerator);
var refQueueGenerator = new ReferenceQueueGenerator();
methodGenerators.put(new MethodReference(ReferenceQueue.class, "<init>", void.class), refQueueGenerator);
methodGenerators.put(new MethodReference(ReferenceQueue.class, "poll", Reference.class), refQueueGenerator);
}
@Override
@ -321,6 +342,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
}
});
dependencyAnalyzer.addDependencyListener(new WeakReferenceDependencyListener());
}
public static void includeStackTraceMethods(DependencyAnalyzer dependencyAnalyzer) {

View File

@ -0,0 +1,58 @@
/*
* Copyright 2023 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.javascript.intrinsics.ref;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
public class ReferenceQueueGenerator implements Generator {
private static final FieldReference INNER_FIELD = new FieldReference(ReferenceQueue.class.getName(), "inner");
private static final FieldReference REGISTRY_FIELD = new FieldReference(ReferenceQueue.class.getName(),
"registry");
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "<init>":
generateInitMethod(context, writer);
break;
case "poll":
generatePollMethod(context, writer);
break;
}
}
private void generateInitMethod(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append(context.getParameterName(0)).append(".").appendField(INNER_FIELD).ws().append("=")
.ws().append("[];").softNewLine();
writer.append(context.getParameterName(0)).append(".").appendField(REGISTRY_FIELD).ws().append("=")
.ws().append("new $rt_globals.FinalizationRegistry(x").ws().append("=>").ws()
.append(context.getParameterName(0)).appendField(INNER_FIELD)
.append(".push(x));").softNewLine();
}
private void generatePollMethod(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("var value").ws().append("=").ws().append(context.getParameterName(0))
.append(".").appendField(INNER_FIELD).append(".shift();").softNewLine();
writer.append("return typeof value").ws().append("!==").ws().append("'undefined'").ws()
.append("?").ws().append("value").ws().append(":").ws().append("null;").softNewLine();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2023 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.javascript.intrinsics.ref;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.ValueType;
public class ReferenceQueueTransformer implements ClassHolderTransformer {
@Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
if (cls.getName().equals(ReferenceQueue.class.getName())) {
var field = new FieldHolder("inner");
field.setType(ValueType.parse(Object.class));
cls.addField(field);
field = new FieldHolder("registry");
field.setType(ValueType.parse(Object.class));
cls.addField(field);
var pollMethod = cls.getMethod(new MethodDescriptor("poll", Reference.class));
pollMethod.setProgram(null);
pollMethod.getModifiers().add(ElementModifier.NATIVE);
var constructor = cls.getMethod(new MethodDescriptor("<init>", void.class));
constructor.setProgram(null);
constructor.getModifiers().add(ElementModifier.NATIVE);
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2023 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.javascript.intrinsics.ref;
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.MethodDependency;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
public class WeakReferenceDependencyListener extends AbstractDependencyListener {
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getMethod().getOwnerName().equals(WeakReference.class.getName())) {
referenceMethodReached(agent, method);
} else if (method.getMethod().getOwnerName().equals(ReferenceQueue.class.getName())) {
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "inner"));
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "registry"));
}
}
private void referenceMethodReached(DependencyAgent agent, MethodDependency method) {
switch (method.getMethod().getName()) {
case "<init>": {
if (method.getParameterCount() == 2) {
var field = agent.linkField(new FieldReference(method.getMethod().getOwnerName(), "value"));
method.getVariable(1).connect(field.getValue());
var pollResult = agent
.linkMethod(new MethodReference(ReferenceQueue.class, "poll", Reference.class))
.getResult();
method.getVariable(0).connect(pollResult);
}
break;
}
case "get": {
var field = agent.linkField(new FieldReference(method.getMethod().getOwnerName(), "value"));
field.getValue().connect(method.getResult());
break;
}
}
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2023 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.javascript.intrinsics.ref;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
public class WeakReferenceGenerator implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "<init>":
generateConstructor(context, writer);
break;
case "get":
generateGet(context, writer);
break;
case "clear":
generateClear(context, writer);
break;
}
}
private void generateConstructor(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("var supported").ws().append("=").ws();
isSupported(writer).append(";").softNewLine();
writer.append("var value").ws().append("=").ws().append("supported").ws()
.append("?").ws().append("new $rt_globals.WeakRef(")
.append(context.getParameterName(1)).append(")").ws();
writer.append(":").ws().append(context.getParameterName(0)).append(";").softNewLine();
writer.append(context.getParameterName(0)).append(".")
.appendField(new FieldReference(WeakReference.class.getName(), "value"))
.ws().append("=").ws().append("value;").softNewLine();
writer.appendIf().append(context.getParameterName(2)).ws().append("!==").ws().append("null")
.ws().append("&&").ws().append("supported)")
.appendBlockStart();
writer.append(context.getParameterName(2)).append(".")
.appendField(new FieldReference(ReferenceQueue.class.getName(), "registry"))
.append(".").append("register(").append(context.getParameterName(1))
.append(",").ws().append("value);").softNewLine();
writer.appendBlockEnd();
}
private void generateGet(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("var value").ws().append("=").ws().append(context.getParameterName(0)).append(".")
.appendField(new FieldReference(WeakReference.class.getName(), "value"))
.append(";").softNewLine();
writer.appendIf();
isSupported(writer).append(")").appendBlockStart();
writer.appendIf().append("value").ws().append("===").ws().append("null)")
.ws().append("return null;").softNewLine();
writer.append("var result").ws().append("=").ws().append("value.deref();").softNewLine();
writer.append("return typeof result").ws().append("!==").ws().append("'undefined'")
.ws().append("?").ws().append("result").ws().append(":").ws().append("null;").softNewLine();
writer.appendElse();
writer.append("return value;").softNewLine();
writer.appendBlockEnd();
}
private void generateClear(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append(context.getParameterName(0)).append(".")
.appendField(new FieldReference(WeakReference.class.getName(), "value")).ws();
writer.append("=").ws().append("null;").softNewLine();
}
private SourceWriter isSupported(SourceWriter writer) throws IOException {
return writer.append("typeof ").append("$rt_globals.WeakRef").ws().append("!==").ws()
.append("'undefined'");
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2023 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.javascript.intrinsics.ref;
import java.lang.ref.WeakReference;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ElementModifier;
public class WeakReferenceTransformer implements ClassHolderTransformer {
@Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
if (!cls.getName().equals(WeakReference.class.getName())) {
return;
}
for (var method : cls.getMethods()) {
switch (method.getName()) {
case "<init>":
if (method.parameterCount() == 2) {
method.setProgram(null);
method.getModifiers().add(ElementModifier.NATIVE);
}
break;
case "get":
case "clear":
method.setProgram(null);
method.getModifiers().add(ElementModifier.NATIVE);
break;
}
}
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2023 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.javascript.runtime.ref;
public final class RuntimeWeakRef {
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2023 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.classlib.java.lang.ref;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.WholeClassCompilation;
@RunWith(TeaVMTestRunner.class)
@WholeClassCompilation
@SkipJVM
public class WeakReferenceTest {
private Node lastNode;
@Test
@Ignore
public void deref() {
var ref = createAndTestRef(null);
for (var i = 0; i < 100; ++i) {
lastNode = createNodes(18);
if (ref.get() == null) {
break;
}
}
assertNull(ref.get());
}
@Test
@Ignore
public void refQueue() {
var queue = new ReferenceQueue<>();
var ref = createAndTestRef(queue);
var hasValue = false;
for (var i = 0; i < 100; ++i) {
lastNode = createNodes(18);
var polledRef = queue.poll();
if (polledRef != null) {
hasValue = true;
assertNull(ref.get());
break;
} else {
assertNotNull(ref.get());
}
}
assertTrue(hasValue);
}
private WeakReference<Object> createAndTestRef(ReferenceQueue<Object> queue) {
var obj = new byte[4 * 1024 * 1024];
var ref = new WeakReference<Object>(obj, queue);
assertSame(obj, ref.get());
return ref;
}
@Test
public void clear() {
var obj = new Object();
var ref = new WeakReference<>(obj);
assertSame(obj, ref.get());
ref.clear();
assertNull(ref.get());
}
private Node createNodes(int depth) {
if (depth == 0) {
return null;
} else {
return new Node(createNodes(depth - 1), createNodes(depth - 1));
}
}
private class Node {
Node left;
Node right;
byte[] data = new byte[64];
Node(Node left, Node right) {
this.left = left;
this.right = right;
}
}
}